From 5815dc4e549a88f90642e7c37581b1bc716e8fe6 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 15 May 2025 12:37:45 +0100 Subject: [PATCH] Feat UI host filter by custom profiles (#29038) For #28759 This is the UI work for being able to filter hosts by a configuration profile status. There are also added tests in this PR. ![image](https://github.com/user-attachments/assets/b2585093-b191-4dc5-a11e-55ad4156d713) - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - [x] Added/updated automated tests - [x] Manual QA for all new/changed functionality --- ...-for-filter-hosts-by-config-profile-status | 1 + .../ConfigProfileStatusModal.tests.tsx | 71 +++++++++++++++++++ .../ConfigProfileStatusTableConfig.tsx | 15 ++-- .../DeleteProfileModal/DeleteProfileModal.tsx | 14 ++-- .../DeleteProfileModal/_styles.scss | 10 +++ .../ResendConfigProfileModal.tsx | 2 +- .../hosts/ManageHostsPage/ManageHostsPage.tsx | 67 +++++++++++++++-- .../components/FilterPill/FilterPill.tsx | 15 ++-- .../components/FilterPill/_styles.scss | 4 ++ .../HostsFilterBlock/HostsFilterBlock.tsx | 55 +++++++++++--- frontend/services/entities/config_profiles.ts | 21 +++--- frontend/services/entities/host_count.ts | 6 ++ frontend/services/entities/hosts.ts | 6 ++ frontend/test/handlers/config-profiles.ts | 24 +++++++ frontend/utilities/endpoints.ts | 2 + frontend/utilities/url/index.ts | 9 +++ 16 files changed, 275 insertions(+), 47 deletions(-) create mode 100644 changes/issue-28759-ui-for-filter-hosts-by-config-profile-status create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusModal/ConfigProfileStatusModal.tests.tsx create mode 100644 frontend/test/handlers/config-profiles.ts diff --git a/changes/issue-28759-ui-for-filter-hosts-by-config-profile-status b/changes/issue-28759-ui-for-filter-hosts-by-config-profile-status new file mode 100644 index 0000000000..03a5c840eb --- /dev/null +++ b/changes/issue-28759-ui-for-filter-hosts-by-config-profile-status @@ -0,0 +1 @@ +- add UI to filter hosts by config profile status. diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusModal/ConfigProfileStatusModal.tests.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusModal/ConfigProfileStatusModal.tests.tsx new file mode 100644 index 0000000000..6c8be1b2de --- /dev/null +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusModal/ConfigProfileStatusModal.tests.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { screen, within } from "@testing-library/react"; +import { noop } from "lodash"; + +import { createCustomRenderer } from "test/test-utils"; +import mockServer from "test/mock-server"; +import { defaultConfigProfileStatusHandler } from "test/handlers/config-profiles"; + +import ConfigProfileStatusModal from "./ConfigProfileStatusModal"; + +describe("ConfigProfileStatusModal", () => { + const render = createCustomRenderer({ + withBackendMock: true, + }); + + it("renders the correct number of hosts for each status", async () => { + mockServer.use(defaultConfigProfileStatusHandler); + render( + + ); + + await screen.findByText("Verified"); + + // get all rows in the table and skip header row + const rows = screen.getAllByRole("row").slice(1); + + const verifiedRow = within(rows[0]).getAllByRole("cell"); + expect(verifiedRow[0]).toHaveTextContent("Verified"); + expect(verifiedRow[1]).toHaveTextContent("---"); + + const verifiyingRow = within(rows[1]).getAllByRole("cell"); + expect(verifiyingRow[0]).toHaveTextContent("Verifying"); + expect(verifiyingRow[1]).toHaveTextContent("1"); + + const pendingRow = within(rows[2]).getAllByRole("cell"); + expect(pendingRow[0]).toHaveTextContent("Pending"); + expect(pendingRow[1]).toHaveTextContent("2"); + + const failedRow = within(rows[3]).getAllByRole("cell"); + expect(failedRow[0]).toHaveTextContent("Failed"); + expect(failedRow[1]).toHaveTextContent("3"); + }); + + it("shows the resend button for a failed row on hover", async () => { + mockServer.use(defaultConfigProfileStatusHandler); + const { user } = render( + + ); + + await screen.findByText("Verified"); + + const failedRow = screen.getByText("Failed").closest("tr"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + user.hover(failedRow!); + + const resendButton = screen.getByRole("button", { name: "Resend" }); + expect(resendButton).toBeVisible(); + }); +}); diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusTable/ConfigProfileStatusTableConfig.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusTable/ConfigProfileStatusTableConfig.tsx index 2ad7ee60b1..47a4fed656 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusTable/ConfigProfileStatusTableConfig.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ConfigProfileStatusTable/ConfigProfileStatusTableConfig.tsx @@ -1,15 +1,17 @@ import React from "react"; import { Column } from "react-table"; -import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon"; import { INumberCellProps, IStringCellProps, } from "interfaces/datatable_config"; +import { MdmProfileStatus } from "interfaces/mdm"; import { IGetConfigProfileStatusResponse } from "services/entities/config_profiles"; -import ConfigProfileHostCountCell from "../ConfigProfileHostCountCell"; -type IConfigProfileStatus = "verified" | "verifying" | "pending" | "failed"; +import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon"; +import { IndicatorStatus } from "components/StatusIndicatorWithIcon/StatusIndicatorWithIcon"; + +import ConfigProfileHostCountCell from "../ConfigProfileHostCountCell"; interface IConfigProfileRowData { status: string; @@ -22,11 +24,10 @@ const STAUTS_ORDER = ["verified", "verifying", "pending", "failed"]; export interface IStatusCellValue { displayName: string; - statusName: IConfigProfileStatus; - value: IConfigProfileStatus; + statusName: IndicatorStatus; } -const STATUS_DISPLAY_OPTIONS = { +const STATUS_DISPLAY_OPTIONS: Record = { verified: { displayName: "Verified", statusName: "success", @@ -43,7 +44,7 @@ const STATUS_DISPLAY_OPTIONS = { displayName: "Failed", statusName: "error", }, -} as const; +}; type IConfigProfileStatusColumnConfig = Column; type IStatusCellProps = IStringCellProps; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/DeleteProfileModal/DeleteProfileModal.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/DeleteProfileModal/DeleteProfileModal.tsx index 8e563b5461..fb84fae8c0 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/DeleteProfileModal/DeleteProfileModal.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/DeleteProfileModal/DeleteProfileModal.tsx @@ -19,7 +19,7 @@ const generateMessageSuffix = (isPremiumTier?: boolean, teamId?: number) => { if (!isPremiumTier) { return ""; } - return teamId ? " assigned to this team" : " with no team"; + return teamId ? "assigned to this team" : "with no team"; }; const DeleteProfileModal = ({ @@ -42,11 +42,13 @@ const DeleteProfileModal = ({ width="large" > <> -

- This action will delete configuration profile{" "} - {profileName}{" "} - from all hosts{messageSuffix}. -

+
+

+ This action will remove the {profileName} configuration + profile from all hosts {messageSuffix}. +

+

Pending profiles will be canceled.

+