fix(ui): restore Jira toasts and check filter chips

This commit is contained in:
Hugo P.Brito committed 2026-07-16 15:01:11 +01:00
1 parent 3eebded9e1
commit 0faeca5586
7 files changed
+142 -10

No files matched your search

+2
View File
@@ -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",
+45 -6
View File
@@ -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<
+19
View File
@@ -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 ──────────────────────────────────────────────────────────