From 999a8d1428cacee15896c8fb62d289c05fa68f77 Mon Sep 17 00:00:00 2001 From: "Hugo P.Brito" Date: Thu, 16 Jul 2026 09:56:29 +0100 Subject: [PATCH] feat(ui): expand finding groups from deep links --- ui/app/(prowler)/findings/page.tsx | 5 + .../table/findings-group-table.test.tsx | 91 +++++++++++++++++++ .../findings/table/findings-group-table.tsx | 27 +++++- 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 77cb1d8d0e..2b0eaac475 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -145,6 +145,10 @@ const SSRDataTable = async ({ }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); + const expandedCheckIdParam = searchParams.expandedCheckId; + const expandedCheckId = Array.isArray(expandedCheckIdParam) + ? expandedCheckIdParam[0] + : expandedCheckIdParam; const { encodedSort } = extractSortAndKey(searchParams); const hasHistoricalData = hasDateOrScanFilter(filters); @@ -178,6 +182,7 @@ const SSRDataTable = async ({ metadata={findingGroupsData?.meta} resolvedFilters={filters} hasHistoricalData={hasHistoricalData} + expandedCheckId={expandedCheckId} /> ); diff --git a/ui/components/findings/table/findings-group-table.test.tsx b/ui/components/findings/table/findings-group-table.test.tsx index 2d8b387ca5..7189e0bbeb 100644 --- a/ui/components/findings/table/findings-group-table.test.tsx +++ b/ui/components/findings/table/findings-group-table.test.tsx @@ -318,6 +318,97 @@ describe("FindingsGroupTable", () => { }); }); + describe("expanded deep link", () => { + it("opens the matching drillable group from expandedCheckId", () => { + // Given + const data = [ + { checkId: "check-a", resourcesTotal: 1 }, + { checkId: "check-b", resourcesTotal: 1 }, + ] as unknown as Parameters[0]["data"]; + + render( + , + ); + + // Then + expect( + screen.getByRole("button", { name: "Select finding-1" }), + ).toBeInTheDocument(); + }); + + it("ignores a missing expandedCheckId", () => { + // Given + const data = [ + { checkId: "check-a", resourcesTotal: 1 }, + ] as unknown as Parameters[0]["data"]; + + render( + , + ); + + // Then + expect( + screen.queryByRole("button", { name: "Select finding-1" }), + ).not.toBeInTheDocument(); + }); + + it("ignores a non-drillable expandedCheckId", () => { + // Given + const data = [ + { checkId: "check-a", resourcesTotal: 0 }, + ] as unknown as Parameters[0]["data"]; + + render( + , + ); + + // Then + expect( + screen.queryByRole("button", { name: "Select finding-1" }), + ).not.toBeInTheDocument(); + }); + + it("allows manual collapse after opening from expandedCheckId", async () => { + // Given + const user = userEvent.setup(); + const data = [ + { checkId: "check-a", resourcesTotal: 1 }, + ] as unknown as Parameters[0]["data"]; + + render( + , + ); + + // When + await user.click(screen.getByRole("button", { name: "Expand check-a" })); + + // Then + expect( + screen.queryByRole("button", { name: "Select finding-1" }), + ).not.toBeInTheDocument(); + }); + }); + describe("bulk Jira action", () => { it("should summarize group-only selections", async () => { // Given diff --git a/ui/components/findings/table/findings-group-table.tsx b/ui/components/findings/table/findings-group-table.tsx index 0c0cb7abe6..947345bfc6 100644 --- a/ui/components/findings/table/findings-group-table.tsx +++ b/ui/components/findings/table/findings-group-table.tsx @@ -2,7 +2,7 @@ import { Row, RowSelectionState } from "@tanstack/react-table"; import { useRouter, useSearchParams } from "next/navigation"; -import { Suspense, useRef, useState } from "react"; +import { Suspense, useEffect, useRef, useState } from "react"; import { resolveFindingIdsByVisibleGroupResources } from "@/actions/findings/findings-by-resource"; import { CustomCheckboxMutedFindings } from "@/components/filters/custom-checkbox-muted-findings"; @@ -78,6 +78,7 @@ interface FindingsGroupTableProps { metadata?: MetaDataProps; resolvedFilters: Record; hasHistoricalData: boolean; + expandedCheckId?: string; } export function FindingsGroupTable({ @@ -85,6 +86,7 @@ export function FindingsGroupTable({ metadata, resolvedFilters, hasHistoricalData, + expandedCheckId: requestedExpandedCheckId, }: FindingsGroupTableProps) { const router = useRouter(); const searchParams = useSearchParams(); @@ -105,6 +107,29 @@ export function FindingsGroupTable({ const filters = resolvedFilters; const groupedJiraDispatchEnabled = isGroupedJiraDispatchEnabled(); + useEffect(() => { + if (!requestedExpandedCheckId) return; + + const requestedGroup = safeData.find( + (group) => group.checkId === requestedExpandedCheckId, + ); + + if (!requestedGroup || !canDrillDownFindingGroup(requestedGroup)) { + setExpandedCheckId(null); + setExpandedGroup(null); + setResourceSearchInput(""); + setResourceSearch(""); + setResourceSelection([]); + return; + } + + setExpandedCheckId(requestedExpandedCheckId); + setExpandedGroup(requestedGroup); + setResourceSearchInput(""); + setResourceSearch(""); + setResourceSelection([]); + }, [requestedExpandedCheckId, safeData]); + // Exclude expanded group from group-level mutes when it has resource selections. const selectedCheckIds = Object.keys(rowSelection) .filter((key) => rowSelection[key])