Macos settings hosts filter (#10385)

## Addresses #9597 
<img width="1121" alt="Screenshot 2023-03-09 at 2 03 58 PM"
src="https://user-images.githubusercontent.com/61553566/224170878-00a1ba60-6477-4c4b-8582-d1711e8b0181.png">

## Notes
The UI for "No teams" filtered state will be implemented in the
**Frontend** portion of #10409
## Checklist
- [x] Manual QA
- [x] Updated testing inventory or added tests
- [x] Change file

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
Jacob Shandling 2023-03-14 10:05:43 -07:00 committed by GitHub
parent c4fa0393af
commit 81c732f34d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 10 deletions

View file

@ -0,0 +1,3 @@
* Add ability to filter data under the Hosts tab by the aggregate status of hosts' MDM-managed macos
settings. This filter is used when clicking Controls > macOS settings > "# hosts" under Latest,
Pending, or Failing.

View file

@ -13,6 +13,7 @@ import globalPoliciesAPI from "services/entities/global_policies";
import hostsAPI, {
ILoadHostsOptions,
ISortOption,
MacSettingsStatusQueryParam,
} from "services/entities/hosts";
import hostCountAPI, {
IHostCountLoadOptions,
@ -73,6 +74,7 @@ import {
DEFAULT_SORT_DIRECTION,
DEFAULT_PAGE_SIZE,
HOST_SELECT_STATUSES,
MAC_SETTINGS_FILTER_OPTIONS,
} from "./constants";
import { isAcceptableStatus, getNextLocationPath } from "./helpers";
import DeleteSecretModal from "../../../components/EnrollSecrets/DeleteSecretModal";
@ -242,6 +244,7 @@ const ManageHostsPage = ({
const routeTemplate = route?.path ?? "";
const policyId = queryParams?.policy_id;
const policyResponse: PolicyResponse = queryParams?.policy_response;
const macSettingsStatus = queryParams?.macos_settings;
const softwareId =
queryParams?.software_id !== undefined
? parseInt(queryParams.software_id, 10)
@ -421,25 +424,20 @@ const ManageHostsPage = ({
setIsHostsLoading(true);
options = {
...options,
teamId: currentTeam?.id,
teamId: queryParams.team_id ? queryParams.team_id : currentTeam?.id,
};
if (queryParams.team_id) {
options.teamId = queryParams.team_id;
}
try {
const {
hosts: returnedHosts,
software,
mobile_device_management_solution,
munki_issue,
mobile_device_management_solution: mdmSolution,
munki_issue: munkiIssue,
} = await hostsAPI.loadHosts(options);
setHosts(returnedHosts);
software && setSoftwareDetails(software);
mobile_device_management_solution &&
setMDMSolutionDetails(mobile_device_management_solution);
munki_issue && setMunkiIssueDetails(munki_issue);
mdmSolution && setMDMSolutionDetails(mdmSolution);
munkiIssue && setMunkiIssueDetails(munkiIssue);
} catch (error) {
console.error(error);
setHasHostErrors(true);
@ -514,6 +512,7 @@ const ManageHostsPage = ({
teamId: selectedTeam?.id,
policyId,
policyResponse,
macSettingsStatus,
softwareId,
status,
mdmId,
@ -632,6 +631,10 @@ const ManageHostsPage = ({
handleClearFilter(["os_id", "os_name", "os_version"]);
};
const handleClearMacSettingsStatusFilter = () => {
handleClearFilter(["macos_settings"]);
};
const handleClearSoftwareFilter = () => {
handleClearFilter(["software_id"]);
};
@ -687,6 +690,21 @@ const ManageHostsPage = ({
);
};
const handleMacSettingsStatusDropdownChange = (
newMacSettingsStatus: MacSettingsStatusQueryParam
) => {
handleResetPageIndex();
router.replace(
getNextLocationPath({
pathPrefix: PATHS.MANAGE_HOSTS,
routeTemplate,
routeParams,
queryParams: { ...queryParams, macos_settings: newMacSettingsStatus },
})
);
};
const onAddLabelClick = () => {
router.push(`${PATHS.NEW_LABEL}`);
};
@ -764,6 +782,8 @@ const ManageHostsPage = ({
if (policyId && policyResponse) {
newQueryParams.policy_id = policyId;
newQueryParams.policy_response = policyResponse;
} else if (macSettingsStatus) {
newQueryParams.macos_settings = macSettingsStatus;
} else if (softwareId) {
newQueryParams.software_id = softwareId;
} else if (mdmId) {
@ -801,6 +821,7 @@ const ManageHostsPage = ({
currentUser,
policyId,
queryParams,
macSettingsStatus,
softwareId,
status,
mdmId,
@ -989,6 +1010,7 @@ const ManageHostsPage = ({
teamId: currentTeam?.id,
policyId,
policyResponse,
macSettingsStatus,
softwareId,
status,
mdmId,
@ -1044,6 +1066,7 @@ const ManageHostsPage = ({
teamId: currentTeam?.id,
policyId,
policyResponse,
macSettingsStatus,
softwareId,
status,
mdmId,
@ -1168,6 +1191,24 @@ const ManageHostsPage = ({
</>
);
const renderMacSettingsStatusFilterBlock = () => {
const label = "macOS settings";
return (
<>
<Dropdown
value={macSettingsStatus}
className={`${baseClass}__macsettings-dropdown`}
options={MAC_SETTINGS_FILTER_OPTIONS}
onChange={handleMacSettingsStatusDropdownChange}
/>
<FilterPill
label={label}
onClear={handleClearMacSettingsStatusFilter}
/>
</>
);
};
const renderSoftwareFilterBlock = () => {
if (!softwareDetails) return null;
@ -1464,6 +1505,7 @@ const ManageHostsPage = ({
teamId: currentTeam?.id,
policyId,
policyResponse,
macSettingsStatus,
softwareId,
status,
mdmId,
@ -1534,6 +1576,7 @@ const ManageHostsPage = ({
if (
showSelectedLabel ||
policyId ||
macSettingsStatus ||
softwareId ||
showSelectedLabel ||
mdmId ||
@ -1570,6 +1613,8 @@ const ManageHostsPage = ({
return renderLabelFilterPill();
case !!policyId:
return renderPoliciesFilterBlock();
case !!macSettingsStatus:
return renderMacSettingsStatusFilterBlock();
case !!softwareId:
return renderSoftwareFilterBlock();
case !!mdmId:

View file

@ -248,6 +248,23 @@
margin-left: $pad-small;
}
&__macsettings-dropdown {
width: 137px;
.Select-value {
display: flex;
align-items: center;
&::before {
position: relative;
content: url(../assets/images/icon-filter-v2-black-16x16@2x.png);
transform: scale(0.5);
left: -8px;
top: 4px;
}
}
}
&__labels-active-filter-wrap {
display: flex;
align-items: center;

View file

@ -36,3 +36,21 @@ export const HOST_SELECT_STATUSES = [
helpText: "Hosts added to Fleet in the last 24 hours.",
},
];
export const MAC_SETTINGS_FILTER_OPTIONS = [
{
disabled: false,
label: "Latest",
value: "latest",
},
{
disabled: false,
label: "Pending",
value: "pending",
},
{
disabled: false,
label: "Failing",
value: "failing",
},
];

View file

@ -14,6 +14,8 @@ export interface ISortOption {
direction: string;
}
export type MacSettingsStatusQueryParam = "latest" | "pending" | "failing";
export interface ILoadHostsOptions {
page?: number;
perPage?: number;
@ -23,6 +25,7 @@ export interface ILoadHostsOptions {
teamId?: number;
policyId?: number;
policyResponse?: string;
macSettingsStatus?: MacSettingsStatusQueryParam;
softwareId?: number;
status?: HostStatus;
mdmId?: number;
@ -46,6 +49,7 @@ export interface IExportHostsOptions {
teamId?: number;
policyId?: number;
policyResponse?: string;
macSettingsStatus?: MacSettingsStatusQueryParam;
softwareId?: number;
status?: HostStatus;
mdmId?: number;
@ -140,6 +144,7 @@ export default {
const policyId = options?.policyId;
const policyResponse = options?.policyResponse || "passing";
const softwareId = options?.softwareId;
const macSettingsStatus = options?.macSettingsStatus;
const status = options?.status;
const mdmId = options?.mdmId;
const mdmEnrollmentStatus = options?.mdmEnrollmentStatus;
@ -186,6 +191,7 @@ export default {
teamId,
policyId,
policyResponse = "passing",
macSettingsStatus,
softwareId,
status,
mdmId,
@ -202,6 +208,12 @@ export default {
const label = getLabel(selectedLabels);
const sortParams = getSortParams(sortBy);
// ensure macos_settings filter is always applied in
// conjuction with a team_id, 0 (no teams) by default
if (macSettingsStatus) {
teamId = teamId ?? 0;
}
const queryParams = {
page,
per_page: perPage,
@ -211,6 +223,7 @@ export default {
order_key: sortParams.order_key,
order_direction: sortParams.order_direction,
status,
macos_settings: macSettingsStatus,
...reconcileMutuallyExclusiveHostParams({
label,
policyId,