mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
feat(ui): connect findings page to finding-groups endpoint
- Replace getFindings with getFindingGroups in findings page - Update skeleton columns to match new table layout - Update exports and selection context for new components Ref: PROWLER-881
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import {
|
||||
adaptFindingGroupsResponse,
|
||||
getFindingGroups,
|
||||
getLatestFindingGroups,
|
||||
} from "@/actions/finding-groups";
|
||||
import {
|
||||
getFindingById,
|
||||
getFindings,
|
||||
getLatestFindings,
|
||||
getLatestMetadataInfo,
|
||||
getMetadataInfo,
|
||||
} from "@/actions/findings";
|
||||
@@ -12,13 +15,12 @@ import { getScans } from "@/actions/scans";
|
||||
import { FindingDetailsSheet } from "@/components/findings";
|
||||
import { FindingsFilters } from "@/components/findings/findings-filters";
|
||||
import {
|
||||
FindingsTableWithSelection,
|
||||
FindingsGroupTable,
|
||||
SkeletonTableFindings,
|
||||
} from "@/components/findings/table";
|
||||
import { ContentLayout } from "@/components/ui";
|
||||
import { FilterTransitionWrapper } from "@/contexts";
|
||||
import {
|
||||
createDict,
|
||||
createScanDetailsMapping,
|
||||
extractFiltersAndQuery,
|
||||
extractSortAndKey,
|
||||
@@ -60,13 +62,12 @@ export default async function Findings({
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
|
||||
// Process the finding data to match the expected structure
|
||||
// Process the finding data to match the expected structure (for detail sheet)
|
||||
const processedFinding = findingByIdData?.data
|
||||
? (() => {
|
||||
const finding = findingByIdData.data;
|
||||
const included = findingByIdData.included || [];
|
||||
|
||||
// Build dictionaries from included data
|
||||
type IncludedItem = {
|
||||
type: string;
|
||||
id: string;
|
||||
@@ -179,65 +180,40 @@ const SSRDataTable = async ({
|
||||
}) => {
|
||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
||||
const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10);
|
||||
const defaultSort = "severity,status,-inserted_at";
|
||||
const defaultSort = "-severity,-fail_count,-last_seen_at";
|
||||
|
||||
const { encodedSort } = extractSortAndKey({
|
||||
...searchParams,
|
||||
sort: searchParams.sort ?? defaultSort,
|
||||
});
|
||||
|
||||
const { filters, query } = extractFiltersAndQuery(searchParams);
|
||||
const { filters } = extractFiltersAndQuery(searchParams);
|
||||
// Check if the searchParams contain any date or scan filter
|
||||
const hasDateOrScan = hasDateOrScanFilter(searchParams);
|
||||
|
||||
const fetchFindings = hasDateOrScan ? getFindings : getLatestFindings;
|
||||
const fetchFindingGroups = hasDateOrScan
|
||||
? getFindingGroups
|
||||
: getLatestFindingGroups;
|
||||
|
||||
const findingsData = await fetchFindings({
|
||||
query,
|
||||
const findingGroupsData = await fetchFindingGroups({
|
||||
page,
|
||||
sort: encodedSort,
|
||||
filters,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
// Create dictionaries for resources, scans, and providers
|
||||
const resourceDict = createDict("resources", findingsData);
|
||||
const scanDict = createDict("scans", findingsData);
|
||||
const providerDict = createDict("providers", findingsData);
|
||||
|
||||
// Expand each finding with its corresponding resource, scan, and provider
|
||||
const expandedFindings = findingsData?.data
|
||||
? findingsData.data.map((finding: FindingProps) => {
|
||||
const scan = scanDict[finding.relationships?.scan?.data?.id];
|
||||
const resource =
|
||||
resourceDict[finding.relationships?.resources?.data?.[0]?.id];
|
||||
const provider = providerDict[scan?.relationships?.provider?.data?.id];
|
||||
|
||||
return {
|
||||
...finding,
|
||||
relationships: { scan, resource, provider },
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
// Create the new object while maintaining the original structure
|
||||
const expandedResponse = {
|
||||
...findingsData,
|
||||
data: expandedFindings,
|
||||
};
|
||||
// Transform API response to FindingGroupRow[]
|
||||
const groups = adaptFindingGroupsResponse(findingGroupsData);
|
||||
|
||||
return (
|
||||
<>
|
||||
{findingsData?.errors && (
|
||||
{findingGroupsData?.errors && (
|
||||
<div className="text-small mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-red-700">
|
||||
<p className="mr-2 font-semibold">Error:</p>
|
||||
<p>{findingsData.errors[0].detail}</p>
|
||||
<p>{findingGroupsData.errors[0].detail}</p>
|
||||
</div>
|
||||
)}
|
||||
<FindingsTableWithSelection
|
||||
data={expandedResponse?.data || []}
|
||||
metadata={findingsData?.meta}
|
||||
/>
|
||||
<FindingsGroupTable data={groups} metadata={findingGroupsData?.meta} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
import { FindingProps } from "@/types";
|
||||
|
||||
interface FindingsSelectionContextValue {
|
||||
selectedFindingIds: string[];
|
||||
selectedFindings: FindingProps[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
selectedFindings: any[];
|
||||
clearSelection: () => void;
|
||||
isSelected: (id: string) => boolean;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
export * from "./column-finding-groups";
|
||||
export * from "./column-finding-resources";
|
||||
export * from "./column-findings";
|
||||
export * from "./data-table-row-actions";
|
||||
export * from "./data-table-row-details";
|
||||
export * from "./finding-detail";
|
||||
export * from "./findings-group-drill-down";
|
||||
export * from "./findings-group-table";
|
||||
export * from "./findings-selection-context";
|
||||
export * from "./findings-table-with-selection";
|
||||
// TODO: PROWLER-379 - Re-export when backend supports grouped findings
|
||||
// export * from "./impacted-resources-cell";
|
||||
export * from "./impacted-providers-cell";
|
||||
export * from "./impacted-resources-cell";
|
||||
export * from "./notification-indicator";
|
||||
export * from "./provider-icon-cell";
|
||||
export * from "./skeleton-table-findings";
|
||||
|
||||
@@ -9,6 +9,10 @@ const SkeletonTableRow = () => {
|
||||
<Skeleton className="size-1.5 rounded-full" />
|
||||
</div>
|
||||
</td>
|
||||
{/* Expand chevron */}
|
||||
<td className="px-1 py-4">
|
||||
<Skeleton className="size-5 rounded" />
|
||||
</td>
|
||||
{/* Checkbox */}
|
||||
<td className="px-2 py-4">
|
||||
<div className="bg-bg-input-primary border-border-input-primary size-6 rounded-sm border shadow-[0_1px_2px_0_rgba(0,0,0,0.1)]" />
|
||||
@@ -24,14 +28,6 @@ const SkeletonTableRow = () => {
|
||||
<Skeleton className="h-4 w-4/5 rounded" />
|
||||
</div>
|
||||
</td>
|
||||
{/* Resource name chip */}
|
||||
<td className="px-3 py-4">
|
||||
<div className="bg-bg-neutral-tertiary flex h-8 w-28 items-center gap-2 rounded-lg px-2">
|
||||
<Skeleton className="size-4 rounded" />
|
||||
<Skeleton className="h-3.5 w-16 rounded" />
|
||||
<Skeleton className="ml-auto size-3.5 rounded" />
|
||||
</div>
|
||||
</td>
|
||||
{/* Severity */}
|
||||
<td className="px-3 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -39,21 +35,17 @@ const SkeletonTableRow = () => {
|
||||
<Skeleton className="h-4 w-12 rounded" />
|
||||
</div>
|
||||
</td>
|
||||
{/* Provider icon */}
|
||||
{/* Provider icons */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="size-9 rounded-lg" />
|
||||
</td>
|
||||
{/* Service */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-20 rounded" />
|
||||
</td>
|
||||
{/* Time */}
|
||||
<td className="px-3 py-4">
|
||||
<div className="space-y-1">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<Skeleton className="h-3 w-20 rounded" />
|
||||
<div className="flex items-center gap-1">
|
||||
<Skeleton className="size-7 rounded-md" />
|
||||
<Skeleton className="size-7 rounded-md" />
|
||||
</div>
|
||||
</td>
|
||||
{/* Resources badge */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-6 w-16 rounded-md" />
|
||||
</td>
|
||||
{/* Actions */}
|
||||
<td className="px-2 py-4">
|
||||
<Skeleton className="size-6 rounded" />
|
||||
@@ -81,6 +73,8 @@ export const SkeletonTableFindings = () => {
|
||||
<tr className="border-border-neutral-secondary border-b">
|
||||
{/* Notification - empty header */}
|
||||
<th className="w-6 py-3" />
|
||||
{/* Expand - empty header */}
|
||||
<th className="w-8 py-3" />
|
||||
{/* Checkbox */}
|
||||
<th className="w-10 px-2 py-3">
|
||||
<div className="bg-bg-input-primary border-border-input-primary size-6 rounded-sm border shadow-[0_1px_2px_0_rgba(0,0,0,0.1)]" />
|
||||
@@ -93,25 +87,17 @@ export const SkeletonTableFindings = () => {
|
||||
<th className="w-[300px] px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-14 rounded" />
|
||||
</th>
|
||||
{/* Resource name */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</th>
|
||||
{/* Severity */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-14 rounded" />
|
||||
</th>
|
||||
{/* Provider */}
|
||||
{/* Providers */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-14 rounded" />
|
||||
<Skeleton className="h-4 w-16 rounded" />
|
||||
</th>
|
||||
{/* Service */}
|
||||
{/* Resources */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-12 rounded" />
|
||||
</th>
|
||||
{/* Time */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-10 rounded" />
|
||||
<Skeleton className="h-4 w-16 rounded" />
|
||||
</th>
|
||||
{/* Actions - empty header */}
|
||||
<th className="w-10 py-3" />
|
||||
|
||||
Reference in New Issue
Block a user