From 078d4de2be873ea5884a1ed9f918469bf5bcf022 Mon Sep 17 00:00:00 2001 From: Alan Buscaglia Date: Wed, 10 Dec 2025 09:59:49 +0100 Subject: [PATCH] feat(ui): add clickable resource count in scans table with filter badge - Make resource count in Scan Jobs table clickable, linking to Resources filtered by scan - Create generic ActiveFilterBadge component for displaying active URL filters - Add ScanFilterBadge and CheckIdFilterBadge as pre-configured adapters - Remove old ActiveCheckIdFilter in favor of generic component - Show active scan filter badge in Resources view for better UX - Fix any type in ScanDetailsCell props --- .../filters/active-check-id-filter.tsx | 35 -------- ui/components/filters/active-filter-badge.tsx | 90 +++++++++++++++++++ ui/components/filters/index.ts | 2 +- .../scans/table/scans/column-get-scans.tsx | 27 +++++- .../ui/table/data-table-filter-custom.tsx | 10 ++- 5 files changed, 121 insertions(+), 43 deletions(-) delete mode 100644 ui/components/filters/active-check-id-filter.tsx create mode 100644 ui/components/filters/active-filter-badge.tsx diff --git a/ui/components/filters/active-check-id-filter.tsx b/ui/components/filters/active-check-id-filter.tsx deleted file mode 100644 index 63690221db..0000000000 --- a/ui/components/filters/active-check-id-filter.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { X } from "lucide-react"; -import { useSearchParams } from "next/navigation"; - -import { Badge } from "@/components/shadcn"; -import { useUrlFilters } from "@/hooks/use-url-filters"; - -export const ActiveCheckIdFilter = () => { - const searchParams = useSearchParams(); - const { clearFilter } = useUrlFilters(); - - const checkIdFilter = searchParams.get("filter[check_id__in]"); - - if (!checkIdFilter) { - return null; - } - - const checkIds = checkIdFilter.split(","); - const displayText = - checkIds.length > 1 - ? `${checkIds.length} Check IDs filtered` - : `Check ID: ${checkIds[0]}`; - - return ( - clearFilter("check_id__in")} - > - {displayText} - - - ); -}; diff --git a/ui/components/filters/active-filter-badge.tsx b/ui/components/filters/active-filter-badge.tsx new file mode 100644 index 0000000000..a8d12aeb68 --- /dev/null +++ b/ui/components/filters/active-filter-badge.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { X } from "lucide-react"; +import { useSearchParams } from "next/navigation"; + +import { Badge } from "@/components/shadcn"; +import { useUrlFilters } from "@/hooks/use-url-filters"; + +export interface ActiveFilterBadgeProps { + /** + * The filter key without the "filter[]" wrapper. + * Example: "scan__in", "check_id__in", "provider__in" + */ + filterKey: string; + + /** + * Label to display before the value. + * Example: "Scan", "Check ID", "Provider" + */ + label: string; + + /** + * Optional function to format a single value for display. + * Useful for truncating UUIDs, etc. + * Default: shows value as-is + */ + formatValue?: (value: string) => string; + + /** + * Optional function to format the display when multiple values are selected. + * Default: "{count} {label}s filtered" + */ + formatMultiple?: (count: number, label: string) => string; +} + +export const ActiveFilterBadge = ({ + filterKey, + label, + formatValue = (v) => v, + formatMultiple = (count, lbl) => `${count} ${lbl}s filtered`, +}: ActiveFilterBadgeProps) => { + const searchParams = useSearchParams(); + const { clearFilter } = useUrlFilters(); + + const fullKey = filterKey.startsWith("filter[") + ? filterKey + : `filter[${filterKey}]`; + + const filterValue = searchParams.get(fullKey); + + if (!filterValue) { + return null; + } + + const values = filterValue.split(","); + const displayText = + values.length > 1 + ? formatMultiple(values.length, label) + : `${label}: ${formatValue(values[0])}`; + + return ( + clearFilter(filterKey)} + > + {displayText} + + + ); +}; + +/** + * Pre-configured filter badges for common use cases + */ +export const ScanFilterBadge = () => ( + `${id.slice(0, 8)}...`} + /> +); + +export const CheckIdFilterBadge = () => ( + `${count} Check IDs filtered`} + /> +); diff --git a/ui/components/filters/index.ts b/ui/components/filters/index.ts index a7f8b28f8b..4d6fa55ebe 100644 --- a/ui/components/filters/index.ts +++ b/ui/components/filters/index.ts @@ -1,4 +1,4 @@ -export * from "./active-check-id-filter"; +export * from "./active-filter-badge"; export * from "./clear-filters-button"; export * from "./custom-account-selection"; export * from "./custom-checkbox-muted-findings"; diff --git a/ui/components/scans/table/scans/column-get-scans.tsx b/ui/components/scans/table/scans/column-get-scans.tsx index 5f55628b29..344a2ed5ce 100644 --- a/ui/components/scans/table/scans/column-get-scans.tsx +++ b/ui/components/scans/table/scans/column-get-scans.tsx @@ -1,9 +1,11 @@ "use client"; -import { ColumnDef } from "@tanstack/react-table"; +import { ColumnDef, Row } from "@tanstack/react-table"; +import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { InfoIcon } from "@/components/icons"; +import { Button } from "@/components/shadcn"; import { TableLink } from "@/components/ui/custom"; import { DateWithTime, EntityInfo } from "@/components/ui/entities"; import { TriggerSheet } from "@/components/ui/sheet"; @@ -19,7 +21,7 @@ const getScanData = (row: { original: ScanProps }) => { return row.original; }; -const ScanDetailsCell = ({ row }: { row: any }) => { +const ScanDetailsCell = ({ row }: { row: Row }) => { const router = useRouter(); const searchParams = useSearchParams(); const scanId = searchParams.get("scanId"); @@ -192,11 +194,28 @@ export const ColumnGetScans: ColumnDef[] = [ ), cell: ({ row }) => { const { - attributes: { unique_resource_count }, + id, + attributes: { unique_resource_count, state }, } = getScanData(row); + const isCompleted = state === "completed"; + + if (!isCompleted) { + return ( +
+ + {unique_resource_count ?? "-"} + +
+ ); + } + return (
- {unique_resource_count} +
); }, diff --git a/ui/components/ui/table/data-table-filter-custom.tsx b/ui/components/ui/table/data-table-filter-custom.tsx index b2735b55f5..0819e510f9 100644 --- a/ui/components/ui/table/data-table-filter-custom.tsx +++ b/ui/components/ui/table/data-table-filter-custom.tsx @@ -3,7 +3,10 @@ import { useSearchParams } from "next/navigation"; import { ComplianceScanInfo } from "@/components/compliance/compliance-header/compliance-scan-info"; -import { ActiveCheckIdFilter } from "@/components/filters/active-check-id-filter"; +import { + CheckIdFilterBadge, + ScanFilterBadge, +} from "@/components/filters/active-filter-badge"; import { ClearFiltersButton } from "@/components/filters/clear-filters-button"; import { MultiSelect, @@ -165,8 +168,9 @@ export const DataTableFilterCustom = ({ ); })} -
- +
+ +