From 9a2bcc2318d4413530b79cf825636f17d07e162b Mon Sep 17 00:00:00 2001 From: Hiram Chirino Date: Thu, 22 Aug 2024 13:08:01 -0400 Subject: [PATCH] Migrate `queries/vulnerabilities.ts` to use `@app/client` (#138) Signed-off-by: Hiram Chirino --- client/openapi/trustd.yaml | 17 ++++- client/src/app/client/schemas.gen.ts | 27 +++++++- client/src/app/client/types.gen.ts | 7 ++- .../vulnerabilities-by-package.tsx | 13 ++-- .../sbom-details/vulnerabilities-by-sbom.tsx | 15 ++--- .../advisories-by-vulnerability.tsx | 7 ++- .../pages/vulnerability-details/overview.tsx | 4 +- .../packages-by-vulnerability.tsx | 26 ++++++-- .../sboms-by-vulnerability.tsx | 7 ++- client/src/app/queries/dataOf.ts | 12 ++++ client/src/app/queries/sboms.ts | 41 +++++------- client/src/app/queries/vulnerabilities.ts | 63 ++++++++----------- 12 files changed, 148 insertions(+), 91 deletions(-) diff --git a/client/openapi/trustd.yaml b/client/openapi/trustd.yaml index 6e2a7697..4e7070ac 100644 --- a/client/openapi/trustd.yaml +++ b/client/openapi/trustd.yaml @@ -2385,7 +2385,7 @@ components: sboms: type: array items: - $ref: '#/components/schemas/SbomStatus' + $ref: '#/components/schemas/VulnerabilitySbomStatus' description: SBOMs claimed by this advisory to be addressed by this vulnerability. VulnerabilityDetails: allOf: @@ -2469,6 +2469,21 @@ components: format: date-time description: The date (in RFC3339 format) of when the vulnerability was last withdrawn, if any. nullable: true + VulnerabilitySbomStatus: + allOf: + - $ref: '#/components/schemas/SbomHead' + - type: object + required: + - status + properties: + status: + type: array + items: + type: string + uniqueItems: true + version: + type: string + nullable: true VulnerabilitySummary: allOf: - $ref: '#/components/schemas/VulnerabilityHead' diff --git a/client/src/app/client/schemas.gen.ts b/client/src/app/client/schemas.gen.ts index 46463e70..8f35e1b0 100644 --- a/client/src/app/client/schemas.gen.ts +++ b/client/src/app/client/schemas.gen.ts @@ -1579,7 +1579,7 @@ export const $VulnerabilityAdvisorySummary = { sboms: { type: "array", items: { - $ref: "#/components/schemas/SbomStatus", + $ref: "#/components/schemas/VulnerabilitySbomStatus", }, description: "SBOMs claimed by this advisory to be addressed by this vulnerability.", @@ -1702,6 +1702,31 @@ CVE identifier.`, }, } as const; +export const $VulnerabilitySbomStatus = { + allOf: [ + { + $ref: "#/components/schemas/SbomHead", + }, + { + type: "object", + required: ["status"], + properties: { + status: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + version: { + type: "string", + nullable: true, + }, + }, + }, + ], +} as const; + export const $VulnerabilitySummary = { allOf: [ { diff --git a/client/src/app/client/types.gen.ts b/client/src/app/client/types.gen.ts index 4cc5a560..f0188c43 100644 --- a/client/src/app/client/types.gen.ts +++ b/client/src/app/client/types.gen.ts @@ -636,7 +636,7 @@ export type VulnerabilityAdvisorySummary = VulnerabilityAdvisoryHead & { /** * SBOMs claimed by this advisory to be addressed by this vulnerability. */ - sboms: Array; + sboms: Array; }; export type VulnerabilityDetails = VulnerabilityHead & { @@ -693,6 +693,11 @@ export type VulnerabilityHead = { withdrawn: string | null; }; +export type VulnerabilitySbomStatus = SbomHead & { + status: Array; + version?: string | null; +}; + export type VulnerabilitySummary = VulnerabilityHead & { advisories: Array; /** diff --git a/client/src/app/pages/package-details/vulnerabilities-by-package.tsx b/client/src/app/pages/package-details/vulnerabilities-by-package.tsx index 5def3e58..cb0d4f1c 100644 --- a/client/src/app/pages/package-details/vulnerabilities-by-package.tsx +++ b/client/src/app/pages/package-details/vulnerabilities-by-package.tsx @@ -17,7 +17,6 @@ import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table"; import { AdvisoryWithinPackage, VulnerabilityStatus, - VulnerabilityIndex, } from "@app/api/models"; import { getVulnerabilityById } from "@app/api/rest"; import { AdvisoryInDrawerInfo } from "@app/components/AdvisoryInDrawerInfo"; @@ -34,13 +33,15 @@ import { useLocalTableControls } from "@app/hooks/table-controls"; import { useFetchPackageById } from "@app/queries/packages"; import { useWithUiId } from "@app/utils/query-utils"; import { VulnerabilityInDrawerInfo } from "@app/components/VulnerabilityInDrawerInfo"; +import {getVulnerability, VulnerabilityDetails} from "../../client"; +import {client} from "../../axios-config/apiInit"; interface TableData { vulnerabilityId: string; advisory: AdvisoryWithinPackage; status: VulnerabilityStatus; context: { cpe: string }; - vulnerability?: VulnerabilityIndex; + vulnerability?: VulnerabilityDetails; } interface VulnerabilitiesByPackageProps { @@ -67,7 +68,7 @@ export const VulnerabilitiesByPackage: React.FC< TableData[] >([]); const [vulnerabilitiesById, setVulnerabilitiesById] = React.useState< - Map + Map >(new Map()); const [isFetchingVulnerabilities, setIsFetchingVulnerabilities] = React.useState(false); @@ -109,7 +110,7 @@ export const VulnerabilitiesByPackage: React.FC< Promise.all( vulnerabilities - .map((item) => getVulnerabilityById(item.vulnerabilityId)) + .map(async (item) => (await getVulnerability({ client, path: { id: item.vulnerabilityId } })).data) .map((vulnerability) => vulnerability.catch(() => null)) ).then((vulnerabilities) => { const validVulnerabilities = vulnerabilities.reduce((prev, current) => { @@ -119,9 +120,9 @@ export const VulnerabilitiesByPackage: React.FC< // Filter out error responses return prev; } - }, [] as VulnerabilityIndex[]); + }, [] as VulnerabilityDetails[]); - const vulnerabilitiesById = new Map(); + const vulnerabilitiesById = new Map(); validVulnerabilities.forEach((vulnerability) => vulnerabilitiesById.set(vulnerability.identifier, vulnerability) ); diff --git a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx index 2dbca847..d9154d43 100644 --- a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx +++ b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx @@ -22,7 +22,7 @@ import { Tr, } from "@patternfly/react-table"; -import { AdvisoryWithinSbom, VulnerabilityStatus, VulnerabilityIndex } from "@app/api/models"; +import { AdvisoryWithinSbom, VulnerabilityStatus } from "@app/api/models"; import { getVulnerabilityById } from "@app/api/rest"; import { AdvisoryInDrawerInfo } from "@app/components/AdvisoryInDrawerInfo"; import { FilterToolbar, FilterType } from "@app/components/FilterToolbar"; @@ -38,7 +38,8 @@ import { VulnerabilityInDrawerInfo } from "@app/components/VulnerabilityInDrawer import { useLocalTableControls } from "@app/hooks/table-controls"; import { useFetchSBOMById } from "@app/queries/sboms"; import { useWithUiId } from "@app/utils/query-utils"; -import {SbomPackage} from "@app/client"; +import {getVulnerability, SbomPackage, VulnerabilityDetails} from "@app/client"; +import {client} from "../../axios-config/apiInit"; interface TableData { vulnerabilityId: string; @@ -46,7 +47,7 @@ interface TableData { status: VulnerabilityStatus; context: { cpe: string }; packages: SbomPackage[]; - vulnerability?: VulnerabilityIndex; + vulnerability?: VulnerabilityDetails; } interface VulnerabilitiesBySbomProps { @@ -73,7 +74,7 @@ export const VulnerabilitiesBySbom: React.FC = ({ TableData[] >([]); const [vulnerabilitiesById, setVulnerabilitiesById] = React.useState< - Map + Map >(new Map()); const [isFetchingVulnerabilities, setIsFetchingVulnerabilities] = React.useState(false); @@ -115,7 +116,7 @@ export const VulnerabilitiesBySbom: React.FC = ({ Promise.all( vulnerabilities - .map((item) => getVulnerabilityById(item.vulnerabilityId)) + .map(async (item) => (await getVulnerability({ client, path: { id: item.vulnerabilityId } })).data) .map((vulnerability) => vulnerability.catch(() => null)) ).then((vulnerabilities) => { const validVulnerabilities = vulnerabilities.reduce((prev, current) => { @@ -125,9 +126,9 @@ export const VulnerabilitiesBySbom: React.FC = ({ // Filter out error responses return prev; } - }, [] as VulnerabilityIndex[]); + }, [] as VulnerabilityDetails[]); - const vulnerabilitiesById = new Map(); + const vulnerabilitiesById = new Map(); validVulnerabilities.forEach((vulnerability) => vulnerabilitiesById.set(vulnerability.identifier, vulnerability) ); diff --git a/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.tsx b/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.tsx index 70ee43f9..41c481ed 100644 --- a/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.tsx +++ b/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.tsx @@ -32,10 +32,11 @@ import { } from "@app/components/TableControls"; import { useLocalTableControls } from "@app/hooks/table-controls"; import { formatDate } from "@app/utils/utils"; +import {VulnerabilityAdvisoryHead} from "@app/client"; interface AdvisoriesByVulnerabilityProps { variant?: TableProps["variant"]; - advisories: AdvisoryWithinVulnerability[]; + advisories: VulnerabilityAdvisoryHead[]; } export const AdvisoriesByVulnerability: React.FC< @@ -45,9 +46,9 @@ export const AdvisoriesByVulnerability: React.FC< const [selectedRowAction, setSelectedRowAction] = React.useState(null); const [selectedRow, setSelectedRow] = - React.useState(null); + React.useState(null); - const showDrawer = (action: RowAction, row: AdvisoryWithinVulnerability) => { + const showDrawer = (action: RowAction, row: VulnerabilityAdvisoryHead) => { setSelectedRowAction(action); setSelectedRow(row); }; diff --git a/client/src/app/pages/vulnerability-details/overview.tsx b/client/src/app/pages/vulnerability-details/overview.tsx index e583f314..fc068c53 100644 --- a/client/src/app/pages/vulnerability-details/overview.tsx +++ b/client/src/app/pages/vulnerability-details/overview.tsx @@ -14,11 +14,11 @@ import { StackItem, } from "@patternfly/react-core"; -import { VulnerabilityIndex } from "@app/api/models"; import { formatDate } from "@app/utils/utils"; +import {VulnerabilityDetails} from "../../client"; interface OverviewProps { - vulnerability: VulnerabilityIndex; + vulnerability: VulnerabilityDetails; } export const Overview: React.FC = ({ vulnerability }) => { diff --git a/client/src/app/pages/vulnerability-details/packages-by-vulnerability.tsx b/client/src/app/pages/vulnerability-details/packages-by-vulnerability.tsx index 0be3f382..d7cb483c 100644 --- a/client/src/app/pages/vulnerability-details/packages-by-vulnerability.tsx +++ b/client/src/app/pages/vulnerability-details/packages-by-vulnerability.tsx @@ -22,7 +22,6 @@ import { } from "@patternfly/react-table"; import { - AdvisoryWithinVulnerability, DecomposedPurl, VulnerabilityStatus, } from "@app/api/models"; @@ -39,6 +38,7 @@ import { import { useLocalTableControls } from "@app/hooks/table-controls"; import { useWithUiId } from "@app/utils/query-utils"; import { decomposePurl } from "@app/utils/utils"; +import {Purl, StatusContext, VulnerabilityAdvisorySummary} from "../../client"; interface TableData { basePurl: { @@ -47,15 +47,29 @@ interface TableData { }; versionRange: string; status: VulnerabilityStatus; - context: { cpe: string }; - advisory: AdvisoryWithinVulnerability; + context: StatusContext | null; + advisory: VulnerabilityAdvisorySummary; decomposedPurl?: DecomposedPurl; } interface PackagesByVulnerabilityProps { variant?: TableProps["variant"]; initialItemsPerPage?: number; - advisories: AdvisoryWithinVulnerability[]; + advisories: VulnerabilityAdvisorySummary[]; +} + +export const ShowStatusContext = ({value}: { + value: StatusContext | null +}) => { + if (!value) { + return null; + } + let cpe = value as { cpe: string; } + if ( cpe.cpe ) { + return cpe.cpe; + } + let purl = value as { purl: Purl; } + return purl.purl; } export const PackagesByVulnerability: React.FC< @@ -83,7 +97,7 @@ export const PackagesByVulnerability: React.FC< basePurl: { ...basePurl }, advisory: { ...advisory }, status: status as VulnerabilityStatus, - context: { ...affectedPackage.context }, + context: affectedPackage.context, versionRange: affectedPackage.version, decomposedPurl: decomposePurl(basePurl.purl), }; @@ -256,7 +270,7 @@ export const PackagesByVulnerability: React.FC< modifier="truncate" {...getTdProps({ columnKey: "context" })} > - {item.context.cpe} + = ({ @@ -101,7 +102,7 @@ export const SbomsByVulnerability: React.FC = ({ return sbom.status.map((status) => { const result: TableData = { sbomId: sbom.id, - status: status, + status: status as VulnerabilityStatus, advisory: { ...advisory }, }; return result; diff --git a/client/src/app/queries/dataOf.ts b/client/src/app/queries/dataOf.ts index 52157b3b..32762301 100644 --- a/client/src/app/queries/dataOf.ts +++ b/client/src/app/queries/dataOf.ts @@ -1,7 +1,19 @@ import { RequestResult } from "@hey-api/client-axios"; +import { AxiosError } from "axios"; +import { UseQueryResult } from "@tanstack/react-query"; export const dataOf = async ( promise: RequestResult ) => { return (await promise).data; }; + +export const convertQuery = ( + q: UseQueryResult +) => { + return { + isFetching: q.isLoading, + fetchError: q.error as AxiosError, + refetch: q.refetch, + }; +}; diff --git a/client/src/app/queries/sboms.ts b/client/src/app/queries/sboms.ts index d645a1d4..78bc401a 100644 --- a/client/src/app/queries/sboms.ts +++ b/client/src/app/queries/sboms.ts @@ -13,7 +13,7 @@ import { updateSbomLabels, } from "@app/client"; import { client } from "@app/axios-config/apiInit"; -import { dataOf } from "@app/queries/dataOf"; +import { convertQuery, dataOf } from "@app/queries/dataOf"; import { uploadSbom } from "@app/api/rest"; import { requestParamsQuery } from "../hooks/table-controls"; @@ -23,7 +23,7 @@ export const useFetchSBOMs = ( params: HubRequestParams = {}, refetchDisabled: boolean = false ) => { - const { data, isLoading, error, refetch } = useQuery({ + const query = useQuery({ queryKey: [SBOMsQueryKey, params], queryFn: () => listSboms({ @@ -33,28 +33,25 @@ export const useFetchSBOMs = ( refetchInterval: !refetchDisabled ? 5000 : false, }); return { + ...convertQuery(query), result: { - data: data?.data.items || [], - total: data?.data.total ?? 0, + data: query.data?.data.items || [], + total: query.data?.data.total ?? 0, params: params, }, - isFetching: isLoading, - fetchError: error, - refetch, }; }; export const useFetchSBOMById = (id: string) => { - const { data, isLoading, error } = useQuery({ + const query = useQuery({ queryKey: [SBOMsQueryKey, id], - queryFn: async () => dataOf(getSbom({ client, path: { id } })), + queryFn: () => dataOf(getSbom({ client, path: { id } })), enabled: id !== undefined, }); return { - sbom: data, - isFetching: isLoading, - fetchError: error as AxiosError, + ...convertQuery(query), + sbom: query.data, }; }; @@ -63,8 +60,7 @@ export const useDeleteSbomMutation = ( onSuccess?: (payload: SbomDetails, id: string) => void ) => { return useMutation({ - mutationFn: async (id: string) => - dataOf(deleteSbom({ client, path: { id } })), + mutationFn: (id: string) => dataOf(deleteSbom({ client, path: { id } })), mutationKey: [SBOMsQueryKey], onSuccess: onSuccess, onError: onError, @@ -72,16 +68,15 @@ export const useDeleteSbomMutation = ( }; export const useFetchSBOMSourceById = (key: string) => { - const { data, isLoading, error } = useQuery({ + const query = useQuery({ queryKey: [SBOMsQueryKey, key, "source"], queryFn: () => dataOf(downloadSbom({ client, path: { key } })), enabled: key !== undefined, }); return { - source: data, - isFetching: isLoading, - fetchError: error as AxiosError, + ...convertQuery(query), + source: query.data, }; }; @@ -119,7 +114,7 @@ export const useFetchSbomsByPackageId = ( packageId: string, params: HubRequestParams = {} ) => { - const { data, isLoading, error, refetch } = useQuery({ + const query = useQuery({ queryKey: [SBOMsQueryKey, "by-package", packageId, params], queryFn: () => dataOf( @@ -130,13 +125,11 @@ export const useFetchSbomsByPackageId = ( ), }); return { + ...convertQuery(query), result: { - data: data?.items || [], - total: data?.total ?? 0, + data: query.data?.items || [], + total: query.data?.total ?? 0, params, }, - isFetching: isLoading, - fetchError: error, - refetch, }; }; diff --git a/client/src/app/queries/vulnerabilities.ts b/client/src/app/queries/vulnerabilities.ts index c2b3deed..2de38079 100644 --- a/client/src/app/queries/vulnerabilities.ts +++ b/client/src/app/queries/vulnerabilities.ts @@ -3,12 +3,14 @@ import { AxiosError } from "axios"; import { HubRequestParams } from "@app/api/models"; import { - getVulnerabilities, - getVulnerabilityById, - getVulnerabilitySourceById, -} from "@app/api/rest"; -import { deleteVulnerability, VulnerabilityDetails } from "@app/client"; + deleteVulnerability, + getVulnerability, + listVulnerabilities, + VulnerabilityDetails, +} from "@app/client"; import { client } from "@app/axios-config/apiInit"; +import { requestParamsQuery } from "@app/hooks/table-controls"; +import { convertQuery, dataOf } from "@app/queries/dataOf"; export const VulnerabilitiesQueryKey = "vulnerabilities"; @@ -16,34 +18,35 @@ export const useFetchVulnerabilities = ( params: HubRequestParams = {}, refetchDisabled: boolean = false ) => { - const { data, isLoading, error, refetch } = useQuery({ + const query = useQuery({ queryKey: [VulnerabilitiesQueryKey, params], - queryFn: () => getVulnerabilities(params), + queryFn: () => + dataOf( + listVulnerabilities({ + client, + query: { ...requestParamsQuery(params) }, + }) + ), refetchInterval: !refetchDisabled ? 5000 : false, }); return { + ...convertQuery(query), result: { - data: data?.data || [], - total: data?.total ?? 0, - params: data?.params ?? params, + data: query.data?.items || [], + total: query.data?.total ?? 0, + params: params, }, - isFetching: isLoading, - fetchError: error, - refetch, }; }; -export const useFetchVulnerabilityById = (id: number | string) => { - const { data, isLoading, error } = useQuery({ +export const useFetchVulnerabilityById = (id: string) => { + const query = useQuery({ queryKey: [VulnerabilitiesQueryKey, id], - queryFn: () => getVulnerabilityById(id), - enabled: id !== undefined, + queryFn: () => dataOf(getVulnerability({ client, path: { id } })), }); - return { - vulnerability: data, - isFetching: isLoading, - fetchError: error as AxiosError, + ...convertQuery(query), + vulnerability: query.data, }; }; @@ -52,24 +55,10 @@ export const useDeleteVulnerabilityMutation = ( onSuccess?: (payload: VulnerabilityDetails, id: string) => void ) => { return useMutation({ - mutationFn: async (id: string) => - (await deleteVulnerability({ client, path: { id } })).data, + mutationFn: (id: string) => + dataOf(deleteVulnerability({ client, path: { id } })), mutationKey: [VulnerabilitiesQueryKey], onSuccess, onError, }); }; - -export const useFetchVulnerabilitySourceById = (id: number | string) => { - const { data, isLoading, error } = useQuery({ - queryKey: [VulnerabilitiesQueryKey, id, "source"], - queryFn: () => getVulnerabilitySourceById(id), - enabled: id !== undefined, - }); - - return { - source: data, - isFetching: isLoading, - fetchError: error as AxiosError, - }; -};