Fleet UI: 4.77 unreleased bug fixes (#35505)
This commit is contained in:
@@ -545,29 +545,87 @@ export interface IHostSoftware {
|
||||
* - Cases where the software inventory has not yet updated to reflect a recent change
|
||||
* (i.e., last_install date vs host software's updated_at date)
|
||||
*/
|
||||
// Error UI statuses
|
||||
export const HOST_SOFTWARE_UI_ERROR_STATUSES = [
|
||||
"failed_install", // Install attempt failed
|
||||
"failed_install_update_available", // Install/update failed; newer installer version available
|
||||
"failed_uninstall", // Uninstall attempt failed
|
||||
"failed_uninstall_update_available", // Uninstall/update failed; newer installer version available
|
||||
"failed_script", // Script package failed to run
|
||||
] as const;
|
||||
export type HostSoftwareUiErrorStatus = typeof HOST_SOFTWARE_UI_ERROR_STATUSES[number];
|
||||
export const isSoftwareErrorStatus = (
|
||||
status: IHostSoftwareUiStatus
|
||||
): status is HostSoftwareUiErrorStatus =>
|
||||
HOST_SOFTWARE_UI_ERROR_STATUSES.includes(status as HostSoftwareUiErrorStatus);
|
||||
|
||||
// Pending UI statuses for OFFLINE hosts
|
||||
export const HOST_SOFTWARE_UI_PENDING_STATUSES = [
|
||||
"pending_install", // Install scheduled (no newer installer version)
|
||||
"pending_uninstall", // Uninstall scheduled
|
||||
"pending_update", // Update scheduled (no newer installer version)
|
||||
"pending_script", // Fleet-initiated script run scheduled
|
||||
] as const;
|
||||
export type HostSoftwareUiPendingStatus = typeof HOST_SOFTWARE_UI_PENDING_STATUSES[number];
|
||||
export const isSoftwarePendingStatus = (
|
||||
status: IHostSoftwareUiStatus
|
||||
): status is HostSoftwareUiPendingStatus =>
|
||||
HOST_SOFTWARE_UI_PENDING_STATUSES.includes(
|
||||
status as HostSoftwareUiPendingStatus
|
||||
);
|
||||
|
||||
// In-progress UI statuses for ONLINE hosts
|
||||
export const HOST_SOFTWARE_UI_IN_PROGRESS_STATUSES = [
|
||||
"installing", // Fleet-initiated install in progress
|
||||
"updating", // Update (install) in progress with newer fleet installer
|
||||
"uninstalling", // Fleet-initiated uninstall in progress
|
||||
"running_script", // Fleet-initiated script run in progress
|
||||
] as const;
|
||||
export type HostSoftwareUiInProgressStatus = typeof HOST_SOFTWARE_UI_IN_PROGRESS_STATUSES[number];
|
||||
export const isSoftwareInProgressStatus = (
|
||||
status: IHostSoftwareUiStatus
|
||||
): status is HostSoftwareUiInProgressStatus =>
|
||||
HOST_SOFTWARE_UI_IN_PROGRESS_STATUSES.includes(
|
||||
status as HostSoftwareUiInProgressStatus
|
||||
);
|
||||
|
||||
// Success/steady-state UI statuses
|
||||
export const HOST_SOFTWARE_UI_SUCCESS_STATUSES = [
|
||||
"installed", // Present in inventory; no newer fleet installer version (tarballs: successful install only)
|
||||
"uninstalled", // Not present in inventory (tarballs: successful uninstall or never installed)
|
||||
"recently_updated", // Update applied (installer newer than inventory), but inventory not yet refreshed
|
||||
"recently_installed", // Install applied (installer NOT newer than inventory), but inventory not yet refreshed
|
||||
"recently_uninstalled", // Uninstall applied, but inventory not yet refreshed
|
||||
"ran_script", // Script package ran successfully
|
||||
"never_ran_script", // Script package never ran before
|
||||
] as const;
|
||||
export type HostSoftwareUiSuccessStatus = typeof HOST_SOFTWARE_UI_SUCCESS_STATUSES[number];
|
||||
export const isSoftwareSuccessStatus = (
|
||||
status: IHostSoftwareUiStatus
|
||||
): status is HostSoftwareUiSuccessStatus =>
|
||||
HOST_SOFTWARE_UI_SUCCESS_STATUSES.includes(
|
||||
status as HostSoftwareUiSuccessStatus
|
||||
);
|
||||
|
||||
// Update-available UI status
|
||||
export const HOST_SOFTWARE_UI_UPDATE_AVAILABLE_STATUSES = [
|
||||
"update_available", // In inventory, but newer fleet installer version is available
|
||||
] as const;
|
||||
export type HostSoftwareUiUpdateAvailableStatus = typeof HOST_SOFTWARE_UI_UPDATE_AVAILABLE_STATUSES[number];
|
||||
export const isSoftwareUpdateAvailableStatus = (
|
||||
status: IHostSoftwareUiStatus
|
||||
): status is HostSoftwareUiUpdateAvailableStatus =>
|
||||
HOST_SOFTWARE_UI_UPDATE_AVAILABLE_STATUSES.includes(
|
||||
status as HostSoftwareUiUpdateAvailableStatus
|
||||
);
|
||||
|
||||
// Master UI status type, combining all:
|
||||
export type IHostSoftwareUiStatus =
|
||||
| "installed" // Present in inventory; no newer fleet installer version (tarballs: successful install only)
|
||||
| "uninstalled" // Not present in inventory (tarballs: successful uninstall or never installed)
|
||||
| "installing" // ONLINE; fleet-initiated install in progress
|
||||
| "uninstalling" // ONLINE; fleet-initiated uninstall in progress
|
||||
| "recently_updated" // Update applied (installer newer than inventory), but inventory not yet refreshed
|
||||
| "recently_installed" // Install applied (installer NOT newer than inventory), but inventory not yet refreshed
|
||||
| "recently_uninstalled" // Uninstall applied, but inventory not yet refreshed
|
||||
| "updating" // ONLINE; update (install) in progress with newer fleet installer
|
||||
| "pending_install" // OFFLINE; install scheduled (no newer installer version)
|
||||
| "pending_uninstall" // OFFLINE; uninstall scheduled
|
||||
| "pending_update" // OFFLINE; update scheduled (no newer installer version)
|
||||
| "failed_install" // Install attempt failed
|
||||
| "failed_install_update_available" // Install/update failed; newer installer version available
|
||||
| "failed_uninstall" // Uninstall attempt failed
|
||||
| "failed_uninstall_update_available" // Uninstall/update failed; newer installer version available
|
||||
| "update_available" // In inventory, but newer fleet installer version is available
|
||||
// Script UI statuses
|
||||
| "ran_script" // Script package ran successfully
|
||||
| "failed_script" // Script package failed to run
|
||||
| "running_script" // ONLINE; fleet-initiated script run in progress
|
||||
| "pending_script" // OFFLINE; fleet-initiated script run scheduled
|
||||
| "never_ran_script"; // Script package never ran before
|
||||
| HostSoftwareUiErrorStatus
|
||||
| HostSoftwareUiPendingStatus
|
||||
| HostSoftwareUiSuccessStatus
|
||||
| HostSoftwareUiInProgressStatus
|
||||
| HostSoftwareUiUpdateAvailableStatus;
|
||||
|
||||
/**
|
||||
* Extends IHostSoftware with a computed `ui_status` field.
|
||||
|
||||
@@ -350,6 +350,7 @@ const PackageForm = ({
|
||||
isExePackage={isExePackage}
|
||||
isTarballPackage={isTarballPackage}
|
||||
isScriptPackage={isScriptPackage}
|
||||
isIpaPackage={isIpaPackage}
|
||||
onClickPreviewEndUserExperience={() =>
|
||||
onClickPreviewEndUserExperience(isIpaPackage)
|
||||
}
|
||||
|
||||
+3
-2
@@ -42,8 +42,9 @@ const CategoriesSelector = ({
|
||||
value={selectedCategories.includes(cat.value)}
|
||||
onChange={onSelectCategory}
|
||||
parseTarget
|
||||
/>
|
||||
<div className={`${baseClass}__label-name`}>{cat.label}</div>
|
||||
>
|
||||
<div className={`${baseClass}__label-name`}>{cat.label}</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
|
||||
&__item-action {
|
||||
min-width: 90px;
|
||||
min-width: 94px; // Fits the "Reinstall" button without shifting
|
||||
|
||||
.component__tooltip-wrapper {
|
||||
margin: 0; // Override 10px vertical margin
|
||||
|
||||
+162
-3
@@ -11,7 +11,9 @@ const makeSoftware = (ui_status: string) =>
|
||||
...createMockHostSoftware(),
|
||||
ui_status,
|
||||
} as IDeviceSoftwareWithUiStatus);
|
||||
|
||||
describe("TileActionStatus", () => {
|
||||
// Active/pending/running statuses
|
||||
it("shows spinner and installing label if installing", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
@@ -23,6 +25,17 @@ describe("TileActionStatus", () => {
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows spinner and installing label if pending_install", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("pending_install")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/Installing.../i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows spinner and updating label if updating", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
@@ -34,6 +47,62 @@ describe("TileActionStatus", () => {
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows spinner and updating label if pending_update", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("pending_update")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/Updating.../i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows spinner and running label if running_script", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("running_script")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/Running.../i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows spinner and running label if pending_script", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("pending_script")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/Running.../i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows spinner and uninstalling label if uninstalling", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("uninstalling")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/Uninstalling.../i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows spinner and uninstalling label if pending_uninstall", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("pending_uninstall")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/Uninstalling.../i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("spinner")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Button/status tests
|
||||
it("shows Install button for uninstalled status", () => {
|
||||
const onClick = jest.fn();
|
||||
render(
|
||||
@@ -48,6 +117,18 @@ describe("TileActionStatus", () => {
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows Install button for recently_uninstalled status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("recently_uninstalled")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Install/i })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Update button for update_available status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
@@ -58,6 +139,16 @@ describe("TileActionStatus", () => {
|
||||
expect(screen.getByRole("button", { name: /Update/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Update button for failed_uninstall_update_available status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("failed_uninstall_update_available")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /Update/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Reinstall button for installed status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
@@ -70,6 +161,62 @@ describe("TileActionStatus", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Reinstall button for recently_installed status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("recently_installed")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Reinstall/i })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Reinstall button for recently_updated status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("recently_updated")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Reinstall/i })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Reinstall button for failed_uninstall status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("failed_uninstall")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Reinstall/i })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Run button for never_ran_script status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("never_ran_script")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /Run/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Rerun button for ran_script status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("ran_script")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /Rerun/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Retry button and error display for failed_install", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
@@ -94,14 +241,26 @@ describe("TileActionStatus", () => {
|
||||
expect(screen.getByTestId("error-icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders nothing if tile label is null", () => {
|
||||
it("shows Retry button for failed_script status", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("failed_script")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /Retry/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Unknown/fallback status
|
||||
it("shows Install button for unknown_status (default)", () => {
|
||||
render(
|
||||
<TileActionStatus
|
||||
software={makeSoftware("unknown_status")}
|
||||
onActionClick={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Failed/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Install/i })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+55
-22
@@ -1,5 +1,11 @@
|
||||
import React from "react";
|
||||
import { IDeviceSoftwareWithUiStatus } from "interfaces/software";
|
||||
import {
|
||||
IDeviceSoftwareWithUiStatus,
|
||||
IHostSoftwareUiStatus,
|
||||
isSoftwareErrorStatus,
|
||||
isSoftwareInProgressStatus,
|
||||
isSoftwarePendingStatus,
|
||||
} from "interfaces/software";
|
||||
import Button from "components/buttons/Button";
|
||||
import Icon from "components/Icon";
|
||||
import Spinner from "components/Spinner";
|
||||
@@ -11,42 +17,69 @@ interface TileActionStatusProps {
|
||||
onActionClick: (software: IDeviceSoftwareWithUiStatus) => void;
|
||||
}
|
||||
|
||||
const getTileActionLabel = (software: IDeviceSoftwareWithUiStatus) => {
|
||||
if (software.ui_status === "uninstalled") {
|
||||
return "Install";
|
||||
const getTileActionLabel = (uiStatus: IHostSoftwareUiStatus): string | null => {
|
||||
switch (uiStatus) {
|
||||
case "uninstalled":
|
||||
case "recently_uninstalled":
|
||||
return "Install";
|
||||
case "failed_install":
|
||||
case "failed_install_update_available":
|
||||
case "failed_script":
|
||||
return "Retry";
|
||||
case "update_available":
|
||||
case "failed_uninstall_update_available":
|
||||
return "Update";
|
||||
case "installed":
|
||||
case "recently_installed":
|
||||
case "recently_updated":
|
||||
case "failed_uninstall": // Mobile UI only shows install action despite ui_status relating to uninstall
|
||||
return "Reinstall";
|
||||
case "never_ran_script":
|
||||
return "Run";
|
||||
case "ran_script":
|
||||
return "Rerun";
|
||||
default:
|
||||
return "Install";
|
||||
}
|
||||
if (
|
||||
software.ui_status === "failed_install" ||
|
||||
software.ui_status === "failed_install_update_available"
|
||||
) {
|
||||
return "Retry";
|
||||
};
|
||||
|
||||
const getPendingOrRunningLabel = (
|
||||
uiStatus: IHostSoftwareUiStatus
|
||||
): string | null => {
|
||||
switch (uiStatus) {
|
||||
case "updating":
|
||||
case "pending_update":
|
||||
return "Updating...";
|
||||
case "installing":
|
||||
case "pending_install":
|
||||
return "Installing...";
|
||||
case "running_script":
|
||||
case "pending_script":
|
||||
return "Running...";
|
||||
case "uninstalling":
|
||||
case "pending_uninstall":
|
||||
return "Uninstalling...";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
if (software.ui_status === "update_available") {
|
||||
return "Update";
|
||||
}
|
||||
if (software.ui_status === "installed") {
|
||||
return "Reinstall";
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const TileActionStatus = ({
|
||||
software,
|
||||
onActionClick,
|
||||
}: TileActionStatusProps) => {
|
||||
const actionLabel = getTileActionLabel(software);
|
||||
const isError =
|
||||
software.ui_status === "failed_install" ||
|
||||
software.ui_status === "failed_install_update_available";
|
||||
const actionLabel = getTileActionLabel(software.ui_status);
|
||||
const isError = isSoftwareErrorStatus(software.ui_status);
|
||||
|
||||
const isActiveAction =
|
||||
software.ui_status === "updating" || software.ui_status === "installing";
|
||||
isSoftwareInProgressStatus(software.ui_status) ||
|
||||
isSoftwarePendingStatus(software.ui_status);
|
||||
|
||||
const renderActiveActionStatus = () => {
|
||||
return (
|
||||
<>
|
||||
<Spinner size="x-small" includeContainer={false} centered={false} />
|
||||
{software.ui_status === "updating" ? "Updating..." : "Installing..."}
|
||||
{getPendingOrRunningLabel(software.ui_status)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -338,6 +338,8 @@ export const getInstallerActionButtonConfig = (
|
||||
case "pending_uninstall":
|
||||
case "uninstalling":
|
||||
case "failed_uninstall":
|
||||
case "recently_installed":
|
||||
case "recently_updated":
|
||||
return { text: "Reinstall", icon: "refresh" };
|
||||
case "pending_update":
|
||||
case "updating":
|
||||
|
||||
Reference in New Issue
Block a user