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 <jacob@fleetdm.com>
This commit is contained in:
Ian Littman
2024-08-30 18:12:19 -05:00
committed by GitHub
co-authored by Jacob Shandling
parent 6d1de32713
commit ebf1a2d8f5
9 changed files with 138 additions and 26 deletions
+1
View File
@@ -0,0 +1 @@
Added "0 items" description on empty software tables for UI consistency
+40 -2
View File
@@ -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>
): 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>
): 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>
): 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>
): ISoftwareTitlesResponse => {
return { ...DEFAULT_SOFTWARE_TITLES_RESPONSE_MOCK, ...overrides };
@@ -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(
<OSTable
currentTeamId={undefined}
@@ -0,0 +1,70 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { createMockOSVersionsResponse } from "__mocks__/softwareMock";
import SoftwareOSTable from "./SoftwareOSTable";
// TODO: figure out how to mock the router properly.
const mockRouter = {
push: jest.fn(),
replace: jest.fn(),
goBack: jest.fn(),
goForward: jest.fn(),
go: jest.fn(),
setRouteLeaveHook: jest.fn(),
isActive: jest.fn(),
createHref: jest.fn(),
createPath: jest.fn(),
};
describe("Software operating systems table", () => {
it("Renders the page-wide disabled state when software inventory is disabled", async () => {
render(
<SoftwareOSTable
router={mockRouter}
isSoftwareEnabled={false} // Set to false
data={createMockOSVersionsResponse({
count: 0,
os_versions: [],
})}
perPage={20}
orderDirection="asc"
orderKey="hosts_count"
currentPage={0}
teamId={1}
isLoading={false}
resetPageIndex={false}
/>
);
expect(screen.getByText("Software inventory disabled")).toBeInTheDocument();
});
it("Renders the page-wide empty state when no software is present", () => {
render(
<SoftwareOSTable
router={mockRouter}
isSoftwareEnabled
data={createMockOSVersionsResponse({
count: 0,
os_versions: [],
})}
perPage={20}
orderDirection="asc"
orderKey="hosts_count"
currentPage={0}
teamId={1}
isLoading={false}
resetPageIndex={false}
/>
);
expect(
screen.getByText("No operating systems detected")
).toBeInTheDocument();
expect(screen.getByText("0 items")).toBeInTheDocument();
expect(screen.queryByText("Search")).toBeNull();
expect(screen.queryByText("Updated")).toBeNull();
});
});
@@ -129,7 +129,7 @@ const SoftwareOSTable = ({
};
const renderSoftwareCount = () => {
if (!data?.os_versions || !data?.count) return null;
if (!data) return null;
return (
<>
@@ -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: [],
})}
@@ -269,7 +269,7 @@ const SoftwareTable = ({
};
const renderSoftwareCount = () => {
if (!tableData || !data?.count) return null;
if (!tableData || !data) return null;
return (
<>
@@ -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: {
@@ -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 (
<>