1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

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
This commit is contained in:
olav 2022-03-23 12:55:00 +01:00 committed by GitHub
parent cc0b9f7291
commit 2ca88b019a
9 changed files with 93 additions and 120 deletions

View File

@ -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<React.SetStateAction<string>>;
setProject: React.Dispatch<React.SetStateAction<string>>;
setEnvironment: React.Dispatch<React.SetStateAction<string>>;
setEnvironment: React.Dispatch<React.SetStateAction<string | undefined>>;
handleSubmit: (e: any) => void;
handleCancel: () => void;
errors: { [key: string]: string };
@ -54,13 +54,15 @@ const ApiTokenForm: React.FC<IApiTokenFormProps> = ({
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 (

View File

@ -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';

View File

@ -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<string>();
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;

View File

@ -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<ISelectMenuProps> = ({
value={option.key}
title={option.title || ''}
data-test={`${SELECT_ITEM_ID}-${option.label}`}
disabled={option.disabled}
>
{option.label}
</MenuItem>

View File

@ -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';

View File

@ -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();
}
};

View File

@ -152,9 +152,11 @@ const EnvironmentListItem = ({
condition={updatePermission}
show={
<Tooltip title="Drag to reorder">
<IconButton>
<DragIndicator />
</IconButton>
<div>
<IconButton>
<DragIndicator titleAccess="Drag" />
</IconButton>
</div>
</Tooltip>
}
/>
@ -162,15 +164,16 @@ const EnvironmentListItem = ({
condition={updatePermission}
show={
<Tooltip title={`${tooltipText} environment`}>
<IconButton
aria-label="disable"
onClick={() => {
setSelectedEnv(env);
setToggleDialog(prev => !prev);
}}
>
<OfflineBolt />
</IconButton>
<div>
<IconButton
onClick={() => {
setSelectedEnv(env);
setToggleDialog(prev => !prev);
}}
>
<OfflineBolt titleAccess="Toggle" />
</IconButton>
</div>
</Tooltip>
}
/>
@ -178,15 +181,16 @@ const EnvironmentListItem = ({
condition={updatePermission}
show={
<Tooltip title="Update environment">
<IconButton
aria-label="update"
disabled={env.protected}
onClick={() => {
history.push(`/environments/${env.name}`);
}}
>
<Edit />
</IconButton>
<div>
<IconButton
disabled={env.protected}
onClick={() => {
history.push(`/environments/${env.name}`);
}}
>
<Edit titleAccess="Edit" />
</IconButton>
</div>
</Tooltip>
}
/>
@ -194,16 +198,17 @@ const EnvironmentListItem = ({
condition={hasAccess(DELETE_ENVIRONMENT)}
show={
<Tooltip title="Delete environment">
<IconButton
aria-label="delete"
disabled={env.protected}
onClick={() => {
setDeldialogue(true);
setSelectedEnv(env);
}}
>
<Delete />
</IconButton>
<div>
<IconButton
disabled={env.protected}
onClick={() => {
setDeldialogue(true);
setSelectedEnv(env);
}}
>
<Delete titleAccess="Delete" />
</IconButton>
</div>
</Tooltip>
}
/>

View File

@ -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<IProjectEnvironment[]>([]);
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();
};

View File

@ -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<IEnvironmentResponse>(PATH, fetcher);
const { data, error } = useSWR<IEnvironmentResponse>(
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<IEnvironmentResponse> => {
return fetch(PATH)
.then(handleErrorResponses('Environments'))
.then(res => res.json());
};