-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SbomList - vulnerabilities column
- Loading branch information
1 parent
d6a3f02
commit 1f1ba9d
Showing
4 changed files
with
193 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { client } from "@app/axios-config/apiInit"; | ||
import { | ||
getVulnerability, | ||
SbomAdvisory, | ||
SbomPackage, | ||
Severity, | ||
VulnerabilityDetails, | ||
} from "@app/client"; | ||
import { useFetchSbomsAdvisory } from "@app/queries/sboms"; | ||
import React from "react"; | ||
import { VulnerabilityStatus } from "@app/api/models"; | ||
|
||
interface SbomVulnerability { | ||
vulnerabilityId: string; | ||
advisory: SbomAdvisory; | ||
status: VulnerabilityStatus; | ||
packages: SbomPackage[]; | ||
vulnerability?: VulnerabilityDetails; | ||
} | ||
|
||
interface SbomVulnerabilitySummary { | ||
total: number; | ||
severities: { [key in Severity]: number }; | ||
} | ||
|
||
const DEFAULT_SBOM_VULNERABILITY_SUMMARY: SbomVulnerabilitySummary = { | ||
total: 0, | ||
severities: { none: 0, low: 0, medium: 0, high: 0, critical: 0 }, | ||
}; | ||
|
||
export const useSbomVulnerabilities = (sbomId: string) => { | ||
const { | ||
advisories, | ||
isFetching: isFetchingAdvisories, | ||
fetchError: fetchErrorAdvisories, | ||
} = useFetchSbomsAdvisory(sbomId); | ||
|
||
const [allVulnerabilities, setAllVulnerabilities] = React.useState< | ||
SbomVulnerability[] | ||
>([]); | ||
const [vulnerabilitiesById, setVulnerabilitiesById] = React.useState< | ||
Map<string, VulnerabilityDetails> | ||
>(new Map()); | ||
const [isFetchingVulnerabilities, setIsFetchingVulnerabilities] = | ||
React.useState(false); | ||
|
||
React.useEffect(() => { | ||
if (advisories.length === 0) { | ||
return; | ||
} | ||
|
||
const vulnerabilities = (advisories ?? []) | ||
.flatMap((advisory) => { | ||
return (advisory.status ?? []).map((status) => { | ||
const result: SbomVulnerability = { | ||
vulnerabilityId: status.vulnerability_id, | ||
status: status.status as VulnerabilityStatus, | ||
packages: status.packages || [], | ||
advisory: { ...advisory }, | ||
}; | ||
return result; | ||
}); | ||
}) | ||
// Take only "affected" | ||
.filter((item) => item.status === "affected") | ||
// Remove duplicates if exists | ||
.reduce((prev, current) => { | ||
const exists = prev.find( | ||
(item) => | ||
item.vulnerabilityId === current.vulnerabilityId && | ||
item.advisory.uuid === current.advisory.uuid | ||
); | ||
if (!exists) { | ||
return [...prev, current]; | ||
} else { | ||
return prev; | ||
} | ||
}, [] as SbomVulnerability[]); | ||
|
||
setAllVulnerabilities(vulnerabilities); | ||
setIsFetchingVulnerabilities(true); | ||
|
||
Promise.all( | ||
vulnerabilities | ||
.map(async (item) => { | ||
const response = await getVulnerability({ | ||
client, | ||
path: { id: item.vulnerabilityId }, | ||
}); | ||
return response.data; | ||
}) | ||
.map((vulnerability) => vulnerability.catch(() => null)) | ||
).then((vulnerabilities) => { | ||
const validVulnerabilities = vulnerabilities.reduce((prev, current) => { | ||
if (current) { | ||
return [...prev, current]; | ||
} else { | ||
// Filter out error responses | ||
return prev; | ||
} | ||
}, [] as VulnerabilityDetails[]); | ||
|
||
const vulnerabilitiesById = new Map<string, VulnerabilityDetails>(); | ||
validVulnerabilities.forEach((vulnerability) => { | ||
vulnerabilitiesById.set(vulnerability.identifier, vulnerability); | ||
}); | ||
|
||
setVulnerabilitiesById(vulnerabilitiesById); | ||
setIsFetchingVulnerabilities(false); | ||
}); | ||
}, [advisories]); | ||
|
||
const allVulnerabilitiesWithMappedData = React.useMemo(() => { | ||
return allVulnerabilities.map((item) => { | ||
const result: SbomVulnerability = { | ||
...item, | ||
vulnerability: vulnerabilitiesById.get(item.vulnerabilityId), | ||
}; | ||
return result; | ||
}); | ||
}, [allVulnerabilities, vulnerabilitiesById]); | ||
|
||
// Summary | ||
|
||
const vulnerabilitiesSummary = React.useMemo(() => { | ||
return allVulnerabilitiesWithMappedData.reduce((prev, current) => { | ||
if (current.vulnerability?.average_severity) { | ||
const severity = current.vulnerability?.average_severity; | ||
return { | ||
...prev, | ||
total: prev.total + 1, | ||
severities: { | ||
...prev.severities, | ||
[severity]: prev.severities[severity] + 1, | ||
}, | ||
}; | ||
} else { | ||
return prev; | ||
} | ||
}, DEFAULT_SBOM_VULNERABILITY_SUMMARY); | ||
}, [allVulnerabilitiesWithMappedData]); | ||
|
||
return { | ||
isFetching: isFetchingAdvisories || isFetchingVulnerabilities, | ||
fetchError: fetchErrorAdvisories, | ||
vulnerabilities: allVulnerabilitiesWithMappedData, | ||
summary: vulnerabilitiesSummary, | ||
}; | ||
}; |
28 changes: 28 additions & 0 deletions
28
client/src/app/pages/sbom-list/components/SbomVulnerabilities.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import React from "react"; | ||
|
||
import { Label, Skeleton } from "@patternfly/react-core"; | ||
|
||
import { VulnerabilityGallery } from "@app/components/VulnerabilityGallery"; | ||
import { useSbomVulnerabilities } from "@app/hooks/useSbomVulnerabilities"; | ||
import { LoadingWrapper } from "@app/components/LoadingWrapper"; | ||
|
||
interface SBOMVulnerabilitiesProps { | ||
sbomId: string; | ||
} | ||
|
||
export const SBOMVulnerabilities: React.FC<SBOMVulnerabilitiesProps> = ({ | ||
sbomId, | ||
}) => { | ||
const { summary, isFetching, fetchError } = useSbomVulnerabilities(sbomId); | ||
|
||
return ( | ||
<LoadingWrapper | ||
isFetching={isFetching} | ||
fetchError={fetchError} | ||
isFetchingState={<Skeleton screenreaderText="Loading contents" />} | ||
fetchErrorState={<Label color="red">Error</Label>} | ||
> | ||
<VulnerabilityGallery severities={summary.severities} /> | ||
</LoadingWrapper> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters