Skip to content

Commit

Permalink
Add expandable section for image vulnerability components
Browse files Browse the repository at this point in the history
  • Loading branch information
dvail committed Mar 13, 2023
1 parent e2e729f commit dc03751
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ function statusCountsFromImageVulnerabilities(

export type ImageSingleVulnerabilitiesProps = {
imageId: string;
imageData: ImageDetailsResponse['image'] | undefined;
};

function ImageSingleVulnerabilities({ imageId }: ImageSingleVulnerabilitiesProps) {
function ImageSingleVulnerabilities({ imageId, imageData }: ImageSingleVulnerabilitiesProps) {
const { searchFilter } = useURLSearch();
// TODO Still need to properly integrate search filter with query
const { data, loading, error } = useImageVulnerabilities(imageId, {});
Expand Down Expand Up @@ -157,6 +158,7 @@ function ImageSingleVulnerabilities({ imageId }: ImageSingleVulnerabilitiesProps
<SplitItem>TODO Pagination</SplitItem>
</Split>
<SingleEntityVulnerabilitiesTable
image={imageData}
imageVulnerabilities={data.image.imageVulnerabilities}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { CodeBlock, CodeBlockCode } from '@patternfly/react-core';
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';

import { ImageVulnerabilitiesResponse } from '../hooks/useImageVulnerabilities';
import { ImageDetailsResponse } from '../hooks/useImageDetails';

export type ImageComponentsTableProps = {
image: ImageDetailsResponse['image'] | undefined;
imageComponents: ImageVulnerabilitiesResponse['image']['imageVulnerabilities'][number]['imageComponents'];
};

function ImageComponentsTable({ image, imageComponents }: ImageComponentsTableProps) {
const layers = image?.metadata?.v1?.layers ?? [];
return (
<TableComposable borders={false}>
<Thead>
<Tr>
<Th>Component</Th>
<Th>Version</Th>
<Th>Fixed In</Th>
<Th>Location</Th>
</Tr>
</Thead>
{imageComponents.map(({ id, name, version, fixedIn, location, layerIndex }) => {
let dockerfileText = `< Dockerfile layer information is not available >`;

if (layerIndex !== null) {
const layer = layers[layerIndex];
if (layer) {
dockerfileText = `${layer.instruction} ${layer.value}`;
}
}
return (
<Tbody
key={id}
style={{
borderBottom: '1px solid var(--pf-c-table--BorderColor)',
}}
>
<Tr>
<Td>{name}</Td>
<Td>{version}</Td>
<Td>{fixedIn || 'TODO - why empty'}</Td>
<Td>{location || 'TODO - why empty'}</Td>
</Tr>
<Tr>
<Td colSpan={4} className="pf-u-pt-0">
<CodeBlock>
<CodeBlockCode>{dockerfileText}</CodeBlockCode>
</CodeBlock>
</Td>
</Tr>
</Tbody>
);
})}
</TableComposable>
);
}

export default ImageComponentsTable;
Original file line number Diff line number Diff line change
@@ -1,44 +1,69 @@
import React from 'react';
import { Button, ButtonVariant } from '@patternfly/react-core';
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
import {
TableComposable,
Thead,
Tr,
Th,
Tbody,
Td,
ExpandableRowContent,
} 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 useTableExpandedRows from 'hooks/patternfly/useTableExpandedRows';
import { vulnerabilitySeverityLabels } from 'messages/common';
import { getDistanceStrictAsPhrase } from 'utils/dateUtils';
import { ImageVulnerabilitiesResponse } from '../hooks/useImageVulnerabilities';
import { getEntityPagePath } from '../searchUtils';
import ImageComponentsTable from './ImageComponentsTable';
import { ImageDetailsResponse } from '../hooks/useImageDetails';

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

function SingleEntityVulnerabilitiesTable({
image,
imageVulnerabilities,
}: SingleEntityVulnerabilitiesTableProps) {
const { isRowExpanded, setRowExpanded } = useTableExpandedRows();
return (
<TableComposable>
<Thead>
<Tr>
<Th>{/* Header for expanded column */}</Th>
<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];
{imageVulnerabilities.map(
(
{ cve, severity, summary, isFixable, imageComponents, discoveredAtImage },
rowIndex
) => {
const SeverityIcon: React.FC<SVGIconProps> | undefined =
SeverityIcons[severity];
const severityLabel: string | undefined = vulnerabilitySeverityLabels[severity];
const isExpanded = isRowExpanded(cve);

return (
<Tr key={cve}>
return (
<Tbody key={cve} isExpanded={isExpanded}>
<Tr>
<Td
expand={{
rowIndex,
isExpanded,
onToggle: () => setRowExpanded(cve, !isExpanded),
}}
/>
<Td dataLabel="CVE">
<Button
variant={ButtonVariant.link}
Expand Down Expand Up @@ -87,10 +112,29 @@ function SingleEntityVulnerabilitiesTable({
{getDistanceStrictAsPhrase(discoveredAtImage, new Date())}
</Td>
</Tr>
);
}
)}
</Tbody>
<Tr isExpanded={isExpanded}>
<Td />
<Td colSpan={5}>
<ExpandableRowContent>
<p>{summary}</p>
<div
className="pf-u-p-md pf-u-mt-md"
style={{
border: '1px solid var(--pf-c-table--BorderColor)',
}}
>
<ImageComponentsTable
image={image}
imageComponents={imageComponents}
/>
</div>
</ExpandableRowContent>
</Td>
</Tr>
</Tbody>
);
}
)}
</TableComposable>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ function WorkloadCvesImageSinglePage() {
eventKey="Vulnerabilities"
title={<TabTitleText>Vulnerabilities</TabTitleText>}
>
<ImageSingleVulnerabilities imageId={imageId} />
<ImageSingleVulnerabilities imageId={imageId} imageData={imageData} />
</Tab>
<Tab
className="pf-u-display-flex pf-u-flex-direction-column pf-u-flex-grow-1"
Expand Down
21 changes: 21 additions & 0 deletions ui/apps/platform/src/hooks/patternfly/useTableExpandedRows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState } from 'react';

export default function useTableExpandedRows() {
const [expandedSet, setExpandedSet] = useState<Set<string>>(new Set());

function isRowExpanded(key: string): boolean {
return expandedSet.has(key);
}

function setRowExpanded(key: string, isExpanded: boolean) {
const nextSet = new Set(expandedSet);
if (isExpanded) {
nextSet.add(key);
} else {
nextSet.delete(key);
}
setExpandedSet(nextSet);
}

return { isRowExpanded, setRowExpanded };
}

0 comments on commit dc03751

Please sign in to comment.