feat(ui): make finding group delta indicator status-filter aware (#10647)

This commit is contained in:
Alejandro Bailo authored and GitHub committed 2026-04-10 11:29:11 +02:00
1 parent 431776bcfd
commit 0d7c5f6ac5
5 files changed
+269 -5

No files matched your search

@@ -11,7 +11,7 @@ import {
} from "@/components/ui/table";
import { cn } from "@/lib";
import {
getFindingGroupDelta,
getFilteredFindingGroupDelta,
isFindingGroupMuted,
} from "@/lib/findings-groups";
import { FindingGroupRow, ProviderType } from "@/types";
@@ -29,6 +29,8 @@ interface GetColumnFindingGroupsOptions {
expandedCheckId?: string | null;
/** True when the expanded group has individually selected resources */
hasResourceSelection?: boolean;
/** Active URL filters — used to make the delta indicator status-aware */
filters?: Record<string, string | string[] | undefined>;
}
const VISIBLE_DISABLED_CHECKBOX_CLASS =
@@ -40,6 +42,7 @@ export function getColumnFindingGroups({
onDrillDown,
expandedCheckId,
hasResourceSelection = false,
filters = {},
}: GetColumnFindingGroupsOptions): ColumnDef<FindingGroupRow>[] {
const selectedCount = Object.values(rowSelection).filter(Boolean).length;
const isAllSelected =
@@ -80,7 +83,7 @@ export function getColumnFindingGroups({
const group = row.original;
const allMuted = isFindingGroupMuted(group);
const isExpanded = expandedCheckId === group.checkId;
const deltaKey = getFindingGroupDelta(group);
const deltaKey = getFilteredFindingGroupDelta(group, filters);
const delta =
deltaKey === "new"
? DeltaValues.NEW
@@ -24,7 +24,7 @@ import { SeverityBadge, StatusFindingBadge } from "@/components/ui/table";
import { useInfiniteResources } from "@/hooks/use-infinite-resources";
import { cn, hasDateOrScanFilter } from "@/lib";
import {
getFindingGroupDelta,
getFilteredFindingGroupDelta,
isFindingGroupMuted,
} from "@/lib/findings-groups";
import { FindingGroupRow, FindingResourceRow } from "@/types";
@@ -156,7 +156,7 @@ export function FindingsGroupDrillDown({
});
// Delta for the sticky header
const deltaKey = getFindingGroupDelta(group);
const deltaKey = getFilteredFindingGroupDelta(group, filters);
const delta =
deltaKey === "new"
? DeltaValues.NEW
@@ -173,6 +173,7 @@ export function FindingsGroupTable({
onDrillDown: handleDrillDown,
expandedCheckId,
hasResourceSelection,
filters,
});
const renderAfterRow = (row: Row<FindingGroupRow>) => {
+137 -1
View File
@@ -2,7 +2,12 @@ import { describe, expect, it } from "vitest";
import type { FindingGroupRow } from "@/types";
import { getFindingGroupDelta, isFindingGroupMuted } from "./findings-groups";
import {
getActiveStatusFilter,
getFilteredFindingGroupDelta,
getFindingGroupDelta,
isFindingGroupMuted,
} from "./findings-groups";
function makeGroup(overrides?: Partial<FindingGroupRow>): FindingGroupRow {
return {
@@ -100,3 +105,134 @@ describe("getFindingGroupDelta", () => {
).toBe("new");
});
});
describe("getActiveStatusFilter", () => {
it("returns null when no status filter is active", () => {
expect(getActiveStatusFilter({})).toBeNull();
});
it("returns the single value from filter[status]", () => {
const result = getActiveStatusFilter({ "filter[status]": "FAIL" });
expect(result).toEqual(new Set(["FAIL"]));
});
it("returns the parsed set from filter[status__in]", () => {
const result = getActiveStatusFilter({
"filter[status__in]": "FAIL,MANUAL",
});
expect(result).toEqual(new Set(["FAIL", "MANUAL"]));
});
it("prefers filter[status] over filter[status__in] when both are present", () => {
const result = getActiveStatusFilter({
"filter[status]": "PASS",
"filter[status__in]": "FAIL,MANUAL",
});
expect(result).toEqual(new Set(["PASS"]));
});
it("ignores unknown status values and returns null if nothing remains", () => {
expect(
getActiveStatusFilter({ "filter[status__in]": "UNKNOWN,FOO" }),
).toBeNull();
});
});
describe("getFilteredFindingGroupDelta", () => {
it("falls back to the aggregate delta when no status filter is active", () => {
expect(
getFilteredFindingGroupDelta(
makeGroup({
newPassCount: 2,
}),
{},
),
).toBe("new");
});
it("ignores deltas that belong to filtered-out statuses", () => {
// Filter is FAIL, but the only delta is a new PASS → should be hidden.
expect(
getFilteredFindingGroupDelta(
makeGroup({
newPassCount: 3,
}),
{ "filter[status__in]": "FAIL" },
),
).toBe("none");
});
it("surfaces FAIL deltas when the filter is FAIL", () => {
expect(
getFilteredFindingGroupDelta(
makeGroup({
newFailCount: 1,
}),
{ "filter[status]": "FAIL" },
),
).toBe("new");
});
it("counts muted breakdown counters towards the filtered status", () => {
// A muted new FAIL still belongs to the FAIL bucket — a FAIL filter
// should still light up the "new" indicator.
expect(
getFilteredFindingGroupDelta(
makeGroup({
newFailMutedCount: 1,
}),
{ "filter[status]": "FAIL" },
),
).toBe("new");
});
it("sums multiple filtered statuses from filter[status__in]", () => {
// Filter is FAIL+MANUAL, new delta is only in MANUAL → should still show.
expect(
getFilteredFindingGroupDelta(
makeGroup({
newManualCount: 1,
}),
{ "filter[status__in]": "FAIL,MANUAL" },
),
).toBe("new");
});
it("prefers new over changed within the filtered status", () => {
expect(
getFilteredFindingGroupDelta(
makeGroup({
newFailCount: 1,
changedFailCount: 2,
}),
{ "filter[status]": "FAIL" },
),
).toBe("new");
});
it("returns changed when only changed counters match the filtered status", () => {
expect(
getFilteredFindingGroupDelta(
makeGroup({
newPassCount: 2, // filtered out
changedFailCount: 1,
}),
{ "filter[status]": "FAIL" },
),
).toBe("changed");
});
it("falls back to the aggregate delta when breakdowns are missing (legacy API)", () => {
// No breakdown fields populated but legacy newCount is set. With a FAIL
// filter active we cannot know which status bucket it belongs to, so we
// fall back to showing the delta rather than silently hiding it.
expect(
getFilteredFindingGroupDelta(
makeGroup({
newCount: 1,
}),
{ "filter[status]": "FAIL" },
),
).toBe("new");
});
});
+124
View File
@@ -75,3 +75,127 @@ export function getFindingGroupDelta(
return "none";
}
const FINDING_GROUP_STATUSES = ["FAIL", "PASS", "MANUAL"] as const;
type FindingGroupStatus = (typeof FINDING_GROUP_STATUSES)[number];
type FindingGroupFiltersRecord = Record<string, string | string[] | undefined>;
function parseStatusFilterValue(
rawValue: string | string[] | undefined,
): FindingGroupStatus[] {
if (!rawValue) {
return [];
}
const joined = Array.isArray(rawValue) ? rawValue.join(",") : rawValue;
return joined
.split(",")
.map((status) => status.trim().toUpperCase())
.filter((status): status is FindingGroupStatus =>
(FINDING_GROUP_STATUSES as readonly string[]).includes(status),
);
}
/**
* Returns the set of statuses the user has explicitly narrowed the findings
* view to, or null when no status filter is active (→ all statuses should be
* considered). Supports both `filter[status]` (single value) and
* `filter[status__in]` (comma-separated values).
*/
export function getActiveStatusFilter(
filters: FindingGroupFiltersRecord,
): Set<FindingGroupStatus> | null {
const direct = parseStatusFilterValue(filters["filter[status]"]);
if (direct.length > 0) {
return new Set(direct);
}
const multi = parseStatusFilterValue(filters["filter[status__in]"]);
if (multi.length > 0) {
return new Set(multi);
}
return null;
}
function hasAnyDeltaBreakdown(group: FindingGroupDeltaState): boolean {
return (
(group.newFailCount ?? 0) > 0 ||
(group.newFailMutedCount ?? 0) > 0 ||
(group.newPassCount ?? 0) > 0 ||
(group.newPassMutedCount ?? 0) > 0 ||
(group.newManualCount ?? 0) > 0 ||
(group.newManualMutedCount ?? 0) > 0 ||
(group.changedFailCount ?? 0) > 0 ||
(group.changedFailMutedCount ?? 0) > 0 ||
(group.changedPassCount ?? 0) > 0 ||
(group.changedPassMutedCount ?? 0) > 0 ||
(group.changedManualCount ?? 0) > 0 ||
(group.changedManualMutedCount ?? 0) > 0
);
}
function getNewDeltaForStatuses(
group: FindingGroupDeltaState,
statuses: Set<FindingGroupStatus>,
): number {
let total = 0;
if (statuses.has("FAIL")) {
total += (group.newFailCount ?? 0) + (group.newFailMutedCount ?? 0);
}
if (statuses.has("PASS")) {
total += (group.newPassCount ?? 0) + (group.newPassMutedCount ?? 0);
}
if (statuses.has("MANUAL")) {
total += (group.newManualCount ?? 0) + (group.newManualMutedCount ?? 0);
}
return total;
}
function getChangedDeltaForStatuses(
group: FindingGroupDeltaState,
statuses: Set<FindingGroupStatus>,
): number {
let total = 0;
if (statuses.has("FAIL")) {
total += (group.changedFailCount ?? 0) + (group.changedFailMutedCount ?? 0);
}
if (statuses.has("PASS")) {
total += (group.changedPassCount ?? 0) + (group.changedPassMutedCount ?? 0);
}
if (statuses.has("MANUAL")) {
total +=
(group.changedManualCount ?? 0) + (group.changedManualMutedCount ?? 0);
}
return total;
}
/**
* Filter-aware variant of {@link getFindingGroupDelta}. When a status filter
* is active, only delta counters belonging to the filtered statuses contribute
* to the indicator. When no status filter is active, or when the API response
* lacks breakdown counters (legacy shape), this falls back to the aggregate
* delta so rows still surface deltas correctly.
*/
export function getFilteredFindingGroupDelta(
group: FindingGroupDeltaState,
filters: FindingGroupFiltersRecord,
): "new" | "changed" | "none" {
const activeStatuses = getActiveStatusFilter(filters);
if (!activeStatuses || !hasAnyDeltaBreakdown(group)) {
return getFindingGroupDelta(group);
}
if (getNewDeltaForStatuses(group, activeStatuses) > 0) {
return "new";
}
if (getChangedDeltaForStatuses(group, activeStatuses) > 0) {
return "changed";
}
return "none";
}