Skip to content

Commit

Permalink
Add images table for Workload CVE Image single page
Browse files Browse the repository at this point in the history
  • Loading branch information
dvail committed Mar 13, 2023
1 parent 3ff1f2f commit 7771160
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,37 @@ import {
EmptyStateBody,
EmptyStateIcon,
EmptyStateVariant,
Flex,
Grid,
GridItem,
Label,
PageSection,
pluralize,
Spinner,
Split,
SplitItem,
Tab,
TabTitleText,
Tabs,
TabsComponent,
Text,
Title,
Flex,
} from '@patternfly/react-core';
import { ExclamationCircleIcon } from '@patternfly/react-icons';
import { gql, useQuery } from '@apollo/client';
import { ExclamationCircleIcon, InfoCircleIcon } from '@patternfly/react-icons';

import { VulnerabilitySeverity } from 'types/cve.proto';
import { getAxiosErrorMessage } from 'utils/responseErrorUtils';
import useURLStringUnion from 'hooks/useURLStringUnion';
import useURLSearch from 'hooks/useURLSearch';
import { getHasSearchApplied } from 'utils/searchUtils';
import { cveStatusTabValues, FixableStatus } from './types';
import WorkloadTableToolbar from './WorkloadTableToolbar';
import BySeveritySummaryCard from './SummaryCards/BySeveritySummaryCard';
import CvesByStatusSummaryCard from './SummaryCards/CvesByStatusSummaryCard';

export type ImageVulnerabilitiesVariables = {
id: string;
};

export type ImageVulnerabilitiesResponse = {
image: {
imageVulnerabilities: {
severity: string;
isFixable: boolean;
}[];
};
};
import SingleEntityVulnerabilitiesTable from './Tables/SingleEntityVulnerabilitiesTable';
import useImageVulnerabilities, {
ImageVulnerabilitiesResponse,
} from './hooks/useImageVulnerabilities';

