fix(ui): centralize default muted findings filter on finding groups (#10819)

Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
Prowler Bot
2026-04-21 14:33:42 +02:00
committed by GitHub
parent ea09ff8902
commit 322a500352
8 changed files with 111 additions and 9 deletions
+8
View File
@@ -2,6 +2,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
+4
View File
@@ -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");
});
});
+11 -8
View File
@@ -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);
@@ -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");
});
});
+4 -1
View File
@@ -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,
+42
View File
@@ -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<string, string> = { "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<string, string>);
expect(result["filter[muted]"]).toBe(MUTED_FILTER.EXCLUDE);
});
});
+36
View File
@@ -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<string, string | string[] | undefined>,
>(filters: T): T {
return {
"filter[muted]": MUTED_FILTER.EXCLUDE,
...filters,
};
}
+1
View File
@@ -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";