always show profile status aggregate UI on macOS Settings page (#11524)
relates to #11450 This will show the profile status aggregate UI at all times when on the macOS settings page. This is a change from showing it conditionally. This also cleans up where some of the requests occur to move it closer to where it is needed and changing the `MdmProfileStatus` enum to a union. - [x] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- change macOS settings UI to always show the profile status aggregate data.
|
||||
@@ -22,6 +22,8 @@ export const MDM_ENROLLMENT_STATUS = {
|
||||
|
||||
export type MdmEnrollmentStatus = keyof typeof MDM_ENROLLMENT_STATUS;
|
||||
|
||||
export type ProfileSummaryResponse = Record<MdmProfileStatus, number>;
|
||||
|
||||
export interface IMdmStatusCardData {
|
||||
status: MdmEnrollmentStatus;
|
||||
hosts: number;
|
||||
@@ -68,11 +70,7 @@ export interface IMdmProfilesResponse {
|
||||
profiles: IMdmProfile[] | null;
|
||||
}
|
||||
|
||||
export enum MdmProfileStatus {
|
||||
VERIFYING = "verifying",
|
||||
PENDING = "pending",
|
||||
FAILED = "failed",
|
||||
}
|
||||
export type MdmProfileStatus = "verifying" | "pending" | "failed";
|
||||
|
||||
export type MacMdmProfileOperationType = "remove" | "install";
|
||||
|
||||
|
||||
+11
-21
@@ -1,11 +1,11 @@
|
||||
import { IconNames } from "components/icons";
|
||||
import { MdmProfileStatus } from "interfaces/mdm";
|
||||
import MacSettingsIndicator from "pages/hosts/details/MacSettingsIndicator";
|
||||
import React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
import paths from "router/paths";
|
||||
import mdmAPI from "services/entities/mdm";
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
import { MdmProfileStatus, ProfileSummaryResponse } from "interfaces/mdm";
|
||||
import MacSettingsIndicator from "pages/hosts/details/MacSettingsIndicator";
|
||||
|
||||
import { IconNames } from "components/icons";
|
||||
|
||||
const baseClass = "aggregate-mac-settings-indicators";
|
||||
|
||||
@@ -18,21 +18,21 @@ interface IAggregateDisplayOption {
|
||||
|
||||
const AGGREGATE_STATUS_DISPLAY_OPTIONS: IAggregateDisplayOption[] = [
|
||||
{
|
||||
value: MdmProfileStatus.VERIFYING,
|
||||
value: "verifying",
|
||||
text: "Verifying",
|
||||
iconName: "success-partial",
|
||||
tooltipText:
|
||||
"Hosts that told Fleet all settings are enforced. Fleet is verifying.",
|
||||
},
|
||||
{
|
||||
value: MdmProfileStatus.PENDING,
|
||||
value: "pending",
|
||||
text: "Pending",
|
||||
iconName: "pending-partial",
|
||||
tooltipText:
|
||||
"Hosts that will have settings enforced when the hosts come online.",
|
||||
},
|
||||
{
|
||||
value: MdmProfileStatus.FAILED,
|
||||
value: "failed",
|
||||
text: "Failed",
|
||||
iconName: "error",
|
||||
tooltipText:
|
||||
@@ -40,28 +40,18 @@ const AGGREGATE_STATUS_DISPLAY_OPTIONS: IAggregateDisplayOption[] = [
|
||||
},
|
||||
];
|
||||
|
||||
type ProfileSummaryResponse = Record<MdmProfileStatus, number>;
|
||||
|
||||
interface AggregateMacSettingsIndicatorsProps {
|
||||
teamId: number;
|
||||
aggregateProfileStatusData: ProfileSummaryResponse;
|
||||
}
|
||||
|
||||
const AggregateMacSettingsIndicators = ({
|
||||
teamId,
|
||||
aggregateProfileStatusData,
|
||||
}: AggregateMacSettingsIndicatorsProps) => {
|
||||
const {
|
||||
data: aggregateProfileStatusesResponse,
|
||||
} = useQuery<ProfileSummaryResponse>(
|
||||
["aggregateProfileStatuses", teamId],
|
||||
() => mdmAPI.getAggregateProfileStatuses(teamId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
|
||||
if (!aggregateProfileStatusesResponse) return null;
|
||||
|
||||
const indicators = AGGREGATE_STATUS_DISPLAY_OPTIONS.map((status) => {
|
||||
const { value, text, iconName, tooltipText } = status;
|
||||
const count = aggregateProfileStatusesResponse[value];
|
||||
const count = aggregateProfileStatusData[value];
|
||||
|
||||
return (
|
||||
<div className="aggregate-mac-settings-indicator">
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import React, { useContext } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Params } from "react-router/lib/Router";
|
||||
|
||||
import { AppContext } from "context/app";
|
||||
import SideNav from "pages/admin/components/SideNav";
|
||||
import { useQuery } from "react-query";
|
||||
import { IMdmProfile, IMdmProfilesResponse } from "interfaces/mdm";
|
||||
import {
|
||||
IMdmProfile,
|
||||
IMdmProfilesResponse,
|
||||
ProfileSummaryResponse,
|
||||
} from "interfaces/mdm";
|
||||
import { API_NO_TEAM_ID, APP_CONTEXT_NO_TEAM_ID } from "interfaces/team";
|
||||
import mdmAPI from "services/entities/mdm";
|
||||
|
||||
@@ -33,14 +37,17 @@ const MacOSSettings = ({
|
||||
? API_NO_TEAM_ID // coerce undefined and -1 to 0 for 'No team'
|
||||
: currentTeam.id;
|
||||
|
||||
const { data: profiles, refetch: refectchProfiles } = useQuery<
|
||||
IMdmProfilesResponse,
|
||||
unknown,
|
||||
IMdmProfile[] | null
|
||||
>(["profiles", teamId], () => mdmAPI.getProfiles(teamId), {
|
||||
select: (data) => data.profiles,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
const {
|
||||
data: aggregateProfileStatusData,
|
||||
refetch: refetchAggregateProfileStatus,
|
||||
} = useQuery<ProfileSummaryResponse>(
|
||||
["aggregateProfileStatuses", teamId],
|
||||
() => mdmAPI.getAggregateProfileStatuses(teamId),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
}
|
||||
);
|
||||
|
||||
const DEFAULT_SETTINGS_SECTION = MAC_OS_SETTINGS_NAV_ITEMS[0];
|
||||
|
||||
@@ -55,7 +62,12 @@ const MacOSSettings = ({
|
||||
<p className={`${baseClass}__description`}>
|
||||
Remotely enforce settings on macOS hosts assigned to this team.
|
||||
</p>
|
||||
{profiles && <AggregateMacSettingsIndicators teamId={teamId} />}
|
||||
{aggregateProfileStatusData && (
|
||||
<AggregateMacSettingsIndicators
|
||||
teamId={teamId}
|
||||
aggregateProfileStatusData={aggregateProfileStatusData}
|
||||
/>
|
||||
)}
|
||||
<SideNav
|
||||
className={`${baseClass}__side-nav`}
|
||||
navItems={MAC_OS_SETTINGS_NAV_ITEMS.map((navItem) => ({
|
||||
@@ -67,9 +79,7 @@ const MacOSSettings = ({
|
||||
<CurrentCard
|
||||
key={teamId}
|
||||
currentTeamId={teamId}
|
||||
profiles={profiles}
|
||||
onProfileUpload={refectchProfiles}
|
||||
onProfileDelete={refectchProfiles}
|
||||
onMutation={refetchAggregateProfileStatus}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
+20
-11
@@ -1,8 +1,9 @@
|
||||
import React, { useContext, useRef, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { IApiError } from "interfaces/errors";
|
||||
import { IMdmProfile } from "interfaces/mdm";
|
||||
import { IMdmProfile, IMdmProfilesResponse } from "interfaces/mdm";
|
||||
import mdmAPI from "services/entities/mdm";
|
||||
import { AppContext } from "context/app";
|
||||
import { NotificationContext } from "context/notification";
|
||||
@@ -20,18 +21,15 @@ import ProfileListHeading from "./components/ProfileListHeading";
|
||||
const baseClass = "custom-settings";
|
||||
|
||||
interface ICustomSettingsProps {
|
||||
profiles: IMdmProfile[];
|
||||
onProfileUpload: () => void;
|
||||
onProfileDelete: () => void;
|
||||
currentTeamId: number;
|
||||
onMutation: () => void;
|
||||
}
|
||||
|
||||
const CustomSettings = ({
|
||||
profiles,
|
||||
onProfileUpload,
|
||||
onProfileDelete,
|
||||
currentTeamId,
|
||||
onMutation,
|
||||
}: ICustomSettingsProps) => {
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
const { currentTeam } = useContext(AppContext);
|
||||
|
||||
const [showDeleteProfileModal, setShowDeleteProfileModal] = useState(false);
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
@@ -43,6 +41,15 @@ const CustomSettings = ({
|
||||
setShowDeleteProfileModal(true);
|
||||
};
|
||||
|
||||
const { data: profiles, refetch: refetchProfiles } = useQuery<
|
||||
IMdmProfilesResponse,
|
||||
unknown,
|
||||
IMdmProfile[] | null
|
||||
>(["profiles", currentTeamId], () => mdmAPI.getProfiles(currentTeamId), {
|
||||
select: (data) => data.profiles,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const onFileUpload = async (files: FileList | null) => {
|
||||
setShowLoading(true);
|
||||
|
||||
@@ -63,8 +70,9 @@ const CustomSettings = ({
|
||||
}
|
||||
|
||||
try {
|
||||
await mdmAPI.uploadProfile(file, currentTeam?.id);
|
||||
onProfileUpload();
|
||||
await mdmAPI.uploadProfile(file, currentTeamId);
|
||||
refetchProfiles();
|
||||
onMutation();
|
||||
renderFlash("success", "Successfully uploaded!");
|
||||
} catch (e) {
|
||||
const error = e as AxiosResponse<IApiError>;
|
||||
@@ -83,7 +91,8 @@ const CustomSettings = ({
|
||||
const onDeleteProfile = async (profileId: number) => {
|
||||
try {
|
||||
await mdmAPI.deleteProfile(profileId);
|
||||
onProfileDelete();
|
||||
refetchProfiles();
|
||||
onMutation();
|
||||
renderFlash("success", "Successfully deleted!");
|
||||
} catch (e) {
|
||||
renderFlash("error", "Couldn’t delete. Please try again.");
|
||||
|
||||
+9
-4
@@ -18,10 +18,14 @@ import DiskEncryptionTable from "./components/DiskEncryptionTable";
|
||||
|
||||
const baseClass = "disk-encryption";
|
||||
interface IDiskEncryptionProps {
|
||||
currentTeamId?: number;
|
||||
currentTeamId: number;
|
||||
onMutation: () => void;
|
||||
}
|
||||
|
||||
const DiskEncryption = ({ currentTeamId }: IDiskEncryptionProps) => {
|
||||
const DiskEncryption = ({
|
||||
currentTeamId,
|
||||
onMutation,
|
||||
}: IDiskEncryptionProps) => {
|
||||
const { isPremiumTier, config, setConfig } = useContext(AppContext);
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
|
||||
@@ -56,11 +60,11 @@ const DiskEncryption = ({ currentTeamId }: IDiskEncryptionProps) => {
|
||||
|
||||
useQuery<ILoadTeamResponse, Error, ITeamConfig>(
|
||||
["team", currentTeamId],
|
||||
() => teamsAPI.load(currentTeamId ?? 0),
|
||||
() => teamsAPI.load(currentTeamId),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
enabled: Boolean(currentTeamId),
|
||||
enabled: currentTeamId !== 0,
|
||||
select: (res) => res.team,
|
||||
onSuccess: (res) => {
|
||||
const enableDiskEncryption =
|
||||
@@ -79,6 +83,7 @@ const DiskEncryption = ({ currentTeamId }: IDiskEncryptionProps) => {
|
||||
"success",
|
||||
"Successfully updated disk encryption enforcement!"
|
||||
);
|
||||
onMutation();
|
||||
setShowAggregate(diskEncryptionEnabled);
|
||||
if (currentTeamId === 0) {
|
||||
getUpdatedAppConfig();
|
||||
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { createCustomRenderer } from "test/test-utils";
|
||||
import { MacMdmProfileOperationType, MdmProfileStatus } from "interfaces/mdm";
|
||||
import { MacMdmProfileOperationType } from "interfaces/mdm";
|
||||
import MacSettingStatusCell from "./MacSettingStatusCell";
|
||||
|
||||
describe("Mac setting status cell", () => {
|
||||
it("Correctly displays the status text of a profile", () => {
|
||||
const status = MdmProfileStatus.VERIFYING;
|
||||
const status = "verifying";
|
||||
const operationType: MacMdmProfileOperationType = "install";
|
||||
|
||||
render(
|
||||
@@ -17,7 +17,7 @@ describe("Mac setting status cell", () => {
|
||||
});
|
||||
|
||||
it("Correctly displays the tooltip text for a profile", async () => {
|
||||
const status = MdmProfileStatus.VERIFYING;
|
||||
const status = "verifying";
|
||||
const operationType: MacMdmProfileOperationType = "install";
|
||||
|
||||
const customRender = createCustomRenderer();
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
import { IHostMacMdmProfile, MdmProfileStatus } from "interfaces/mdm";
|
||||
import { IHostMacMdmProfile } from "interfaces/mdm";
|
||||
|
||||
import Icon from "components/Icon";
|
||||
import Button from "components/buttons/Button";
|
||||
@@ -40,10 +40,10 @@ const getMacSettingsStatus = (
|
||||
hostMacSettings?: IHostMacMdmProfile[]
|
||||
): MacSettingsStatus => {
|
||||
const statuses = hostMacSettings?.map((setting) => setting.status);
|
||||
if (statuses?.includes(MdmProfileStatus.FAILED)) {
|
||||
if (statuses?.includes("failed")) {
|
||||
return "Failing";
|
||||
}
|
||||
if (statuses?.includes(MdmProfileStatus.PENDING)) {
|
||||
if (statuses?.includes("pending")) {
|
||||
return "Pending";
|
||||
}
|
||||
return "Verifying";
|
||||
|
||||
Reference in New Issue
Block a user