From 78d5e13a9759b38b12f317808ecca6d4f0fd4c22 Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Mon, 6 Dec 2021 20:04:40 -0600 Subject: [PATCH] Show all teams for global users and save team selection between pages (#3204) --- changes/issue-3193-show-all-teams | 1 + cypress/integration/premium/teamflow.spec.ts | 3 + frontend/pages/Homepage/Homepage.tsx | 63 ++++++----- .../hosts/ManageHostsPage/ManageHostsPage.tsx | 33 +++--- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 26 +++-- .../ManageSchedulePage/ManageSchedulePage.tsx | 102 +++++++++++------- 6 files changed, 134 insertions(+), 94 deletions(-) create mode 100644 changes/issue-3193-show-all-teams diff --git a/changes/issue-3193-show-all-teams b/changes/issue-3193-show-all-teams new file mode 100644 index 0000000000..bbee949641 --- /dev/null +++ b/changes/issue-3193-show-all-teams @@ -0,0 +1 @@ +* Ensure show all teams is displayed to global users and save team selection between pages diff --git a/cypress/integration/premium/teamflow.spec.ts b/cypress/integration/premium/teamflow.spec.ts index f28ca02411..2c946a89e7 100644 --- a/cypress/integration/premium/teamflow.spec.ts +++ b/cypress/integration/premium/teamflow.spec.ts @@ -102,6 +102,9 @@ describe("Teams flow", () => { cy.visit("/schedule/manage"); cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting + + cy.get(".component__team-dropdown").click(); + cy.findByText(/valor/i).should("exist"); cy.findByText(/query all window crashes/i).should("exist"); diff --git a/frontend/pages/Homepage/Homepage.tsx b/frontend/pages/Homepage/Homepage.tsx index 06f86a988d..ba927cd952 100644 --- a/frontend/pages/Homepage/Homepage.tsx +++ b/frontend/pages/Homepage/Homepage.tsx @@ -8,7 +8,6 @@ import hostSummaryAPI from "services/entities/host_summary"; import teamsAPI from "services/entities/teams"; import { IHostSummary, IHostSummaryPlatforms } from "interfaces/host_summary"; import { ITeam } from "interfaces/team"; -import { getSortedTeamOptions } from "fleet/helpers"; import sortUtils from "utilities/sort"; import TeamsDropdown from "components/TeamsDropdown"; @@ -38,6 +37,7 @@ const Homepage = (): JSX.Element => { config, currentTeam, isPremiumTier, + isFreeTier, isPreviewMode, isOnGlobalTeam, setCurrentTeam, @@ -53,24 +53,20 @@ const Homepage = (): JSX.Element => { const [offlineCount, setOfflineCount] = useState(); const [newCount, setNewCount] = useState(); - const { data: teams, isLoading: isLoadingTeams } = useQuery< - ITeamsResponse, - Error, - ITeam[] - >(["teams"], () => teamsAPI.loadAll(), { - enabled: !!isPremiumTier, - select: (data: ITeamsResponse) => - data.teams.sort((a, b) => sortUtils.caseInsensitiveAsc(a.name, b.name)), - onSuccess: (responseTeams) => { - if (!isOnGlobalTeam) { - const sortedTeams = getSortedTeamOptions(responseTeams); - const firstTeamOption = responseTeams.find( - (responseTeam) => responseTeam.id === sortedTeams[0].value - ); - setCurrentTeam(firstTeamOption); - } - }, - }); + const { data: teams } = useQuery( + ["teams"], + () => teamsAPI.loadAll(), + { + enabled: !!isPremiumTier, + select: (data: ITeamsResponse) => + data.teams.sort((a, b) => sortUtils.caseInsensitiveAsc(a.name, b.name)), + onSuccess: (responseTeams) => { + if (!currentTeam && !isOnGlobalTeam && responseTeams.length) { + setCurrentTeam(responseTeams[0]); + } + }, + } + ); const handleTeamSelect = (teamId: number) => { const selectedTeam = find(teams, ["id", teamId]); @@ -108,19 +104,22 @@ const Homepage = (): JSX.Element => {
- {isPremiumTier && teams && teams.length > 1 && ( - - handleTeamSelect(newSelectedValue) - } - /> - )} - {isPremiumTier && teams && teams.length === 1 && ( -

{teams[0].name}

- )} - {!isPremiumTier &&

{config?.org_name}

} + {isFreeTier &&

{config?.org_name}

} + {isPremiumTier && + teams && + (teams.length > 1 || isOnGlobalTeam) && ( + + handleTeamSelect(newSelectedValue) + } + /> + )} + {isPremiumTier && + !isOnGlobalTeam && + teams && + teams.length === 1 &&

{teams[0].name}

}
diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index bf80d0ae0d..7850066826 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -122,7 +122,7 @@ const ManageHostsPage = ({ router, params: routeParams, location, -}: IManageHostsProps) => { +}: IManageHostsProps): JSX.Element => { const dispatch = useDispatch(); const queryParams = location.query; const { @@ -137,6 +137,7 @@ const ManageHostsPage = ({ isOnGlobalTeam, isOnlyObserver, isPremiumTier, + isFreeTier, currentTeam, setCurrentTeam, } = useContext(AppContext); @@ -316,7 +317,7 @@ const ManageHostsPage = ({ select: (data: ITeamsResponse) => data.teams.sort((a, b) => sortUtils.caseInsensitiveAsc(a.name, b.name)), onSuccess: (responseTeams: ITeam[]) => { - if (responseTeams.length === 1) { + if (!currentTeam && !isOnGlobalTeam && responseTeams.length) { setCurrentTeam(responseTeams[0]); } }, @@ -460,7 +461,9 @@ const ManageHostsPage = ({ // set the team object in context const teamId = parseInt(queryParams?.team_id, 10) || 0; const selectedTeam = find(teams, ["id", teamId]); - setCurrentTeam(selectedTeam); + if (selectedTeam) { + setCurrentTeam(selectedTeam); + } setShowNoEnrollSecretBanner(true); // set selected label @@ -495,7 +498,9 @@ const ManageHostsPage = ({ // set the team object in context const teamId = parseInt(queryParams?.team_id, 10) || 0; const selectedTeam = find(teams, ["id", teamId]); - setCurrentTeam(selectedTeam); + if (selectedTeam) { + setCurrentTeam(selectedTeam); + } setShowNoEnrollSecretBanner(true); // set selected label @@ -620,12 +625,11 @@ const ManageHostsPage = ({ setSoftwareDetails(null); }; - // The handleChange method below is for the filter-by-team dropdown rather than the dropdown used in modals - const handleChangeSelectedTeamFilter = (selectedTeam: number) => { + const handleTeamSelect = (teamId: number) => { const { MANAGE_HOSTS } = PATHS; const teamIdParam = getValidatedTeamId( teams || [], - selectedTeam, + teamId, currentUser, isOnGlobalTeam as boolean ); @@ -647,6 +651,8 @@ const ManageHostsPage = ({ queryParams: newQueryParams, }); router.replace(nextLocation); + const selectedTeam = find(teams, ["id", teamId]); + setCurrentTeam(selectedTeam); }; const handleStatusDropdownChange = (statusName: string) => { @@ -1078,7 +1084,7 @@ const ManageHostsPage = ({ (policyId && policy?.team_id) || (currentTeam?.id as number) } onChange={(newSelectedValue: number) => - handleChangeSelectedTeamFilter(newSelectedValue) + handleTeamSelect(newSelectedValue) } /> ); @@ -1340,14 +1346,15 @@ const ManageHostsPage = ({
- {!isPremiumTier &&

Hosts

} + {isFreeTier &&

Hosts

} {isPremiumTier && teams && - teams.length > 1 && + (teams.length > 1 || isOnGlobalTeam) && renderTeamsFilterDropdown()} - {isPremiumTier && teams && teams.length === 1 && ( -

{teams[0].name}

- )} + {isPremiumTier && + !isOnGlobalTeam && + teams && + teams.length === 1 &&

{teams[0].name}

}
diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 02c4fa9ddc..e902d69fb6 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useContext, useEffect, useState } from "react"; import { useQuery } from "react-query"; import { useDispatch } from "react-redux"; -import { noop } from "lodash"; +import { find, noop } from "lodash"; // @ts-ignore import { renderFlash } from "redux/nodes/notifications/actions"; @@ -67,6 +67,8 @@ const ManagePolicyPage = (managePoliciesPageProps: { isOnGlobalTeam, isFreeTier, isPremiumTier, + currentTeam, + setCurrentTeam, } = useContext(AppContext); const { @@ -181,6 +183,8 @@ const ManagePolicyPage = (managePoliciesPageProps: { setShowInheritedPolicies(false); setSelectedPolicyIds([]); setPolicyTeamId(id); + const selectedTeam = find(teams, ["id", id]); + setCurrentTeam(selectedTeam); }; const toggleAddPolicyModal = () => setShowAddPolicyModal(!showAddPolicyModal); @@ -266,7 +270,7 @@ const ManagePolicyPage = (managePoliciesPageProps: { if (userTeams && !userTeams.find((t) => t.id === teamId)) { if (isOnGlobalTeam) { // For global users, default to zero (i.e. all teams). - if (teamId === undefined) { + if (teamId === undefined && !currentTeam) { handleTeamSelect(0); return; } @@ -275,7 +279,7 @@ const ManagePolicyPage = (managePoliciesPageProps: { // If there is no default team, set teamId to null so that getPolicies // API request will not be triggered. teamId = userTeams[0]?.id || null; - if (teamId) { + if (!currentTeam && teamId) { handleTeamSelect(teamId); return; } @@ -284,7 +288,11 @@ const ManagePolicyPage = (managePoliciesPageProps: { // Null case must be distinguished from 0 (which is used as the id for the "All teams" option) // so a falsiness check cannot be used here. Null case here allows us to skip API call // that would be triggered on a change to selectedTeamId. - teamId !== null && setSelectedTeamId(teamId); + if (currentTeam) { + setSelectedTeamId(currentTeam.id); + } else { + teamId !== null && setSelectedTeamId(teamId); + } }, [isOnGlobalTeam, location, userTeams]); // Watch for selected team changes and call getPolicies to make new policies API request. @@ -348,8 +356,7 @@ const ManagePolicyPage = (managePoliciesPageProps: { {isFreeTier &&

Policies

} {isPremiumTier && userTeams && - userTeams.length > 1 && - selectedTeamId >= 0 && ( + (userTeams.length > 1 || isOnGlobalTeam) && ( )} - {isPremiumTier && userTeams && userTeams.length === 1 && ( -

{userTeams[0].name}

- )} + {isPremiumTier && + !isOnGlobalTeam && + userTeams && + userTeams.length === 1 &&

{userTeams[0].name}

} diff --git a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx index a374f47ecc..5ae51cfe5a 100644 --- a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx @@ -5,6 +5,7 @@ import { useQuery } from "react-query"; import { useDispatch, useSelector } from "react-redux"; import { AppContext } from "context/app"; import { push } from "react-router-redux"; +import { find } from "lodash"; // @ts-ignore import deepDifference from "utilities/deep_difference"; @@ -123,9 +124,14 @@ const ManageSchedulePage = ({ const { MANAGE_PACKS, MANAGE_SCHEDULE, MANAGE_TEAM_SCHEDULE } = paths; const handleAdvanced = () => dispatch(push(MANAGE_PACKS)); - const { currentUser, isOnGlobalTeam, isPremiumTier, isFreeTier } = useContext( - AppContext - ); + const { + currentUser, + isOnGlobalTeam, + isPremiumTier, + isFreeTier, + currentTeam, + setCurrentTeam, + } = useContext(AppContext); const filterAndSortTeamOptions = (allTeams: ITeam[], userTeams: ITeam[]) => { const filteredSortedTeams = allTeams @@ -167,31 +173,39 @@ const ManageSchedulePage = ({ } ); - const teamId = team_id ? parseInt(team_id, 10) : 0; + let selectedTeamId: number; - const handleTeamSelect = (selectedTeamId: number) => { - if (selectedTeamId) { - dispatch(push(MANAGE_TEAM_SCHEDULE(selectedTeamId))); + if (currentTeam) { + selectedTeamId = currentTeam.id; + } else { + selectedTeamId = team_id ? parseInt(team_id, 10) : 0; + } + + const handleTeamSelect = (teamId: number) => { + if (teamId) { + dispatch(push(MANAGE_TEAM_SCHEDULE(teamId))); } else { dispatch(push(MANAGE_SCHEDULE)); } + const selectedTeam = find(teams, ["id", teamId]); + setCurrentTeam(selectedTeam); }; - if (!isOnGlobalTeam && !teamId && teams) { + if (!isOnGlobalTeam && !selectedTeamId && teams) { handleTeamSelect(teams[0].id); } // TODO: move team scheduled queries and global scheduled queries into services entities, remove redux useEffect(() => { dispatch( - teamId - ? teamScheduledQueryActions.loadAll(teamId) + selectedTeamId + ? teamScheduledQueryActions.loadAll(selectedTeamId) : globalScheduledQueryActions.loadAll() ); - }, [dispatch, teamId]); + }, [dispatch, selectedTeamId]); const allScheduledQueries = useSelector((state: IRootState) => { - if (teamId) { + if (selectedTeamId) { return state.entities.team_scheduled_queries; } return state.entities.global_scheduled_queries; @@ -212,7 +226,7 @@ const ManageSchedulePage = ({ const inheritedQueryOrQueries = allTeamsScheduledQueriesList.length === 1 ? "query" : "queries"; - const selectedTeam = !teamId ? "global" : teamId; + const selectedTeam = !selectedTeamId ? "global" : selectedTeamId; const selectedTeamData = teams?.find((team: ITeam) => selectedTeam === team.id) || undefined; @@ -267,8 +281,8 @@ const ManageSchedulePage = ({ const onRemoveScheduledQuerySubmit = useCallback(() => { const promises = selectedQueryIds.map((id: number) => { return dispatch( - teamId - ? teamScheduledQueryActions.destroy(teamId, id) + selectedTeamId + ? teamScheduledQueryActions.destroy(selectedTeamId, id) : globalScheduledQueryActions.destroy({ id }) ); }); @@ -283,8 +297,8 @@ const ManageSchedulePage = ({ ); toggleRemoveScheduledQueryModal(); dispatch( - teamId - ? teamScheduledQueryActions.loadAll(teamId) + selectedTeamId + ? teamScheduledQueryActions.loadAll(selectedTeamId) : globalScheduledQueryActions.loadAll() ); }) @@ -297,7 +311,12 @@ const ManageSchedulePage = ({ ); toggleRemoveScheduledQueryModal(); }); - }, [dispatch, teamId, selectedQueryIds, toggleRemoveScheduledQueryModal]); + }, [ + dispatch, + selectedTeamId, + selectedQueryIds, + toggleRemoveScheduledQueryModal, + ]); const onAddScheduledQuerySubmit = useCallback( ( @@ -307,7 +326,7 @@ const ManageSchedulePage = ({ if (editQuery) { const updatedAttributes = deepDifference(formData, editQuery); dispatch( - teamId + selectedTeamId ? teamScheduledQueryActions.update(editQuery, updatedAttributes) : globalScheduledQueryActions.update(editQuery, updatedAttributes) ) @@ -319,8 +338,8 @@ const ManageSchedulePage = ({ ) ); dispatch( - teamId - ? teamScheduledQueryActions.loadAll(teamId) + selectedTeamId + ? teamScheduledQueryActions.loadAll(selectedTeamId) : globalScheduledQueryActions.loadAll() ); }) @@ -334,7 +353,7 @@ const ManageSchedulePage = ({ }); } else { dispatch( - teamId + selectedTeamId ? teamScheduledQueryActions.create({ ...formData }) : globalScheduledQueryActions.create({ ...formData }) ) @@ -346,8 +365,8 @@ const ManageSchedulePage = ({ ) ); dispatch( - teamId - ? teamScheduledQueryActions.loadAll(teamId) + selectedTeamId + ? teamScheduledQueryActions.loadAll(selectedTeamId) : globalScheduledQueryActions.loadAll() ); }) @@ -362,7 +381,7 @@ const ManageSchedulePage = ({ } toggleScheduleEditorModal(); }, - [dispatch, teamId, toggleScheduleEditorModal] + [dispatch, selectedTeamId, toggleScheduleEditorModal] ); return ( @@ -373,18 +392,21 @@ const ManageSchedulePage = ({
{isFreeTier &&

Schedule

} - {isPremiumTier && teams && teams.length > 1 && ( - - handleTeamSelect(newSelectedValue) - } - /> - )} - {isPremiumTier && teams && teams.length === 1 && ( -

{teams[0].name}

- )} + {isPremiumTier && + teams && + (teams.length > 1 || isOnGlobalTeam) && ( + + handleTeamSelect(newSelectedValue) + } + /> + )} + {isPremiumTier && + !isOnGlobalTeam && + teams && + teams.length === 1 &&

{teams[0].name}

}
@@ -413,7 +435,7 @@ const ManageSchedulePage = ({
{!isLoadingTeams && (
- {!teamId ? ( + {!selectedTeamId ? (

Schedule queries to run at regular intervals across{" "} all of your hosts. @@ -440,7 +462,7 @@ const ManageSchedulePage = ({ )}

{/* must use ternary for NaN */} - {teamId && allTeamsScheduledQueriesList.length > 0 ? ( + {selectedTeamId && allTeamsScheduledQueriesList.length > 0 ? ( <>