From 2ca88b019aa16a2737fb34c354e5a837a036f824 Mon Sep 17 00:00:00 2001 From: olav Date: Wed, 23 Mar 2022 12:55:00 +0100 Subject: [PATCH] refactor: restrict API tokens to enabled environments (#809) * refactor: add missing Tooltip wrapper elements * refactor: rewrite useEnvironments * refactor: disable environments in select box * refactor: make sure initial environment is enabled --- .../api-token/ApiTokenForm/ApiTokenForm.tsx | 16 +++-- .../CreateApiToken/CreateApiToken.tsx | 2 +- .../admin/api-token/hooks/useApiTokenForm.ts | 42 +++--------- .../common/GeneralSelect/GeneralSelect.tsx | 2 + .../CreateEnvironment/CreateEnvironment.tsx | 2 +- .../EnvironmentList/EnvironmentList.tsx | 17 ++--- .../EnvironmentListItem.tsx | 67 ++++++++++--------- .../ProjectEnvironment/ProjectEnvironment.tsx | 12 ++-- .../useEnvironments/useEnvironments.ts | 53 +++++++-------- 9 files changed, 93 insertions(+), 120 deletions(-) diff --git a/frontend/src/component/admin/api-token/ApiTokenForm/ApiTokenForm.tsx b/frontend/src/component/admin/api-token/ApiTokenForm/ApiTokenForm.tsx index 2f2e3edc4e..2f8790423f 100644 --- a/frontend/src/component/admin/api-token/ApiTokenForm/ApiTokenForm.tsx +++ b/frontend/src/component/admin/api-token/ApiTokenForm/ApiTokenForm.tsx @@ -1,7 +1,7 @@ import { Button } from '@material-ui/core'; import { KeyboardArrowDownOutlined } from '@material-ui/icons'; import React from 'react'; -import useEnvironments from '../../../../hooks/api/getters/useEnvironments/useEnvironments'; +import { useEnvironments } from '../../../../hooks/api/getters/useEnvironments/useEnvironments'; import useProjects from '../../../../hooks/api/getters/useProjects/useProjects'; import GeneralSelect from '../../../common/GeneralSelect/GeneralSelect'; import Input from '../../../common/Input/Input'; @@ -10,11 +10,11 @@ interface IApiTokenFormProps { username: string; type: string; project: string; - environment: string; + environment?: string; setTokenType: (value: string) => void; setUsername: React.Dispatch>; setProject: React.Dispatch>; - setEnvironment: React.Dispatch>; + setEnvironment: React.Dispatch>; handleSubmit: (e: any) => void; handleCancel: () => void; errors: { [key: string]: string }; @@ -54,13 +54,15 @@ const ApiTokenForm: React.FC = ({ title: i.name, }) ); + const selectableEnvs = type === TYPE_ADMIN ? [{ key: '*', label: 'ALL' }] - : environments.map(i => ({ - key: i.name, - label: i.name, - title: i.name, + : environments.map(environment => ({ + key: environment.name, + label: environment.name, + title: environment.name, + disabled: !environment.enabled, })); return ( diff --git a/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx index 7fdaf53e02..18d27d5903 100644 --- a/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx +++ b/frontend/src/component/admin/api-token/CreateApiToken/CreateApiToken.tsx @@ -5,7 +5,7 @@ import { CreateButton } from 'component/common/CreateButton/CreateButton'; import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; -import useApiTokenForm from '../hooks/useApiTokenForm'; +import { useApiTokenForm } from '../hooks/useApiTokenForm'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ConfirmToken } from '../ConfirmToken/ConfirmToken'; import { useState } from 'react'; diff --git a/frontend/src/component/admin/api-token/hooks/useApiTokenForm.ts b/frontend/src/component/admin/api-token/hooks/useApiTokenForm.ts index 11045cab7a..aef68b6704 100644 --- a/frontend/src/component/admin/api-token/hooks/useApiTokenForm.ts +++ b/frontend/src/component/admin/api-token/hooks/useApiTokenForm.ts @@ -1,37 +1,19 @@ import { useEffect, useState } from 'react'; +import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; -const useApiToken = ( - initialUserName = '', - initialtype = 'CLIENT', - initialProject = '*', - initialEnvironment = 'default' -) => { - const [username, setUsername] = useState(initialUserName); - const [type, setType] = useState(initialtype); - const [project, setProject] = useState(initialtype); - const [environment, setEnvironment] = useState(initialEnvironment); +export const useApiTokenForm = () => { + const { environments } = useEnvironments(); + const initialEnvironment = environments?.find(e => e.enabled)?.name; + + const [username, setUsername] = useState(''); + const [type, setType] = useState('CLIENT'); + const [project, setProject] = useState('*'); + const [environment, setEnvironment] = useState(); const [errors, setErrors] = useState({}); useEffect(() => { - setUsername(initialUserName); - }, [initialUserName]); - - useEffect(() => { - setType(initialtype); - if (type === 'ADMIN') { - setProject('*'); - setEnvironment('*'); - } - //eslint-disable-next-line react-hooks/exhaustive-deps - }, [initialtype]); - - useEffect(() => { - setProject(initialProject); - }, [initialProject]); - - useEffect(() => { - setEnvironment(initialEnvironment); - }, [initialEnvironment]); + setEnvironment(type === 'ADMIN' ? '*' : initialEnvironment); + }, [type, initialEnvironment]); const setTokenType = (value: string) => { if (value === 'ADMIN') { @@ -82,5 +64,3 @@ const useApiToken = ( errors, }; }; - -export default useApiToken; diff --git a/frontend/src/component/common/GeneralSelect/GeneralSelect.tsx b/frontend/src/component/common/GeneralSelect/GeneralSelect.tsx index 90017e3881..7055cb2487 100644 --- a/frontend/src/component/common/GeneralSelect/GeneralSelect.tsx +++ b/frontend/src/component/common/GeneralSelect/GeneralSelect.tsx @@ -7,6 +7,7 @@ export interface ISelectOption { key: string; title?: string; label?: string; + disabled?: boolean; } export interface ISelectMenuProps { @@ -52,6 +53,7 @@ const GeneralSelect: React.FC = ({ value={option.key} title={option.title || ''} data-test={`${SELECT_ITEM_ID}-${option.label}`} + disabled={option.disabled} > {option.label} diff --git a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx index d7c46f6a50..d2d77cd764 100644 --- a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx +++ b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx @@ -8,7 +8,7 @@ import { CreateButton } from 'component/common/CreateButton/CreateButton'; import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; -import useEnvironments from 'hooks/api/getters/useEnvironments/useEnvironments'; +import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; import ConditionallyRender from 'component/common/ConditionallyRender'; import PageContent from 'component/common/PageContent/PageContent'; diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx index 533b34f496..38934205c7 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentList.tsx @@ -3,9 +3,7 @@ import ResponsiveButton from '../../common/ResponsiveButton/ResponsiveButton'; import { Add } from '@material-ui/icons'; import PageContent from '../../common/PageContent'; import { List } from '@material-ui/core'; -import useEnvironments, { - ENVIRONMENT_CACHE_KEY, -} from '../../../hooks/api/getters/useEnvironments/useEnvironments'; +import { useEnvironments } from '../../../hooks/api/getters/useEnvironments/useEnvironments'; import { IEnvironment, ISortOrderPayload, @@ -16,7 +14,6 @@ import EnvironmentDeleteConfirm from './EnvironmentDeleteConfirm/EnvironmentDele import useToast from '../../../hooks/useToast'; import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem'; -import { mutate } from 'swr'; import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm'; import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; @@ -32,7 +29,7 @@ const EnvironmentList = () => { enabled: true, protected: false, }; - const { environments, refetch } = useEnvironments(); + const { environments, refetchEnvironments } = useEnvironments(); const { uiConfig } = useUiConfig(); const { refetch: refetchProjectRolePermissions } = useProjectRolePermissions(); @@ -58,8 +55,7 @@ const EnvironmentList = () => { const item = newEnvList.splice(dragIndex, 1)[0]; newEnvList.splice(hoverIndex, 0, item); - - mutate(ENVIRONMENT_CACHE_KEY, { environments: newEnvList }, false); + refetchEnvironments({ environments: newEnvList }, false); return newEnvList; }; @@ -75,7 +71,6 @@ const EnvironmentList = () => { try { await sortOrderAPICall(sortOrder); - refetch(); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); } @@ -104,7 +99,7 @@ const EnvironmentList = () => { setDeldialogue(false); setSelectedEnv(defaultEnv); setConfirmName(''); - refetch(); + refetchEnvironments(); } }; @@ -128,7 +123,7 @@ const EnvironmentList = () => { } catch (error: unknown) { setToastApiError(formatUnknownError(error)); } finally { - refetch(); + refetchEnvironments(); } }; @@ -144,7 +139,7 @@ const EnvironmentList = () => { } catch (error: unknown) { setToastApiError(formatUnknownError(error)); } finally { - refetch(); + refetchEnvironments(); } }; diff --git a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx index 5bfddb0f0f..e29b86710f 100644 --- a/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx +++ b/frontend/src/component/environments/EnvironmentList/EnvironmentListItem/EnvironmentListItem.tsx @@ -152,9 +152,11 @@ const EnvironmentListItem = ({ condition={updatePermission} show={ - - - +
+ + + +
} /> @@ -162,15 +164,16 @@ const EnvironmentListItem = ({ condition={updatePermission} show={ - { - setSelectedEnv(env); - setToggleDialog(prev => !prev); - }} - > - - +
+ { + setSelectedEnv(env); + setToggleDialog(prev => !prev); + }} + > + + +
} /> @@ -178,15 +181,16 @@ const EnvironmentListItem = ({ condition={updatePermission} show={ - { - history.push(`/environments/${env.name}`); - }} - > - - +
+ { + history.push(`/environments/${env.name}`); + }} + > + + +
} /> @@ -194,16 +198,17 @@ const EnvironmentListItem = ({ condition={hasAccess(DELETE_ENVIRONMENT)} show={ - { - setDeldialogue(true); - setSelectedEnv(env); - }} - > - - +
+ { + setDeldialogue(true); + setSelectedEnv(env); + }} + > + + +
} /> diff --git a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx index 08e6bd9f71..4a6d835d43 100644 --- a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx +++ b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx @@ -10,7 +10,7 @@ import { UPDATE_PROJECT } from '../../providers/AccessProvider/permissions'; import ApiError from '../../common/ApiError/ApiError'; import useToast from '../../../hooks/useToast'; import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; -import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments'; +import { useEnvironments } from '../../../hooks/api/getters/useEnvironments/useEnvironments'; import useProject from '../../../hooks/api/getters/useProject/useProject'; import { FormControlLabel, FormGroup } from '@material-ui/core'; import useProjectApi from '../../../hooks/api/actions/useProjectApi/useProjectApi'; @@ -32,12 +32,8 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => { const [envs, setEnvs] = useState([]); const { setToastData, setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); - const { - environments, - loading, - error, - refetch: refetchEnvs, - } = useEnvironments(); + const { environments, loading, error, refetchEnvironments } = + useEnvironments(); const { project, refetch: refetchProject } = useProject(projectId); const { removeEnvironmentFromProject, addEnvironmentToProject } = useProjectApi(); @@ -59,7 +55,7 @@ const ProjectEnvironmentList = ({ projectId }: ProjectEnvironmentListProps) => { }, [environments, project?.environments]); const refetch = () => { - refetchEnvs(); + refetchEnvironments(); refetchProject(); }; diff --git a/frontend/src/hooks/api/getters/useEnvironments/useEnvironments.ts b/frontend/src/hooks/api/getters/useEnvironments/useEnvironments.ts index 454961d198..ca6c296bb3 100644 --- a/frontend/src/hooks/api/getters/useEnvironments/useEnvironments.ts +++ b/frontend/src/hooks/api/getters/useEnvironments/useEnvironments.ts @@ -1,42 +1,35 @@ -import useSWR, { mutate, SWRConfiguration } from 'swr'; -import { useState, useEffect } from 'react'; -import { IEnvironmentResponse } from '../../../../interfaces/environments'; -import { formatApiPath } from '../../../../utils/format-path'; +import useSWR, { mutate } from 'swr'; +import { useCallback, useMemo } from 'react'; +import { IEnvironmentResponse } from 'interfaces/environments'; +import { formatApiPath } from 'utils/format-path'; import handleErrorResponses from '../httpErrorResponseHandler'; -export const ENVIRONMENT_CACHE_KEY = `api/admin/environments`; +const PATH = formatApiPath(`api/admin/environments`); -const useEnvironments = (options: SWRConfiguration = {}) => { - const fetcher = () => { - const path = formatApiPath(`api/admin/environments`); - return fetch(path, { - method: 'GET', - }) - .then(handleErrorResponses('Environments')) - .then(res => res.json()); - }; +export const useEnvironments = () => { + const { data, error } = useSWR(PATH, fetcher); - const { data, error } = useSWR( - ENVIRONMENT_CACHE_KEY, - fetcher, - options + const refetchEnvironments = useCallback( + (data?: IEnvironmentResponse, revalidate?: boolean) => { + mutate(PATH, data, revalidate).catch(console.warn); + }, + [] ); - const [loading, setLoading] = useState(!error && !data); - const refetch = () => { - mutate(ENVIRONMENT_CACHE_KEY); - }; - - useEffect(() => { - setLoading(!error && !data); - }, [data, error]); + const environments = useMemo(() => { + return data?.environments || []; + }, [data]); return { - environments: data?.environments || [], + environments, + refetchEnvironments, + loading: !error && !data, error, - loading, - refetch, }; }; -export default useEnvironments; +const fetcher = (): Promise => { + return fetch(PATH) + .then(handleErrorResponses('Environments')) + .then(res => res.json()); +};