From 90fe417ce002e919ab86a7e23e951d9d9bb83420 Mon Sep 17 00:00:00 2001 From: gillespi314 <73313222+gillespi314@users.noreply.github.com> Date: Sun, 6 Feb 2022 10:07:43 -0600 Subject: [PATCH] Sync current team to url params on manage hosts page (#4010) --- .../components/top_nav/SiteTopNav/navItems.ts | 2 +- frontend/fleet/helpers.ts | 6 +- .../hosts/ManageHostsPage/ManageHostsPage.tsx | 246 ++++++++++-------- 3 files changed, 148 insertions(+), 106 deletions(-) diff --git a/frontend/components/top_nav/SiteTopNav/navItems.ts b/frontend/components/top_nav/SiteTopNav/navItems.ts index d3b29db7b5..7cf2ce8f41 100644 --- a/frontend/components/top_nav/SiteTopNav/navItems.ts +++ b/frontend/components/top_nav/SiteTopNav/navItems.ts @@ -46,7 +46,7 @@ export default ( regex: new RegExp(`^${URL_PREFIX}/hosts/`), pathname: PATHS.MANAGE_HOSTS, }, - // withContext: true, + withContext: true, }, { icon: "software", diff --git a/frontend/fleet/helpers.ts b/frontend/fleet/helpers.ts index 4a0327c087..29831e65de 100644 --- a/frontend/fleet/helpers.ts +++ b/frontend/fleet/helpers.ts @@ -4,7 +4,7 @@ import moment from "moment"; import yaml from "js-yaml"; import { ILabel } from "interfaces/label"; -import { ITeam } from "interfaces/team"; +import { ITeam, ITeamSummary } from "interfaces/team"; import { IUser } from "interfaces/user"; import { IPackQueryFormData } from "interfaces/scheduled_query"; @@ -688,12 +688,12 @@ export const getSortedTeamOptions = memoize((teams: ITeam[]) => ); export const getValidatedTeamId = ( - teams: ITeam[], + teams: ITeam[] | ITeamSummary[], teamId: number, currentUser: IUser | null, isOnGlobalTeam: boolean ): number => { - let currentUserTeams: ITeam[] = []; + let currentUserTeams: ITeamSummary[] = []; if (isOnGlobalTeam) { currentUserTeams = teams; } else if (currentUser && currentUser.teams) { diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 38ae0d6b09..f59fd17447 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext, useEffect } from "react"; +import React, { useState, useContext, useEffect, useCallback } from "react"; import { useDispatch } from "react-redux"; import { useQuery } from "react-query"; import { InjectedRouter, Params } from "react-router/lib/Router"; @@ -9,6 +9,7 @@ import ReactTooltip from "react-tooltip"; import enrollSecretsAPI from "services/entities/enroll_secret"; import labelsAPI from "services/entities/labels"; import teamsAPI from "services/entities/teams"; +import usersAPI, { IGetMeResponse } from "services/entities/users"; import globalPoliciesAPI from "services/entities/global_policies"; import teamPoliciesAPI from "services/entities/team_policies"; import hostsAPI, { @@ -132,8 +133,10 @@ const ManageHostsPage = ({ const dispatch = useDispatch(); const queryParams = location.query; const { - currentUser, + availableTeams, config, + currentTeam, + currentUser, isGlobalAdmin, isGlobalMaintainer, isTeamMaintainer, @@ -142,9 +145,32 @@ const ManageHostsPage = ({ isOnlyObserver, isPremiumTier, isFreeTier, - currentTeam, + setAvailableTeams, setCurrentTeam, + setCurrentUser, } = useContext(AppContext); + + useQuery(["me"], () => usersAPI.me(), { + onSuccess: ({ user, available_teams }: IGetMeResponse) => { + setCurrentUser(user); + setAvailableTeams(available_teams); + if (queryParams.team_id) { + const teamIdParam = parseInt(queryParams.team_id, 10); + if ( + isNaN(teamIdParam) || + (teamIdParam && + available_teams && + !available_teams.find((t) => t.id === teamIdParam)) + ) { + router.replace({ + pathname: location.pathname, + query: omit(queryParams, "team_id"), + }); + } + } + }, + }); + const { selectedOsqueryTable, setSelectedOsqueryTable } = useContext( QueryContext ); @@ -398,7 +424,7 @@ const ManageHostsPage = ({ options = { ...options, teamId: getValidatedTeamId( - teams || [], + availableTeams || [], options.teamId as number, currentUser, isOnGlobalTeam as boolean @@ -429,7 +455,7 @@ const ManageHostsPage = ({ options = { ...options, teamId: getValidatedTeamId( - teams || [], + availableTeams || [], options.teamId as number, currentUser, isOnGlobalTeam as boolean @@ -459,9 +485,21 @@ const ManageHostsPage = ({ retrieveHostCount(options); }; + let teamSync = false; + if (currentUser && availableTeams) { + const teamIdParam = queryParams.team_id + ? parseInt(queryParams.team_id, 10) // we don't want to parse undefined so we can differntiate non-numeric strings as NaN + : undefined; + if (currentTeam?.id && !teamIdParam) { + teamSync = true; + } else if (teamIdParam === currentTeam?.id) { + teamSync = true; + } + } + useEffect(() => { const teamId = parseInt(queryParams?.team_id, 10) || 0; - const selectedTeam = find(teams, ["id", teamId]); + const selectedTeam = find(availableTeams, ["id", teamId]); if (selectedTeam) { setCurrentTeam(selectedTeam); } @@ -490,11 +528,12 @@ const ManageHostsPage = ({ if (isEqual(options, currentQueryOptions)) { return; } - - retrieveHosts(options); - retrieveHostCount(options); - setCurrentQueryOptions(options); - }, [location, labels]); + if (teamSync) { + retrieveHosts(options); + retrieveHostCount(options); + setCurrentQueryOptions(options); + } + }, [availableTeams, currentTeam, location, labels]); const handleLabelChange = ({ slug }: ILabel): boolean => { if (!slug) { @@ -575,24 +614,15 @@ const ManageHostsPage = ({ }; const handleClearSoftwareFilter = () => { - // TODO: In current UX, clearing the software filter resets all URL params. - // The code below can be reimplemented if other URL params are to be preserved. - // router.replace( - // getNextLocationPath({ - // pathPrefix: PATHS.MANAGE_HOSTS, - // routeTemplate, - // routeParams, - // queryParams: omit(queryParams, ["software_id"]), - // }) - // ); router.replace(PATHS.MANAGE_HOSTS); + setCurrentTeam(undefined); setSoftwareDetails(null); }; const handleTeamSelect = (teamId: number) => { const { MANAGE_HOSTS } = PATHS; const teamIdParam = getValidatedTeamId( - teams || [], + availableTeams || [], teamId, currentUser, isOnGlobalTeam as boolean @@ -615,7 +645,7 @@ const ManageHostsPage = ({ queryParams: newQueryParams, }); router.replace(nextLocation); - const selectedTeam = find(teams, ["id", teamId]); + const selectedTeam = find(availableTeams, ["id", teamId]); setCurrentTeam(selectedTeam); }; @@ -663,82 +693,87 @@ const ManageHostsPage = ({ }; // NOTE: this is called once on initial render and every time the query changes - const onTableQueryChange = async (newTableQuery: ITableQueryProps) => { - if (isEqual(newTableQuery, tableQueryData)) { - return; - } + const onTableQueryChange = useCallback( + async (newTableQuery: ITableQueryProps) => { + if (isEqual(newTableQuery, tableQueryData)) { + return; + } - setTableQueryData({ ...newTableQuery }); + setTableQueryData({ ...newTableQuery }); - const { - searchQuery: searchText, - sortHeader, - sortDirection, - } = newTableQuery; + const { + searchQuery: searchText, + sortHeader, + sortDirection, + } = newTableQuery; - const teamId = getValidatedTeamId( - teams || [], - currentTeam?.id as number, + let sort = sortBy; + if (sortHeader) { + sort = [ + { + key: sortHeader, + direction: sortDirection || DEFAULT_SORT_DIRECTION, + }, + ]; + } else if (!sortBy.length) { + sort = [ + { key: DEFAULT_SORT_HEADER, direction: DEFAULT_SORT_DIRECTION }, + ]; + } + + if (!isEqual(sort, sortBy)) { + setSortBy([...sort]); + } + + if (!isEqual(searchText, searchQuery)) { + setSearchQuery(searchText); + } + + // Rebuild queryParams to dispatch new browser location to react-router + const newQueryParams: { [key: string]: any } = {}; + if (!isEmpty(searchText)) { + newQueryParams.query = searchText; + } + + newQueryParams.order_key = sort[0].key || DEFAULT_SORT_HEADER; + newQueryParams.order_direction = + sort[0].direction || DEFAULT_SORT_DIRECTION; + + if (currentTeam?.id) { + newQueryParams.team_id = currentTeam.id; + } + + if (policyId) { + newQueryParams.policy_id = policyId; + } + + if (policyResponse) { + newQueryParams.policy_response = policyResponse; + } + + if (softwareId && !policyId) { + newQueryParams.software_id = softwareId; + } + + router.replace( + getNextLocationPath({ + pathPrefix: PATHS.MANAGE_HOSTS, + routeTemplate, + routeParams, + queryParams: newQueryParams, + }) + ); + }, + [ + availableTeams, + currentTeam, currentUser, - isOnGlobalTeam as boolean - ); - - let sort = sortBy; - if (sortHeader) { - sort = [ - { key: sortHeader, direction: sortDirection || DEFAULT_SORT_DIRECTION }, - ]; - } else if (!sortBy.length) { - sort = [{ key: DEFAULT_SORT_HEADER, direction: DEFAULT_SORT_DIRECTION }]; - } - - if (!isEqual(sort, sortBy)) { - setSortBy([...sort]); - } - - if (!isEqual(searchText, searchQuery)) { - setSearchQuery(searchText); - } - - // Rebuild queryParams to dispatch new browser location to react-router - const newQueryParams: { [key: string]: any } = {}; - if (!isEmpty(searchText)) { - newQueryParams.query = searchText; - } - - newQueryParams.order_key = sort[0].key || DEFAULT_SORT_HEADER; - newQueryParams.order_direction = - sort[0].direction || DEFAULT_SORT_DIRECTION; - - if (teamId) { - newQueryParams.team_id = teamId; - } - - if (queryParams.team_id) { - newQueryParams.team_id = queryParams.team_id; - } - - if (policyId) { - newQueryParams.policy_id = policyId; - } - - if (policyResponse) { - newQueryParams.policy_response = policyResponse; - } - - if (softwareId && !policyId) { - newQueryParams.software_id = softwareId; - } - - router.replace( - getNextLocationPath({ - pathPrefix: PATHS.MANAGE_HOSTS, - routeTemplate, - routeParams, - queryParams: newQueryParams, - }) - ); - }; + policyId, + queryParams, + softwareId, + sortBy, + ] + ); const onSaveSecret = async (enrollSecretString: string) => { const { MANAGE_HOSTS } = PATHS; @@ -1045,7 +1080,7 @@ const ManageHostsPage = ({ const renderTeamsFilterDropdown = () => ( {isFreeTier &&

Hosts

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

{teams[0].name}

} + availableTeams && + availableTeams.length === 1 &&

{availableTeams[0].name}

} @@ -1430,7 +1465,8 @@ const ManageHostsPage = ({ !currentUser || !hosts || selectedFilters.length === 0 || - selectedLabel === undefined + selectedLabel === undefined || + !teamSync ) { return ; } @@ -1442,9 +1478,11 @@ const ManageHostsPage = ({ // There are no hosts for this instance yet if ( getStatusSelected() === ALL_HOSTS_LABEL && + !isHostCountLoading && filteredHostCount === 0 && searchQuery === "" && - !isHostsLoading + !isHostsLoading && + teamSync ) { const { software_id, policy_id } = queryParams || {}; const includesSoftwareOrPolicyFilter = !!(software_id || policy_id); @@ -1549,6 +1587,10 @@ const ManageHostsPage = ({ ); }; + if (!teamSync) { + return ; + } + return (
{renderForm()}