feat: tweaks filters

This commit is contained in:
Pablo Lara
2024-11-20 13:58:45 +01:00
parent ebc96bed06
commit 7fd53c1bc3
6 changed files with 129 additions and 31 deletions
+84 -2
View File
@@ -2,6 +2,8 @@ import { Spacer } from "@nextui-org/react";
import React, { Suspense } from "react";
import { getFindings } 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 {
@@ -20,14 +22,94 @@ export default async function Findings({
}) {
const searchParamsKey = JSON.stringify(searchParams || {});
// Get findings data
const findingsData = await getFindings({});
const providersData = await getProviders({});
const scansData = await getScans({});
// Extract provider UIDs
const providerUIDs = providersData?.data
?.map((provider: any) => provider.attributes.uid)
.filter(Boolean);
// Extract scan UUIDs with "completed" state and more than one resource
const completedScans = scansData?.data
?.filter(
(scan: any) =>
scan.attributes.state === "completed" &&
scan.attributes.unique_resource_count > 1 &&
scan.attributes.name, // Ensure it has a name
)
.map((scan: any) => ({
id: scan.id,
name: scan.attributes.name,
}));
const completedScanIds = completedScans?.map((scan: any) => scan.id) || [];
// Create resource dictionary
const resourceDict = createDict("resources", findingsData);
// Get unique regions and services
const allRegionsAndServices = findingsData?.data
?.flatMap((finding: FindingProps) => {
const resource =
resourceDict[finding.relationships?.resources?.data?.[0]?.id];
return {
region: resource?.attributes?.region,
service: resource?.attributes?.service,
};
})
.filter(Boolean);
const uniqueRegions = Array.from(
new Set<string>(
allRegionsAndServices
.map((item: { region: string }) => item.region)
.filter(Boolean),
),
);
const uniqueServices = Array.from(
new Set<string>(
allRegionsAndServices
.map((item: { service: string }) => item.service)
.filter(Boolean),
),
);
return (
<>
<Header title="Findings" icon="ph:list-checks-duotone" />
<Spacer />
<Spacer y={4} />
<FilterControls search providers date />
<FilterControls search date />
<Spacer y={8} />
<DataTableFilterCustom filters={filterFindings || []} />
<DataTableFilterCustom
filters={[
...filterFindings,
{
key: "region__in",
labelCheckboxGroup: "Regions",
values: uniqueRegions,
},
{
key: "service__in",
labelCheckboxGroup: "Services",
values: uniqueServices,
},
{
key: "provider_uid__in",
labelCheckboxGroup: "Account",
values: providerUIDs,
},
{
key: "scan__in",
labelCheckboxGroup: "Scans",
values: completedScanIds, // Use UUIDs in the filter
},
]}
defaultOpen={true}
/>
<Spacer y={8} />
<Suspense key={searchParamsKey} fallback={<SkeletonTableFindings />}>
<SSRDataTable searchParams={searchParams} />
+15 -17
View File
@@ -16,27 +16,25 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
title,
passingRequirements,
totalRequirements,
prevPassingRequirements,
prevTotalRequirements,
}) => {
const ratingPercentage = Math.floor(
(passingRequirements / totalRequirements) * 100,
);
const prevRatingPercentage = Math.floor(
(prevPassingRequirements / prevTotalRequirements) * 100,
);
// const prevRatingPercentage = Math.floor(
// (prevPassingRequirements / prevTotalRequirements) * 100,
// );
const getScanChange = () => {
const scanDifference = ratingPercentage - prevRatingPercentage;
if (scanDifference < 0 && scanDifference <= -1) {
return `${scanDifference}% from last scan`;
}
if (scanDifference > 0 && scanDifference >= 1) {
return `+${scanDifference}% from last scan`;
}
return "No change from last scan";
};
// const getScanChange = () => {
// const scanDifference = ratingPercentage - prevRatingPercentage;
// if (scanDifference < 0 && scanDifference <= -1) {
// return `${scanDifference}% from last scan`;
// }
// if (scanDifference > 0 && scanDifference >= 1) {
// return `+${scanDifference}% from last scan`;
// }
// return "No changes from last scan";
// };
const getRatingColor = (ratingPercentage: number) => {
if (ratingPercentage <= 10) {
@@ -50,7 +48,7 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
return (
<Card fullWidth isPressable isHoverable shadow="sm">
<CardBody className="flex flex-row items-center justify-between space-x-4">
<CardBody className="flex flex-row items-center justify-between space-x-4 dark:bg-prowler-blue-800">
<div className="flex w-full items-center space-x-4">
<Image
src={getComplianceIcon(title)}
@@ -75,7 +73,7 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
</span>
Passing Requirements
</small>
<small>{getScanChange()}</small>
{/* <small>{getScanChange()}</small> */}
</div>
</div>
</div>
+8 -3
View File
@@ -35,20 +35,25 @@ export const filterScans = [
export const filterFindings = [
{
key: "severity",
key: "severity__in",
labelCheckboxGroup: "Severity",
values: ["critical", "high", "medium", "low", "informational"],
},
{
key: "status",
key: "status__in",
labelCheckboxGroup: "Status",
values: ["PASS", "FAIL", "MANUAL", "MUTED"],
},
{
key: "delta",
key: "delta__in",
labelCheckboxGroup: "Delta",
values: ["new", "changed"],
},
{
key: "provider_type__in",
labelCheckboxGroup: "Provider",
values: ["aws", "azure", "gcp", "kubernetes"],
},
// Add more filter categories as needed
];
@@ -31,7 +31,6 @@ export function DataTableRowActions<FindingProps>({
row,
}: DataTableRowActionsProps<FindingProps>) {
const findingId = (row.original as { id: string }).id;
console.log(findingId);
return (
<>
{/* <CustomAlertModal
@@ -79,6 +78,7 @@ export function DataTableRowActions<FindingProps>({
startContent={<EditDocumentBulkIcon className={iconClasses} />}
// onClick={() => setIsEditOpen(true)}
>
<span className="hidden text-sm">{findingId}</span>
Send to Jira
</DropdownItem>
<DropdownItem
@@ -18,7 +18,7 @@ import { PlusCircleIcon } from "@/components/icons";
import { CustomDropdownFilterProps } from "@/types";
const filterSelectedClass =
"inline-flex items-center border py-0.5 text-xs transition-colors border-transparent bg-default-500 text-secondary-foreground hover:bg-default-500/80 rounded-md px-2 font-normal";
"inline-flex items-center border py-1 text-xs transition-colors border-transparent bg-default-500 text-secondary-foreground hover:bg-default-500/80 rounded-md px-2 font-normal";
export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
filter,
@@ -97,9 +97,9 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
<Popover backdrop="transparent" placement="bottom-start">
<PopoverTrigger>
<Button
className="border-input hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center justify-center whitespace-nowrap rounded-md border border-dashed bg-background px-3 text-xs font-medium shadow-sm transition-colors focus-visible:outline-none disabled:opacity-50 dark:bg-prowler-blue-800"
className="border-input hover:bg-accent hover:text-accent-foreground inline-flex h-10 items-center justify-center whitespace-nowrap rounded-md border border-dashed bg-background px-3 text-xs font-medium shadow-sm transition-colors focus-visible:outline-none disabled:opacity-50 dark:bg-prowler-blue-800"
startContent={<PlusCircleIcon size={16} />}
size="sm"
size="md"
>
<h3 className="text-small">{filter?.labelCheckboxGroup}</h3>
@@ -10,14 +10,16 @@ import { FilterOption } from "@/types";
export interface DataTableFilterCustomProps {
filters: FilterOption[];
defaultOpen?: boolean;
}
export const DataTableFilterCustom = ({
filters,
defaultOpen = false,
}: DataTableFilterCustomProps) => {
const router = useRouter();
const searchParams = useSearchParams();
const [showFilters, setShowFilters] = useState(false);
const [showFilters, setShowFilters] = useState(defaultOpen);
const pushDropdownFilter = useCallback(
(key: string, values: string[]) => {
@@ -36,14 +38,19 @@ export const DataTableFilterCustom = ({
);
return (
<div className="flex flex-col gap-4 md:flex-row md:items-start">
<div
className={`flex ${
filters.length > 4 ? "flex-col" : "flex-col md:flex-row"
} gap-4`}
>
<CustomButton
ariaLabel={showFilters ? "Hide Filters" : "Show Filters"}
variant="flat"
color={showFilters ? "action" : "primary"}
size="sm"
size="md"
startContent={<CustomFilterIcon size={16} />}
onPress={() => setShowFilters(!showFilters)}
className="w-fit"
>
<h3 className="text-small">
{showFilters ? "Hide Filters" : "Show Filters"}
@@ -53,11 +60,17 @@ export const DataTableFilterCustom = ({
<div
className={`transition-all duration-700 ease-in-out ${
showFilters
? "max-h-96 w-full translate-x-0 overflow-visible opacity-100 md:max-w-80"
? "max-h-96 w-full translate-x-0 overflow-visible opacity-100"
: "max-h-0 -translate-x-full overflow-hidden opacity-0"
}`}
>
<div className="flex flex-col gap-4 md:flex-row">
<div
className={`grid gap-4 ${
filters.length > 4
? "grid-cols-1 md:grid-cols-4"
: "grid-cols-1 md:grid-cols-3"
}`}
>
{filters.map((filter) => (
<CustomDropdownFilter
key={filter.key}