Fix for mdm solution rollup to handle empty string for rollup correctly. (#17366)

relates to #17335

This fixes the issue with empty strings for mdm solution names. I also
cleans up a bit around the code and typing for this feature.

- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Gabriel Hernandez 2024-03-05 15:48:55 +00:00 committed by GitHub
parent 7a7b043014
commit 252848720c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 70 additions and 42 deletions

View File

@ -1,5 +1,9 @@
import { IHostMdmData } from "interfaces/host";
import { IMdmSolution, IMdmProfile } from "interfaces/mdm";
import {
IMdmSolution,
IMdmProfile,
IMdmSummaryMdmSolution,
} from "interfaces/mdm";
const DEFAULT_MDM_SOLUTION_MOCK: IMdmSolution = {
id: 1,
@ -14,6 +18,19 @@ export const createMockMdmSolution = (
return { ...DEFAULT_MDM_SOLUTION_MOCK, ...overrides };
};
const DEFAULT_HOST_SUMMARY_MDM_SOLUTION_MOCK: IMdmSummaryMdmSolution = {
id: 1,
name: "MDM Solution",
server_url: "http://mdmsolution.com",
hosts_count: 5,
};
export const createMockMdmSummaryMdmSolution = (
overrides?: Partial<IMdmSummaryMdmSolution>
): IMdmSummaryMdmSolution => {
return { ...DEFAULT_HOST_SUMMARY_MDM_SOLUTION_MOCK, ...overrides };
};
const DEFAULT_MDM_PROFILE_DATA: IMdmProfile = {
profile_uuid: "123-abc",
team_id: 0,

View File

@ -41,6 +41,12 @@ export interface IMdmSolution {
hosts_count: number;
}
/** This is the mdm solution that comes back from the host/summary/mdm
request. We will always get a string for the solution name in this case */
export interface IMdmSummaryMdmSolution extends IMdmSolution {
name: string;
}
interface IMdmStatus {
enrolled_manual_hosts_count: number;
enrolled_automated_hosts_count: number;
@ -52,7 +58,7 @@ interface IMdmStatus {
export interface IMdmSummaryResponse {
counts_updated_at: string;
mobile_device_management_enrollment_status: IMdmStatus;
mobile_device_management_solution: IMdmSolution[] | null;
mobile_device_management_solution: IMdmSummaryMdmSolution[] | null;
}
export type ProfilePlatform = "darwin" | "windows";

View File

@ -18,8 +18,8 @@ import {
} from "interfaces/macadmins";
import {
IMdmStatusCardData,
IMdmSolution,
IMdmSummaryResponse,
IMdmSummaryMdmSolution,
} from "interfaces/mdm";
import { SelectedPlatform } from "interfaces/platform";
import { ISoftwareResponse, ISoftwareCountResponse } from "interfaces/software";
@ -137,9 +137,11 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
const [showOperatingSystemsUI, setShowOperatingSystemsUI] = useState(false);
const [showHostsUI, setShowHostsUI] = useState(false); // Hides UI on first load only
const [mdmStatusData, setMdmStatusData] = useState<IMdmStatusCardData[]>([]);
const [mdmSolutions, setMdmSolutions] = useState<IMdmSolution[] | null>([]);
const [mdmSolutions, setMdmSolutions] = useState<
IMdmSummaryMdmSolution[] | null
>([]);
const selectedMdmSolution = useRef<string | null>(null);
const selectedMdmSolutionName = useRef<string>("");
const [munkiIssuesData, setMunkiIssuesData] = useState<
IMunkiIssuesAggregate[]
@ -619,7 +621,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
selectedPlatformLabelId={selectedPlatformLabelId}
selectedTeamId={currentTeamId}
onClickMdmSolution={(mdmSolution) => {
selectedMdmSolution.current = mdmSolution.name;
selectedMdmSolutionName.current = mdmSolution.name;
setShowMdmSolutionModal(true);
}}
/>
@ -727,7 +729,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
}
const selectedMdmSolutions = mdmSolutions?.filter(
(solution) => solution.name === selectedMdmSolution.current
(solution) => solution.name === selectedMdmSolutionName.current
);
return (
@ -737,7 +739,7 @@ const DashboardPage = ({ router, location }: IDashboardProps): JSX.Element => {
selectedTeamId={currentTeamId}
onCancel={() => {
setShowMdmSolutionModal(false);
selectedMdmSolution.current = null;
selectedMdmSolutionName.current = "";
}}
/>
);

View File

@ -3,26 +3,27 @@ import { noop } from "lodash";
import { render, screen } from "@testing-library/react";
import { renderWithSetup } from "test/test-utils";
import { createMockMdmSolution } from "__mocks__/mdmMock";
import { createMockMdmSummaryMdmSolution } from "__mocks__/mdmMock";
import MDM from "./MDM";
describe("MDM Card", () => {
it("rolls up the data by mdm solution name and render the correct number of MDM solutions", () => {
const { debug } = render(
render(
<MDM
onClickMdmSolution={noop}
error={null}
isFetching={false}
mdmStatusData={[]}
mdmSolutions={[
createMockMdmSolution(),
createMockMdmSolution({ id: 2 }),
createMockMdmSolution({ name: "Test Solution", id: 3 }),
createMockMdmSolution({ name: "Test Solution", id: 4 }),
createMockMdmSolution({ name: "Test Solution 2", id: 5 }),
createMockMdmSolution({ name: null, id: 6 }),
createMockMdmSolution({ name: null, id: 7 }),
createMockMdmSummaryMdmSolution(),
createMockMdmSummaryMdmSolution({ id: 2 }),
createMockMdmSummaryMdmSolution({ name: "Test Solution", id: 3 }),
createMockMdmSummaryMdmSolution({ name: "Test Solution", id: 4 }),
createMockMdmSummaryMdmSolution({ name: "Test Solution 2", id: 5 }),
// "" should render a row of "Unknown"
createMockMdmSummaryMdmSolution({ name: "", id: 8 }),
createMockMdmSummaryMdmSolution({ name: "", id: 9 }),
]}
/>
);

View File

@ -2,7 +2,11 @@ import React, { useMemo, useState } from "react";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import { Row } from "react-table";
import { IMdmStatusCardData, IMdmSolution } from "interfaces/mdm";
import {
IMdmStatusCardData,
IMdmSolution,
IMdmSummaryMdmSolution,
} from "interfaces/mdm";
import TabsWrapper from "components/TabsWrapper";
import TableContainer from "components/TableContainer";
@ -20,18 +24,23 @@ import {
generateStatusDataSet,
} from "./MDMStatusTableConfig";
export type IMdmSolutionTableData = Pick<
IMdmSummaryMdmSolution,
"name" | "hosts_count"
>;
interface IRowProps extends Row {
original: IMdmSolution;
original: IMdmSolutionTableData;
}
interface IMdmCardProps {
error: Error | null;
isFetching: boolean;
mdmStatusData: IMdmStatusCardData[];
mdmSolutions: IMdmSolution[] | null;
mdmSolutions: IMdmSummaryMdmSolution[] | null;
selectedPlatformLabelId?: number;
selectedTeamId?: number;
onClickMdmSolution: (solution: IMdmSolution) => void;
onClickMdmSolution: (solution: IMdmSolutionTableData) => void;
}
const DEFAULT_SORT_DIRECTION = "desc";
@ -65,22 +74,17 @@ const EmptyMdmSolutions = (): JSX.Element => (
/>
);
type IMdmSolutionMap = Record<string, IMdmSolution>;
type IMdmSolutionMap = Record<string, IMdmSolutionTableData>;
const reduceSolutionsToObj = (mdmSolutions: IMdmSolution[]) => {
const reduceSolutionsToObj = (mdmSolutions: IMdmSummaryMdmSolution[]) => {
return mdmSolutions.reduce<IMdmSolutionMap>((acc, nextSolution) => {
// The solution name can be null so we add an Unknown key to the
// accumulator in this case.
if (nextSolution.name === null) {
if (acc.Unknown) {
acc.Unknown.hosts_count += nextSolution.hosts_count;
} else {
acc.Unknown = Object.assign({ ...nextSolution });
}
} else if (acc[nextSolution.name]) {
acc[nextSolution.name].hosts_count += nextSolution.hosts_count;
// The solution name can be an empty string so we add a key for "Unknown"
// for this case.
const key = nextSolution.name || "Unknown";
if (acc[key]) {
acc[key].hosts_count += nextSolution.hosts_count;
} else {
acc[nextSolution.name] = Object.assign({ ...nextSolution });
acc[key] = Object.assign({ ...nextSolution });
}
return acc;

View File

@ -4,6 +4,7 @@ import { IMdmSolution } from "interfaces/mdm";
import TextCell from "components/TableContainer/DataTable/TextCell";
import InternalLinkCell from "../../../../components/TableContainer/DataTable/InternalLinkCell";
import { IMdmSolutionTableData } from "./MDM";
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
@ -58,16 +59,12 @@ export const generateSolutionsTableHeaders = (): IDataColumn[] => [
];
export const generateSolutionsDataSet = (
solutions: IMdmSolution[] | null
): IMdmSolution[] => {
if (!solutions) {
return [];
}
solutions: IMdmSolutionTableData[]
): IMdmSolutionTableData[] => {
return solutions.map((solution) => {
return {
...solution,
displayName: solution.name ?? "Unknown",
displayName: solution.name || "Unknown",
};
});
};

View File

@ -15,6 +15,7 @@ const baseClass = "mdm-solution-modal";
const SOLUTIONS_DEFAULT_SORT_HEADER = "hosts_count";
const DEFAULT_SORT_DIRECTION = "desc";
const DEFAULT_TITLE = "Unknown MDM solution";
interface IMdmSolutionModalProps {
mdmSolutions: IMdmSolution[];
@ -41,7 +42,7 @@ const MdmSolutionsModal = ({
return (
<Modal
className={baseClass}
title={mdmSolutions[0].name ?? "Unknown mdm solution"}
title={mdmSolutions[0].name || DEFAULT_TITLE}
width="large"
onExit={onCancel}
>