mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
fix(ui): centralize default muted findings filter on finding groups (#10818)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
co-authored by
Pepe Fagoaga
parent
570c86948e
commit
bcce8d6236
@@ -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)
|
## [1.24.1] (Prowler v5.24.1)
|
||||||
|
|
||||||
### 🐞 Fixed
|
### 🐞 Fixed
|
||||||
|
|||||||
@@ -36,4 +36,8 @@ describe("findings page", () => {
|
|||||||
it("guards errors array access with a length check", () => {
|
it("guards errors array access with a length check", () => {
|
||||||
expect(source).toContain("errors?.length > 0");
|
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 { ContentLayout } from "@/components/ui";
|
||||||
import { FilterTransitionWrapper } from "@/contexts";
|
import { FilterTransitionWrapper } from "@/contexts";
|
||||||
import {
|
import {
|
||||||
|
applyDefaultMutedFilter,
|
||||||
createScanDetailsMapping,
|
createScanDetailsMapping,
|
||||||
extractFiltersAndQuery,
|
extractFiltersAndQuery,
|
||||||
extractSortAndKey,
|
extractSortAndKey,
|
||||||
@@ -39,14 +40,16 @@ export default async function Findings({
|
|||||||
getScans({ pageSize: 50 }),
|
getScans({ pageSize: 50 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const filtersWithScanDates = await resolveFindingScanDateFilters({
|
const filtersWithScanDates = applyDefaultMutedFilter(
|
||||||
filters,
|
await resolveFindingScanDateFilters({
|
||||||
scans: scansData?.data || [],
|
filters,
|
||||||
loadScan: async (scanId: string) => {
|
scans: scansData?.data || [],
|
||||||
const response = await getScan(scanId);
|
loadScan: async (scanId: string) => {
|
||||||
return response?.data;
|
const response = await getScan(scanId);
|
||||||
},
|
return response?.data;
|
||||||
});
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const hasHistoricalData = hasDateOrScanFilter(filtersWithScanDates);
|
const hasHistoricalData = hasDateOrScanFilter(filtersWithScanDates);
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,9 @@ describe("useFindingGroupResourceState", () => {
|
|||||||
it("enables muted findings only for the finding-group resource drawer", () => {
|
it("enables muted findings only for the finding-group resource drawer", () => {
|
||||||
expect(source).toContain("includeMutedInOtherFindings: true");
|
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 { canMuteFindingResource } from "@/components/findings/table/finding-resource-selection";
|
||||||
import { useResourceDetailDrawer } from "@/components/findings/table/resource-detail-drawer";
|
import { useResourceDetailDrawer } from "@/components/findings/table/resource-detail-drawer";
|
||||||
import { useFindingGroupResources } from "@/hooks/use-finding-group-resources";
|
import { useFindingGroupResources } from "@/hooks/use-finding-group-resources";
|
||||||
|
import { applyDefaultMutedFilter } from "@/lib";
|
||||||
import { FindingGroupRow, FindingResourceRow } from "@/types";
|
import { FindingGroupRow, FindingResourceRow } from "@/types";
|
||||||
|
|
||||||
interface UseFindingGroupResourceStateOptions {
|
interface UseFindingGroupResourceStateOptions {
|
||||||
@@ -67,11 +68,13 @@ export function useFindingGroupResourceState({
|
|||||||
setIsLoading(loading);
|
setIsLoading(loading);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const effectiveFilters = applyDefaultMutedFilter(filters);
|
||||||
|
|
||||||
const { sentinelRef, refresh, loadMore, totalCount } =
|
const { sentinelRef, refresh, loadMore, totalCount } =
|
||||||
useFindingGroupResources({
|
useFindingGroupResources({
|
||||||
checkId: group.checkId,
|
checkId: group.checkId,
|
||||||
hasDateOrScanFilter: hasHistoricalData,
|
hasDateOrScanFilter: hasHistoricalData,
|
||||||
filters,
|
filters: effectiveFilters,
|
||||||
onSetResources: handleSetResources,
|
onSetResources: handleSetResources,
|
||||||
onAppendResources: handleAppendResources,
|
onAppendResources: handleAppendResources,
|
||||||
onSetLoading: handleSetLoading,
|
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 "./error-mappings";
|
||||||
export * from "./external-urls";
|
export * from "./external-urls";
|
||||||
|
export * from "./findings-filters";
|
||||||
export * from "./helper";
|
export * from "./helper";
|
||||||
export * from "./helper-filters";
|
export * from "./helper-filters";
|
||||||
export * from "./menu-list";
|
export * from "./menu-list";
|
||||||
|
|||||||
Reference in New Issue
Block a user