function severityCountsFromImageVulnerabilities(
imageVulnerabilities: ImageVulnerabilitiesResponse['image']['imageVulnerabilities']
Expand Down Expand Up @@ -78,30 +74,14 @@ function statusCountsFromImageVulnerabilities(
return statusCounts;
}

export const imageVulnerabilitiesQuery = gql`
query getImageVulnerabilities($id: ID!) {
image(id: $id) {
id
imageVulnerabilities {
severity
isFixable
}
}
}
`;

export type ImageSingleVulnerabilitiesProps = {
imageId: string;
};

function ImageSingleVulnerabilities({ imageId }: ImageSingleVulnerabilitiesProps) {
// TODO Needs integration with URL search filter
const { data, loading, error } = useQuery<
ImageVulnerabilitiesResponse,
ImageVulnerabilitiesVariables
>(imageVulnerabilitiesQuery, {
variables: { id: imageId },
});
const { searchFilter } = useURLSearch();
// TODO Still need to properly integrate search filter with query
const { data, loading, error } = useImageVulnerabilities(imageId, {});

const [activeTabKey, setActiveTabKey] = useURLStringUnion('cveStatus', cveStatusTabValues);

Expand Down Expand Up @@ -135,23 +115,51 @@ function ImageSingleVulnerabilities({ imageId }: ImageSingleVulnerabilitiesProps
const hiddenStatuses = new Set<FixableStatus>([]);

mainContent = (
<div className="pf-u-px-lg pf-u-pb-lg">
<Grid hasGutter>
<GridItem sm={12} md={6} xl2={4}>
<BySeveritySummaryCard
title="CVEs by severity"
severityCounts={severityCounts}
hiddenSeverities={hiddenSeverities}
/>
</GridItem>
<GridItem sm={12} md={6} xl2={4}>
<CvesByStatusSummaryCard
cveStatusCounts={cveStatusCounts}
hiddenStatuses={hiddenStatuses}
/>
</GridItem>
</Grid>
</div>
<>
<div className="pf-u-px-lg pf-u-pb-lg">
<Grid hasGutter>
<GridItem sm={12} md={6} xl2={4}>
<BySeveritySummaryCard
title="CVEs by severity"
severityCounts={severityCounts}
hiddenSeverities={hiddenSeverities}
/>
</GridItem>
<GridItem sm={12} md={6} xl2={4}>
<CvesByStatusSummaryCard
cveStatusCounts={cveStatusCounts}
hiddenStatuses={hiddenStatuses}
/>
</GridItem>
</Grid>
</div>
<Divider />
<div className="pf-u-p-lg">
<Split className="pf-u-pb-lg">
<SplitItem isFilled>
<Flex alignContent={{ default: 'alignContentCenter' }}>
<Title headingLevel="h2">
{pluralize(
data.image.imageVulnerabilities.length,
'result',
'results'
)}{' '}
found
</Title>
{getHasSearchApplied(searchFilter) && (
<Label isCompact color="blue" icon={<InfoCircleIcon />}>
Filtered view
</Label>
)}
</Flex>
</SplitItem>
<SplitItem>TODO Pagination</SplitItem>
</Split>
<SingleEntityVulnerabilitiesTable
imageVulnerabilities={data.image.imageVulnerabilities}
/>
</div>
</>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';
import { Button, ButtonVariant } from '@patternfly/react-core';
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
import { CheckCircleIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
import { SVGIconProps } from '@patternfly/react-icons/dist/js/createIcon';

import LinkShim from 'Components/PatternFly/LinkShim';
import SeverityIcons from 'Components/PatternFly/SeverityIcons';
import { vulnerabilitySeverityLabels } from 'messages/common';
import { getDistanceStrictAsPhrase } from 'utils/dateUtils';
import { ImageVulnerabilitiesResponse } from '../hooks/useImageVulnerabilities';
import { getEntityPagePath } from '../searchUtils';

export type SingleEntityVulnerabilitiesTableProps = {
imageVulnerabilities: ImageVulnerabilitiesResponse['image']['imageVulnerabilities'];
};

function SingleEntityVulnerabilitiesTable({
imageVulnerabilities,
}: SingleEntityVulnerabilitiesTableProps) {
return (
<TableComposable>
<Thead>
<Tr>
<Th>CVE</Th>
<Th>Severity</Th>
<Th>CVE Status</Th>
<Th>Affected components</Th>
<Th>First discovered</Th>
</Tr>
</Thead>
<Tbody>
{imageVulnerabilities.map(
({ cve, severity, isFixable, imageComponents, discoveredAtImage }) => {
const SeverityIcon: React.FC<SVGIconProps> | undefined =
SeverityIcons[severity];
const severityLabel: string | undefined =
vulnerabilitySeverityLabels[severity];

return (
<Tr key={cve}>
<Td dataLabel="CVE">
<Button
variant={ButtonVariant.link}
isInline
component={LinkShim}
href={getEntityPagePath('CVE', cve)}
>
{cve}
</Button>
</Td>
<Td dataLabel="Severity">
<span>
{SeverityIcon && (
<SeverityIcon className="pf-u-display-inline" />
)}
{severityLabel && (
<span className="pf-u-pl-sm">{severityLabel}</span>
)}
</span>
</Td>
<Td dataLabel="CVE Status">
{isFixable ? (
<span>
<CheckCircleIcon
className="pf-u-display-inline"
color="var(--pf-global--success-color--100)"
/>
<span className="pf-u-pl-sm">Fixable</span>
</span>
) : (
<span>
<ExclamationCircleIcon
className="pf-u-display-inline"
color="var(--pf-global--danger-color--100)"
/>
<span className="pf-u-pl-sm">Not fixable</span>
</span>
)}
</Td>
<Td dataLabel="Affected components">
{imageComponents.length === 1
? imageComponents[0].name
: `${imageComponents.length} components`}
</Td>
<Td dataLabel="First discovered">
{getDistanceStrictAsPhrase(discoveredAtImage, new Date())}
</Td>
</Tr>
);
}
)}
</Tbody>
</TableComposable>
);
}

export default SingleEntityVulnerabilitiesTable;
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
Tooltip,
} from '@patternfly/react-core';
import { CopyIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
import { gql, useQuery } from '@apollo/client';
import { useQuery } from '@apollo/client';
import { useParams } from 'react-router-dom';

import BreadcrumbItemLink from 'Components/BreadcrumbItemLink';
Expand All @@ -31,6 +31,11 @@ import ImageSingleVulnerabilities from './ImageSingleVulnerabilities';
import ImageSingleResources from './ImageSingleResources';
import { detailsTabValues } from './types';
import { getOverviewCvesPath } from './searchUtils';
import {
ImageDetailsResponse,
ImageDetailsVariables,
imageDetailsQuery,
} from './hooks/useImageDetails';

const workloadCveOverviewImagePath = getOverviewCvesPath({
cveStatusTab: 'Observed',
Expand Down Expand Up @@ -89,51 +94,6 @@ function ImageDetailBadges({ imageData }: { imageData: ImageDetailsResponse['ima
);
}

export type ImageDetailsVariables = {
id: string;
};

export type ImageDetailsResponse = {
image: {
deploymentCount: number;
name: {
fullName: string;
} | null;
operatingSystem: string;
metadata: {
v1: {
created: Date | null;
digest: string;
} | null;
} | null;
dataSource: { name: string } | null;
scanTime: Date | null;
};
};

export const imageDetailsQuery = gql`
query getImageDetails($id: ID!) {
image(id: $id) {
id
deploymentCount
name {
fullName
}
operatingSystem
metadata {
v1 {
created
digest
}
}
dataSource {
name
}
scanTime
}
}
`;

function WorkloadCvesImageSinglePage() {
const { imageId } = useParams();
const { data, error } = useQuery<ImageDetailsResponse, ImageDetailsVariables>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { gql } from '@apollo/client';

export type ImageDetailsVariables = {
id: string;
};

export type ImageDetailsResponse = {
image: {
id: string;
deploymentCount: number;
name: {
fullName: string;
} | null;
operatingSystem: string;
metadata: {
v1: {
created: Date | null;
digest: string;
layers: {
instruction: string;
value: string;
}[];
} | null;
} | null;
dataSource: { name: string } | null;
scanTime: Date | null;
};
};

export const imageDetailsQuery = gql`
query getImageDetails($id: ID!) {
image(id: $id) {
id
deploymentCount
name {
fullName
}
operatingSystem
metadata {
v1 {
created
digest
layers {
instruction
value
}
}
}
dataSource {
name
}
scanTime
}
}
`;

export default function useImageDetails() {}

0 comments on commit 7771160

Please sign in to comment.