From eb06ef804910ca4ec5c592b8b024d89019e80fc0 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Mon, 12 Sep 2022 16:18:12 +0100 Subject: [PATCH] =?UTF-8?q?simplify=20TeamsDropdown=20component=20and=20up?= =?UTF-8?q?date=20ManageHostPage=20to=20keep=20po=E2=80=A6=20(#7606)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * simplify TeamsDropdown component and update ManageHostPage to keep policy filter across team change * fix TeamDropdown for users not on global team --- ...981-keep-policy-filter-across-team-changes | 1 + .../TeamsDropdown/TeamsDropdown.tests.tsx | 82 +++++++++++++++++++ .../TeamsDropdown/TeamsDropdown.tsx | 22 ++--- .../AutoSizeInputField/AutoSizeInputField.tsx | 4 +- frontend/context/app.tsx | 42 +++++----- .../TeamDetailsWrapper/TeamDetailsWrapper.tsx | 2 +- .../hosts/ManageHostsPage/ManageHostsPage.tsx | 29 ++----- frontend/test/test-utils.tsx | 23 ++++++ 8 files changed, 145 insertions(+), 60 deletions(-) create mode 100644 changes/issue-6981-keep-policy-filter-across-team-changes create mode 100644 frontend/components/TeamsDropdown/TeamsDropdown.tests.tsx create mode 100644 frontend/test/test-utils.tsx diff --git a/changes/issue-6981-keep-policy-filter-across-team-changes b/changes/issue-6981-keep-policy-filter-across-team-changes new file mode 100644 index 0000000000..29394340d0 --- /dev/null +++ b/changes/issue-6981-keep-policy-filter-across-team-changes @@ -0,0 +1 @@ +- keep policy filter on Manage Hosts Page changes across a team changes diff --git a/frontend/components/TeamsDropdown/TeamsDropdown.tests.tsx b/frontend/components/TeamsDropdown/TeamsDropdown.tests.tsx new file mode 100644 index 0000000000..93f427bf90 --- /dev/null +++ b/frontend/components/TeamsDropdown/TeamsDropdown.tests.tsx @@ -0,0 +1,82 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { noop } from "lodash"; +import { renderWithAppContext } from "test/test-utils"; + +import TeamsDropdown from "./TeamsDropdown"; + +describe("TeamsDropdown - component", () => { + const USER_TEAMS = [ + { id: 1, name: "Team 1" }, + { id: 2, name: "Team 2" }, + ]; + + it("renders the given selected team from selectedTeamId", () => { + render( + + ); + + const selectedTeam = screen.getByText("Team 1"); + expect(selectedTeam).toBeInTheDocument(); + }); + + it("renders the first team option when includeAll is false and when no selectedTeamId is given", () => { + render( + + ); + + const selectedTeam = screen.getByText("Team 1"); + expect(selectedTeam).toBeInTheDocument(); + }); + + describe("user is on the global team", () => { + const contextValue = { + isOnGlobalTeam: true, + }; + + it("renders 'All teams' when no selectedTeamId is given", () => { + renderWithAppContext( + , + { contextValue } + ); + + const selectedTeam = screen.getByText("All teams"); + expect(selectedTeam).toBeInTheDocument(); + }); + + it("renders the first team option when includeAll is false and when no selectedTeamId is given", () => { + renderWithAppContext( + , + { contextValue } + ); + + const selectedTeam = screen.getByText("Team 1"); + expect(selectedTeam).toBeInTheDocument(); + }); + }); + + describe("user is not on the global team", () => { + const contextValue = { isOnGlobalTeam: false }; + + it("renders the first team when no selectedTeamId is given", () => { + renderWithAppContext( + , + { contextValue } + ); + + expect(screen.getByText("Team 1")).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/components/TeamsDropdown/TeamsDropdown.tsx b/frontend/components/TeamsDropdown/TeamsDropdown.tsx index a58f7b00d4..30b0f0c883 100644 --- a/frontend/components/TeamsDropdown/TeamsDropdown.tsx +++ b/frontend/components/TeamsDropdown/TeamsDropdown.tsx @@ -1,14 +1,14 @@ import React, { useContext, useMemo } from "react"; import classnames from "classnames"; -import { AppContext } from "context/app"; import { ITeamSummary } from "interfaces/team"; // @ts-ignore import Dropdown from "components/forms/fields/Dropdown"; +import { AppContext } from "context/app"; const generateDropdownOptions = ( teams: ITeamSummary[] | undefined, - includeAll: boolean | undefined + includeAll: boolean ) => { if (!teams) { return []; @@ -30,12 +30,10 @@ const generateDropdownOptions = ( return options; }; - interface ITeamsDropdownProps { currentUserTeams: ITeamSummary[]; - selectedTeamId: number; - includeAll?: boolean; // Include "All Teams" option for all users - disableAll?: boolean; // Disable "All Teams" option for global users + selectedTeamId?: number; + includeAll?: boolean; // Include the "All Teams" option; isDisabled?: boolean; onChange: (newSelectedValue: number) => void; onOpen?: () => void; @@ -47,22 +45,18 @@ const baseClass = "component__team-dropdown"; const TeamsDropdown = ({ currentUserTeams, selectedTeamId, - includeAll = false, - disableAll = false, + includeAll = true, isDisabled, onChange, onOpen, onClose, }: ITeamsDropdownProps): JSX.Element => { - const { isOnGlobalTeam } = useContext(AppContext); + const { isOnGlobalTeam = false } = useContext(AppContext); const teamOptions = useMemo( () => - generateDropdownOptions( - currentUserTeams, - (isOnGlobalTeam && !disableAll) || includeAll - ), - [currentUserTeams, isOnGlobalTeam] + generateDropdownOptions(currentUserTeams, includeAll && isOnGlobalTeam), + [currentUserTeams, includeAll, isOnGlobalTeam] ); const selectedValue = teamOptions.find( diff --git a/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx b/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx index a63ae03b16..1ef1d34caa 100644 --- a/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx +++ b/frontend/components/forms/fields/AutoSizeInputField/AutoSizeInputField.tsx @@ -24,7 +24,7 @@ interface IAutoSizeInputFieldProps { const baseClass = "component__auto-size-input-field"; -const TeamsDropdown = ({ +const AutoSizeInputField = ({ name, placeholder, value, @@ -102,4 +102,4 @@ const TeamsDropdown = ({ ); }; -export default TeamsDropdown; +export default AutoSizeInputField; diff --git a/frontend/context/app.tsx b/frontend/context/app.tsx index b693479ec7..c397e67809 100644 --- a/frontend/context/app.tsx +++ b/frontend/context/app.tsx @@ -57,32 +57,32 @@ type Props = { }; type InitialStateType = { - availableTeams: ITeamSummary[] | undefined; + availableTeams?: ITeamSummary[]; config: IConfig | null; currentUser: IUser | null; - currentTeam: ITeamSummary | undefined; + currentTeam?: ITeamSummary; enrollSecret: IEnrollSecret[] | null; - isPreviewMode: boolean | undefined; - isSandboxMode: boolean | undefined; - isFreeTier: boolean | undefined; - isPremiumTier: boolean | undefined; - isGlobalAdmin: boolean | undefined; - isGlobalMaintainer: boolean | undefined; - isGlobalObserver: boolean | undefined; - isOnGlobalTeam: boolean | undefined; - isAnyTeamMaintainer: boolean | undefined; - isAnyTeamMaintainerOrTeamAdmin: boolean | undefined; - isTeamObserver: boolean | undefined; - isTeamMaintainer: boolean | undefined; - isTeamMaintainerOrTeamAdmin: boolean | undefined; - isAnyTeamAdmin: boolean | undefined; - isTeamAdmin: boolean | undefined; - isOnlyObserver: boolean | undefined; - isNoAccess: boolean | undefined; + isPreviewMode?: boolean; + isSandboxMode?: boolean; + isFreeTier?: boolean; + isPremiumTier?: boolean; + isGlobalAdmin?: boolean; + isGlobalMaintainer?: boolean; + isGlobalObserver?: boolean; + isOnGlobalTeam?: boolean; + isAnyTeamMaintainer?: boolean; + isAnyTeamMaintainerOrTeamAdmin?: boolean; + isTeamObserver?: boolean; + isTeamMaintainer?: boolean; + isTeamMaintainerOrTeamAdmin?: boolean; + isAnyTeamAdmin?: boolean; + isTeamAdmin?: boolean; + isOnlyObserver?: boolean; + isNoAccess?: boolean; sandboxExpiry?: string; setAvailableTeams: (availableTeams: ITeamSummary[]) => void; setCurrentUser: (user: IUser) => void; - setCurrentTeam: (team: ITeamSummary | undefined) => void; + setCurrentTeam: (team?: ITeamSummary) => void; setConfig: (config: IConfig) => void; setEnrollSecret: (enrollSecret: IEnrollSecret[]) => void; setSandboxExpiry: (sandboxExpiry: string) => void; @@ -90,7 +90,7 @@ type InitialStateType = { export type IAppContext = InitialStateType; -const initialState = { +export const initialState = { availableTeams: undefined, config: null, currentUser: null, diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx index 9206e50dc2..1c54fa78cb 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx @@ -414,7 +414,7 @@ const TeamDetailsWrapper = ({ selectedTeamId={toNumber(routeParams.team_id)} currentUserTeams={adminTeams || []} isDisabled={isLoadingTeams} - disableAll + includeAll={false} onChange={(newSelectedValue: number) => handleTeamSelect(newSelectedValue) } diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index ef3d1f8661..8a6b478695 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -338,13 +338,7 @@ const ManageHostsPage = ({ useQuery( ["policy"], - () => { - const teamId = parseInt(queryParams?.team_id, 10) || 0; - const request = teamId - ? teamPoliciesAPI.load(teamId, policyId) - : globalPoliciesAPI.load(policyId); - return request; - }, + () => globalPoliciesAPI.load(policyId), { enabled: !!policyId, onSuccess: ({ policy: policyAPIResponse }) => { @@ -408,10 +402,6 @@ const ManageHostsPage = ({ } }; - const getLabelSelected = () => { - return selectedFilters.find((f) => f.includes(LABEL_SLUG_PREFIX)); - }; - const getStatusSelected = () => { return selectedFilters.find((f) => !f.includes(LABEL_SLUG_PREFIX)); }; @@ -714,22 +704,19 @@ const ManageHostsPage = ({ const handleTeamSelect = (teamId: number) => { const { MANAGE_HOSTS } = PATHS; + const teamIdParam = getValidatedTeamId( availableTeams || [], teamId, currentUser, - isOnGlobalTeam as boolean + isOnGlobalTeam ?? false ); - const slimmerParams = omit(queryParams, [ - "policy_id", - "policy_response", - "team_id", - ]); + const slimmerParams = omit(queryParams, ["team_id"]); const newQueryParams = !teamIdParam ? slimmerParams - : Object.assign({}, slimmerParams, { team_id: teamIdParam }); + : Object.assign(slimmerParams, { team_id: teamIdParam }); const nextLocation = getNextLocationPath({ pathPrefix: MANAGE_HOSTS, @@ -1168,9 +1155,7 @@ const ManageHostsPage = ({ const renderTeamsFilterDropdown = () => ( handleTeamSelect(newSelectedValue) @@ -1257,7 +1242,7 @@ const ManageHostsPage = ({ /> diff --git a/frontend/test/test-utils.tsx b/frontend/test/test-utils.tsx new file mode 100644 index 0000000000..a26af30994 --- /dev/null +++ b/frontend/test/test-utils.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { render, RenderOptions } from "@testing-library/react"; + +import { AppContext, IAppContext, initialState } from "context/app"; + +type RenderOptionsWithProviderProps = RenderOptions & { + contextValue: Partial; +}; + +/** + * A custom render method that provides a configurable App context when testing components + */ +// eslint-disable-next-line import/prefer-default-export +export const renderWithAppContext = ( + component: React.ReactNode, + { contextValue, ...renderOptions }: RenderOptionsWithProviderProps +) => { + const value: IAppContext = { ...initialState, ...contextValue }; + return render( + {component}, + renderOptions + ); +};