diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 04f90c81d7..6f1426da12 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -25,6 +25,7 @@ module.exports = { eqeqeq: 2, quotes: ["error", "double", "avoid-escape"], "@typescript-eslint/no-explicit-any": "off", + "security/detect-object-injection": "off", "prettier/prettier": [ "error", { diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 0e7dd6c9b6..f75638f792 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -10,6 +10,7 @@ import { } from "@/components/findings/table"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; +import { createDict } from "@/lib"; import { FindingProps, SearchParamsProps } from "@/types/components"; export default async function Findings({ @@ -22,7 +23,7 @@ export default async function Findings({
- + }> @@ -49,60 +50,34 @@ const SSRDataTable = async ({ const findingsData = await getFindings({ query, page, sort, filters }); - // Create dictionaries from the included data - const resourceDict = Object.fromEntries( - findingsData.included - .filter((item: { type: string }) => item.type === "Resource") - .map((resource: { id: string }) => [resource.id, resource]), - ); + // Create dictionaries for resources, scans, and providers + const resourceDict = createDict("Resource", findingsData); + const scanDict = createDict("Scan", findingsData); + const providerDict = createDict("Provider", findingsData); - const scanDict = Object.fromEntries( - findingsData.included - .filter((item: { type: string }) => item.type === "Scan") - .map((scan: { id: string }) => [scan.id, scan]), - ); - - const providerDict = Object.fromEntries( - findingsData.included - .filter((item: { type: string }) => item.type === "Provider") - .map((provider: { id: string }) => [provider.id, provider]), - ); - - // Enrich each finding with its corresponding resource, scan, and provider - const enrichedFindings = findingsData.data.map((finding: FindingProps) => { - const scanId = finding.relationships?.scan?.data?.id; - // eslint-disable-next-line security/detect-object-injection - const scan = scanId ? scanDict[scanId] : undefined; - - const resourceId = finding.relationships?.resources?.data?.[0]?.id; - console.log(resourceId, "resourceId"); + // Expand each finding with its corresponding resource, scan, and provider + const expandedFindings = findingsData.data.map((finding: FindingProps) => { + const scan = scanDict[finding.relationships?.scan?.data?.id]; const resource = - resourceId && resourceDict ? resourceDict[resourceId] : undefined; - - const providerId = resource?.relationships?.provider?.data?.id; - // eslint-disable-next-line security/detect-object-injection - const provider = providerId ? providerDict[providerId] : undefined; + resourceDict[finding.relationships?.resources?.data?.[0]?.id]; + const provider = providerDict[resource?.relationships?.provider?.data?.id]; return { ...finding, - relationships: { - scan: scan, - resource: resource, - provider: provider, - }, + relationships: { scan, resource, provider }, }; }); // Create the new object while maintaining the original structure - const enrichedResponse = { + const expandedResponse = { ...findingsData, - data: enrichedFindings, + data: expandedFindings, }; return ( diff --git a/components/findings/table/column-findings.tsx b/components/findings/table/column-findings.tsx index 98803d2a7c..3391207093 100644 --- a/components/findings/table/column-findings.tsx +++ b/components/findings/table/column-findings.tsx @@ -2,18 +2,25 @@ import { ColumnDef } from "@tanstack/react-table"; -import { DateWithTime } from "@/components/ui/entities"; import { DataTableColumnHeader, SeverityBadge, + Status, StatusBadge, } from "@/components/ui/table"; import { FindingProps } from "@/types"; import { DataTableRowActions } from "./data-table-row-actions"; +const statusMap: Record<"PASS" | "FAIL" | "MANUAL" | "MUTED", Status> = { + PASS: "completed", + FAIL: "failed", + MANUAL: "completed", + MUTED: "cancelled", +}; + const getFindingsData = (row: { original: FindingProps }) => { - // console.log(row.original); + console.log(row.original); return row.original; }; @@ -26,7 +33,6 @@ const getResourceData = ( field: keyof FindingProps["relationships"]["resource"]["attributes"], ) => { return ( - // eslint-disable-next-line security/detect-object-injection row.original.relationships?.resource?.attributes?.[field] || `No ${field} found in resource` ); @@ -37,7 +43,6 @@ const getProviderData = ( field: keyof FindingProps["relationships"]["provider"]["attributes"], ) => { return ( - // eslint-disable-next-line security/detect-object-injection row.original.relationships?.provider?.attributes?.[field] || `No ${field} found in provider` ); @@ -48,72 +53,38 @@ const getScanData = ( field: keyof FindingProps["relationships"]["scan"]["attributes"], ) => { return ( - // eslint-disable-next-line security/detect-object-injection row.original.relationships?.scan?.attributes?.[field] || `No ${field} found in scan` ); }; export const ColumnFindings: ColumnDef[] = [ - // { - // header: " ", - // cell: ({ row }) =>

{row.index + 1}

, - // }, { accessorKey: "check", - header: ({ column }) => ( - - ), + header: "Check", cell: ({ row }) => { const { checktitle } = getFindingsMetadata(row); return

{checktitle}

; }, }, { - accessorKey: "region", - header: "Region", + accessorKey: "scanName", + header: "Scan Name", cell: ({ row }) => { - const region = getResourceData(row, "region"); + const name = getScanData(row, "name"); return ( - <> -
{region}
- +

+ {typeof name === "string" || typeof name === "number" + ? name + : "Invalid data"} +

); }, }, - - // { - // accessorKey: "uid", - // header: ({ column }) => ( - // - // ), - // cell: ({ row }) => { - // const { - // attributes: { uid }, - // } = getFindingsData(row); - // return ; - // }, - // }, - // { - // accessorKey: "provider", - // header: "Provider Alias", - // cell: ({ row }) => { - // const { - // attributes: { alias }, - // } = getProviderData(row, "alias"); - // return

{alias}

; - // }, - // }, { accessorKey: "severity", - header: ({ column }) => ( - - ), + header: "Severity", cell: ({ row }) => { const { attributes: { severity }, @@ -128,38 +99,44 @@ export const ColumnFindings: ColumnDef[] = [ const { attributes: { status }, } = getFindingsData(row); - // Temporarily overwriting the value until the API is functional. - return ; + + const mappedStatus = statusMap[status]; + + return ; + }, + }, + { + accessorKey: "region", + header: "Region", + cell: ({ row }) => { + const region = getResourceData(row, "region"); + + return ( + <> +
{typeof region === "string" ? region : "Invalid region"}
+ + ); }, }, { accessorKey: "service", - header: ({ column }) => ( - - ), + header: "Service", cell: ({ row }) => { const { servicename } = getFindingsMetadata(row); return

{servicename}

; }, }, { - accessorKey: "added", - header: ({ column }) => ( - - ), + accessorKey: "account", + header: "Account", cell: ({ row }) => { - const { - attributes: { inserted_at }, - } = getFindingsData(row); - return ; + const account = getProviderData(row, "uid"); + + return ( + <> +
{typeof account === "string" ? account : "Invalid account"}
+ + ); }, }, { diff --git a/components/findings/table/data-table-row-actions.tsx b/components/findings/table/data-table-row-actions.tsx index dea023d359..9513bb11a8 100644 --- a/components/findings/table/data-table-row-actions.tsx +++ b/components/findings/table/data-table-row-actions.tsx @@ -14,7 +14,6 @@ import { EditDocumentBulkIcon, } from "@nextui-org/shared-icons"; import { Row } from "@tanstack/react-table"; -import clsx from "clsx"; // import { useState } from "react"; import { VerticalDotsIcon } from "@/components/icons"; @@ -74,30 +73,22 @@ export function DataTableRowActions({ > } // onClick={() => setIsEditOpen(true)} > - Edit Finding + Send to Jira - - - } - // onClick={() => setIsDeleteOpen(true)} + key="slack" + description="Allows you to send the finding to Slack" + textValue="Send to Slack" + startContent={} + // onClick={() => setIsEditOpen(true)} > - Delete Finding + Send to Slack diff --git a/components/ui/table/status-badge.tsx b/components/ui/table/status-badge.tsx index c0f0356407..932d4105d6 100644 --- a/components/ui/table/status-badge.tsx +++ b/components/ui/table/status-badge.tsx @@ -1,7 +1,7 @@ import { Chip } from "@nextui-org/react"; import React from "react"; -type Status = +export type Status = | "available" | "scheduled" | "executing" diff --git a/lib/custom.ts b/lib/helper.ts similarity index 76% rename from lib/custom.ts rename to lib/helper.ts index a33f07d6fe..7eeee17a6f 100644 --- a/lib/custom.ts +++ b/lib/helper.ts @@ -1,5 +1,17 @@ import { MetaDataProps } from "@/types"; +// Helper function to create dictionaries by type +export const createDict = ( + type: string, + data: any, + includedField: string = "included", +) => + Object.fromEntries( + data[includedField] + .filter((item: { type: string }) => item.type === type) + .map((item: { id: string }) => [item.id, item]), + ); + export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value)); export const convertFileToUrl = (file: File) => URL.createObjectURL(file); diff --git a/lib/index.ts b/lib/index.ts index 38d384fb6e..56ebefcc21 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,2 +1,2 @@ -export * from "./custom"; +export * from "./helper"; export * from "./utils"; diff --git a/types/components.ts b/types/components.ts index 89dee77381..086e6e4a60 100644 --- a/types/components.ts +++ b/types/components.ts @@ -154,17 +154,37 @@ export interface FindingProps { updated_at: string; }; relationships: { + resources: { + data: { + type: "Resource"; + id: string; + }[]; + }; scan: { data: { type: "Scan"; id: string; }; + attributes: { + name: string; + trigger: string; + state: string; + unique_resource_count: number; + progress: number; + scanner_args: { + checks_to_execute: string[]; + }; + duration: number; + started_at: string; + completed_at: string; + scheduled_at: string | null; + }; }; resource: { data: { type: "Resource"; id: string; - }; + }[]; attributes: { uid: string; name: string;