Fix wrong number of host in mdm solutions modal and fix mdm solutions table UI to work for null named solutions (#17336)

relates to #16837, #17334, #17335

This fixes a UI bug for the case where the mdm solution name can be
null. We now handle this case properly and show the mdm solution data in
the modal.

This also fixes a UI bug where we showed the incorrect number of hosts
in the mdm solutions modal.

There is various cleanup here to the js and scss code in this PR too. 

- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Gabriel Hernandez 2024-03-05 11:55:00 +00:00 committed by GitHub
parent 6c7130efa6
commit 60167ff0c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 74 additions and 97 deletions

View file

@ -21,15 +21,16 @@ describe("MDM Card", () => {
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 }),
]}
/>
);
debug();
expect(screen.getAllByText("MDM Solution").length).toBe(1);
expect(screen.getAllByText("Test Solution").length).toBe(1);
expect(screen.getAllByText("Test Solution 2").length).toBe(1);
expect(screen.getAllByText("Unknown").length).toBe(1);
});
it("render the correct number of Enrollment status", async () => {

View file

@ -65,6 +65,28 @@ const EmptyMdmSolutions = (): JSX.Element => (
/>
);
type IMdmSolutionMap = Record<string, IMdmSolution>;
const reduceSolutionsToObj = (mdmSolutions: IMdmSolution[]) => {
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;
} else {
acc[nextSolution.name] = Object.assign({ ...nextSolution });
}
return acc;
}, {});
};
const Mdm = ({
isFetching,
error,
@ -85,19 +107,7 @@ const Mdm = ({
return [];
}
return mdmSolutions.reduce<IMdmSolution[]>((acc, nextSolution) => {
const existingSolution = acc.find(
(solution) => solution.name === nextSolution.name
);
if (existingSolution) {
existingSolution.hosts_count += nextSolution.hosts_count;
} else {
acc.push(nextSolution);
}
return acc;
}, []);
return Object.values(reduceSolutionsToObj(mdmSolutions));
}, [mdmSolutions]);
const solutionsTableHeaders = useMemo(
@ -108,10 +118,7 @@ const Mdm = ({
() => generateStatusTableHeaders(selectedTeamId),
[selectedTeamId]
);
const solutionsDataSet = generateSolutionsDataSet(
rolledupMdmSolutionsData,
selectedPlatformLabelId
);
const solutionsDataSet = generateSolutionsDataSet(rolledupMdmSolutionsData);
const statusDataSet = generateStatusDataSet(
mdmStatusData,
selectedPlatformLabelId
@ -143,6 +150,7 @@ const Mdm = ({
<TableDataError card />
) : (
<TableContainer<IRowProps>
className={`${baseClass}__mdm-solutions-table`}
columnConfigs={solutionsTableHeaders}
data={solutionsDataSet}
isLoading={isFetching}
@ -163,6 +171,7 @@ const Mdm = ({
<TableDataError card />
) : (
<TableContainer
className={`${baseClass}__mdm-status-table`}
columnConfigs={statusTableHeaders}
data={statusDataSet}
isLoading={isFetching}

View file

@ -2,11 +2,7 @@ import React from "react";
import { IMdmSolution } from "interfaces/mdm";
import { greyCell } from "utilities/helpers";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
import TextCell from "components/TableContainer/DataTable/TextCell";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import LinkCell from "components/TableContainer/DataTable/LinkCell";
import InternalLinkCell from "../../../../components/TableContainer/DataTable/InternalLinkCell";
// NOTE: cellProps come from react-table
@ -47,7 +43,7 @@ export const generateSolutionsTableHeaders = (): IDataColumn[] => [
title: "Name",
Header: "Name",
disableSortBy: true,
accessor: "name",
accessor: "displayName",
Cell: (cellProps: ICellProps) => (
<InternalLinkCell value={cellProps.cell.value} />
),
@ -61,27 +57,17 @@ export const generateSolutionsTableHeaders = (): IDataColumn[] => [
},
];
const enhanceSolutionsData = (
solutions: IMdmSolution[],
selectedPlatformLabelId?: number
): IMdmSolution[] => {
return Object.values(solutions).map((solution) => {
return {
id: solution.id,
name: solution.name || "Unknown",
server_url: solution.server_url,
hosts_count: solution.hosts_count,
selectedPlatformLabelId,
};
});
};
export const generateSolutionsDataSet = (
solutions: IMdmSolution[] | null,
selectedPlatformLabelId?: number
solutions: IMdmSolution[] | null
): IMdmSolution[] => {
if (!solutions) {
return [];
}
return [...enhanceSolutionsData(solutions, selectedPlatformLabelId)];
return solutions.map((solution) => {
return {
...solution,
displayName: solution.name ?? "Unknown",
};
});
};

View file

@ -7,47 +7,39 @@
display: none;
}
.data-table-block {
.data-table__table {
table-layout: fixed;
&__mdm-status-table {
.data-table-block {
.data-table__table {
thead {
.status__header {
width: 30%;
}
thead {
.name__header,
.status__header {
width: 30%;
}
.server_url__header {
width: 30%;
}
.hosts_count__header,
.hosts__header {
border-right: 0;
padding-right: 0;
width: 60px;
}
.linkToFilteredHosts__header {
width: 140px;
}
}
.hosts__header {
border-right: 0;
padding-right: 0;
width: 60px;
}
tbody {
.mdm-solution-link {
opacity: 0;
transition: 250ms;
text-overflow: none;
}
tr:hover {
.mdm-solution-link {
opacity: 1;
.linkToFilteredHosts__header {
width: 140px;
}
}
}
}
}
.count-loading {
color: $ui-fleet-black-50;
}
.count-error {
color: $ui-error;
tbody {
.mdm-solution-link {
opacity: 0;
transition: 250ms;
text-overflow: none;
}
tr:hover {
.mdm-solution-link {
opacity: 1;
}
}
}
}
}

View file

@ -29,8 +29,6 @@ const MdmSolutionsModal = ({
selectedTeamId,
onCancel,
}: IMdmSolutionModalProps) => {
console.log(mdmSolutions);
const solutionsTableHeaders = useMemo(
() => generateSolutionsTableHeaders(selectedTeamId),
[selectedTeamId]
@ -43,7 +41,7 @@ const MdmSolutionsModal = ({
return (
<Modal
className={baseClass}
title={mdmSolutions[0].name ?? "Mdm solution"}
title={mdmSolutions[0].name ?? "Unknown mdm solution"}
width="large"
onExit={onCancel}
>

View file

@ -86,21 +86,6 @@ export const generateSolutionsTableHeaders = (
},
];
const enhanceSolutionsData = (
solutions: IMdmSolution[],
selectedPlatformLabelId?: number
): IMdmSolution[] => {
return Object.values(solutions).map((solution) => {
return {
id: solution.id,
name: solution.name || "Unknown",
server_url: solution.server_url,
hosts_count: solution.hosts_count,
selectedPlatformLabelId,
};
});
};
export const generateSolutionsDataSet = (
solutions: IMdmSolution[] | null,
selectedPlatformLabelId?: number
@ -108,5 +93,11 @@ export const generateSolutionsDataSet = (
if (!solutions) {
return [];
}
return [...enhanceSolutionsData(solutions, selectedPlatformLabelId)];
return solutions.map((solution) => {
return {
...solution,
selectedPlatformLabelId,
};
});
};