mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-05-06 08:47:18 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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,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";
|
||||
|
||||
Reference in New Issue
Block a user