diff --git a/changes/20131-self-service-reinstall b/changes/20131-self-service-reinstall new file mode 100644 index 0000000000..0891a4a1e9 --- /dev/null +++ b/changes/20131-self-service-reinstall @@ -0,0 +1 @@ +- Updated UI to allow device users to reinstall self-service software. \ No newline at end of file diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx index 18179aa90d..60c5ea7d6b 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx @@ -6,7 +6,23 @@ import mockServer from "test/mock-server"; import { customDeviceSoftwareHandler } from "test/handlers/device-handler"; import { createMockDeviceSoftware } from "__mocks__/deviceUserMock"; -import SelfService from "./SelfService"; +import SelfService, { ISoftwareSelfServiceProps } from "./SelfService"; + +const TEST_PROPS: ISoftwareSelfServiceProps = { + contactUrl: "http://example.com", + deviceToken: "123-456", + isSoftwareEnabled: true, + pathname: "/test", + queryParams: { + page: 1, + query: "", + order_key: "name", + order_direction: "asc", + per_page: 10, + vulnerable: true, + }, + router: createMockRouter(), +}; describe("SelfService", () => { it("should render the self service items correctly", async () => { @@ -23,23 +39,7 @@ describe("SelfService", () => { const render = createCustomRenderer({ withBackendMock: true }); - render( - - ); + render(); // waiting for the device software data to render await screen.findByText("test1"); @@ -55,6 +55,31 @@ describe("SelfService", () => { it("should render the contact link text if contact url is provided", () => { mockServer.use(customDeviceSoftwareHandler()); + const render = createCustomRenderer({ withBackendMock: true }); + render(); + + expect(screen.getByText("reach out to IT")).toBeInTheDocument(); + expect(screen.getByText("reach out to IT").getAttribute("href")).toBe( + "http://example.com" + ); + }); + + it("renders 'Reinstall' action button with 'Installed' status", async () => { + mockServer.use( + customDeviceSoftwareHandler({ + software: [ + createMockDeviceSoftware({ + name: "test-software", + status: "installed", + last_install: { + install_uuid: "test-uuid", + installed_at: "2021-08-18T15:11:35Z", + }, + }), + ], + }) + ); + const render = createCustomRenderer({ withBackendMock: true }); const expectedUrl = "http://example.com"; @@ -77,9 +102,96 @@ describe("SelfService", () => { /> ); - expect(screen.getByText("reach out to IT")).toBeInTheDocument(); - expect(screen.getByText("reach out to IT").getAttribute("href")).toBe( - expectedUrl + // waiting for the device software data to render + await screen.findByText("test-software"); + + expect( + screen.getByTestId("self-service-item__status--test") + ).toHaveTextContent("Installed"); + + expect( + screen.getByTestId("self-service-item__item-action-button--test") + ).toHaveTextContent("Reinstall"); + }); + + it("renders 'Retry' action button with 'Failed' status", async () => { + mockServer.use( + customDeviceSoftwareHandler({ + software: [ + createMockDeviceSoftware({ + name: "test-software", + status: "failed", + }), + ], + }) ); + + const render = createCustomRenderer({ withBackendMock: true }); + render(); + + // waiting for the device software data to render + await screen.findByText("test-software"); + + expect( + screen.getByTestId("self-service-item__status--test") + ).toHaveTextContent("Failed"); + + expect( + screen.getByTestId("self-service-item__item-action-button--test") + ).toHaveTextContent("Retry"); + }); + + it("renders 'Install' action button with no status", async () => { + mockServer.use( + customDeviceSoftwareHandler({ + software: [ + createMockDeviceSoftware({ + name: "test-software", + status: null, + }), + ], + }) + ); + + const render = createCustomRenderer({ withBackendMock: true }); + render(); + + // waiting for the device software data to render + await screen.findByText("test-software"); + + expect( + screen.queryByTestId("self-service-item__status--test") + ).not.toBeInTheDocument(); + + expect( + screen.getByTestId("self-service-item__item-action-button--test") + ).toHaveTextContent("Install"); + }); + + it("renders no action button with 'Install in progress...' status", async () => { + mockServer.use( + customDeviceSoftwareHandler({ + software: [ + createMockDeviceSoftware({ + name: "test-software", + status: "pending", + }), + ], + }) + ); + + const render = createCustomRenderer({ withBackendMock: true }); + render(); + + // waiting for the device software data to render + await screen.findByText("test-software"); + + expect( + screen.getByTestId("self-service-item__status--test") + ).toHaveTextContent("Install in progress..."); + + expect( + screen.queryByTestId("self-service-item__item-action-button--test") + ).not.toBeInTheDocument(); }); }); diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx index dddc18c49e..f72b02f468 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx @@ -32,7 +32,7 @@ const DEFAULT_SELF_SERVICE_QUERY_PARAMS = { self_service: true, } as const; -interface ISoftwareSelfServiceProps { +export interface ISoftwareSelfServiceProps { contactUrl: string; deviceToken: string; isSoftwareEnabled?: boolean; diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx index d04e235e66..79befcff85 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfServiceItem/SelfServiceItem.tsx @@ -94,7 +94,9 @@ const InstallerStatus = ({ data-for={`install-tooltip__${id}`} > - {displayConfig.displayText} + + {displayConfig.displayText} + void; } +const getInstallButtonText = (status: SoftwareInstallStatus | null) => { + switch (status) { + case null: + return "Install"; + case "failed": + return "Retry"; + case "installed": + return "Reinstall"; + default: + // we don't show a button for pending installs + return ""; + } +}; + const InstallerStatusAction = ({ deviceToken, software: { id, status, last_install }, @@ -134,6 +150,7 @@ const InstallerStatusAction = ({ // displayStatus allows us to display the localStatus (if any) or the status from the list // software reponse const displayStatus = localStatus || status; + const installButtonText = getInstallButtonText(displayStatus); // if the localStatus is "failed", we don't our tooltip to include the old installed_at date so we // set this to null, which tells the tooltip to omit the parenthetical date @@ -172,7 +189,7 @@ const InstallerStatusAction = ({ />
- {(displayStatus === "failed" || displayStatus === null) && ( + {!!installButtonText && ( )}