mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-08 12:24:45 +00:00
feat(ui): expand finding groups from deep links
This commit is contained in:
3 files changed
+122
-1
No files matched your search
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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<typeof FindingsGroupTable>[0]["data"];
|
||||
|
||||
render(
|
||||
<FindingsGroupTable
|
||||
data={data}
|
||||
resolvedFilters={{}}
|
||||
hasHistoricalData={false}
|
||||
expandedCheckId="check-b"
|
||||
/>,
|
||||
);
|
||||
|
||||
// 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<typeof FindingsGroupTable>[0]["data"];
|
||||
|
||||
render(
|
||||
<FindingsGroupTable
|
||||
data={data}
|
||||
resolvedFilters={{}}
|
||||
hasHistoricalData={false}
|
||||
expandedCheckId="check-missing"
|
||||
/>,
|
||||
);
|
||||
|
||||
// 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<typeof FindingsGroupTable>[0]["data"];
|
||||
|
||||
render(
|
||||
<FindingsGroupTable
|
||||
data={data}
|
||||
resolvedFilters={{}}
|
||||
hasHistoricalData={false}
|
||||
expandedCheckId="check-a"
|
||||
/>,
|
||||
);
|
||||
|
||||
// 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<typeof FindingsGroupTable>[0]["data"];
|
||||
|
||||
render(
|
||||
<FindingsGroupTable
|
||||
data={data}
|
||||
resolvedFilters={{}}
|
||||
hasHistoricalData={false}
|
||||
expandedCheckId="check-a"
|
||||
/>,
|
||||
);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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<string, string>;
|
||||
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])
|
||||
|
||||
Reference in new issue
Block a user