From 963d628588845cea6c2c50266754ffd3f09fa766 Mon Sep 17 00:00:00 2001
From: Gabriel Hernandez
Date: Thu, 30 Mar 2023 15:29:54 +0100
Subject: [PATCH] move out manage host filters into their own component
(#10749)
# Checklist for submitter
This removes the filtering UI from the manage hosts page. Currently it
keeps the same code (I felt it was too risky to move out the code AND do
a big rewrite) but I have other work still in progress where I try to
improve the code for filtering.
Basically this is the first step to making the code for creating and
maintaining filters a bit easier.
- [x] Manual QA for all new/changed functionality
---
.../hosts/ManageHostsPage/ManageHostsPage.tsx | 410 ++---------------
.../pages/hosts/ManageHostsPage/_styles.scss | 28 --
.../FilterPill/FilterPill.tests.tsx | 59 +++
.../components/FilterPill/FilterPill.tsx | 4 +-
.../HostsFilterBlock/HostsFilterBlock.tsx | 420 ++++++++++++++++++
.../components/HostsFilterBlock/_styles.scss | 32 ++
.../components/HostsFilterBlock/index.ts | 1 +
frontend/test/test-utils.tsx | 4 +
8 files changed, 560 insertions(+), 398 deletions(-)
create mode 100644 frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tests.tsx
create mode 100644 frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx
create mode 100644 frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/_styles.scss
create mode 100644 frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/index.ts
diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
index e598a19cfa..8926de0224 100644
--- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
+++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
@@ -2,7 +2,7 @@ import React, { useState, useContext, useEffect, useCallback } from "react";
import { useQuery } from "react-query";
import { InjectedRouter, Params } from "react-router/lib/Router";
import { RouteProps } from "react-router/lib/Route";
-import { find, isEmpty, isEqual, omit, invert } from "lodash";
+import { find, isEmpty, isEqual, omit } from "lodash";
import { format } from "date-fns";
import FileSaver from "file-saver";
@@ -35,11 +35,8 @@ import {
import { IHost } from "interfaces/host";
import { ILabel } from "interfaces/label";
import { IMunkiIssuesAggregate } from "interfaces/macadmins";
-import { IMdmSolution, MDM_ENROLLMENT_STATUS } from "interfaces/mdm";
-import {
- formatOperatingSystemDisplayName,
- IOperatingSystemVersion,
-} from "interfaces/operating_system";
+import { IMdmSolution } from "interfaces/mdm";
+import { IOperatingSystemVersion } from "interfaces/operating_system";
import { IPolicy, IStoredPolicyResponse } from "interfaces/policy";
import { ISoftware } from "interfaces/software";
import { ITeam } from "interfaces/team";
@@ -49,7 +46,6 @@ import sortUtils from "utilities/sort";
import {
HOSTS_SEARCH_BOX_PLACEHOLDER,
HOSTS_SEARCH_BOX_TOOLTIP,
- PLATFORM_LABEL_DISPLAY_NAMES,
PolicyResponse,
} from "utilities/constants";
@@ -74,27 +70,22 @@ 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";
import SecretEditorModal from "../../../components/EnrollSecrets/SecretEditorModal";
import AddHostsModal from "../../../components/AddHostsModal";
import EnrollSecretModal from "../../../components/EnrollSecrets/EnrollSecretModal";
-import PoliciesFilter from "./components/PoliciesFilter";
// @ts-ignore
import EditColumnsModal from "./components/EditColumnsModal/EditColumnsModal";
import TransferHostModal from "../components/TransferHostModal";
import DeleteHostModal from "../components/DeleteHostModal";
import DeleteLabelModal from "./components/DeleteLabelModal";
import EditColumnsIcon from "../../../../assets/images/icon-edit-columns-16x16@2x.png";
-import PencilIcon from "../../../../assets/images/icon-pencil-14x14@2x.png";
-import TrashIcon from "../../../../assets/images/icon-trash-14x14@2x.png";
import CloseIconBlack from "../../../../assets/images/icon-close-fleet-black-16x16@2x.png";
-import PolicyIcon from "../../../../assets/images/icon-policy-fleet-black-12x12@2x.png";
import DownloadIcon from "../../../../assets/images/icon-download-12x12@2x.png";
import LabelFilterSelect from "./components/LabelFilterSelect";
-import FilterPill from "./components/FilterPill";
+import HostsFilterBlock from "./components/HostsFilterBlock";
interface IManageHostsProps {
route: RouteProps;
@@ -280,18 +271,13 @@ const ManageHostsPage = ({
const canEnrollGlobalHosts = isGlobalAdmin || isGlobalMaintainer;
const canAddNewLabels = (isGlobalAdmin || isGlobalMaintainer) ?? false;
- const {
- isLoading: isLoadingLabels,
- data: labels,
- error: labelsError,
- refetch: refetchLabels,
- } = useQuery(
- ["labels"],
- () => labelsAPI.loadAll(),
- {
- select: (data: ILabelsResponse) => data.labels,
- }
- );
+ const { data: labels, refetch: refetchLabels } = useQuery<
+ ILabelsResponse,
+ Error,
+ ILabel[]
+ >(["labels"], () => labelsAPI.loadAll(), {
+ select: (data: ILabelsResponse) => data.labels,
+ });
const {
isLoading: isGlobalSecretsLoading,
@@ -623,38 +609,6 @@ const ManageHostsPage = ({
);
};
- const handleClearPoliciesFilter = () => {
- handleClearFilter(["policy_id", "policy_response"]);
- };
-
- const handleClearOSFilter = () => {
- handleClearFilter(["os_id", "os_name", "os_version"]);
- };
-
- const handleClearMacSettingsStatusFilter = () => {
- handleClearFilter(["macos_settings"]);
- };
-
- const handleClearSoftwareFilter = () => {
- handleClearFilter(["software_id"]);
- };
-
- const handleClearMDMSolutionFilter = () => {
- handleClearFilter(["mdm_id"]);
- };
-
- const handleClearMDMEnrollmentFilter = () => {
- handleClearFilter(["mdm_enrollment_status"]);
- };
-
- const handleClearMunkiIssueFilter = () => {
- handleClearFilter(["munki_issue_id"]);
- };
-
- const handleClearLowDiskSpaceFilter = () => {
- handleClearFilter(["low_disk_space"]);
- };
-
const handleTeamSelect = (teamId: number) => {
const { MANAGE_HOSTS } = PATHS;
@@ -1105,247 +1059,6 @@ const ManageHostsPage = ({
/>
);
- const renderLabelFilterPill = () => {
- if (selectedLabel) {
- const { description, display_text, label_type } = selectedLabel;
- const pillLabel =
- PLATFORM_LABEL_DISPLAY_NAMES[display_text] ?? display_text;
-
- return (
- <>
-
- {label_type !== "builtin" && !isOnlyObserver && (
- <>
-
-
- >
- )}
- >
- );
- }
-
- return null;
- };
-
- const renderOSFilterBlock = () => {
- if (!osId && !(osName && osVersion)) return null;
-
- let os: IOperatingSystemVersion | undefined;
- if (osId) {
- os = osVersions?.find((v) => v.os_id === osId);
- } else if (osName && osVersion) {
- const name: string = osName;
- const vers: string = osVersion;
-
- os = osVersions?.find(
- ({ name_only, version }) =>
- name_only.toLowerCase() === name.toLowerCase() &&
- version.toLowerCase() === vers.toLowerCase()
- );
- }
- if (!os) return null;
-
- const { name, name_only, version } = os;
- const label = formatOperatingSystemDisplayName(
- name_only || version
- ? `${name_only || ""} ${version || ""}`
- : `${name || ""}`
- );
- const TooltipDescription = (
-
- Hosts with {formatOperatingSystemDisplayName(name_only || name)},
-
- {version && `${version} installed`}
-
- );
-
- return (
-
- );
- };
-
- const renderPoliciesFilterBlock = () => (
- <>
-
-
- >
- );
-
- const renderMacSettingsStatusFilterBlock = () => {
- const label = "macOS settings";
- return (
- <>
-
-
- >
- );
- };
-
- const renderSoftwareFilterBlock = () => {
- if (!softwareDetails) return null;
-
- const { name, version } = softwareDetails;
- const label = `${name || "Unknown software"} ${version || ""}`;
-
- const TooltipDescription = (
-
- Hosts with {name || "Unknown software"},
-
- {version || "version unknown"} installed
-
- );
-
- return (
-
- );
- };
-
- const renderMDMSolutionFilterBlock = () => {
- if (!mdmSolutionDetails) return null;
-
- const { name, server_url } = mdmSolutionDetails;
- const label = name ? `${name} ${server_url}` : `${server_url}`;
-
- const TooltipDescription = (
-
- Host enrolled
- {name !== "Unknown" && ` to ${name}`}
-
at {server_url}
-
- );
-
- return (
-
- );
- };
-
- const renderMDMEnrollmentFilterBlock = () => {
- if (!mdmEnrollmentStatus) return null;
-
- const label = `MDM status: ${
- invert(MDM_ENROLLMENT_STATUS)[mdmEnrollmentStatus]
- }`;
-
- // More narrow tooltip than other MDM tooltip
- const MDM_STATUS_PILL_TOOLTIP: Record = {
- automatic: (
-
- MDM was turned on
- automatically using Apple
- Automated Device
- Enrollment (DEP) or
- Windows Autopilot.
- Administrators can block
- device users from turning
-
MDM off.
-
- ),
- manual: (
-
- MDM was turned on
- manually. Device users
- can turn MDM off.
-
- ),
- unenrolled: (
-
- Hosts with MDM off
- don't receive macOS
- settings and macOS
- update encouragement.
-
- ),
- pending: (
-
- Hosts ordered using Apple
- Business Manager (ABM).
- They will automatically enroll
- to Fleet and turn on MDM
- when they're unboxed.
-
- ),
- };
-
- return (
-
- );
- };
-
- const renderMunkiIssueFilterBlock = () => {
- if (munkiIssueDetails) {
- return (
-
- Hosts that reported this Munki issue
- the last time Munki ran on each host.
-
- }
- onClear={handleClearMunkiIssueFilter}
- />
- );
- }
- return null;
- };
-
- const renderLowDiskSpaceFilterBlock = () => {
- const TooltipDescription = (
-
- Hosts that have {lowDiskSpaceHosts} GB or less
- disk space available.
-
- );
-
- return (
-
- );
- };
-
const renderEditColumnsModal = () => {
if (!config || !currentUser) {
return null;
@@ -1570,76 +1283,6 @@ const ManageHostsPage = ({
);
}, [isHostCountLoading, filteredHostCount]);
- const renderActiveFilterBlock = () => {
- const showSelectedLabel = selectedLabel && selectedLabel.type !== "all";
-
- if (
- showSelectedLabel ||
- policyId ||
- macSettingsStatus ||
- softwareId ||
- showSelectedLabel ||
- mdmId ||
- mdmEnrollmentStatus ||
- lowDiskSpaceHosts ||
- osId ||
- (osName && osVersion) ||
- munkiIssueId
- ) {
- const renderFilterPill = () => {
- switch (true) {
- // backend allows for pill combos (label + low disk space) OR
- // (label + mdm solution) OR (label + mdm enrollment status)
- case showSelectedLabel && !!lowDiskSpaceHosts:
- return (
- <>
- {renderLabelFilterPill()} {renderLowDiskSpaceFilterBlock()}
- >
- );
- case showSelectedLabel && !!mdmId:
- return (
- <>
- {renderLabelFilterPill()} {renderMDMSolutionFilterBlock()}
- >
- );
-
- case showSelectedLabel && !!mdmEnrollmentStatus:
- return (
- <>
- {renderLabelFilterPill()} {renderMDMEnrollmentFilterBlock()}
- >
- );
- case showSelectedLabel:
- return renderLabelFilterPill();
- case !!policyId:
- return renderPoliciesFilterBlock();
- case !!macSettingsStatus:
- return renderMacSettingsStatusFilterBlock();
- case !!softwareId:
- return renderSoftwareFilterBlock();
- case !!mdmId:
- return renderMDMSolutionFilterBlock();
- case !!mdmEnrollmentStatus:
- return renderMDMEnrollmentFilterBlock();
- case !!osId || (!!osName && !!osVersion):
- return renderOSFilterBlock();
- case !!munkiIssueId:
- return renderMunkiIssueFilterBlock();
- case !!lowDiskSpaceHosts:
- return renderLowDiskSpaceFilterBlock();
- default:
- return null;
- }
- };
-
- return (
-
- {renderFilterPill()}
-
- );
- }
- };
-
const renderCustomControls = () => {
// we filter out the status labels as we dont want to display them in the label
// filter select dropdown.
@@ -1901,7 +1544,36 @@ const ManageHostsPage = ({
)}
- {renderActiveFilterBlock()}
+ {/* TODO: look at improving the props API for this component. Im thinking
+ some of the props can be defined inside HostsFilterBlock */}
+
{renderNoEnrollSecretBanner()}
{renderTable()}
diff --git a/frontend/pages/hosts/ManageHostsPage/_styles.scss b/frontend/pages/hosts/ManageHostsPage/_styles.scss
index 1f1e6b8b8c..7335c03bba 100644
--- a/frontend/pages/hosts/ManageHostsPage/_styles.scss
+++ b/frontend/pages/hosts/ManageHostsPage/_styles.scss
@@ -248,34 +248,6 @@
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;
- margin-bottom: $pad-medium;
- gap: $pad-small; // between multiple filter pills
- }
-
- &__policies-filter-pill {
- margin-left: $pad-medium;
- }
-
&__enroll-hosts {
padding: $pad-small;
margin-right: $pad-small;
diff --git a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tests.tsx b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tests.tsx
new file mode 100644
index 0000000000..ad390871f4
--- /dev/null
+++ b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tests.tsx
@@ -0,0 +1,59 @@
+import React from "react";
+import { noop } from "lodash";
+import { render, screen } from "@testing-library/react";
+
+import { renderWithSetup } from "test/test-utils";
+
+import FilterPill from "./FilterPill";
+
+import PolicyIcon from "../../../../../../assets/images/icon-policy-fleet-black-12x12@2x.png";
+
+describe("Filter Pill Component", () => {
+ it("renders the pill text", () => {
+ render();
+
+ expect(screen.getByText("Test Pill")).toBeInTheDocument();
+ });
+
+ it("renders an passed in icon properly", () => {
+ render();
+
+ expect(screen.getByTestId("filter-pill__icon")).toBeInTheDocument();
+ });
+
+ it("renders a passed in string tooltip", () => {
+ render(
+
+ );
+
+ expect(screen.getByText("Test Tooltip")).toBeInTheDocument();
+ });
+
+ it("renders a passed in ReactNode tooltip", () => {
+ render(
+ This is a ReactNode
}
+ onClear={noop}
+ />
+ );
+
+ expect(screen.getByText("This is a ReactNode")).toBeInTheDocument();
+ });
+
+ it("calls the onCancel callback when a user clicks on the remove button", async () => {
+ const spy = jest.fn();
+
+ const { user } = renderWithSetup(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: "Remove filter" }));
+
+ expect(spy).toHaveBeenCalled();
+ });
+});
diff --git a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx
index 62d7964360..3251236ab2 100644
--- a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx
+++ b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx
@@ -40,7 +40,9 @@ const FilterPill = ({
data-for={`filter-pill-tooltip-${label}`}
>
- {icon &&

}
+ {icon && (
+

+ )}
{label}