From bcce8d623651a4dd08a9e5b5a29ed4f6f2d736d7 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:26:51 +0200 Subject: [PATCH] fix(ui): centralize default muted findings filter on finding groups (#10818) Co-authored-by: Pepe Fagoaga --- ui/CHANGELOG.md | 8 ++++ ui/app/(prowler)/findings/page.test.ts | 4 ++ ui/app/(prowler)/findings/page.tsx | 19 +++++---- .../use-finding-group-resource-state.test.ts | 5 +++ ui/hooks/use-finding-group-resource-state.ts | 5 ++- ui/lib/findings-filters.test.ts | 42 +++++++++++++++++++ ui/lib/findings-filters.ts | 36 ++++++++++++++++ ui/lib/index.ts | 1 + 8 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 ui/lib/findings-filters.test.ts create mode 100644 ui/lib/findings-filters.ts diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index d4f378adf8..5cd038666b 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -11,6 +11,14 @@ All notable changes to the **Prowler UI** are documented in this file. --- +## [1.24.2] (Prowler v5.24.2) + +### 🐞 Fixed + +- Default muted filter now applied consistently on the findings page and the finding-group resource drill-down, keeping muted findings hidden unless the "include muted findings" checkbox is opted in [(#10818)](https://github.com/prowler-cloud/prowler/pull/10818) + +--- + ## [1.24.1] (Prowler v5.24.1) ### 🐞 Fixed diff --git a/ui/app/(prowler)/findings/page.test.ts b/ui/app/(prowler)/findings/page.test.ts index 444ed47f9e..2038b92291 100644 --- a/ui/app/(prowler)/findings/page.test.ts +++ b/ui/app/(prowler)/findings/page.test.ts @@ -36,4 +36,8 @@ describe("findings page", () => { it("guards errors array access with a length check", () => { expect(source).toContain("errors?.length > 0"); }); + + it("applies the shared default muted filter so muted findings are hidden unless the caller opts in", () => { + expect(source).toContain("applyDefaultMutedFilter"); + }); }); diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 22c90330bb..dea6e9b4fb 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -16,6 +16,7 @@ import { import { ContentLayout } from "@/components/ui"; import { FilterTransitionWrapper } from "@/contexts"; import { + applyDefaultMutedFilter, createScanDetailsMapping, extractFiltersAndQuery, extractSortAndKey, @@ -39,14 +40,16 @@ export default async function Findings({ getScans({ pageSize: 50 }), ]); - const filtersWithScanDates = await resolveFindingScanDateFilters({ - filters, - scans: scansData?.data || [], - loadScan: async (scanId: string) => { - const response = await getScan(scanId); - return response?.data; - }, - }); + const filtersWithScanDates = applyDefaultMutedFilter( + await resolveFindingScanDateFilters({ + filters, + scans: scansData?.data || [], + loadScan: async (scanId: string) => { + const response = await getScan(scanId); + return response?.data; + }, + }), + ); const hasHistoricalData = hasDateOrScanFilter(filtersWithScanDates); diff --git a/ui/hooks/use-finding-group-resource-state.test.ts b/ui/hooks/use-finding-group-resource-state.test.ts index 789ea592c2..c0ed5a4abe 100644 --- a/ui/hooks/use-finding-group-resource-state.test.ts +++ b/ui/hooks/use-finding-group-resource-state.test.ts @@ -12,4 +12,9 @@ describe("useFindingGroupResourceState", () => { it("enables muted findings only for the finding-group resource drawer", () => { expect(source).toContain("includeMutedInOtherFindings: true"); }); + + it("applies the shared default muted filter before fetching group resources", () => { + expect(source).toContain("applyDefaultMutedFilter(filters)"); + expect(source).toContain("filters: effectiveFilters"); + }); }); diff --git a/ui/hooks/use-finding-group-resource-state.ts b/ui/hooks/use-finding-group-resource-state.ts index 309a69a6dc..1249f8c5a8 100644 --- a/ui/hooks/use-finding-group-resource-state.ts +++ b/ui/hooks/use-finding-group-resource-state.ts @@ -6,6 +6,7 @@ import { useState } from "react"; import { canMuteFindingResource } from "@/components/findings/table/finding-resource-selection"; import { useResourceDetailDrawer } from "@/components/findings/table/resource-detail-drawer"; import { useFindingGroupResources } from "@/hooks/use-finding-group-resources"; +import { applyDefaultMutedFilter } from "@/lib"; import { FindingGroupRow, FindingResourceRow } from "@/types"; interface UseFindingGroupResourceStateOptions { @@ -67,11 +68,13 @@ export function useFindingGroupResourceState({ setIsLoading(loading); }; + const effectiveFilters = applyDefaultMutedFilter(filters); + const { sentinelRef, refresh, loadMore, totalCount } = useFindingGroupResources({ checkId: group.checkId, hasDateOrScanFilter: hasHistoricalData, - filters, + filters: effectiveFilters, onSetResources: handleSetResources, onAppendResources: handleAppendResources, onSetLoading: handleSetLoading, diff --git a/ui/lib/findings-filters.test.ts b/ui/lib/findings-filters.test.ts new file mode 100644 index 0000000000..3f1143b4d7 --- /dev/null +++ b/ui/lib/findings-filters.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; + +import { applyDefaultMutedFilter, MUTED_FILTER } from "./findings-filters"; + +describe("applyDefaultMutedFilter", () => { + it("injects filter[muted]=false when the caller has not set it", () => { + const input: Record = { "filter[status__in]": "FAIL" }; + const result = applyDefaultMutedFilter(input); + + expect(result["filter[muted]"]).toBe(MUTED_FILTER.EXCLUDE); + expect(result["filter[status__in]"]).toBe("FAIL"); + }); + + it("preserves an explicit filter[muted]=include opt-in from the checkbox", () => { + const result = applyDefaultMutedFilter({ + "filter[muted]": MUTED_FILTER.INCLUDE, + }); + + expect(result["filter[muted]"]).toBe(MUTED_FILTER.INCLUDE); + }); + + it("preserves an explicit filter[muted]=false (no silent overwrite)", () => { + const result = applyDefaultMutedFilter({ + "filter[muted]": MUTED_FILTER.EXCLUDE, + }); + + expect(result["filter[muted]"]).toBe(MUTED_FILTER.EXCLUDE); + }); + + it("does not mutate the input object", () => { + const input = { "filter[status__in]": "FAIL" }; + applyDefaultMutedFilter(input); + + expect(input).not.toHaveProperty("filter[muted]"); + }); + + it("returns a default-filled object when called with no caller filters", () => { + const result = applyDefaultMutedFilter({} as Record); + + expect(result["filter[muted]"]).toBe(MUTED_FILTER.EXCLUDE); + }); +}); diff --git a/ui/lib/findings-filters.ts b/ui/lib/findings-filters.ts new file mode 100644 index 0000000000..9a2aae1d80 --- /dev/null +++ b/ui/lib/findings-filters.ts @@ -0,0 +1,36 @@ +/** + * Shared helpers for findings filter handling. + * + * The `/findings` SSR page and the finding-group resource drill-down both + * need to hide muted findings by default — unless the user has opted in via + * the "include muted findings" checkbox. Keeping that default in one place + * prevents surfaces from drifting. + */ + +export const MUTED_FILTER = { + /** Wire value sent to the API to exclude muted findings. */ + EXCLUDE: "false", + /** + * Sentinel value that tells the API to return both muted and non-muted + * findings. The checkbox writes this to the URL when the user opts in. + */ + INCLUDE: "include", +} as const; + +export type MutedFilterValue = (typeof MUTED_FILTER)[keyof typeof MUTED_FILTER]; + +/** + * Returns a new filter object with the default muted behaviour applied: + * hide muted findings unless the caller already set `filter[muted]`. + * + * The default is spread BEFORE the caller filters so any explicit value + * (including `"false"` or the `"include"` opt-in) wins. + */ +export function applyDefaultMutedFilter< + T extends Record, +>(filters: T): T { + return { + "filter[muted]": MUTED_FILTER.EXCLUDE, + ...filters, + }; +} diff --git a/ui/lib/index.ts b/ui/lib/index.ts index 316348a0d6..75250e5c68 100644 --- a/ui/lib/index.ts +++ b/ui/lib/index.ts @@ -1,5 +1,6 @@ export * from "./error-mappings"; export * from "./external-urls"; +export * from "./findings-filters"; export * from "./helper"; export * from "./helper-filters"; export * from "./menu-list";