From ebf1a2d8f5a5d91414435b9800709c22da8a04ec Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Fri, 30 Aug 2024 18:12:19 -0500 Subject: [PATCH] Show zeroes on software/software OSes/software vulns tables (#21584) #18897 # Checklist for submitter - [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 --- changes/18897-shoe-zeroes | 1 + frontend/__mocks__/softwareMock.ts | 42 ++++++++++- .../cards/OperatingSystems/OSTable.tests.tsx | 2 +- .../SoftwareOSTable/SoftwareOSTable.tests.tsx | 70 +++++++++++++++++++ .../SoftwareOSTable/SoftwareOSTable.tsx | 2 +- .../SoftwareTable/SoftwareTable.tests.tsx | 26 +++---- .../SoftwareTable/SoftwareTable.tsx | 2 +- .../SoftwareVulnerabilitiesTable.tests.tsx | 15 ++-- .../SoftwareVulnerabilitiesTable.tsx | 4 +- 9 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 changes/18897-shoe-zeroes create mode 100644 frontend/pages/SoftwarePage/SoftwareOS/SoftwareOSTable/SoftwareOSTable.tests.tsx diff --git a/changes/18897-shoe-zeroes b/changes/18897-shoe-zeroes new file mode 100644 index 0000000000..7faddd522d --- /dev/null +++ b/changes/18897-shoe-zeroes @@ -0,0 +1 @@ +Added "0 items" description on empty software tables for UI consistency diff --git a/frontend/__mocks__/softwareMock.ts b/frontend/__mocks__/softwareMock.ts index 02a5f2d185..9ef0b14e9a 100644 --- a/frontend/__mocks__/softwareMock.ts +++ b/frontend/__mocks__/softwareMock.ts @@ -14,6 +14,8 @@ import { ISoftwareVersionsResponse, ISoftwareVersionResponse, } from "services/entities/software"; +import { IOSVersionsResponse } from "../services/entities/operating_systems"; +import { IOperatingSystemVersion } from "../interfaces/operating_system"; const DEFAULT_SOFTWARE_MOCK: ISoftware = { hosts_count: 1, @@ -93,12 +95,48 @@ const DEFAULT_SOFTWARE_VERSIONS_RESPONSE_MOCK: ISoftwareVersionsResponse = { }, }; -export const createMockSoftwareVersionsReponse = ( +export const createMockSoftwareVersionsResponse = ( overrides?: Partial ): ISoftwareVersionsResponse => { return { ...DEFAULT_SOFTWARE_VERSIONS_RESPONSE_MOCK, ...overrides }; }; +const DEFAULT_OS_VERSION_MOCK = { + os_version_id: 1, + name: "macOS 14.6.1", + name_only: "macOS", + version: "14.6.1", + platform: "darwin", + hosts_count: 42, + generated_cpes: [], + vulnerabilities: [], +}; + +export const createMockOSVersion = ( + overrides?: Partial +): IOperatingSystemVersion => { + return { + ...DEFAULT_OS_VERSION_MOCK, + ...overrides, + }; +}; + +const DEFAULT_OS_VERSIONS_RESPONSE_MOCK: IOSVersionsResponse = { + counts_updated_at: "2020-01-01T00:00:00.000Z", + count: 1, + os_versions: [createMockOSVersion()], + meta: { + has_next_results: false, + has_previous_results: false, + }, +}; + +export const createMockOSVersionsResponse = ( + overrides?: Partial +): IOSVersionsResponse => { + return { ...DEFAULT_OS_VERSIONS_RESPONSE_MOCK, ...overrides }; +}; + const DEFAULT_APP_STORE_APP_MOCK: IAppStoreApp = { name: "test app", app_store_id: 1, @@ -208,7 +246,7 @@ const DEFAULT_SOFTWARE_TITLES_RESPONSE_MOCK: ISoftwareTitlesResponse = { }, }; -export const createMockSoftwareTitlesReponse = ( +export const createMockSoftwareTitlesResponse = ( overrides?: Partial ): ISoftwareTitlesResponse => { return { ...DEFAULT_SOFTWARE_TITLES_RESPONSE_MOCK, ...overrides }; diff --git a/frontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tests.tsx b/frontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tests.tsx index eb2672562e..934bd5db44 100644 --- a/frontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tests.tsx +++ b/frontend/pages/DashboardPage/cards/OperatingSystems/OSTable.tests.tsx @@ -4,7 +4,7 @@ import { render, screen } from "@testing-library/react"; import OSTable from "./OSTable"; describe("Dashboard OS table", () => { - it("renders data normally when present", async () => { + it("renders data normally when present", () => { render( { + it("Renders the page-wide disabled state when software inventory is disabled", async () => { + render( + + ); + + expect(screen.getByText("Software inventory disabled")).toBeInTheDocument(); + }); + + it("Renders the page-wide empty state when no software is present", () => { + render( + + ); + + expect( + screen.getByText("No operating systems detected") + ).toBeInTheDocument(); + expect(screen.getByText("0 items")).toBeInTheDocument(); + expect(screen.queryByText("Search")).toBeNull(); + expect(screen.queryByText("Updated")).toBeNull(); + }); +}); diff --git a/frontend/pages/SoftwarePage/SoftwareOS/SoftwareOSTable/SoftwareOSTable.tsx b/frontend/pages/SoftwarePage/SoftwareOS/SoftwareOSTable/SoftwareOSTable.tsx index b04f99fbe5..9be6ee4d16 100644 --- a/frontend/pages/SoftwarePage/SoftwareOS/SoftwareOSTable/SoftwareOSTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareOS/SoftwareOSTable/SoftwareOSTable.tsx @@ -129,7 +129,7 @@ const SoftwareOSTable = ({ }; const renderSoftwareCount = () => { - if (!data?.os_versions || !data?.count) return null; + if (!data) return null; return ( <> diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tests.tsx b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tests.tsx index add93fc3b1..1c07bc5cd7 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tests.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tests.tsx @@ -4,8 +4,8 @@ import { createCustomRenderer } from "test/test-utils"; import createMockUser from "__mocks__/userMock"; import { - createMockSoftwareTitlesReponse, - createMockSoftwareVersionsReponse, + createMockSoftwareTitlesResponse, + createMockSoftwareVersionsResponse, } from "__mocks__/softwareMock"; import { noop } from "lodash"; @@ -25,7 +25,7 @@ const mockRouter = { }; describe("Software table", () => { - it("Renders the page-wide disabled state when software inventory is disabled", async () => { + it("Renders the page-wide disabled state when software inventory is disabled", () => { const render = createCustomRenderer({ context: { app: { @@ -40,7 +40,7 @@ describe("Software table", () => { router={mockRouter} isSoftwareEnabled={false} // Set to false showVersions={false} - data={createMockSoftwareTitlesReponse({ + data={createMockSoftwareTitlesResponse({ counts_updated_at: null, software_titles: [], })} @@ -68,7 +68,7 @@ describe("Software table", () => { expect(screen.queryByText("Vulnerability")).toBeNull(); }); - it("Renders the page-wide empty state when no software are present", async () => { + it("Renders the page-wide empty state when no software are present", () => { const render = createCustomRenderer({ context: { app: { @@ -83,7 +83,8 @@ describe("Software table", () => { router={mockRouter} isSoftwareEnabled showVersions={false} - data={createMockSoftwareTitlesReponse({ + data={createMockSoftwareTitlesResponse({ + count: 0, counts_updated_at: null, software_titles: [], })} @@ -111,11 +112,12 @@ describe("Software table", () => { expect( screen.getByText("Expecting to see software? Check back later.") ).toBeInTheDocument(); + expect(screen.getByText("0 items")).toBeInTheDocument(); expect(screen.queryByText("Search")).toBeNull(); expect(screen.queryByText("Updated")).toBeNull(); }); - it("Renders the page-wide empty state when search query does not exist but versions toggle is applied", async () => { + it("Renders the page-wide empty state when search query does not exist but versions toggle is applied", () => { const render = createCustomRenderer({ context: { app: { @@ -130,7 +132,7 @@ describe("Software table", () => { router={mockRouter} isSoftwareEnabled showVersions // Versions toggle applied - data={createMockSoftwareVersionsReponse({ + data={createMockSoftwareVersionsResponse({ counts_updated_at: null, software: [], })} @@ -160,7 +162,7 @@ describe("Software table", () => { ).toBeInTheDocument(); }); - it("Renders the empty search state when search query does not exist but dropdown is applied", async () => { + it("Renders the empty search state when search query does not exist but dropdown is applied", () => { const render = createCustomRenderer({ context: { app: { @@ -175,7 +177,7 @@ describe("Software table", () => { router={mockRouter} isSoftwareEnabled showVersions={false} - data={createMockSoftwareTitlesReponse({ + data={createMockSoftwareTitlesResponse({ counts_updated_at: null, software_titles: [], })} @@ -209,7 +211,7 @@ describe("Software table", () => { ).toBeInTheDocument(); }); - it("Renders the empty search state when search query does not exist but vulnerability filter is applied", async () => { + it("Renders the empty search state when search query does not exist but vulnerability filter is applied", () => { const render = createCustomRenderer({ context: { app: { @@ -224,7 +226,7 @@ describe("Software table", () => { router={mockRouter} isSoftwareEnabled showVersions={false} - data={createMockSoftwareTitlesReponse({ + data={createMockSoftwareTitlesResponse({ counts_updated_at: null, software_titles: [], })} diff --git a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx index 4985990537..9b8df49c2c 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTable.tsx @@ -269,7 +269,7 @@ const SoftwareTable = ({ }; const renderSoftwareCount = () => { - if (!tableData || !data?.count) return null; + if (!tableData || !data) return null; return ( <> diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx index ea0ac48389..2e869892d1 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tests.tsx @@ -24,7 +24,7 @@ const mockRouter = { }; describe("Software Vulnerabilities table", () => { - it("Renders the page-wide disabled state when software inventory is disabled", async () => { + it("Renders the page-wide disabled state when software inventory is disabled", () => { const render = createCustomRenderer({ context: { app: { @@ -62,7 +62,7 @@ describe("Software Vulnerabilities table", () => { }); // TODO: Reinstate collecting software view - it("Renders the page-wide empty state when no software vulnerabilities are present", async () => { + it("Renders the page-wide empty state when no software vulnerabilities are present", () => { const render = createCustomRenderer({ context: { app: { @@ -97,13 +97,14 @@ describe("Software Vulnerabilities table", () => { ); expect(screen.getByText("No vulnerabilities detected")).toBeInTheDocument(); + expect(screen.getByText("0 items")).toBeInTheDocument(); expect( screen.getByText("Expecting to see vulnerabilities? Check back later.") ).toBeInTheDocument(); expect(screen.queryByText("Vulnerability")).toBeNull(); }); - it("Renders the empty search state when search query does not exist but exploited vulnerabilities dropdown is applied", async () => { + it("Renders the empty search state when search query does not exist but exploited vulnerabilities dropdown is applied", () => { const render = createCustomRenderer({ context: { app: { @@ -145,7 +146,7 @@ describe("Software Vulnerabilities table", () => { expect(screen.queryByText("Vulnerability")).toBeNull(); }); - it("Renders the invalid CVE empty search state when search query wrapped in quotes is invalid with no results", async () => { + it("Renders the invalid CVE empty search state when search query wrapped in quotes is invalid with no results", () => { const render = createCustomRenderer({ context: { app: { @@ -188,7 +189,7 @@ describe("Software Vulnerabilities table", () => { expect(screen.queryByText("Vulnerability")).toBeNull(); }); - it("Renders the valid known CVE empty search state when search query wrapped in quotes is valid known CVE with no results", async () => { + it("Renders the valid known CVE empty search state when search query wrapped in quotes is valid known CVE with no results", () => { const render = createCustomRenderer({ context: { app: { @@ -233,7 +234,7 @@ describe("Software Vulnerabilities table", () => { expect(screen.queryByText("Vulnerability")).toBeNull(); }); - it("Renders the valid unknown CVE empty search state when search query wrapped in quotes is not a valid known CVE with no results", async () => { + it("Renders the valid unknown CVE empty search state when search query wrapped in quotes is not a valid known CVE with no results", () => { const render = createCustomRenderer({ context: { app: { @@ -276,7 +277,7 @@ describe("Software Vulnerabilities table", () => { expect(screen.queryByText("Vulnerability")).toBeNull(); }); - it("Renders premium columns", async () => { + it("Renders premium columns", () => { const render = createCustomRenderer({ context: { app: { diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx index d37eb63acb..390a3dc866 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilities/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx @@ -197,9 +197,9 @@ const SoftwareVulnerabilitiesTable = ({ }; const renderVulnerabilityCount = () => { - if (!data?.count) return null; + if (!data) return null; - const count = data.count; + const count = data?.count; return ( <>