import { useMemo } from "react"; import { faArrowDown, faArrowUp, faBan, faMagnifyingGlass, faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; import { EmptyState, IconButton, Input, Pagination, Table, TableContainer, TableSkeleton, TBody, Th, THead, Tr } from "@app/components/v2"; import { ProjectPermissionSub, useProjectPermission } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; import { RESOURCE_DESCRIPTION_HELPER } from "@app/helpers/secretScanningV2"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { SecretScanningScanStatus, TSecretScanningDataSource, useListSecretScanningResources } from "@app/hooks/api/secretScanningV2"; import { SecretScanningResourceRow } from "./SecretScanningResourceRow"; enum ResourcesOrderBy { Name = "name", Findings = "findings", LastScanned = "last-scanned" } type Props = { dataSource: TSecretScanningDataSource; }; export const SecretScanningResourcesTable = ({ dataSource }: Props) => { const { permission } = useProjectPermission(); const canReadResources = permission.can( ProjectPermissionSecretScanningDataSourceActions.ReadResources, ProjectPermissionSub.SecretScanningDataSources ); const { data: resources = [], isPending: isResourcesPending } = useListSecretScanningResources( { dataSourceId: dataSource.id, type: dataSource.type }, { refetchInterval: 30000, enabled: canReadResources } ); const { search, setSearch, setPage, page, perPage, setPerPage, offset, orderDirection, toggleOrderDirection, orderBy, setOrderDirection, setOrderBy } = usePagination(ResourcesOrderBy.Name, { initPerPage: 10 }); const filteredResources = useMemo( () => resources .filter((resource) => { const { name } = resource; const searchValue = search.trim().toLowerCase(); return name.toLowerCase().includes(searchValue); }) .sort((a, b) => { const [resourceOne, resourceTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; switch (orderBy) { case ResourcesOrderBy.Findings: if ( resourceOne.unresolvedFindings === 0 && resourceOne.lastScanStatus === SecretScanningScanStatus.Failed ) return 1; if ( resourceTwo.unresolvedFindings === 0 && resourceTwo.lastScanStatus === SecretScanningScanStatus.Failed ) return -1; return resourceTwo.unresolvedFindings - resourceOne.unresolvedFindings; case ResourcesOrderBy.LastScanned: return ( new Date(resourceTwo.lastScannedAt).getTime() - new Date(resourceOne.lastScannedAt).getTime() ); case ResourcesOrderBy.Name: default: return resourceOne.name.toLowerCase().localeCompare(resourceTwo.name.toLowerCase()); } }), [resources, orderDirection, search, orderBy] ); useResetPageHelper({ totalCount: filteredResources.length, offset, setPage }); const handleSort = (column: ResourcesOrderBy) => { if (column === orderBy) { toggleOrderDirection(); return; } setOrderBy(column); setOrderDirection(OrderByDirection.ASC); }; const getClassName = (col: ResourcesOrderBy) => twMerge("ml-2", orderBy === col ? "" : "opacity-30"); const getColSortIcon = (col: ResourcesOrderBy) => orderDirection === OrderByDirection.DESC && orderBy === col ? faArrowUp : faArrowDown; const resourceDetails = RESOURCE_DESCRIPTION_HELPER[dataSource.type]; return (
setSearch(e.target.value)} leftIcon={} placeholder={`Search ${resourceDetails.pluralNoun}...`} className="flex-1" /> {canReadResources && isResourcesPending && ( )} {filteredResources.slice(offset, perPage * page).map((resource) => ( ))}
Name handleSort(ResourcesOrderBy.Name)} >
Findings handleSort(ResourcesOrderBy.Findings)} >
Last Scan handleSort(ResourcesOrderBy.LastScanned)} >
{!canReadResources && ( )} {Boolean(filteredResources.length) && ( )} {!isResourcesPending && !filteredResources?.length && ( )}
); };