mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-08 12:24:45 +00:00
fix(ui): restore Jira toasts and check filter chips
This commit is contained in:
7 files changed
+142
-10
No files matched your search
@@ -3,6 +3,8 @@ import { FILTER_FIELD, FilterParam } from "@/types/filters";
|
||||
/** Findings-only filter fields not shared with other views. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const FINDINGS_EXTRA_FIELD = {
|
||||
CHECK_ID: "check_id",
|
||||
CHECK_ID_IN: "check_id__in",
|
||||
DELTA_IN: "delta__in",
|
||||
SCAN_EXACT: "scan",
|
||||
SCAN_ID: "scan_id",
|
||||
|
||||
@@ -42,10 +42,16 @@ interface FindingsFiltersProps {
|
||||
uniqueResourceTypes: string[];
|
||||
uniqueCategories: string[];
|
||||
uniqueGroups: string[];
|
||||
checkOptions?: FindingCheckFilterOption[];
|
||||
trailingControls?: ReactNode;
|
||||
variant?: "default" | "alerts-edit";
|
||||
}
|
||||
|
||||
interface FindingCheckFilterOption {
|
||||
checkId: string;
|
||||
checkTitle?: string;
|
||||
}
|
||||
|
||||
interface FindingsFilterBatchControlsProps extends FindingsFiltersProps {
|
||||
appliedFilters: Record<string, string[]>;
|
||||
pendingFilters: Record<string, string[]>;
|
||||
@@ -72,6 +78,10 @@ const FILTER_CONTROL_COLUMN_CLASS =
|
||||
"min-w-0 flex-none basis-full sm:basis-[calc((100%_-_0.75rem)/2)] lg:basis-[calc((100%_-_1.5rem)/3)] xl:basis-[calc((100%_-_2.25rem)/4)] 2xl:basis-[calc((100%_-_3rem)/5)]";
|
||||
const FILTER_GRID_ITEM_CLASS = "min-w-0";
|
||||
|
||||
function uniqueNonEmptyValues(values: string[]): string[] {
|
||||
return Array.from(new Set(values.filter(Boolean)));
|
||||
}
|
||||
|
||||
export const FindingsFilterBatchControls = ({
|
||||
providers,
|
||||
// Undefined = caller opted out (the alert editor shares this component but
|
||||
@@ -85,6 +95,7 @@ export const FindingsFilterBatchControls = ({
|
||||
uniqueResourceTypes,
|
||||
uniqueCategories,
|
||||
uniqueGroups,
|
||||
checkOptions = [],
|
||||
trailingControls,
|
||||
appliedFilters,
|
||||
pendingFilters,
|
||||
@@ -102,6 +113,17 @@ export const FindingsFilterBatchControls = ({
|
||||
}: FindingsFilterBatchControlsProps) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const isAlertsEdit = variant === "alerts-edit";
|
||||
const checkTitles = Object.fromEntries(
|
||||
checkOptions.map(({ checkId, checkTitle }) => [
|
||||
checkId,
|
||||
checkTitle || checkId,
|
||||
]),
|
||||
);
|
||||
const checkFilterValues = uniqueNonEmptyValues([
|
||||
...checkOptions.map((option) => option.checkId),
|
||||
...getFilterValue("filter[check_id]"),
|
||||
...getFilterValue("filter[check_id__in]"),
|
||||
]);
|
||||
|
||||
const customFilters = [
|
||||
...filterFindings
|
||||
@@ -112,39 +134,54 @@ export const FindingsFilterBatchControls = ({
|
||||
getFindingsFilterDisplayValue(`filter[${filter.key}]`, value, {
|
||||
providers,
|
||||
scans: scanDetails,
|
||||
checkTitles,
|
||||
}),
|
||||
})),
|
||||
...(checkFilterValues.length > 0
|
||||
? [
|
||||
{
|
||||
key: "check_id",
|
||||
labelCheckboxGroup: "Finding Group",
|
||||
values: checkFilterValues,
|
||||
labelFormatter: (value: string) =>
|
||||
getFindingsFilterDisplayValue("filter[check_id]", value, {
|
||||
checkTitles,
|
||||
}),
|
||||
index: 3,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: FILTER_FIELD.REGION,
|
||||
labelCheckboxGroup: "Regions",
|
||||
values: uniqueRegions,
|
||||
index: 3,
|
||||
index: 4,
|
||||
},
|
||||
{
|
||||
key: FILTER_FIELD.SERVICE,
|
||||
labelCheckboxGroup: "Services",
|
||||
values: uniqueServices,
|
||||
index: 4,
|
||||
index: 5,
|
||||
},
|
||||
{
|
||||
key: FILTER_FIELD.RESOURCE_TYPE,
|
||||
labelCheckboxGroup: "Resource Type",
|
||||
values: uniqueResourceTypes,
|
||||
index: 8,
|
||||
index: 9,
|
||||
},
|
||||
{
|
||||
key: FILTER_FIELD.CATEGORY,
|
||||
labelCheckboxGroup: "Category",
|
||||
values: uniqueCategories,
|
||||
labelFormatter: getCategoryLabel,
|
||||
index: 5,
|
||||
index: 6,
|
||||
},
|
||||
{
|
||||
key: FILTER_FIELD.RESOURCE_GROUPS,
|
||||
labelCheckboxGroup: "Resource Group",
|
||||
values: uniqueGroups,
|
||||
labelFormatter: getGroupLabel,
|
||||
index: 6,
|
||||
index: 7,
|
||||
},
|
||||
...(isAlertsEdit
|
||||
? []
|
||||
@@ -164,7 +201,7 @@ export const FindingsFilterBatchControls = ({
|
||||
scans: scanDetails,
|
||||
},
|
||||
),
|
||||
index: 7,
|
||||
index: 8,
|
||||
},
|
||||
]),
|
||||
];
|
||||
@@ -177,6 +214,7 @@ export const FindingsFilterBatchControls = ({
|
||||
providers,
|
||||
providerGroups,
|
||||
scans: scanDetails,
|
||||
checkTitles,
|
||||
},
|
||||
);
|
||||
const pendingFilterChips: FilterChip[] = buildFindingsFilterChips(
|
||||
@@ -185,6 +223,7 @@ export const FindingsFilterBatchControls = ({
|
||||
providers,
|
||||
providerGroups,
|
||||
scans: scanDetails,
|
||||
checkTitles,
|
||||
},
|
||||
);
|
||||
const appliedCount = countVisibleFilterKeys(appliedFilters);
|
||||
|
||||
@@ -164,6 +164,30 @@ describe("getFindingsFilterDisplayValue", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the finding group title for check_id filters when available", () => {
|
||||
expect(
|
||||
getFindingsFilterDisplayValue(
|
||||
"filter[check_id]",
|
||||
"teams_external_users_can_join",
|
||||
{
|
||||
checkTitles: {
|
||||
teams_external_users_can_join:
|
||||
"External Teams users can join meetings",
|
||||
},
|
||||
},
|
||||
),
|
||||
).toBe("External Teams users can join meetings");
|
||||
});
|
||||
|
||||
it("keeps the check id when no finding group title is available", () => {
|
||||
expect(
|
||||
getFindingsFilterDisplayValue(
|
||||
"filter[check_id]",
|
||||
"teams_external_users_can_join",
|
||||
),
|
||||
).toBe("teams_external_users_can_join");
|
||||
});
|
||||
|
||||
it("uses the provider display name regardless of account alias/uid", () => {
|
||||
expect(
|
||||
getFindingsFilterDisplayValue("filter[scan__in]", "scan-2", {
|
||||
@@ -298,6 +322,45 @@ describe("buildFindingsFilterChips", () => {
|
||||
expect(chipsPlural[0].displayValues).toEqual(["New", "Changed"]);
|
||||
});
|
||||
|
||||
it("renders filter[check_id] as a first-class Finding Group chip", () => {
|
||||
// Given - exact deep-link params from the grouped findings page.
|
||||
const chips = buildFindingsFilterChips(
|
||||
{
|
||||
"filter[check_id]": ["teams_external_users_can_join"],
|
||||
},
|
||||
{
|
||||
checkTitles: {
|
||||
teams_external_users_can_join:
|
||||
"External Teams users can join meetings",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(chips).toEqual([
|
||||
{
|
||||
key: "filter[check_id]",
|
||||
label: "Finding Group",
|
||||
value: "teams_external_users_can_join",
|
||||
displayValue: "External Teams users can join meetings",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("renders filter[check_id__in] with the Finding Group chip label", () => {
|
||||
const chips = buildFindingsFilterChips({
|
||||
"filter[check_id__in]": ["teams_external_users_can_join"],
|
||||
});
|
||||
|
||||
expect(chips).toEqual([
|
||||
{
|
||||
key: "filter[check_id__in]",
|
||||
label: "Finding Group",
|
||||
value: "teams_external_users_can_join",
|
||||
displayValue: "teams_external_users_can_join",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("skips muted filters because the table toolbar owns that control", () => {
|
||||
const chips = buildFindingsFilterChips({
|
||||
"filter[muted]": ["include"],
|
||||
|
||||
@@ -15,6 +15,7 @@ interface GetFindingsFilterDisplayValueOptions {
|
||||
providers?: ProviderProps[];
|
||||
scans?: Array<{ [scanId: string]: ScanEntity }>;
|
||||
providerGroups?: ProviderGroup[];
|
||||
checkTitles?: Record<string, string>;
|
||||
}
|
||||
|
||||
const FINDING_DELTA_DISPLAY_NAMES: Record<string, string> = {
|
||||
@@ -64,6 +65,12 @@ export function getFindingsFilterDisplayValue(
|
||||
if (filterKey === "filter[scan__in]" || filterKey === "filter[scan]") {
|
||||
return getScanDisplayValue(value, options.scans || []);
|
||||
}
|
||||
if (
|
||||
filterKey === "filter[check_id]" ||
|
||||
filterKey === "filter[check_id__in]"
|
||||
) {
|
||||
return options.checkTitles?.[value] || value;
|
||||
}
|
||||
if (filterKey === "filter[severity__in]") {
|
||||
return (
|
||||
SEVERITY_DISPLAY_NAMES[
|
||||
@@ -108,6 +115,8 @@ export function getFindingsFilterDisplayValue(
|
||||
* label is missing.
|
||||
*/
|
||||
export const FILTER_KEY_LABELS: Record<FindingsFilterParam, string> = {
|
||||
"filter[check_id]": "Finding Group",
|
||||
"filter[check_id__in]": "Finding Group",
|
||||
"filter[provider_type__in]": "Provider",
|
||||
"filter[provider_id__in]": "Account",
|
||||
"filter[provider_groups__in]": "Provider Group",
|
||||
@@ -134,6 +143,7 @@ interface BuildFindingsFilterChipsOptions {
|
||||
providers?: ProviderProps[];
|
||||
scans?: Array<{ [scanId: string]: ScanEntity }>;
|
||||
providerGroups?: ProviderGroup[];
|
||||
checkTitles?: Record<string, string>;
|
||||
includeMuted?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ vi.mock("@/actions/integrations/jira-dispatch", () => ({
|
||||
sendJiraDispatch: sendJiraDispatchMock,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shadcn", () => ({
|
||||
useToast: () => ({ toast: toastMock }),
|
||||
vi.mock("@/components/shadcn/toast", () => ({
|
||||
toast: toastMock,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shadcn/select/enhanced-multi-select", () => ({
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
sendFindingToJira,
|
||||
sendJiraDispatch,
|
||||
} from "@/actions/integrations/jira-dispatch";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { CustomBanner } from "@/components/shadcn/custom/custom-banner";
|
||||
import { Form, FormField, FormMessage } from "@/components/shadcn/form";
|
||||
import { FormButtons } from "@/components/shadcn/form/form-buttons";
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
} from "@/components/shadcn/radio-group/radio-group";
|
||||
import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select";
|
||||
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
|
||||
import { toast } from "@/components/shadcn/toast";
|
||||
import {
|
||||
IntegrationProps,
|
||||
JIRA_DISPATCH_MODE,
|
||||
@@ -102,7 +102,6 @@ export const SendToJiraModal = ({
|
||||
selectedResourceCount,
|
||||
description,
|
||||
}: SendToJiraModalProps) => {
|
||||
const { toast } = useToast();
|
||||
const [integrations, setIntegrations] = useState<IntegrationProps[]>([]);
|
||||
const [isFetchingIntegrations, setIsFetchingIntegrations] = useState(false);
|
||||
const [fetchedIssueTypes, setFetchedIssueTypes] = useState<
|
||||
|
||||
@@ -98,6 +98,25 @@ describe("useFilterBatch", () => {
|
||||
"filter[delta]": ["new"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse filter[check_id] from grouped finding deep links", () => {
|
||||
// Given - URL produced by the grouped finding resources panel deep link.
|
||||
setSearchParams({
|
||||
"filter[check_id]": "teams_external_users_can_join",
|
||||
expandedCheckId: "teams_external_users_can_join",
|
||||
});
|
||||
|
||||
// When
|
||||
const { result } = renderHook(() => useFilterBatch());
|
||||
|
||||
// Then - expandedCheckId is not a filter chip, but check_id is URL-backed.
|
||||
expect(result.current.pendingFilters).toEqual({
|
||||
"filter[check_id]": ["teams_external_users_can_join"],
|
||||
});
|
||||
expect(result.current.getFilterValue("filter[check_id]")).toEqual([
|
||||
"teams_external_users_can_join",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Excluded keys ──────────────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user