From a693d82c117284608f0d89804cff1d33e02323b6 Mon Sep 17 00:00:00 2001 From: gillespi314 <73313222+gillespi314@users.noreply.github.com> Date: Wed, 16 Mar 2022 10:01:35 -0500 Subject: [PATCH] Improve TypeScript definitions for app context (#4603) * Refactor AppContext with improved TypeScript definitions for actions * Sort teams on setAvailableTeams action * Add case insensitive sort by name to team table on team settings page --- frontend/context/app.tsx | 125 ++++++++++++------ frontend/interfaces/team.ts | 6 +- .../TeamDetailsWrapper/TeamDetailsWrapper.tsx | 15 ++- .../TeamManagementPage/TeamTableConfig.tsx | 2 + 4 files changed, 99 insertions(+), 49 deletions(-) diff --git a/frontend/context/app.tsx b/frontend/context/app.tsx index 2f9cf6fd26..849676987c 100644 --- a/frontend/context/app.tsx +++ b/frontend/context/app.tsx @@ -1,10 +1,49 @@ import React, { createContext, useReducer, ReactNode } from "react"; -import { IUser } from "interfaces/user"; import { IConfig } from "interfaces/config"; -import { ITeamSummary } from "interfaces/team"; -import permissions from "utilities/permissions"; import { IEnrollSecret } from "interfaces/enroll_secret"; +import { ITeamSummary } from "interfaces/team"; +import { IUser } from "interfaces/user"; +import permissions from "utilities/permissions"; +import sort from "utilities/sort"; + +enum ACTIONS { + SET_AVAILABLE_TEAMS = "SET_AVAILABLE_TEAMS", + SET_CURRENT_USER = "SET_CURRENT_USER", + SET_CURRENT_TEAM = "SET_CURRENT_TEAM", + SET_CONFIG = "SET_CONFIG", + SET_ENROLL_SECRET = "SET_ENROLL_SECRET", +} + +interface ISetAvailableTeamsAction { + type: ACTIONS.SET_AVAILABLE_TEAMS; + availableTeams: ITeamSummary[]; +} + +interface ISetConfigAction { + type: ACTIONS.SET_CONFIG; + config: IConfig; +} + +interface ISetCurrentTeamAction { + type: ACTIONS.SET_CURRENT_TEAM; + currentTeam: ITeamSummary | undefined; +} +interface ISetCurrentUserAction { + type: ACTIONS.SET_CURRENT_USER; + currentUser: IUser; +} +interface ISetEnrollSecretAction { + type: ACTIONS.SET_ENROLL_SECRET; + enrollSecret: IEnrollSecret[]; +} + +type IAction = + | ISetAvailableTeamsAction + | ISetConfigAction + | ISetCurrentTeamAction + | ISetCurrentUserAction + | ISetEnrollSecretAction; type Props = { children: ReactNode; @@ -70,21 +109,17 @@ const initialState = { setEnrollSecret: () => null, }; -const actions = { - SET_AVAILABLE_TEAMS: "SET_AVAILABLE_TEAMS", - SET_CURRENT_USER: "SET_CURRENT_USER", - SET_CURRENT_TEAM: "SET_CURRENT_TEAM", - SET_CONFIG: "SET_CONFIG", - SET_ENROLL_SECRET: "SET_ENROLL_SECRET", -}; - const detectPreview = () => { return window.location.origin === "http://localhost:1337"; }; // helper function - this is run every // time currentUser, currentTeam, config, or teamId is changed -const setPermissions = (user: IUser, config: IConfig, teamId = 0) => { +const setPermissions = ( + user: IUser | null, + config: IConfig | null, + teamId = 0 +) => { if (!user || !config) { return {}; } @@ -113,44 +148,52 @@ const setPermissions = (user: IUser, config: IConfig, teamId = 0) => { }; }; -const reducer = (state: any, action: any) => { +const reducer = (state: InitialStateType, action: IAction) => { switch (action.type) { - case actions.SET_AVAILABLE_TEAMS: + case ACTIONS.SET_AVAILABLE_TEAMS: { + const { availableTeams } = action; + return { ...state, - availableTeams: action.availableTeams, - }; - case actions.SET_CURRENT_USER: - return { - ...state, - currentUser: action.currentUser, - ...setPermissions( - action.currentUser, - state.config, - state.currentTeam?.id + availableTeams: availableTeams.sort( + (a: ITeamSummary, b: ITeamSummary) => + sort.caseInsensitiveAsc(a.name, b.name) ), }; - case actions.SET_CURRENT_TEAM: + } + case ACTIONS.SET_CURRENT_USER: { + const { currentUser } = action; + return { ...state, - currentTeam: action.currentTeam, - ...setPermissions( - state.currentUser, - state.config, - action.currentTeam?.id - ), + currentUser, + ...setPermissions(currentUser, state.config, state.currentTeam?.id), }; - case actions.SET_CONFIG: + } + case ACTIONS.SET_CURRENT_TEAM: { + const { currentTeam } = action; return { ...state, - config: action.config, - ...setPermissions(state.currentUser, action.config), + currentTeam, + ...setPermissions(state.currentUser, state.config, currentTeam?.id), }; - case actions.SET_ENROLL_SECRET: + } + case ACTIONS.SET_CONFIG: { + const { config } = action; + return { ...state, - enrollSecret: action.enrollSecret, + config, + ...setPermissions(state.currentUser, config), }; + } + case ACTIONS.SET_ENROLL_SECRET: { + const { enrollSecret } = action; + return { + ...state, + enrollSecret, + }; + } default: return state; } @@ -184,19 +227,19 @@ const AppProvider = ({ children }: Props): JSX.Element => { isOnlyObserver: state.isOnlyObserver, isNoAccess: state.isNoAccess, setAvailableTeams: (availableTeams: ITeamSummary[]) => { - dispatch({ type: actions.SET_AVAILABLE_TEAMS, availableTeams }); + dispatch({ type: ACTIONS.SET_AVAILABLE_TEAMS, availableTeams }); }, setCurrentUser: (currentUser: IUser) => { - dispatch({ type: actions.SET_CURRENT_USER, currentUser }); + dispatch({ type: ACTIONS.SET_CURRENT_USER, currentUser }); }, setCurrentTeam: (currentTeam: ITeamSummary | undefined) => { - dispatch({ type: actions.SET_CURRENT_TEAM, currentTeam }); + dispatch({ type: ACTIONS.SET_CURRENT_TEAM, currentTeam }); }, setConfig: (config: IConfig) => { - dispatch({ type: actions.SET_CONFIG, config }); + dispatch({ type: ACTIONS.SET_CONFIG, config }); }, setEnrollSecret: (enrollSecret: IEnrollSecret[]) => { - dispatch({ type: actions.SET_ENROLL_SECRET, enrollSecret }); + dispatch({ type: ACTIONS.SET_ENROLL_SECRET, enrollSecret }); }, }; diff --git a/frontend/interfaces/team.ts b/frontend/interfaces/team.ts index 98f7fd64d1..83828df8ec 100644 --- a/frontend/interfaces/team.ts +++ b/frontend/interfaces/team.ts @@ -20,7 +20,7 @@ export interface ITeamSummary { id: number; name: string; description?: string; - host_count: number; + host_count?: number; } /** @@ -32,8 +32,8 @@ export interface ITeam extends ITeamSummary { count?: number; created_at?: string; agent_options?: any; - user_count: number; - host_count: number; + user_count?: number; + host_count?: number; secrets?: IEnrollSecret[]; role?: string; // role value is included when the team is in the context of a user } diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx index cd6d95b19a..2fd0eddf6c 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx @@ -382,8 +382,11 @@ const TeamDetailsWrapper = ({ } const hostCount = currentTeam.host_count; - const hostsTotalDisplay = - hostCount >= 2 ? `${hostCount} hosts` : `${hostCount} host`; + let hostsTotalDisplay: string | undefined; + if (hostCount !== undefined) { + hostsTotalDisplay = + hostCount === 1 ? `${hostCount} host` : `${hostCount} hosts`; + } const adminTeams = isGlobalAdmin ? availableTeams @@ -417,9 +420,11 @@ const TeamDetailsWrapper = ({ onClose={handleTeamMenuClose} /> )} - - {hostsTotalDisplay} - + {!!hostsTotalDisplay && ( + + {hostsTotalDisplay} + + )}
diff --git a/frontend/pages/admin/TeamManagementPage/TeamTableConfig.tsx b/frontend/pages/admin/TeamManagementPage/TeamTableConfig.tsx index 4da55982c3..dd0971fbeb 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamTableConfig.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamTableConfig.tsx @@ -40,6 +40,7 @@ interface IDataColumn { | ((props: IDropdownCellProps) => JSX.Element); disableHidden?: boolean; disableSortBy?: boolean; + sortType?: string; } interface ITeamTableData extends ITeam { @@ -56,6 +57,7 @@ const generateTableHeaders = ( title: "Name", Header: "Name", disableSortBy: true, + sortType: "caseInsensitive", accessor: "name", Cell: (cellProps: ICellProps) => (