Map additional pending statuses to "pending" UI in host details > OS Settings table (#37375)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #36683 # Checklist for submitter - [x] Added/updated automated tests - [ ] QA'd all new/changed functionality manually --------- Co-authored-by: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com>
This commit is contained in:
co-authored by
Victor Lyuboslavsky
parent
b8b50ed7e2
commit
9a6ef5ba52
@@ -97,7 +97,7 @@ export interface IMunkiData {
|
||||
|
||||
export type MacDiskEncryptionActionRequired = "log_out" | "rotate_key";
|
||||
|
||||
export type HostCertStatus =
|
||||
export type HostAndroidCertStatus =
|
||||
| "verified"
|
||||
| "failed"
|
||||
// all below display "pending" in UI
|
||||
@@ -105,9 +105,9 @@ export type HostCertStatus =
|
||||
| "delivering"
|
||||
| "delivered";
|
||||
|
||||
export interface IHostCert {
|
||||
export interface IHostAndroidCert {
|
||||
name: string;
|
||||
status: HostCertStatus;
|
||||
status: HostAndroidCertStatus;
|
||||
operation_type: "install" | "remove";
|
||||
detail: string;
|
||||
}
|
||||
@@ -117,7 +117,7 @@ export interface IOSSettings {
|
||||
status: DiskEncryptionStatus | null;
|
||||
detail: string;
|
||||
};
|
||||
certificates: IHostCert[];
|
||||
certificates: IHostAndroidCert[];
|
||||
}
|
||||
|
||||
interface IMdmMacOsSettings {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IConfigServerSettings } from "./config";
|
||||
import { HostAndroidCertStatus } from "./host";
|
||||
|
||||
export interface IMdmApple {
|
||||
common_name: string;
|
||||
@@ -185,7 +186,11 @@ export interface IHostMdmProfile {
|
||||
name: string;
|
||||
operation_type: ProfileOperationType | null;
|
||||
platform: ProfilePlatform;
|
||||
status: MdmProfileStatus | MdmDDMProfileStatus | LinuxDiskEncryptionStatus;
|
||||
status:
|
||||
| MdmProfileStatus
|
||||
| MdmDDMProfileStatus
|
||||
| LinuxDiskEncryptionStatus
|
||||
| HostAndroidCertStatus;
|
||||
detail: string;
|
||||
scope: ProfileScope | null;
|
||||
managed_local_account: string | null;
|
||||
@@ -200,7 +205,7 @@ export type DiskEncryptionStatus =
|
||||
| "failed"
|
||||
| "removing_enforcement";
|
||||
|
||||
/** Currently windows disk enxryption status will only be one of these four
|
||||
/** Currently windows disk encryption status will only be one of these four
|
||||
values. In the future we may add more. */
|
||||
export type WindowsDiskEncryptionStatus = Extract<
|
||||
DiskEncryptionStatus,
|
||||
|
||||
+8
-22
@@ -4,11 +4,7 @@ import { http, HttpResponse } from "msw";
|
||||
import { screen, waitFor } from "@testing-library/react";
|
||||
import { ICertificate } from "services/entities/certificates";
|
||||
import mockServer from "test/mock-server";
|
||||
import {
|
||||
renderWithSetup,
|
||||
baseUrl,
|
||||
createCustomRenderer,
|
||||
} from "test/test-utils";
|
||||
import { baseUrl, createCustomRenderer } from "test/test-utils";
|
||||
|
||||
import AddCertModal from "./AddCertificateModal";
|
||||
import { INVALID_NAME_MSG, NAME_TOO_LONG_MSG, USED_NAME_MSG } from "./helpers";
|
||||
@@ -49,9 +45,15 @@ const mockExistingCerts: ICertificate[] = [
|
||||
];
|
||||
|
||||
describe("AddCertModal", () => {
|
||||
it("renders the modal with all form fields", async () => {
|
||||
beforeEach(() => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
});
|
||||
afterEach(() => {
|
||||
mockServer.resetHandlers();
|
||||
});
|
||||
|
||||
it("renders the modal with all form fields", async () => {
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -80,8 +82,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("disables Create button when Name field is empty", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -109,8 +109,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("shows error for Name with invalid characters and disables Create button", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -138,8 +136,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("shows error for Name that already exists and disables Create button", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -167,8 +163,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("shows error for Name with more than 255 characters and disables Create button", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -197,8 +191,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("disables Create button when Certificate authority is not selected", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -227,8 +219,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("disables Create button when Subject name is empty", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -263,8 +253,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("full flow is okay when all fields are valid", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
@@ -307,8 +295,6 @@ describe("AddCertModal", () => {
|
||||
});
|
||||
|
||||
it("calls onExit when Cancel button is clicked", async () => {
|
||||
mockServer.use(getCAsHandler);
|
||||
mockServer.use(createCertHandler);
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
|
||||
+69
-1
@@ -1,7 +1,10 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { createCustomRenderer } from "test/test-utils";
|
||||
import { ProfileOperationType } from "interfaces/mdm";
|
||||
import {
|
||||
FLEET_ANDROID_CERTIFICATE_TEMPLATE_PROFILE_ID,
|
||||
ProfileOperationType,
|
||||
} from "interfaces/mdm";
|
||||
import OSSettingStatusCell from "./OSSettingStatusCell";
|
||||
|
||||
describe("OS setting status cell", () => {
|
||||
@@ -40,4 +43,69 @@ describe("OS setting status cell", () => {
|
||||
|
||||
expect(screen.getByText(/verifying/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Android cert statuses
|
||||
it("Displays Pending UI for 'pending' status with optype 'install'", async () => {
|
||||
const customRender = createCustomRenderer();
|
||||
|
||||
const { user } = customRender(
|
||||
<OSSettingStatusCell
|
||||
profileName="Test cert"
|
||||
status="pending"
|
||||
operationType="install"
|
||||
hostPlatform="android"
|
||||
profileUUID={FLEET_ANDROID_CERTIFICATE_TEMPLATE_PROFILE_ID}
|
||||
/>
|
||||
);
|
||||
|
||||
const statusText = screen.getByText("Enforcing (pending)");
|
||||
expect(statusText).toBeInTheDocument();
|
||||
|
||||
await user.hover(statusText);
|
||||
expect(
|
||||
screen.getByText(/The host is running the command/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
it("Displays Pending UI for 'delivering' status with optype 'install'", async () => {
|
||||
const customRender = createCustomRenderer();
|
||||
|
||||
const { user } = customRender(
|
||||
<OSSettingStatusCell
|
||||
profileName="Test cert"
|
||||
status="delivering"
|
||||
operationType="install"
|
||||
hostPlatform="android"
|
||||
profileUUID={FLEET_ANDROID_CERTIFICATE_TEMPLATE_PROFILE_ID}
|
||||
/>
|
||||
);
|
||||
|
||||
const statusText = screen.getByText("Enforcing (pending)");
|
||||
expect(statusText).toBeInTheDocument();
|
||||
|
||||
await user.hover(statusText);
|
||||
expect(
|
||||
screen.getByText(/The host is running the command/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
it("Displays Pending UI for 'delivered' status with optype 'install'", async () => {
|
||||
const customRender = createCustomRenderer();
|
||||
|
||||
const { user } = customRender(
|
||||
<OSSettingStatusCell
|
||||
profileName="Test cert"
|
||||
status="delivered"
|
||||
operationType="install"
|
||||
hostPlatform="android"
|
||||
profileUUID={FLEET_ANDROID_CERTIFICATE_TEMPLATE_PROFILE_ID}
|
||||
/>
|
||||
);
|
||||
|
||||
const statusText = screen.getByText("Enforcing (pending)");
|
||||
expect(statusText).toBeInTheDocument();
|
||||
|
||||
await user.hover(statusText);
|
||||
expect(
|
||||
screen.getByText(/The host is running the command/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+40
-13
@@ -19,7 +19,9 @@ import {
|
||||
LINUX_DISK_ENCRYPTION_DISPLAY_CONFIG,
|
||||
PROFILE_DISPLAY_CONFIG,
|
||||
ProfileDisplayOption,
|
||||
ProfileStatus,
|
||||
WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG,
|
||||
WindowsDiskEncryptionDisplayStatus,
|
||||
} from "./helpers";
|
||||
|
||||
const baseClass = "os-settings-status-cell";
|
||||
@@ -52,17 +54,38 @@ const OSSettingStatusCell = ({
|
||||
) {
|
||||
switch (status) {
|
||||
case "pending":
|
||||
displayOption = {
|
||||
statusText:
|
||||
operationType === "install"
|
||||
? "Enforcing (pending)"
|
||||
: "Removing enforcement (pending)",
|
||||
iconName: "pending-outline",
|
||||
tooltip: () =>
|
||||
operationType === "install"
|
||||
? "The host is running the command to apply settings or will run it when the host comes online."
|
||||
: "The host is running the command to remove settings or will run it when the host comes online.",
|
||||
};
|
||||
if (operationType === "install") {
|
||||
displayOption = {
|
||||
statusText: "Enforcing (pending)",
|
||||
iconName: "pending-outline",
|
||||
tooltip:
|
||||
"The host is running the command to apply settings or will run it when the host comes online.",
|
||||
};
|
||||
} else {
|
||||
displayOption = {
|
||||
statusText: "Removing enforcement (pending)",
|
||||
iconName: "pending-outline",
|
||||
tooltip:
|
||||
"The host is running the command to remove settings or will run it when the host comes online.",
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "delivering":
|
||||
case "delivered":
|
||||
if (operationType === "install") {
|
||||
// note that thise case is identical to the "pending" case above for install operation
|
||||
// separation allows catching the below error case
|
||||
displayOption = {
|
||||
statusText: "Enforcing (pending)",
|
||||
iconName: "pending-outline",
|
||||
tooltip:
|
||||
"The host is running the command to apply settings or will run it when the host comes online.",
|
||||
};
|
||||
} else {
|
||||
throw new Error(
|
||||
"Received unexpected 'delivering' or 'delivered' status for remove operation"
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "verified":
|
||||
displayOption = {
|
||||
@@ -90,9 +113,13 @@ const OSSettingStatusCell = ({
|
||||
status !== "success" &&
|
||||
status !== "acknowledged"
|
||||
) {
|
||||
displayOption = WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG[status];
|
||||
displayOption =
|
||||
WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG[
|
||||
status as WindowsDiskEncryptionDisplayStatus
|
||||
];
|
||||
} else if (operationType) {
|
||||
displayOption = PROFILE_DISPLAY_CONFIG[operationType]?.[status];
|
||||
displayOption =
|
||||
PROFILE_DISPLAY_CONFIG[operationType]?.[status as ProfileStatus];
|
||||
}
|
||||
|
||||
const isDeviceUser = window.location.pathname
|
||||
|
||||
+14
-6
@@ -22,10 +22,14 @@ export type ProfileDisplayOption = {
|
||||
tooltip: TooltipInnerContentOption | null;
|
||||
} | null;
|
||||
|
||||
type OperationTypeOption = Record<
|
||||
type MacProfileSpecificStatus = "success" | "acknowledged";
|
||||
type AndroidCertSpecificStatus = "delivered" | "delivering";
|
||||
|
||||
export type ProfileStatus = Exclude<
|
||||
OsSettingsTableStatusValue,
|
||||
ProfileDisplayOption
|
||||
AndroidCertSpecificStatus
|
||||
>;
|
||||
type OperationTypeOption = Record<ProfileStatus, ProfileDisplayOption>;
|
||||
|
||||
type ProfileDisplayConfig = Record<ProfileOperationType, OperationTypeOption>;
|
||||
|
||||
@@ -103,10 +107,14 @@ export const PROFILE_DISPLAY_CONFIG: ProfileDisplayConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
type WindowsDiskEncryptionDisplayConfig = Omit<
|
||||
export type WindowsDiskEncryptionDisplayStatus = Exclude<
|
||||
ProfileStatus,
|
||||
MacProfileSpecificStatus | AndroidCertSpecificStatus
|
||||
>;
|
||||
|
||||
type WindowsDiskEncryptionDisplayConfig = Pick<
|
||||
OperationTypeOption,
|
||||
// windows disk encryption does not have these states
|
||||
"success" | "acknowledged"
|
||||
WindowsDiskEncryptionDisplayStatus
|
||||
>;
|
||||
|
||||
export const WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG: WindowsDiskEncryptionDisplayConfig = {
|
||||
@@ -144,7 +152,7 @@ export const WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG: WindowsDiskEncryptionDispla
|
||||
|
||||
type LinuxDiskEncryptionDisplayConfig = Omit<
|
||||
OperationTypeOption,
|
||||
"success" | "pending" | "acknowledged" | "verifying"
|
||||
MacProfileSpecificStatus | AndroidCertSpecificStatus | "pending" | "verifying"
|
||||
>;
|
||||
|
||||
export const LINUX_DISK_ENCRYPTION_DISPLAY_CONFIG: LinuxDiskEncryptionDisplayConfig = {
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { Column } from "react-table";
|
||||
|
||||
import { IStringCellProps } from "interfaces/datatable_config";
|
||||
import { IHostMdmData } from "interfaces/host";
|
||||
import { HostAndroidCertStatus, IHostMdmData } from "interfaces/host";
|
||||
import {
|
||||
FLEET_FILEVAULT_PROFILE_DISPLAY_NAME,
|
||||
IHostMdmProfile,
|
||||
@@ -37,7 +37,8 @@ export type INonDDMProfileStatus = MdmProfileStatus | "action_required";
|
||||
|
||||
export type OsSettingsTableStatusValue =
|
||||
| MdmDDMProfileStatus
|
||||
| INonDDMProfileStatus;
|
||||
| INonDDMProfileStatus
|
||||
| HostAndroidCertStatus;
|
||||
|
||||
const generateTableConfig = (
|
||||
canResendProfiles: boolean,
|
||||
|
||||
+8
-4
@@ -47,7 +47,11 @@ const countHostProfilesByStatus = (
|
||||
(acc, { status }) => {
|
||||
if (status === "failed") {
|
||||
acc.failed += 1;
|
||||
} else if (status === "pending" || status === "action_required") {
|
||||
} else if (
|
||||
["pending", "action_required", "delivering", "delivered"].includes(
|
||||
status
|
||||
)
|
||||
) {
|
||||
acc.pending += 1;
|
||||
} else if (status === "verifying") {
|
||||
acc.verifying += 1;
|
||||
@@ -77,9 +81,9 @@ const countHostProfilesByStatus = (
|
||||
* https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks
|
||||
*/
|
||||
const getHostProfilesStatusForDisplay = (
|
||||
hostMacSettings: IHostMdmProfile[]
|
||||
hostProfiles: IHostMdmProfile[]
|
||||
): MdmProfileStatusForDisplay => {
|
||||
const counts = countHostProfilesByStatus(hostMacSettings);
|
||||
const counts = countHostProfilesByStatus(hostProfiles);
|
||||
switch (true) {
|
||||
case !!counts.failed:
|
||||
return "Failed";
|
||||
@@ -87,7 +91,7 @@ const getHostProfilesStatusForDisplay = (
|
||||
return "Pending";
|
||||
case !!counts.verifying:
|
||||
return "Verifying";
|
||||
case counts.verified === hostMacSettings.length:
|
||||
case counts.verified === hostProfiles.length:
|
||||
return "Verified";
|
||||
default:
|
||||
// something is broken
|
||||
|
||||
Reference in New Issue
Block a user