mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
Fleet UI: Update Android status tooltips + global activities (#37185)
This commit is contained in:
parent
d8a3af4e88
commit
229481fc79
6 changed files with 225 additions and 46 deletions
|
|
@ -270,7 +270,7 @@ export interface IActivityDetails {
|
|||
}
|
||||
|
||||
export const ACTIVITY_DISPLAY_NAME_MAP: Record<ActivityType, string> = {
|
||||
added_app_store_app: "Added App Store (VPP) app",
|
||||
added_app_store_app: "Added App Store app", // Includes VPP and Android Playstore apps
|
||||
added_bootstrap_package: "Added bootstrap package",
|
||||
added_conditional_access_microsoft: "Added conditional access: Microsoft",
|
||||
added_custom_scep_proxy: "Added certificate authority (CA): custom SCEP",
|
||||
|
|
@ -299,7 +299,7 @@ export const ACTIVITY_DISPLAY_NAME_MAP: Record<ActivityType, string> = {
|
|||
created_team: "Added team",
|
||||
created_user: "Added user",
|
||||
created_windows_profile: "Added configuration profile: Windows",
|
||||
deleted_app_store_app: "Deleted App Store (VPP) app",
|
||||
deleted_app_store_app: "Deleted App Store app", // Includes VPP and Android Playstore apps
|
||||
deleted_bootstrap_package: "Deleted bootstrap package",
|
||||
deleted_conditional_access_microsoft: "Deleted conditional access: Microsoft",
|
||||
deleted_custom_scep_proxy: "Deleted certificate authority (CA): custom SCEP",
|
||||
|
|
@ -334,7 +334,7 @@ export const ACTIVITY_DISPLAY_NAME_MAP: Record<ActivityType, string> = {
|
|||
disabled_windows_mdm_migration: "Turned off Windows MDM migration",
|
||||
edited_activity_automations: "Edited activity automations",
|
||||
edited_agent_options: "Edited agent options",
|
||||
edited_app_store_app: "Edited App Store (VPP) app",
|
||||
edited_app_store_app: "Edited App Store app", // Includes VPP and Android Playstore apps
|
||||
edited_conditional_access_microsoft: "Edited conditional access: Microsoft",
|
||||
edited_custom_scep_proxy: "Edited certificate authority (CA): custom SCEP",
|
||||
edited_declaration_profile: "GitOps: edited declaration (DDM) profiles",
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
flex: 1;
|
||||
min-width: 0; // allows the dropdowns to shrink properly in flex container
|
||||
}
|
||||
|
||||
|
||||
&__dropdown-filters {
|
||||
display: grid;
|
||||
grid-template-areas: "type date sort";
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: 4fr 3fr 3fr;
|
||||
gap: $pad-medium;
|
||||
}
|
||||
|
||||
|
|
@ -30,8 +30,8 @@
|
|||
// this container is defined in the dashboard page .scss file
|
||||
@container activity-feed-card (width < 436px) {
|
||||
.activity-feed-filters__dropdown-filters {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-areas:
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-areas:
|
||||
"type type"
|
||||
"date sort";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import React from "react";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { screen, within, waitFor } from "@testing-library/react";
|
||||
import { createCustomRenderer } from "test/test-utils";
|
||||
import { getPathWithQueryParams } from "utilities/url";
|
||||
import PATHS from "router/paths";
|
||||
|
||||
import InstallerStatusTable from "./InstallerStatusTable";
|
||||
|
||||
describe("InstallerStatusTable", () => {
|
||||
|
|
@ -17,13 +20,168 @@ describe("InstallerStatusTable", () => {
|
|||
|
||||
// Check cell values (always "hosts", even for 1)
|
||||
const cells = screen.getAllByRole("cell");
|
||||
expect(cells[0]).toHaveTextContent("0 hosts");
|
||||
expect(cells[1]).toHaveTextContent("1 host");
|
||||
expect(cells[2]).toHaveTextContent("3 hosts");
|
||||
|
||||
// Check the anchor and its text in each cell
|
||||
expect(cells[0].querySelector("a.link-cell")).toHaveTextContent("0 hosts");
|
||||
expect(cells[1].querySelector("a.link-cell")).toHaveTextContent("1 host");
|
||||
expect(cells[2].querySelector("a.link-cell")).toHaveTextContent("3 hosts");
|
||||
const installedLink = cells[0].querySelector("a.link-cell");
|
||||
const pendingLink = cells[1].querySelector("a.link-cell");
|
||||
const failedLink = cells[2].querySelector("a.link-cell");
|
||||
|
||||
expect(installedLink).toHaveTextContent("0 hosts");
|
||||
expect(pendingLink).toHaveTextContent("1 host");
|
||||
expect(failedLink).toHaveTextContent("3 hosts");
|
||||
});
|
||||
|
||||
it("renders correct header titles for install vs script package", () => {
|
||||
const { rerender } = render(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isScriptPackage={false}
|
||||
/>
|
||||
);
|
||||
|
||||
let headers = screen.getAllByRole("columnheader");
|
||||
expect(headers[0]).toHaveTextContent("Installed");
|
||||
expect(headers[1]).toHaveTextContent("Pending");
|
||||
expect(headers[2]).toHaveTextContent("Failed");
|
||||
|
||||
rerender(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isScriptPackage
|
||||
/>
|
||||
);
|
||||
|
||||
headers = screen.getAllByRole("columnheader");
|
||||
expect(headers[0]).toHaveTextContent("Ran");
|
||||
expect(headers[1]).toHaveTextContent("Pending");
|
||||
expect(headers[2]).toHaveTextContent("Failed");
|
||||
});
|
||||
|
||||
it("renders different tooltips for Android Play Store vs non-Android for pending", async () => {
|
||||
// non-Android: pending install/uninstall message
|
||||
const { user, rerender } = render(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isAndroidPlayStoreApp={false}
|
||||
/>
|
||||
);
|
||||
|
||||
let pendingHeader = screen.getByText(/pending/i);
|
||||
|
||||
await user.hover(pendingHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/Fleet is installing\/uninstalling or will/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Android: Play Store–style message
|
||||
rerender(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isAndroidPlayStoreApp
|
||||
/>
|
||||
);
|
||||
|
||||
pendingHeader = screen.getByText(/pending/i);
|
||||
|
||||
await user.hover(pendingHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/Software will be installed or configuration will/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("hides installed tooltip for Android Play Store app", async () => {
|
||||
const { user, rerender } = render(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isAndroidPlayStoreApp={false}
|
||||
/>
|
||||
);
|
||||
|
||||
let installedHeader = screen.getByText(/installed/i);
|
||||
|
||||
await user.hover(installedHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/Software is installed on these hosts/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
rerender(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isAndroidPlayStoreApp
|
||||
/>
|
||||
);
|
||||
|
||||
installedHeader = screen.getByText(/installed/i);
|
||||
|
||||
await user.hover(installedHeader);
|
||||
|
||||
// Installed tooltip returns null for Android Play Store
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByText(/Software is installed on these hosts/i)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders failed tooltip text correctly for Android vs non-Android", async () => {
|
||||
const { user, rerender } = render(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isAndroidPlayStoreApp={false}
|
||||
/>
|
||||
);
|
||||
|
||||
let failedHeader = screen.getByText(/failed/i);
|
||||
|
||||
await user.hover(failedHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/These hosts failed to install\/uninstall software/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
rerender(
|
||||
<InstallerStatusTable
|
||||
softwareId={1}
|
||||
teamId={1}
|
||||
status={{ installed: 0, pending: 0, failed: 0 }}
|
||||
isAndroidPlayStoreApp
|
||||
/>
|
||||
);
|
||||
|
||||
failedHeader = screen.getByText(/failed/i);
|
||||
|
||||
await user.hover(failedHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Software failed to install or configuration failed to apply/i
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface IInstallerStatusTableProps {
|
|||
status: ISoftwarePackageStatus | ISoftwareAppStoreAppStatus;
|
||||
isLoading?: boolean;
|
||||
isScriptPackage?: boolean;
|
||||
isAndroidPlayStoreApp?: boolean;
|
||||
}
|
||||
|
||||
const InstallerStatusTable = ({
|
||||
|
|
@ -26,6 +27,7 @@ const InstallerStatusTable = ({
|
|||
status,
|
||||
isLoading = false,
|
||||
isScriptPackage = false,
|
||||
isAndroidPlayStoreApp = false,
|
||||
}: IInstallerStatusTableProps) => {
|
||||
const classNames = classnames(baseClass, className);
|
||||
|
||||
|
|
@ -34,6 +36,7 @@ const InstallerStatusTable = ({
|
|||
softwareId,
|
||||
teamId,
|
||||
isScriptPackage,
|
||||
isAndroidPlayStoreApp,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
import { ISoftwareTitleVersion } from "interfaces/software";
|
||||
import PATHS from "router/paths";
|
||||
|
|
@ -9,12 +9,14 @@ import LinkCell from "components/TableContainer/DataTable/LinkCell";
|
|||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
import Icon from "components/Icon";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import { isAndroid } from "interfaces/platform";
|
||||
|
||||
interface ISoftwareTitleDetailsTableConfigProps {
|
||||
softwareId?: number;
|
||||
teamId?: number;
|
||||
baseClass?: string;
|
||||
isScriptPackage?: boolean;
|
||||
isAndroidPlayStoreApp?: boolean;
|
||||
}
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
|
|
@ -28,7 +30,7 @@ interface ICellProps {
|
|||
interface IStatusDisplayOption {
|
||||
displayName: string;
|
||||
iconName: "success" | "pending-outline" | "error";
|
||||
tooltip: React.ReactNode;
|
||||
tooltip: (isAndroidPlayStoreApp?: boolean) => React.ReactNode;
|
||||
}
|
||||
|
||||
// "pending" and "failed" each encompass both "_install" and "_uninstall" sub-statuses
|
||||
|
|
@ -45,42 +47,56 @@ const STATUS_DISPLAY_OPTIONS: Record<
|
|||
installed: {
|
||||
displayName: "Installed",
|
||||
iconName: "success",
|
||||
tooltip: (
|
||||
<>
|
||||
Software is installed on these hosts (install script finished
|
||||
<br />
|
||||
with exit code 0). Currently, if the software is uninstalled, the
|
||||
<br />
|
||||
"Installed" status won't be updated.
|
||||
</>
|
||||
),
|
||||
tooltip: (isAndroidPlayStoreApp) => {
|
||||
return isAndroidPlayStoreApp ? null : (
|
||||
<>
|
||||
Software is installed on these hosts (install script finished
|
||||
<br />
|
||||
with exit code 0). Currently, if the software is uninstalled, the
|
||||
<br />
|
||||
"Installed" status won't be updated.
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
pending: {
|
||||
displayName: "Pending",
|
||||
iconName: "pending-outline",
|
||||
tooltip: (
|
||||
<>
|
||||
Fleet is installing/uninstalling or will
|
||||
<br />
|
||||
do so when the host comes online.
|
||||
</>
|
||||
),
|
||||
tooltip: (isAndroidPlayStoreApp) => {
|
||||
return isAndroidPlayStoreApp ? (
|
||||
<>
|
||||
Software will be installed or configuration will
|
||||
<br />
|
||||
be applied the next time the host checks in.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Fleet is installing/uninstalling or will
|
||||
<br />
|
||||
do so when the host comes online.
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
failed: {
|
||||
displayName: "Failed",
|
||||
iconName: "error",
|
||||
tooltip: (
|
||||
<>
|
||||
These hosts failed to install/uninstall software.
|
||||
<br />
|
||||
Click on a host to view error(s).
|
||||
</>
|
||||
),
|
||||
tooltip: (isAndroidPlayStoreApp) => {
|
||||
return isAndroidPlayStoreApp ? (
|
||||
<>Software failed to install or configuration failed to apply.</>
|
||||
) : (
|
||||
<>
|
||||
These hosts failed to install/uninstall software.
|
||||
<br />
|
||||
Click on a host to view error(s).
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
ran_script: {
|
||||
displayName: "Ran",
|
||||
iconName: "success",
|
||||
tooltip: (
|
||||
tooltip: () => (
|
||||
<>
|
||||
The script successfully
|
||||
<br />
|
||||
|
|
@ -91,7 +107,7 @@ const STATUS_DISPLAY_OPTIONS: Record<
|
|||
pending_script: {
|
||||
displayName: "Pending",
|
||||
iconName: "pending-outline",
|
||||
tooltip: (
|
||||
tooltip: () => (
|
||||
<>
|
||||
Fleet is running the script or will do so
|
||||
<br />
|
||||
|
|
@ -102,7 +118,7 @@ const STATUS_DISPLAY_OPTIONS: Record<
|
|||
failed_script: {
|
||||
displayName: "Failed",
|
||||
iconName: "error",
|
||||
tooltip: (
|
||||
tooltip: () => (
|
||||
<>
|
||||
These hosts failed to run the script.
|
||||
<br />
|
||||
|
|
@ -117,6 +133,7 @@ const generateSoftwareTitleDetailsTableConfig = ({
|
|||
teamId,
|
||||
baseClass,
|
||||
isScriptPackage,
|
||||
isAndroidPlayStoreApp,
|
||||
}: ISoftwareTitleDetailsTableConfigProps) => {
|
||||
const tableHeaders = [
|
||||
{
|
||||
|
|
@ -130,7 +147,7 @@ const generateSoftwareTitleDetailsTableConfig = ({
|
|||
const titleWithTooltip = (
|
||||
<TooltipWrapper
|
||||
position="top"
|
||||
tipContent={displayData.tooltip}
|
||||
tipContent={displayData.tooltip(isAndroidPlayStoreApp)}
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={10}
|
||||
|
|
@ -167,7 +184,7 @@ const generateSoftwareTitleDetailsTableConfig = ({
|
|||
return (
|
||||
<TooltipWrapper
|
||||
position="top"
|
||||
tipContent={displayData.tooltip}
|
||||
tipContent={displayData.tooltip(isAndroidPlayStoreApp)}
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={10}
|
||||
|
|
@ -203,7 +220,7 @@ const generateSoftwareTitleDetailsTableConfig = ({
|
|||
return (
|
||||
<TooltipWrapper
|
||||
position="top"
|
||||
tipContent={displayData.tooltip}
|
||||
tipContent={displayData.tooltip(isAndroidPlayStoreApp)}
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={10}
|
||||
|
|
|
|||
|
|
@ -320,6 +320,7 @@ const SoftwareInstallerCard = ({
|
|||
<div className={`${baseClass}__installer-status-table`}>
|
||||
<InstallerStatusTable
|
||||
isScriptPackage={isScriptPackage}
|
||||
isAndroidPlayStoreApp={isAndroidPlayStoreApp}
|
||||
softwareId={softwareId}
|
||||
teamId={teamId}
|
||||
status={status}
|
||||
|
|
|
|||
Loading…
Reference in a new issue