diff --git a/frontend/__mocks__/mdmMock.ts b/frontend/__mocks__/mdmMock.ts index 355bf7c4a6..62ec7b89f2 100644 --- a/frontend/__mocks__/mdmMock.ts +++ b/frontend/__mocks__/mdmMock.ts @@ -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 => { + return { ...DEFAULT_HOST_SUMMARY_MDM_SOLUTION_MOCK, ...overrides }; +}; + const DEFAULT_MDM_PROFILE_DATA: IMdmProfile = { profile_uuid: "123-abc", team_id: 0, diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts index 316295eae0..c7b4071fac 100644 --- a/frontend/interfaces/mdm.ts +++ b/frontend/interfaces/mdm.ts @@ -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"; diff --git a/frontend/pages/DashboardPage/DashboardPage.tsx b/frontend/pages/DashboardPage/DashboardPage.tsx index 6053fcc0b4..77556c7254 100644 --- a/frontend/pages/DashboardPage/DashboardPage.tsx +++ b/frontend/pages/DashboardPage/DashboardPage.tsx @@ -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([]); - const [mdmSolutions, setMdmSolutions] = useState([]); + const [mdmSolutions, setMdmSolutions] = useState< + IMdmSummaryMdmSolution[] | null + >([]); - const selectedMdmSolution = useRef(null); + const selectedMdmSolutionName = useRef(""); 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 = ""; }} /> ); diff --git a/frontend/pages/DashboardPage/cards/MDM/MDM.tests.tsx b/frontend/pages/DashboardPage/cards/MDM/MDM.tests.tsx index ad49d09198..ab7f10494e 100644 --- a/frontend/pages/DashboardPage/cards/MDM/MDM.tests.tsx +++ b/frontend/pages/DashboardPage/cards/MDM/MDM.tests.tsx @@ -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( ); diff --git a/frontend/pages/DashboardPage/cards/MDM/MDM.tsx b/frontend/pages/DashboardPage/cards/MDM/MDM.tsx index 5a92c85469..e102869a4d 100644 --- a/frontend/pages/DashboardPage/cards/MDM/MDM.tsx +++ b/frontend/pages/DashboardPage/cards/MDM/MDM.tsx @@ -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; +type IMdmSolutionMap = Record; -const reduceSolutionsToObj = (mdmSolutions: IMdmSolution[]) => { +const reduceSolutionsToObj = (mdmSolutions: IMdmSummaryMdmSolution[]) => { return mdmSolutions.reduce((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; diff --git a/frontend/pages/DashboardPage/cards/MDM/MDMSolutionsTableConfig.tsx b/frontend/pages/DashboardPage/cards/MDM/MDMSolutionsTableConfig.tsx index 7a6ca8d05d..a7e3c933b8 100644 --- a/frontend/pages/DashboardPage/cards/MDM/MDMSolutionsTableConfig.tsx +++ b/frontend/pages/DashboardPage/cards/MDM/MDMSolutionsTableConfig.tsx @@ -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", }; }); }; diff --git a/frontend/pages/DashboardPage/components/MdmSolutionModal/MdmSolutionModal.tsx b/frontend/pages/DashboardPage/components/MdmSolutionModal/MdmSolutionModal.tsx index e8063c86d1..aeae56538e 100644 --- a/frontend/pages/DashboardPage/components/MdmSolutionModal/MdmSolutionModal.tsx +++ b/frontend/pages/DashboardPage/components/MdmSolutionModal/MdmSolutionModal.tsx @@ -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 (