From e50ca4ece7a93e6961bc853174d2a6579540bbc4 Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Thu, 28 Oct 2021 16:23:23 -0500 Subject: [PATCH] Allow team admins to manage scheduled queries (#2738) --- changes/fix-2687-team-admin-scheduled-queries | 1 + frontend/context/app.tsx | 6 + .../ManageSchedulePage/ManageSchedulePage.tsx | 196 +++++++++++------- .../ScheduleListWrapper.tsx | 48 +++-- frontend/utilities/permissions/permissions.ts | 9 + 5 files changed, 168 insertions(+), 92 deletions(-) create mode 100644 changes/fix-2687-team-admin-scheduled-queries diff --git a/changes/fix-2687-team-admin-scheduled-queries b/changes/fix-2687-team-admin-scheduled-queries new file mode 100644 index 0000000000..cefec68a57 --- /dev/null +++ b/changes/fix-2687-team-admin-scheduled-queries @@ -0,0 +1 @@ +* Bug fix: Allow Team Admins to manage scheduled queries \ No newline at end of file diff --git a/frontend/context/app.tsx b/frontend/context/app.tsx index f856a15b9e..0ba845d146 100644 --- a/frontend/context/app.tsx +++ b/frontend/context/app.tsx @@ -51,6 +51,7 @@ const initialState = { isAnyTeamMaintainerOrTeamAdmin: undefined, isTeamObserver: undefined, isTeamMaintainer: undefined, + isTeamMaintainerOrTeamAdmin: undefined, isAnyTeamAdmin: undefined, isTeamAdmin: undefined, isOnlyObserver: undefined, @@ -93,6 +94,10 @@ const setPermissions = (user: IUser, config: IConfig, teamId = 0) => { isTeamObserver: permissions.isTeamObserver(user, teamId), isTeamMaintainer: permissions.isTeamMaintainer(user, teamId), isTeamAdmin: permissions.isTeamAdmin(user, teamId), + isTeamMaintainerOrTeamAdmin: permissions.isTeamMaintainerOrTeamAdmin( + user, + teamId + ), isOnlyObserver: permissions.isOnlyObserver(user), }; }; @@ -153,6 +158,7 @@ const AppProvider = ({ children }: Props) => { isTeamObserver: state.isTeamObserver, isTeamMaintainer: state.isTeamMaintainer, isTeamAdmin: state.isTeamAdmin, + isTeamMaintainerOrTeamAdmin: state.isTeamMaintainer, isAnyTeamAdmin: state.isAnyTeamAdmin, isOnlyObserver: state.isOnlyObserver, setCurrentUser: (currentUser: IUser) => { diff --git a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx index c21a940345..22a6e81f31 100644 --- a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx @@ -1,7 +1,8 @@ /* Conditionally renders global schedule and team schedules */ -import React, { useState, useCallback, useEffect } from "react"; +import React, { useState, useCallback, useEffect, useContext } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { AppContext } from "context/app"; import { push } from "react-router-redux"; // @ts-ignore @@ -44,7 +45,8 @@ const renderTable = ( allScheduledQueriesError: { name: string; reason: string }[], toggleScheduleEditorModal: () => void, teamId: number, - isTeamMaintainer: boolean + isTeamMaintainerOrTeamAdmin: boolean, + isOnGlobalTeam: boolean ): JSX.Element => { if (Object.keys(allScheduledQueriesError).length !== 0) { return ; @@ -57,16 +59,18 @@ const renderTable = ( allScheduledQueriesList={allScheduledQueriesList} toggleScheduleEditorModal={toggleScheduleEditorModal} teamId={teamId} - isTeamMaintainer={isTeamMaintainer} + isTeamMaintainerOrTeamAdmin={isTeamMaintainerOrTeamAdmin} + isOnGlobalTeam={isOnGlobalTeam} /> ); }; const renderAllTeamsTable = ( - teamId: number, - isTeamMaintainer: boolean, allTeamsScheduledQueriesList: IGlobalScheduledQuery[], - allTeamsScheduledQueriesError: { name: string; reason: string }[] + allTeamsScheduledQueriesError: { name: string; reason: string }[], + teamId: number, + isTeamMaintainerOrTeamAdmin: boolean, + isOnGlobalTeam: boolean ): JSX.Element => { if (Object.keys(allTeamsScheduledQueriesError).length > 0) { return ; @@ -78,7 +82,8 @@ const renderAllTeamsTable = ( inheritedQueries allScheduledQueriesList={allTeamsScheduledQueriesList} teamId={teamId} - isTeamMaintainer={isTeamMaintainer} + isTeamMaintainerOrTeamAdmin={isTeamMaintainerOrTeamAdmin} + isOnGlobalTeam={isOnGlobalTeam} /> ); @@ -138,11 +143,57 @@ interface ITeamOptions { const ManageSchedulePage = ({ params: { team_id }, }: ITeamSchedulesPageProps): JSX.Element => { - const teamId = parseInt(team_id, 10); + let teamId = parseInt(team_id, 10); const dispatch = useDispatch(); const { MANAGE_PACKS } = paths; const handleAdvanced = () => dispatch(push(MANAGE_PACKS)); + const { currentUser, isOnGlobalTeam } = useContext(AppContext); + + const isTeamMaintainerOrTeamAdmin = (() => { + return !!permissionUtils.isTeamMaintainerOrTeamAdmin(currentUser, teamId); + })(); + + const onChangeSelectedTeam = (selectedTeamId: number) => { + if (isNaN(selectedTeamId)) { + dispatch(push(`${paths.MANAGE_SCHEDULE}`)); + } else { + dispatch(push(`${paths.MANAGE_TEAM_SCHEDULE(selectedTeamId)}`)); + } + }; + + const loadFirstMaintainerOrAdminTeam = () => { + if (currentUser) { + const adminOrMaintainerTeam = currentUser.teams.find((team) => { + return team.role === "admin" || team.role === "maintainer" + ? team.id + : null; + }); + if (adminOrMaintainerTeam) { + teamId = adminOrMaintainerTeam.id; + onChangeSelectedTeam(teamId); + } + } + }; + + if (!isOnGlobalTeam && !isTeamMaintainerOrTeamAdmin && !teamId) { + loadFirstMaintainerOrAdminTeam(); + } + + if (!isOnGlobalTeam && !isTeamMaintainerOrTeamAdmin && teamId) { + if (currentUser) { + const canLoadTeam = currentUser.teams.find((team) => { + return ( + (team.role === "admin" || team.role === "maintainer") && + team.id === teamId + ); + }); + if (!canLoadTeam) { + loadFirstMaintainerOrAdminTeam(); + } + } + } + useEffect(() => { dispatch(queryActions.loadAll()); dispatch(teamActions.loadAll()); @@ -163,10 +214,6 @@ const ManageSchedulePage = ({ } ); - const isTeamMaintainer = useSelector((state: IRootState): boolean => { - return permissionUtils.isAnyTeamMaintainer(state.auth.user); - }); - const allQueries = useSelector((state: IRootState) => state.entities.queries); const allQueriesList = Object.values(allQueries.data); @@ -197,38 +244,6 @@ const ManageSchedulePage = ({ const selectedTeam = isNaN(teamId) ? "global" : teamId; - const generateTeamOptionsDropdownItems = (): ITeamOptions[] => { - const teamOptions: ITeamOptions[] = []; - - if (isTeamMaintainer) { - user.teams.forEach((team) => { - if (team.role === "maintainer") { - teamOptions.push({ - disabled: false, - label: team.name, - value: team.id, - }); - } - }); - } else { - teamOptions.push({ - disabled: false, - label: "All teams", - value: "global", - }); - - allTeamsList.forEach((team) => { - teamOptions.push({ - disabled: false, - label: team.name, - value: team.id, - }); - }); - } - - return teamOptions; - }; - const [showInheritedQueries, setShowInheritedQueries] = useState( false ); @@ -257,6 +272,55 @@ const ManageSchedulePage = ({ setShowRemoveScheduledQueryModal(!showRemoveScheduledQueryModal); }, [showRemoveScheduledQueryModal, setShowRemoveScheduledQueryModal]); + const generateTeamOptionsDropdownItems = (): ITeamOptions[] => { + const teamOptions: ITeamOptions[] = []; + + if (isTeamMaintainerOrTeamAdmin) { + user.teams.forEach((team) => { + if (team.role === "admin" || team.role === "maintainer") { + teamOptions.push({ + disabled: false, + label: team.name, + value: team.id, + }); + } + }); + } else if (isOnGlobalTeam) { + teamOptions.push({ + disabled: false, + label: "All teams", + value: "global", + }); + + allTeamsList.forEach((team) => { + teamOptions.push({ + disabled: false, + label: team.name, + value: team.id, + }); + }); + } + + return teamOptions; + }; + + const renderTitleOrDropdown = (): JSX.Element => { + const dropDownOptions = generateTeamOptionsDropdownItems(); + return dropDownOptions.length === 1 ? ( +

{dropDownOptions[0].label}

+ ) : ( + + onChangeSelectedTeam(newSelectedValue) + } + /> + ); + }; + const onRemoveScheduledQueryClick = ( selectedTableQueryIds: number[] ): void => { @@ -372,21 +436,15 @@ const ManageSchedulePage = ({ [dispatch, teamId, toggleScheduleEditorModal] ); - const onChangeSelectedTeam = (selectedTeamId: number) => { - if (isNaN(selectedTeamId)) { - dispatch(push(`${paths.MANAGE_SCHEDULE}`)); - } else { - dispatch(push(`${paths.MANAGE_TEAM_SCHEDULE(selectedTeamId)}`)); - } - }; - - if (selectedTeam === "global" && isTeamMaintainer) { + if (selectedTeam === "global" && isTeamMaintainerOrTeamAdmin) { const teamMaintainerTeams = generateTeamOptionsDropdownItems(); - dispatch( - push( - `${paths.MANAGE_TEAM_SCHEDULE(Number(teamMaintainerTeams[0].value))}` - ) - ); + if (teamMaintainerTeams.length) { + dispatch( + push( + `${paths.MANAGE_TEAM_SCHEDULE(Number(teamMaintainerTeams[0].value))}` + ) + ); + } } return ( @@ -409,15 +467,7 @@ const ManageSchedulePage = ({ ) : (
- - onChangeSelectedTeam(newSelectedValue) - } - /> + {renderTitleOrDropdown()}
{isNaN(teamId) ? (

@@ -438,7 +488,7 @@ const ManageSchedulePage = ({ {allScheduledQueriesList.length !== 0 && allScheduledQueriesError.length !== 0 && (

- {!isTeamMaintainer && ( + {!isTeamMaintainerOrTeamAdmin && ( - {!isTeamMaintainer && ( + {(isOnGlobalTeam || isTeamMaintainerOrTeamAdmin) && ( +
- )} -
+ {isOnGlobalTeam && ( + + )} +
+ )}
diff --git a/frontend/utilities/permissions/permissions.ts b/frontend/utilities/permissions/permissions.ts index fa4e0271c3..93d054ccb1 100644 --- a/frontend/utilities/permissions/permissions.ts +++ b/frontend/utilities/permissions/permissions.ts @@ -44,6 +44,14 @@ const isTeamAdmin = (user: IUser | null, teamId: number | null): boolean => { return userTeamRole === "admin"; }; +const isTeamMaintainerOrTeamAdmin = ( + user: IUser | null, + teamId: number | null +): boolean => { + const userTeamRole = user?.teams.find((team) => team.id === teamId)?.role; + return userTeamRole === "admin" || userTeamRole === "maintainer"; +}; + // This checks against all teams const isAnyTeamMaintainer = (user: IUser): boolean => { if (!isOnGlobalTeam(user)) { @@ -95,6 +103,7 @@ export default { isOnGlobalTeam, isTeamObserver, isTeamMaintainer, + isTeamMaintainerOrTeamAdmin, isAnyTeamMaintainer, isAnyTeamMaintainerOrTeamAdmin, isTeamAdmin,