mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
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:
parent
e60917f4c7
commit
faa65ac350
2
changes/9834-vulnerability-publishe-date-column
Normal file
2
changes/9834-vulnerability-publishe-date-column
Normal file
@ -0,0 +1,2 @@
|
||||
* Added a premium-only "Published" column to the vulnerabilities table to display when a
|
||||
vulnerability was first published.
|
@ -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;
|
||||
|
@ -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> = {
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user