Fix unreleased UI bug in OS settings modal (#14434)

This commit is contained in:
gillespi314 2023-10-12 10:57:39 -05:00 committed by GitHub
parent e139eb412f
commit 719e761f28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 60 deletions

View File

@ -98,7 +98,15 @@ export type IWindowsDiskEncryptionStatus = Extract<
export const isWindowsDiskEncryptionStatus = ( export const isWindowsDiskEncryptionStatus = (
status: DiskEncryptionStatus status: DiskEncryptionStatus
): status is IWindowsDiskEncryptionStatus => { ): status is IWindowsDiskEncryptionStatus => {
return !["action_required", "removing_enforcement"].includes(status); switch (status) {
case "verified":
case "verifying":
case "enforcing":
case "failed":
return true;
default:
return false;
}
}; };
export const FLEET_FILEVAULT_PROFILE_DISPLAY_NAME = "Disk encryption"; export const FLEET_FILEVAULT_PROFILE_DISPLAY_NAME = "Disk encryption";

View File

@ -19,13 +19,15 @@ const MacSettingsModal = ({
hostMDMData, hostMDMData,
onClose, onClose,
}: IMacSettingsModalProps) => { }: IMacSettingsModalProps) => {
// the caller should ensure that hostMDMData is not undefined and that platform is "windows" or
// "darwin", otherwise we will allow an empty modal will be rendered.
// https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks
const memoizedTableData = useMemo( const memoizedTableData = useMemo(
() => generateTableData(hostMDMData, platform), () => generateTableData(hostMDMData, platform),
[hostMDMData, platform] [hostMDMData, platform]
); );
if (!platform) return null;
return ( return (
<Modal <Modal
title="OS settings" title="OS settings"
@ -34,7 +36,7 @@ const MacSettingsModal = ({
width="large" width="large"
> >
<> <>
<MacSettingsTable tableData={memoizedTableData} /> <MacSettingsTable tableData={memoizedTableData || []} />
<div className="modal-cta-wrap"> <div className="modal-cta-wrap">
<Button variant="brand" onClick={onClose}> <Button variant="brand" onClick={onClose}>
Done Done

View File

@ -12,7 +12,7 @@ import {
import { import {
isMdmProfileStatus, isMdmProfileStatus,
MacSettingsTableStatusValue, OsSettingsTableStatusValue,
} from "../MacSettingsTableConfig"; } from "../MacSettingsTableConfig";
import TooltipContent, { import TooltipContent, {
TooltipInnerContentFunc, TooltipInnerContentFunc,
@ -29,7 +29,7 @@ type ProfileDisplayOption = {
} | null; } | null;
type OperationTypeOption = Record< type OperationTypeOption = Record<
MacSettingsTableStatusValue, OsSettingsTableStatusValue,
ProfileDisplayOption ProfileDisplayOption
>; >;
type ProfileDisplayConfig = Record< type ProfileDisplayConfig = Record<
@ -135,7 +135,7 @@ const WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG: WindowsDiskEncryptionDisplayConfig
}; };
interface IMacSettingStatusCellProps { interface IMacSettingStatusCellProps {
status: MacSettingsTableStatusValue; status: OsSettingsTableStatusValue;
operationType: MacMdmProfileOperationType | null; operationType: MacMdmProfileOperationType | null;
profileName: string; profileName: string;
} }

View File

@ -1,12 +1,12 @@
import React from "react"; import React from "react";
import TableContainer from "components/TableContainer"; import TableContainer from "components/TableContainer";
import tableHeaders, { IMacSettingsTableRow } from "./MacSettingsTableConfig"; import tableHeaders, { ITableRowOsSettings } from "./MacSettingsTableConfig";
const baseClass = "macsettings-table"; const baseClass = "macsettings-table";
interface IMacSettingsTableProps { interface IMacSettingsTableProps {
tableData?: IMacSettingsTableRow[]; tableData?: ITableRowOsSettings[];
} }
const MacSettingsTable = ({ tableData }: IMacSettingsTableProps) => { const MacSettingsTable = ({ tableData }: IMacSettingsTableProps) => {

View File

@ -14,11 +14,11 @@ import TruncatedTextCell from "components/TableContainer/DataTable/TruncatedText
import MacSettingStatusCell from "./MacSettingStatusCell"; import MacSettingStatusCell from "./MacSettingStatusCell";
import { generateWinDiskEncryptionProfile } from "../../helpers"; import { generateWinDiskEncryptionProfile } from "../../helpers";
export interface IMacSettingsTableRow extends Omit<IHostMdmProfile, "status"> { export interface ITableRowOsSettings extends Omit<IHostMdmProfile, "status"> {
status: MacSettingsTableStatusValue; status: OsSettingsTableStatusValue;
} }
export type MacSettingsTableStatusValue = MdmProfileStatus | "action_required"; export type OsSettingsTableStatusValue = MdmProfileStatus | "action_required";
export const isMdmProfileStatus = ( export const isMdmProfileStatus = (
status: string status: string
@ -38,7 +38,7 @@ interface ICellProps {
value: string; value: string;
}; };
row: { row: {
original: IMacSettingsTableRow; original: ITableRowOsSettings;
}; };
} }
@ -98,42 +98,32 @@ const tableHeaders: IDataColumn[] = [
}, },
]; ];
export const generateTableData = ( const makeWindowsRows = ({ os_settings }: IHostMdmData) => {
hostMDMData?: IHostMdmData,
platform?: string
) => {
if (!platform) return [];
let rows: IMacSettingsTableRow[] = [];
if (!hostMDMData) {
return rows;
}
if ( if (
platform === "windows" && !os_settings?.disk_encryption?.status ||
hostMDMData.os_settings?.disk_encryption.status && !isWindowsDiskEncryptionStatus(os_settings.disk_encryption.status)
isWindowsDiskEncryptionStatus(
hostMDMData.os_settings.disk_encryption.status
)
) { ) {
rows.push( return null;
generateWinDiskEncryptionProfile(
hostMDMData.os_settings.disk_encryption.status
)
);
return rows;
} }
const { profiles, macos_settings } = hostMDMData; const rows: ITableRowOsSettings[] = [];
rows.push(
generateWinDiskEncryptionProfile(os_settings.disk_encryption.status)
);
return rows;
};
const makeDarwinRows = ({
profiles,
macos_settings,
}: IHostMdmData): ITableRowOsSettings[] | null => {
if (!profiles) { if (!profiles) {
return rows; return null;
} }
if ( let rows: ITableRowOsSettings[] = profiles;
platform === "darwin" && if (macos_settings?.disk_encryption === "action_required") {
macos_settings?.disk_encryption === "action_required"
) {
rows = profiles.map((p) => { rows = profiles.map((p) => {
// TODO: this is a brittle check for the filevault profile // TODO: this is a brittle check for the filevault profile
// it would be better to match on the identifier but it is not // it would be better to match on the identifier but it is not
@ -148,4 +138,22 @@ export const generateTableData = (
return rows; return rows;
}; };
export const generateTableData = (
hostMDMData?: IHostMdmData,
platform?: string
) => {
if (!platform || !hostMDMData) {
return null;
}
switch (platform) {
case "windows":
return makeWindowsRows(hostMDMData);
case "darwin":
return makeDarwinRows(hostMDMData);
default:
return null;
}
};
export default tableHeaders; export default tableHeaders;

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import ReactTooltip from "react-tooltip"; import ReactTooltip from "react-tooltip";
import { IHostMdmProfile } from "interfaces/mdm"; import { IHostMdmProfile, MdmProfileStatus } from "interfaces/mdm";
import Icon from "components/Icon"; import Icon from "components/Icon";
import Button from "components/buttons/Button"; import Button from "components/buttons/Button";
@ -9,7 +9,11 @@ import { IconNames } from "components/icons";
const baseClass = "mac-settings-indicator"; const baseClass = "mac-settings-indicator";
type MacProfileStatus = "Failed" | "Verifying" | "Pending" | "Verified"; type MdmProfileStatusForDisplay =
| "Failed"
| "Pending"
| "Verifying"
| "Verified";
interface IStatusDisplayOption { interface IStatusDisplayOption {
iconName: Extract< iconName: Extract<
@ -18,7 +22,10 @@ interface IStatusDisplayOption {
>; >;
tooltipText: string; tooltipText: string;
} }
type StatusDisplayOptions = Record<MacProfileStatus, IStatusDisplayOption>; type StatusDisplayOptions = Record<
MdmProfileStatusForDisplay,
IStatusDisplayOption
>;
const STATUS_DISPLAY_OPTIONS: StatusDisplayOptions = { const STATUS_DISPLAY_OPTIONS: StatusDisplayOptions = {
Verified: { Verified: {
@ -44,27 +51,59 @@ const STATUS_DISPLAY_OPTIONS: StatusDisplayOptions = {
}, },
}; };
const countHostProfilesByStatus = (
hostSettings: IHostMdmProfile[]
): Record<MdmProfileStatus, number> => {
return hostSettings.reduce(
(acc, { status }) => {
if (status === "failed") {
acc.failed += 1;
} else if (status === "pending") {
acc.pending += 1;
} else if (status === "verifying") {
acc.verifying += 1;
} else if (status === "verified") {
acc.verified += 1;
}
return acc;
},
{
failed: 0,
pending: 0,
verifying: 0,
verified: 0,
}
);
};
/** /**
* Returns the displayed status of the macOS settings field based on the * Returns the displayed status of the macOS settings field based on the
* profile statuses. * profile statuses.
* If any profile has a status of "failed", the status will be displayed as "Failed" and * If any profile has a status of "failed", the status will be displayed as "Failed" and
* continues to fall through to "Pending" and "Verifying" if any profiles have those statuses. * continues to fall through to "Pending" and "Verifying" if any profiles have those statuses.
* Finally if all profiles have a status of "verified", the status will be displayed as "Verified". * If all profiles have a status of "verified", the status will be displayed as "Verified".
*
* The default status will be displayed as "Failed".
* https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks
*/ */
const getMacProfileStatus = ( const getHostProfilesStatusForDisplay = (
hostMacSettings: IHostMdmProfile[] hostMacSettings: IHostMdmProfile[]
): MacProfileStatus => { ): MdmProfileStatusForDisplay => {
const statuses = hostMacSettings.map((setting) => setting.status); const counts = countHostProfilesByStatus(hostMacSettings);
if (statuses.includes("failed")) { switch (true) {
return "Failed"; case !!counts.failed:
return "Failed";
case !!counts.pending:
return "Pending";
case !!counts.verifying:
return "Verifying";
case counts.verified === hostMacSettings.length:
return "Verified";
default:
// something is broken
return "Failed";
} }
if (statuses.includes("pending")) {
return "Pending";
}
if (statuses.includes("verifying")) {
return "Verifying";
}
return "Verified";
}; };
interface IMacSettingsIndicatorProps { interface IMacSettingsIndicatorProps {
@ -75,9 +114,16 @@ const MacSettingsIndicator = ({
profiles, profiles,
onClick, onClick,
}: IMacSettingsIndicatorProps): JSX.Element => { }: IMacSettingsIndicatorProps): JSX.Element => {
const macProfileStatus = getMacProfileStatus(profiles); if (!profiles.length) {
// the caller should ensure that this never happens, but just in case we return a default
// to make it more obvious that something is wrong.
// https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks
return <span className={`${baseClass} info-flex__data`}>Unavailable</span>;
}
const statusDisplayOption = STATUS_DISPLAY_OPTIONS[macProfileStatus]; const displayStatus = getHostProfilesStatusForDisplay(profiles);
const statusDisplayOption = STATUS_DISPLAY_OPTIONS[displayStatus];
return ( return (
<span className={`${baseClass} info-flex__data`}> <span className={`${baseClass} info-flex__data`}>
@ -93,7 +139,7 @@ const MacSettingsIndicator = ({
variant="text-link" variant="text-link"
className={`${baseClass}__button`} className={`${baseClass}__button`}
> >
{macProfileStatus} {displayStatus}
</Button> </Button>
</span> </span>
<ReactTooltip <ReactTooltip