UI: Add column for published date to Vulnerabilities table (#10656)

## Addresses #9834 
<img width="1215" alt="added date to vuln table"
src="https://user-images.githubusercontent.com/61553566/226730586-4165f5c9-2a42-4378-b58b-7900838a8707.png">

## Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
Jacob Shandling 2023-03-23 09:32:32 -07:00 committed by GitHub
parent e60917f4c7
commit faa65ac350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 6 deletions

View File

@ -0,0 +1,2 @@
* Added a premium-only "Published" column to the vulnerabilities table to display when a
vulnerability was first published.

View File

@ -2,7 +2,7 @@ import React from "react";
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
interface ITextCellProps {
value: string | number | boolean | { timeString: string };
value?: string | number | boolean | { timeString: string };
formatter?: (val: any) => JSX.Element | string; // string, number, or null
greyed?: boolean;
classes?: string;

View File

@ -24,16 +24,15 @@ export interface IGetSoftwareByIdResponse {
}
export interface ISoftware {
hosts_count?: number;
id: number;
name: string; // e.g., "Figma.app"
version: string; // e.g., "2.1.11"
bundle_identifier?: string | null; // e.g., "com.figma.Desktop"
source: string; // e.g., "apps"
generated_cpe: string;
vulnerabilities: IVulnerability[] | null;
hosts_count?: number;
last_opened_at?: string | null; // e.g., "2021-08-18T15:11:35Z”
bundle_identifier?: string | null; // e.g., "com.figma.Desktop"
// type: string;
}
export const TYPE_CONVERSION: Record<string, string> = {

View File

@ -11,10 +11,11 @@ export interface IHostsAffected {
url: string;
}
export interface IVulnerability {
cisa_known_exploit?: boolean;
cve: string;
cvss_score?: number;
details_link: string;
cvss_score?: number;
epss_probability?: number;
cisa_known_exploit?: boolean;
cve_published?: string;
hosts_affected?: IHostsAffected[];
}

View File

@ -8,6 +8,7 @@ import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCel
import TextCell from "components/TableContainer/DataTable/TextCell";
import TooltipWrapper from "components/TooltipWrapper";
import CustomLink from "components/CustomLink";
import HumanTimeDiffWithDateTip from "components/HumanTimeDiffWithDateTip";
interface IHeaderProps {
column: {
@ -159,6 +160,36 @@ const generateVulnTableHeaders = (isPremiumTier: boolean): IDataColumn[] => {
<TextCell value={value ? "Yes" : "No"} />
),
},
{
title: "Published",
accessor: "cve_published",
disableSortBy: false,
sortType: "boolean",
Header: (headerProps: IHeaderProps): JSX.Element => {
const titleWithToolTip = (
<TooltipWrapper
tipContent={`The date this vulnerability was published in the National Vulnerability Database (NVD).`}
>
Published
</TooltipWrapper>
);
return (
<HeaderCell
value={titleWithToolTip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
);
},
Cell: ({ cell: { value } }: ITextCellProps): JSX.Element => {
const valString = typeof value === "number" ? value.toString() : value;
return (
<TextCell
value={valString ? { timeString: valString } : undefined}
formatter={valString ? HumanTimeDiffWithDateTip : undefined}
/>
);
},
},
];
return isPremiumTier ? tableHeaders.concat(premiumHeaders) : tableHeaders;

View File

@ -0,0 +1,89 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import createMockSoftware from "__mocks__/softwareMock";
import Vulnerabilities from "./Vulnerabilities";
describe("Vulnerabilities", () => {
const [mockSoftwareWithVuln, mockSoftwareNoVulns] = [
createMockSoftware({
vulnerabilities: [
{
cve: "CVE_333",
details_link: "https://its.really.bad",
cvss_score: 9.5,
epss_probability: 1,
cisa_known_exploit: false,
cve_published: "2023-02-14T20:15:00Z",
},
],
}),
createMockSoftware(),
];
it("renders the empty state when no vulnerabilities are provided", () => {
render(
<Vulnerabilities
isLoading={false}
isPremiumTier
software={mockSoftwareNoVulns}
/>
);
// Empty state
expect(
screen.getByText("No vulnerabilities detected for this software item.")
).toBeInTheDocument();
expect(
screen.getByText("Expecting to see vulnerabilities?")
).toBeInTheDocument();
expect(screen.getByText("File an issue on GitHub")).toBeInTheDocument();
});
it("correctly renders a table when 1 vulnerability is provided, Premium tier", () => {
render(
<Vulnerabilities
isLoading={false}
isPremiumTier
software={mockSoftwareWithVuln}
/>
);
// Rendered table
expect(screen.getByText("Vulnerability")).toBeInTheDocument();
expect(screen.getByText("Probability of exploit")).toBeInTheDocument();
expect(screen.getByText("Severity")).toBeInTheDocument();
expect(screen.getByText("Known exploit")).toBeInTheDocument();
expect(screen.getByText("Published")).toBeInTheDocument();
expect(screen.getByText("CVE_333")).toBeInTheDocument();
expect(screen.getByText("100%")).toBeInTheDocument();
expect(screen.getByText("Critical", { exact: false })).toBeInTheDocument();
expect(screen.getByText("ago", { exact: false })).toBeInTheDocument();
});
it("Only renders the 'Vulnerability' column when 1 vulnerability is provided on Free tier", () => {
render(
<Vulnerabilities
isLoading={false}
isPremiumTier={false}
software={mockSoftwareWithVuln}
/>
);
// Rendered table
expect(screen.getByText("Vulnerability")).toBeInTheDocument();
// No premium-only columns
expect(screen.queryByText("Probability of exploit")).toBeNull();
expect(screen.queryByText("Severity")).toBeNull();
expect(screen.queryByText("Known exploit")).toBeNull();
expect(screen.queryByText("Published")).toBeNull();
// Row data
expect(screen.getByText("CVE_333")).toBeInTheDocument();
expect(screen.queryByText("100%")).toBeNull();
expect(screen.queryByText("Critical", { exact: false })).toBeNull();
expect(screen.queryByText("ago", { exact: false })).toBeNull();
});
});