From e247a3b871834f639e137961c04362ce26ad0765 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Wed, 29 Jan 2025 11:12:28 -0600 Subject: [PATCH] Update policies page empty state (#25726) for #23312 # Checklist for submitter - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. This PR updates the verbiage on the Policies page when no policies are present for the selected team (or All Teams). It also does a little bit of code cleanup. Existing test was updated and a new test added. I've also added VSCode test runners to easily run Jest tests from the IDE. The [original request](https://github.com/fleetdm/fleet/issues/23073) mentioned removing the button from the page if All Teams is selected, but I don't think we should do that -- you can add All Teams policies with it. ## Screenshots Empty state for "All teams" (admin): image Empty state for a team (admin): image Empty state for "All teams" (non-admin): image Empty state for a team (non-admin): image --- .vscode/launch.json | 25 +++++++ changes/23312-update-policies-empty-state | 1 + .../PoliciesTable/PoliciesTable.tests.tsx | 55 ++++++++++---- .../PoliciesTable/PoliciesTable.tsx | 73 +++++++++++-------- 4 files changed, 108 insertions(+), 46 deletions(-) create mode 100644 changes/23312-update-policies-empty-state diff --git a/.vscode/launch.json b/.vscode/launch.json index 885f407fb8e5..ed3343bd9a39 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -101,6 +101,31 @@ "path": "${workspaceFolder}/frontend" } ] + }, + { + "name": "Jest: test current file", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/node_modules/.bin/jest", + "args": [ + "--config", + "./frontend/test/jest.config.ts", + "${relativeFile}" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Jest: run all tests", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/node_modules/.bin/jest", + "args": [ + "--config", + "./frontend/test/jest.config.ts" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" } ] } \ No newline at end of file diff --git a/changes/23312-update-policies-empty-state b/changes/23312-update-policies-empty-state new file mode 100644 index 000000000000..b02eb7f53f82 --- /dev/null +++ b/changes/23312-update-policies-empty-state @@ -0,0 +1 @@ +- Clarified text on the Policies page when no policies exist for the selected team (or All Teams) diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx index 22a53a7fa5e8..3b4f9c73a195 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx @@ -8,7 +8,7 @@ import createMockPolicy from "__mocks__/policyMock"; import PoliciesTable from "./PoliciesTable"; describe("Policies table", () => { - it("Renders the page-wide empty state when no policies are present", async () => { + it("Renders the page-wide empty state when no policies are present (all teams)", async () => { const render = createCustomRenderer({ context: { app: { @@ -22,8 +22,7 @@ describe("Policies table", () => { {}} + onDeletePolicyClick={noop} currentTeam={{ id: -1, name: "All teams" }} isPremiumTier searchQuery="" @@ -34,7 +33,40 @@ describe("Policies table", () => { /> ); - expect(screen.getByText("You don't have any policies")).toBeInTheDocument(); + expect( + screen.getByText("You don't have any policies that apply to all teams") + ).toBeInTheDocument(); + expect(screen.queryByText("Name")).toBeNull(); + }); + + it("Renders the page-wide empty state when no policies are present (specific team)", async () => { + const render = createCustomRenderer({ + context: { + app: { + isGlobalAdmin: true, + currentUser: createMockUser(), + }, + }, + }); + + render( + null} + resetPageIndex={false} + /> + ); + + expect( + screen.getByText("You don't have any policies that apply to this team") + ).toBeInTheDocument(); expect(screen.queryByText("Name")).toBeNull(); }); @@ -52,8 +84,7 @@ describe("Policies table", () => { {}} + onDeletePolicyClick={noop} currentTeam={{ id: -1, name: "All teams" }} isPremiumTier searchQuery="shouldn't match anything" @@ -84,8 +115,7 @@ describe("Policies table", () => { {}} + onDeletePolicyClick={noop} currentTeam={{ id: -1, name: "All teams" }} isPremiumTier searchQuery="" @@ -123,8 +153,7 @@ describe("Policies table", () => { {}} + onDeletePolicyClick={noop} currentTeam={{ id: 2, name: "Team 2" }} isPremiumTier searchQuery="" @@ -162,8 +191,7 @@ describe("Policies table", () => { {}} + onDeletePolicyClick={noop} currentTeam={{ id: -1, name: "All teams" }} isPremiumTier searchQuery="" @@ -202,8 +230,7 @@ describe("Policies table", () => { {}} + onDeletePolicyClick={noop} currentTeam={{ id: 2, name: "Team 2" }} isPremiumTier searchQuery="" diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx index 88dc32998330..ee3f09828637 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx @@ -2,7 +2,7 @@ import React, { useContext } from "react"; import { AppContext } from "context/app"; import { IPolicyStats } from "interfaces/policy"; -import { ITeamSummary } from "interfaces/team"; +import { ITeamSummary, APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; import { IEmptyTableProps } from "interfaces/empty_table"; import Button from "components/buttons/Button"; @@ -55,34 +55,43 @@ const PoliciesTable = ({ }: IPoliciesTableProps): JSX.Element => { const { config } = useContext(AppContext); - const emptyState = () => { - const emptyPolicies: IEmptyTableProps = { - graphicName: "empty-policies", - header: "You don't have any policies", - info: - "Add policies to detect device health issues and trigger automations.", - }; - if (canAddOrDeletePolicy) { - emptyPolicies.primaryButton = ( - - ); - } - if (searchQuery) { - delete emptyPolicies.graphicName; - delete emptyPolicies.primaryButton; - emptyPolicies.header = "No matching policies"; - emptyPolicies.info = "No policies match the current filters."; - } - - return emptyPolicies; + const emptyState: IEmptyTableProps = { + graphicName: "empty-policies", + header: "You don't have any policies", + info: + "Add policies to detect device health issues and trigger automations.", }; + if ( + currentTeam?.id === null || + currentTeam?.id === APP_CONTEXT_ALL_TEAMS_ID + ) { + emptyState.header += " that apply to all teams"; + } else { + emptyState.header += " that apply to this team"; + } + + if (canAddOrDeletePolicy) { + emptyState.primaryButton = ( + + ); + } else { + emptyState.info = ""; + } + + if (searchQuery) { + delete emptyState.graphicName; + delete emptyState.primaryButton; + emptyState.header = "No matching policies"; + emptyState.info = "No policies match the current filters."; + } + const searchable = !(policiesList?.length === 0 && searchQuery === ""); const hasPermissionAndPoliciesToDelete = @@ -120,11 +129,11 @@ const PoliciesTable = ({ }} emptyComponent={() => EmptyTable({ - graphicName: emptyState().graphicName, - header: emptyState().header, - info: emptyState().info, - additionalInfo: emptyState().additionalInfo, - primaryButton: emptyState().primaryButton, + graphicName: emptyState.graphicName, + header: emptyState.header, + info: emptyState.info, + additionalInfo: emptyState.additionalInfo, + primaryButton: emptyState.primaryButton, }) } renderCount={renderPoliciesCount}