Skip to content

Commit

Permalink
feat: CVE List - related documents column (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosthe19916 authored Nov 14, 2024
1 parent 8a4eee4 commit 8257938
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 149 deletions.
141 changes: 141 additions & 0 deletions client/src/app/hooks/domain-controls/useSbomsOfVulnerability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React from "react";

import { VulnerabilityStatus } from "@app/api/models";
import { client } from "@app/axios-config/apiInit";
import {
getSbom,
SbomSummary,
VulnerabilityAdvisorySummary,
} from "@app/client";
import { useFetchVulnerabilityById } from "@app/queries/vulnerabilities";

interface SbomOfVulnerability {
sbomId: string;
advisory: VulnerabilityAdvisorySummary;
status: VulnerabilityStatus;
sbom?: SbomSummary;
}

interface SbomOfVulnerabilitySummary {
total: number;
status: { [key in VulnerabilityStatus]: number };
}

const DEFAULT_SUMMARY: SbomOfVulnerabilitySummary = {
total: 0,
status: { affected: 0, fixed: 0, known_not_affected: 0, not_affected: 0 },
};

export const useSbomsOfVulnerability = (sbomId: string) => {
const {
vulnerability,
isFetching: isFetchingAdvisories,
fetchError: fetchErrorAdvisories,
} = useFetchVulnerabilityById(sbomId);

const [allSboms, setAllSboms] = React.useState<SbomOfVulnerability[]>([]);
const [sbomsById, setSbomsById] = React.useState<Map<string, SbomSummary>>(
new Map()
);
const [isFetchingSboms, setIsFetchingSboms] = React.useState(false);

React.useEffect(() => {
if (vulnerability?.advisories.length === 0) {
return;
}

const sboms = (vulnerability?.advisories ?? [])
.flatMap((advisory) => {
return (advisory.sboms ?? []).flatMap((sbom) => {
return sbom.status.map((status) => {
const result: SbomOfVulnerability = {
sbomId: sbom.id,
status: status as VulnerabilityStatus,
advisory: { ...advisory },
};
return result;
});
});
})
// Remove duplicates if exists
.reduce((prev, current) => {
const exists = prev.find(
(item) =>
item.sbomId === current.sbomId &&
item.advisory.uuid === current.advisory.uuid
);
if (!exists) {
return [...prev, current];
} else {
return prev;
}
}, [] as SbomOfVulnerability[]);

setAllSboms(sboms);
setIsFetchingSboms(true);

Promise.all(
sboms
.map(async (item) => {
const response = await getSbom({
client,
path: { id: item.sbomId },
});
return response.data;
})
.map((sbom) => sbom.catch(() => null))
).then((sboms) => {
const validSboms = sboms.reduce((prev, current) => {
if (current) {
return [...prev, current];
} else {
// Filter out error responses
return prev;
}
}, [] as SbomSummary[]);

const sbomsById = new Map<string, SbomSummary>();
validSboms.forEach((sbom) => sbomsById.set(sbom.id, sbom));

setSbomsById(sbomsById);
setIsFetchingSboms(false);
});
}, [vulnerability]);

const allSbomsWithMappedData = React.useMemo(() => {
return allSboms.map((item) => {
const result: SbomOfVulnerability = {
...item,
sbom: sbomsById.get(item.sbomId),
};
return result;
});
}, [allSboms, sbomsById]);

// Summary

const sbomsSummary = React.useMemo(() => {
return allSbomsWithMappedData.reduce((prev, current) => {
if (current.status) {
const status = current.status;
return {
...prev,
total: prev.total + 1,
status: {
...prev.status,
[status]: prev.status[status] + 1,
},
};
} else {
return prev;
}
}, DEFAULT_SUMMARY);
}, [allSbomsWithMappedData]);

return {
isFetching: isFetchingAdvisories || isFetchingSboms,
fetchError: fetchErrorAdvisories,
sboms: allSbomsWithMappedData,
summary: sbomsSummary,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,32 @@ import {
} from "@app/client";
import { useFetchPackageById } from "@app/queries/packages";

interface PackageVulnerability {
interface VulnerabilityOfPackage {
vulnerabilityId: string;
advisory: PurlAdvisory;
status: VulnerabilityStatus;
vulnerability?: VulnerabilityDetails;
}

interface PackageVulnerabilitySummary {
interface VulnerabilityOfPackageSummary {
total: number;
severities: { [key in Severity]: number };
}

const DEFAULT_SBOM_VULNERABILITY_SUMMARY: PackageVulnerabilitySummary = {
const DEFAULT_SUMMARY: VulnerabilityOfPackageSummary = {
total: 0,
severities: { none: 0, low: 0, medium: 0, high: 0, critical: 0 },
};

export const usePackageVulnerabilities = (packageId: string) => {
export const useVulnerabilitiesOfPackage = (packageId: string) => {
const {
pkg,
isFetching: isFetchingPackage,
fetchError: fetchErrorPackage,
} = useFetchPackageById(packageId);

const [allVulnerabilities, setAllVulnerabilities] = React.useState<
PackageVulnerability[]
VulnerabilityOfPackage[]
>([]);
const [vulnerabilitiesById, setVulnerabilitiesById] = React.useState<
Map<string, VulnerabilityDetails>
Expand All @@ -51,7 +51,7 @@ export const usePackageVulnerabilities = (packageId: string) => {
const vulnerabilities = (pkg?.advisories ?? [])
.flatMap((advisory) => {
return (advisory.status ?? []).map((status) => {
const result: PackageVulnerability = {
const result: VulnerabilityOfPackage = {
vulnerabilityId: status.vulnerability.identifier,
status: status.status as VulnerabilityStatus,
advisory: advisory,
Expand All @@ -73,7 +73,7 @@ export const usePackageVulnerabilities = (packageId: string) => {
} else {
return prev;
}
}, [] as PackageVulnerability[]);
}, [] as VulnerabilityOfPackage[]);

setAllVulnerabilities(vulnerabilities);
setIsFetchingVulnerabilities(true);
Expand Down Expand Up @@ -110,7 +110,7 @@ export const usePackageVulnerabilities = (packageId: string) => {

const allVulnerabilitiesWithMappedData = React.useMemo(() => {
return allVulnerabilities.map((item) => {
const result: PackageVulnerability = {
const result: VulnerabilityOfPackage = {
...item,
vulnerability: vulnerabilitiesById.get(item.vulnerabilityId),
};
Expand All @@ -135,7 +135,7 @@ export const usePackageVulnerabilities = (packageId: string) => {
} else {
return prev;
}
}, DEFAULT_SBOM_VULNERABILITY_SUMMARY);
}, DEFAULT_SUMMARY);
}, [allVulnerabilitiesWithMappedData]);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,33 @@ import {
} from "@app/client";
import { useFetchSbomsAdvisory } from "@app/queries/sboms";

interface SbomVulnerability {
interface VulnerabilityOfSbom {
vulnerabilityId: string;
advisory: SbomAdvisory;
status: VulnerabilityStatus;
packages: SbomPackage[];
vulnerability?: VulnerabilityDetails;
}

interface SbomVulnerabilitySummary {
interface VulnerabilityOfSbomSummary {
total: number;
severities: { [key in Severity]: number };
}

const DEFAULT_SBOM_VULNERABILITY_SUMMARY: SbomVulnerabilitySummary = {
const DEFAULT_SUMMARY: VulnerabilityOfSbomSummary = {
total: 0,
severities: { none: 0, low: 0, medium: 0, high: 0, critical: 0 },
};

export const useSbomVulnerabilities = (sbomId: string) => {
export const useVulnerabilitiesOfSbom = (sbomId: string) => {
const {
advisories,
isFetching: isFetchingAdvisories,
fetchError: fetchErrorAdvisories,
} = useFetchSbomsAdvisory(sbomId);

const [allVulnerabilities, setAllVulnerabilities] = React.useState<
SbomVulnerability[]
VulnerabilityOfSbom[]
>([]);
const [vulnerabilitiesById, setVulnerabilitiesById] = React.useState<
Map<string, VulnerabilityDetails>
Expand All @@ -53,7 +53,7 @@ export const useSbomVulnerabilities = (sbomId: string) => {
const vulnerabilities = (advisories ?? [])
.flatMap((advisory) => {
return (advisory.status ?? []).map((status) => {
const result: SbomVulnerability = {
const result: VulnerabilityOfSbom = {
vulnerabilityId: status.vulnerability_id,
status: status.status as VulnerabilityStatus,
packages: status.packages || [],
Expand All @@ -76,7 +76,7 @@ export const useSbomVulnerabilities = (sbomId: string) => {
} else {
return prev;
}
}, [] as SbomVulnerability[]);
}, [] as VulnerabilityOfSbom[]);

setAllVulnerabilities(vulnerabilities);
setIsFetchingVulnerabilities(true);
Expand Down Expand Up @@ -113,7 +113,7 @@ export const useSbomVulnerabilities = (sbomId: string) => {

const allVulnerabilitiesWithMappedData = React.useMemo(() => {
return allVulnerabilities.map((item) => {
const result: SbomVulnerability = {
const result: VulnerabilityOfSbom = {
...item,
vulnerability: vulnerabilitiesById.get(item.vulnerabilityId),
};
Expand All @@ -138,7 +138,7 @@ export const useSbomVulnerabilities = (sbomId: string) => {
} else {
return prev;
}
}, DEFAULT_SBOM_VULNERABILITY_SUMMARY);
}, DEFAULT_SUMMARY);
}, [allVulnerabilitiesWithMappedData]);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Label, Skeleton } from "@patternfly/react-core";

import { LoadingWrapper } from "@app/components/LoadingWrapper";
import { VulnerabilityGallery } from "@app/components/VulnerabilityGallery";
import { usePackageVulnerabilities } from "@app/hooks/domain-controls/usePackageVulnerabilities";
import { useVulnerabilitiesOfPackage } from "@app/hooks/domain-controls/useVulnerabilitiesOfPackage";

interface PackageVulnerabilitiesProps {
packageId: string;
Expand All @@ -14,7 +14,7 @@ export const PackageVulnerabilities: React.FC<PackageVulnerabilitiesProps> = ({
packageId,
}) => {
const { summary, isFetching, fetchError } =
usePackageVulnerabilities(packageId);
useVulnerabilitiesOfPackage(packageId);

return (
<LoadingWrapper
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
TableHeaderContentWithControls,
TableRowContentWithControls,
} from "@app/components/TableControls";
import { useSbomVulnerabilities } from "@app/hooks/domain-controls/useSbomVulnerabilities";
import { useVulnerabilitiesOfSbom } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
import { useLocalTableControls } from "@app/hooks/table-controls";
import { useFetchSBOMById } from "@app/queries/sboms";
import { useWithUiId } from "@app/utils/query-utils";
Expand All @@ -61,7 +61,7 @@ export const VulnerabilitiesBySbom: React.FC<VulnerabilitiesBySbomProps> = ({
summary: vulnerabilitiesSummary,
isFetching: isFetchingVulnerabilities,
fetchError: fetchErrorVulnerabilities,
} = useSbomVulnerabilities(sbomId);
} = useVulnerabilitiesOfSbom(sbomId);

const tableDataWithUiId = useWithUiId(
vulnerabilities,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Label, Skeleton } from "@patternfly/react-core";

import { LoadingWrapper } from "@app/components/LoadingWrapper";
import { VulnerabilityGallery } from "@app/components/VulnerabilityGallery";
import { useSbomVulnerabilities } from "@app/hooks/domain-controls/useSbomVulnerabilities";
import { useVulnerabilitiesOfSbom } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";

interface SBOMVulnerabilitiesProps {
sbomId: string;
Expand All @@ -13,7 +13,7 @@ interface SBOMVulnerabilitiesProps {
export const SBOMVulnerabilities: React.FC<SBOMVulnerabilitiesProps> = ({
sbomId,
}) => {
const { summary, isFetching, fetchError } = useSbomVulnerabilities(sbomId);
const { summary, isFetching, fetchError } = useVulnerabilitiesOfSbom(sbomId);

return (
<LoadingWrapper
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/pages/sbom-list/sbom-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import { SbomSummary } from "@app/client";
import { ConfirmDialog } from "@app/components/ConfirmDialog";
import { NotificationsContext } from "@app/components/NotificationsContext";
import { PackagesCount } from "@app/components/PackagesCount";
import { SimplePagination } from "@app/components/SimplePagination";
import {
ConditionalTableBody,
Expand All @@ -28,6 +27,7 @@ import { useDownload } from "@app/hooks/domain-controls/useDownload";
import { useDeleteSbomMutation } from "@app/queries/sboms";
import { formatDate } from "@app/utils/utils";

import { PackagesCount } from "./components/PackagesCount";
import { SBOMVulnerabilities } from "./components/SbomVulnerabilities";
import { SbomSearchContext } from "./sbom-context";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ type Story = StoryObj<typeof meta>;

export const Primary: Story = {
args: {
variant: "compact",
advisories: [],
vulnerabilityId: "1",
},
decorators: [
(Story) => (
Expand All @@ -32,8 +31,7 @@ export const Primary: Story = {

export const EmptyState: Story = {
args: {
variant: "compact",
advisories: [],
vulnerabilityId: "2",
},
decorators: [
(Story) => (
Expand Down
Loading

0 comments on commit 8257938

Please sign in to comment.