feat: filters relationships in findings and scans page (#8046)

Co-authored-by: Pablo Lara <larabjj@gmail.com>
This commit is contained in:
Alejandro BailoandPablo Lara authored and GitHub committed 2025-06-18 17:19:41 +02:00
1 parent b3f2a1c532
commit 6b7b700a98
22 files changed
+576 -265

No files matched your search

+19 -51
View File
@@ -9,14 +9,13 @@ import {
} from "@/actions/findings";
import { getProviders } from "@/actions/providers";
import { getScans } from "@/actions/scans";
import { filterFindings } from "@/components/filters/data-filters";
import { FilterControls } from "@/components/filters/filter-controls";
import { FindingsFilters } from "@/components/findings/findings-filters";
import {
ColumnFindings,
SkeletonTableFindings,
} from "@/components/findings/table";
import { ContentLayout } from "@/components/ui";
import { DataTable, DataTableFilterCustom } from "@/components/ui/table";
import { DataTable } from "@/components/ui/table";
import {
createDict,
createScanDetailsMapping,
@@ -28,7 +27,7 @@ import {
createProviderDetailsMapping,
extractProviderUIDs,
} from "@/lib/provider-helpers";
import { ScanProps } from "@/types";
import { FilterEntity, ScanEntity, ScanProps } from "@/types";
import { FindingProps, SearchParamsProps } from "@/types/components";
export default async function Findings({
@@ -61,21 +60,11 @@ export default async function Findings({
// Extract provider UIDs and details using helper functions
const providerUIDs = providersData ? extractProviderUIDs(providersData) : [];
const providerDetails = providersData
? createProviderDetailsMapping(providerUIDs, providersData)
? (createProviderDetailsMapping(providerUIDs, providersData) as {
[uid: string]: FilterEntity;
}[])
: [];
// Update the Provider UID filter
const updatedFilters = filterFindings.map((filter) => {
if (filter.key === "provider_uid__in") {
return {
...filter,
values: providerUIDs,
valueLabelMapping: providerDetails,
};
}
return filter;
});
// Extract scan UUIDs with "completed" state and more than one resource
const completedScans = scansData?.data?.filter(
(scan: ScanProps) =>
@@ -86,45 +75,24 @@ export default async function Findings({
const completedScanIds =
completedScans?.map((scan: ScanProps) => scan.id) || [];
const scanDetails = createScanDetailsMapping(completedScans, providersData);
const scanDetails = createScanDetailsMapping(
completedScans,
providersData,
) as { [uid: string]: ScanEntity }[];
return (
<ContentLayout title="Findings" icon="carbon:data-view-alt">
<FilterControls search date />
<Spacer y={8} />
<DataTableFilterCustom
filters={[
...updatedFilters,
{
key: "region__in",
labelCheckboxGroup: "Regions",
values: uniqueRegions,
index: 5,
},
{
key: "service__in",
labelCheckboxGroup: "Services",
values: uniqueServices,
index: 6,
},
{
key: "resource_type__in",
labelCheckboxGroup: "Resource Type",
values: uniqueResourceTypes,
index: 7,
},
{
key: "scan__in",
labelCheckboxGroup: "Scan ID",
values: completedScanIds,
valueLabelMapping: scanDetails,
index: 9,
},
]}
defaultOpen={true}
<FindingsFilters
providerUIDs={providerUIDs}
providerDetails={providerDetails}
completedScans={completedScans || []}
completedScanIds={completedScanIds}
scanDetails={scanDetails}
uniqueRegions={uniqueRegions}
uniqueServices={uniqueServices}
uniqueResourceTypes={uniqueResourceTypes}
/>
<Spacer y={8} />
<Suspense key={searchParamsKey} fallback={<SkeletonTableFindings />}>
<SSRDataTable searchParams={searchParams} />
</Suspense>
+25 -27
View File
@@ -33,37 +33,35 @@ export default function Home({
const searchParamsKey = JSON.stringify(searchParams || {});
return (
<ContentLayout title="Overview" icon="solar:pie-chart-2-outline">
<Spacer y={4} />
<FilterControls providers />
<div className="mx-auto space-y-8 px-0 py-6">
<div className="grid grid-cols-12 gap-6">
<div className="col-span-12 lg:col-span-4">
<Suspense fallback={<SkeletonProvidersOverview />}>
<SSRProvidersOverview />
</Suspense>
</div>
<div className="col-span-12 lg:col-span-4">
<Suspense fallback={<SkeletonFindingsBySeverityChart />}>
<SSRFindingsBySeverity searchParams={searchParams} />
</Suspense>
</div>
<div className="grid grid-cols-12 gap-12 lg:gap-6">
<div className="col-span-12 lg:col-span-4">
<Suspense fallback={<SkeletonProvidersOverview />}>
<SSRProvidersOverview />
</Suspense>
</div>
<div className="col-span-12 lg:col-span-4">
<Suspense fallback={<SkeletonFindingsByStatusChart />}>
<SSRFindingsByStatus searchParams={searchParams} />
</Suspense>
</div>
<div className="col-span-12 lg:col-span-4">
<Suspense fallback={<SkeletonFindingsBySeverityChart />}>
<SSRFindingsBySeverity searchParams={searchParams} />
</Suspense>
</div>
<div className="col-span-12">
<Spacer y={16} />
<Suspense
key={searchParamsKey}
fallback={<SkeletonTableNewFindings />}
>
<SSRDataNewFindingsTable />
</Suspense>
</div>
<div className="col-span-12 lg:col-span-4">
<Suspense fallback={<SkeletonFindingsByStatusChart />}>
<SSRFindingsByStatus searchParams={searchParams} />
</Suspense>
</div>
<div className="col-span-12">
<Spacer y={16} />
<Suspense
key={searchParamsKey}
fallback={<SkeletonTableNewFindings />}
>
<SSRDataNewFindingsTable />
</Suspense>
</div>
</div>
</ContentLayout>
+2 -4
View File
@@ -10,7 +10,7 @@ import {
SkeletonTableProviders,
} from "@/components/providers/table";
import { ContentLayout } from "@/components/ui";
import { DataTable, DataTableFilterCustom } from "@/components/ui/table";
import { DataTable } from "@/components/ui/table";
import { ProviderProps, SearchParamsProps } from "@/types";
export default async function Providers({
@@ -22,14 +22,12 @@ export default async function Providers({
return (
<ContentLayout title="Cloud Providers" icon="fluent:cloud-sync-24-regular">
<FilterControls search />
<FilterControls search customFilters={filterProviders || []} />
<Spacer y={8} />
<div className="flex items-center gap-4 md:justify-end">
<ManageGroupsButton />
<AddProviderButton />
</div>
<Spacer y={4} />
<DataTableFilterCustom filters={filterProviders || []} />
<Spacer y={8} />
<div className="grid grid-cols-12 gap-4">
+32 -55
View File
@@ -3,17 +3,17 @@ import { Suspense } from "react";
import { getProvider, getProviders } from "@/actions/providers";
import { getScans, getScansByState } from "@/actions/scans";
import { FilterControls, filterScans } from "@/components/filters";
import {
AutoRefresh,
NoProvidersAdded,
NoProvidersConnected,
ScansFilters,
} from "@/components/scans";
import { LaunchScanWorkflow } from "@/components/scans/launch-workflow";
import { SkeletonTableScans } from "@/components/scans/table";
import { ColumnGetScans } from "@/components/scans/table/scans";
import { ContentLayout } from "@/components/ui";
import { DataTable, DataTableFilterCustom } from "@/components/ui/table";
import { DataTable } from "@/components/ui/table";
import {
createProviderDetailsMapping,
extractProviderUIDs,
@@ -49,8 +49,7 @@ export default async function Scans({
filters: { "filter[connected]": true },
pageSize: 50,
});
const thereIsNoProviders =
!providersCountConnected?.data || providersCountConnected.data.length === 0;
const thereIsNoProviders = !providersCountConnected?.data;
const thereIsNoProvidersConnected = providersCountConnected?.data?.every(
(provider: ProviderProps) => !provider.attributes.connection.connected,
@@ -71,59 +70,37 @@ export default async function Scans({
? createProviderDetailsMapping(providerUIDs, providersData)
: [];
// Update the Provider UID filter
const updatedFilters = filterScans.map((filter) => {
if (filter.key === "provider_uid__in") {
return {
...filter,
values: providerUIDs,
valueLabelMapping: providerDetails,
};
}
return filter;
});
if (thereIsNoProviders) {
return (
<ContentLayout title="Scans" icon="lucide:scan-search">
<NoProvidersAdded />
</ContentLayout>
);
}
return (
<>
{thereIsNoProviders && (
<>
<Spacer y={4} />
<NoProvidersAdded />
</>
)}
{!thereIsNoProviders && (
<>
{thereIsNoProvidersConnected ? (
<ContentLayout title="Scans" icon="lucide:scan-search">
<Spacer y={8} />
<NoProvidersConnected />
<Spacer y={8} />
</ContentLayout>
) : (
<ContentLayout title="Scans" icon="lucide:scan-search">
<AutoRefresh hasExecutingScan={hasExecutingScan} />
<LaunchScanWorkflow providers={providerInfo} />
<Spacer y={8} />
</ContentLayout>
)}
<div className="grid grid-cols-12 items-start gap-4 px-6 py-4 sm:px-8 xl:px-10">
<div className="col-span-12">
<div className="flex flex-row items-center justify-between">
<DataTableFilterCustom filters={updatedFilters || []} />
<Spacer x={4} />
<FilterControls />
</div>
<Spacer y={8} />
<Suspense key={searchParamsKey} fallback={<SkeletonTableScans />}>
<SSRDataTableScans searchParams={searchParams} />
</Suspense>
</div>
</div>
</>
)}
</>
<ContentLayout title="Scans" icon="lucide:scan-search">
<AutoRefresh hasExecutingScan={hasExecutingScan} />
<>
{thereIsNoProvidersConnected ? (
<>
<Spacer y={8} />
<NoProvidersConnected />
<Spacer y={8} />
</>
) : (
<LaunchScanWorkflow providers={providerInfo} />
)}
<ScansFilters
providerUIDs={providerUIDs}
providerDetails={providerDetails}
/>
<Spacer y={8} />
<Suspense key={searchParamsKey} fallback={<SkeletonTableScans />}>
<SSRDataTableScans searchParams={searchParams} />
</Suspense>
</>
</ContentLayout>
);
}