mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Update UI to allow device users to reinstall self-service software (#20472)
This commit is contained in:
parent
e4044c5d63
commit
ac368bdcf4
4 changed files with 157 additions and 25 deletions
1
changes/20131-self-service-reinstall
Normal file
1
changes/20131-self-service-reinstall
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Updated UI to allow device users to reinstall self-service software.
|
||||
|
|
@ -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(
|
||||
<SelfService
|
||||
contactUrl={"http://example.com"}
|
||||
deviceToken={"123-456"}
|
||||
isSoftwareEnabled
|
||||
pathname={"/test"}
|
||||
queryParams={{
|
||||
page: 1,
|
||||
query: "",
|
||||
order_key: "name",
|
||||
order_direction: "asc",
|
||||
per_page: 10,
|
||||
vulnerable: true,
|
||||
}}
|
||||
router={createMockRouter()}
|
||||
/>
|
||||
);
|
||||
render(<SelfService {...TEST_PROPS} />);
|
||||
|
||||
// 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(<SelfService {...TEST_PROPS} router={createMockRouter()} />);
|
||||
|
||||
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(<SelfService {...TEST_PROPS} />);
|
||||
|
||||
// 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(<SelfService {...TEST_PROPS} />);
|
||||
|
||||
// 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(<SelfService {...TEST_PROPS} />);
|
||||
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,9 @@ const InstallerStatus = ({
|
|||
data-for={`install-tooltip__${id}`}
|
||||
>
|
||||
<Icon name={displayConfig.iconName} />
|
||||
<span>{displayConfig.displayText}</span>
|
||||
<span data-testid={`${baseClass}__status--test`}>
|
||||
{displayConfig.displayText}
|
||||
</span>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className={`${baseClass}__status-tooltip`}
|
||||
|
|
@ -119,6 +121,20 @@ interface IInstallerStatusActionProps {
|
|||
onInstall: () => 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 = ({
|
|||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__item-action`}>
|
||||
{(displayStatus === "failed" || displayStatus === null) && (
|
||||
{!!installButtonText && (
|
||||
<Button
|
||||
variant="text-icon"
|
||||
type="button"
|
||||
|
|
@ -181,7 +198,9 @@ const InstallerStatusAction = ({
|
|||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{displayStatus === "failed" ? "Retry" : "Install"}
|
||||
<span data-testid={`${baseClass}__item-action-button--test`}>
|
||||
{installButtonText}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue