From a2ce8455dea7c4b69fad8af63d86bf41aa9fb95a Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Feb 2023 15:10:26 +0200 Subject: [PATCH] Create Project API Token sidebar (#3057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: andreas-unleash Create Project API Token sidebar ## About the changes Closes # [1-633](https://linear.app/unleash/issue/1-633/add-api-key-creation-screen-into-project) ### Important files ## Discussion points --------- Signed-off-by: andreas-unleash --- .../apiToken/ApiTokenForm/ApiTokenForm.tsx | 28 +++++++++++------- .../apiToken/ApiTokenForm/useApiTokenForm.ts | 7 +++-- .../apiToken/ApiTokenTable/ApiTokenTable.tsx | 17 +++++++++-- .../ApiTokenTable/ProjectApiTokenCreate.tsx | 29 +++++++++++++++++++ .../CreateApiToken/CreateApiToken.tsx | 18 ++++++++++-- .../CreateApiTokenButton.tsx | 7 ++++- 6 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 frontend/src/component/admin/apiToken/ApiTokenTable/ProjectApiTokenCreate.tsx diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx b/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx index da396729b5..ffad8ce36b 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/ApiTokenForm.tsx @@ -36,6 +36,7 @@ interface IApiTokenFormProps { errors: { [key: string]: string }; mode: 'Create' | 'Edit'; clearErrors: (error?: ApiTokenFormErrorType) => void; + disableProjectSelection?: boolean; } const StyledContainer = styled('div')(() => ({ @@ -84,6 +85,7 @@ const ApiTokenForm: React.FC = ({ username, type, projects, + disableProjectSelection = false, environment, setUsername, setTokenType, @@ -205,17 +207,21 @@ const ApiTokenForm: React.FC = ({ ))} - - Which project do you want to give access to? - - clearErrors('projects')} - /> + {!Boolean(disableProjectSelection) && ( + <> + + Which project do you want to give access to? + + clearErrors('projects')} + /> + + )} Which environment should the token have access to? diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/useApiTokenForm.ts b/frontend/src/component/admin/apiToken/ApiTokenForm/useApiTokenForm.ts index 7c28a3416c..04e5cd84d9 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/useApiTokenForm.ts +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/useApiTokenForm.ts @@ -3,14 +3,15 @@ import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironmen import { IApiTokenCreate } from 'hooks/api/actions/useApiTokensApi/useApiTokensApi'; export type ApiTokenFormErrorType = 'username' | 'projects'; - -export const useApiTokenForm = () => { +export const useApiTokenForm = (project?: string) => { const { environments } = useEnvironments(); const initialEnvironment = environments?.find(e => e.enabled)?.name; const [username, setUsername] = useState(''); const [type, setType] = useState('CLIENT'); - const [projects, setProjects] = useState(['*']); + const [projects, setProjects] = useState([ + project ? project : '*', + ]); const [memorizedProjects, setMemorizedProjects] = useState(projects); const [environment, setEnvironment] = useState(); diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx index 4f833dd96a..18759da395 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx @@ -1,12 +1,12 @@ import { useApiTokens } from 'hooks/api/getters/useApiTokens/useApiTokens'; -import { useTable, useGlobalFilter, useSortBy } from 'react-table'; +import { useGlobalFilter, useSortBy, useTable } from 'react-table'; import { PageContent } from 'component/common/PageContent/PageContent'; import { SortableTableHeader, TableCell, TablePlaceholder, } from 'component/common/Table'; -import { Table, TableBody, Box, TableRow, useMediaQuery } from '@mui/material'; +import { Box, Table, TableBody, TableRow, useMediaQuery } from '@mui/material'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { ApiTokenDocs } from 'component/admin/apiToken/ApiTokenDocs/ApiTokenDocs'; @@ -26,6 +26,8 @@ import { HighlightCell } from 'component/common/Table/cells/HighlightCell/Highli import { Search } from 'component/common/Search/Search'; import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns'; import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; +import { Route, Routes } from 'react-router-dom'; +import { ProjectApiTokenCreate } from './ProjectApiTokenCreate'; const hiddenColumnsSmall = ['Icon', 'createdAt']; const hiddenColumnsCompact = ['Icon', 'project', 'seenAt']; @@ -176,6 +178,17 @@ export const ApiTokenTable = ({ /> } /> + + } + /> + + } + /> ); }; diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ProjectApiTokenCreate.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ProjectApiTokenCreate.tsx new file mode 100644 index 0000000000..26eaec1398 --- /dev/null +++ b/frontend/src/component/admin/apiToken/ApiTokenTable/ProjectApiTokenCreate.tsx @@ -0,0 +1,29 @@ +import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import useProjectAccess from 'hooks/api/getters/useProjectAccess/useProjectAccess'; +import { useAccess } from 'hooks/api/getters/useAccess/useAccess'; +import { GO_BACK } from 'constants/navigate'; +import { CreateApiToken } from '../CreateApiToken/CreateApiToken'; +import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; +import { useNavigate } from 'react-router-dom'; + +export const ProjectApiTokenCreate = () => { + const projectId = useRequiredPathParam('projectId'); + const navigate = useNavigate(); + + const { access } = useProjectAccess(projectId); + const { users, serviceAccounts, groups } = useAccess(); + + if (!access || !users || !serviceAccounts || !groups) { + return null; + } + + return ( + navigate(GO_BACK)} + label={`Create API token`} + > + + + ); +}; diff --git a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx index 471f28b26e..391843903b 100644 --- a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx +++ b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx @@ -13,10 +13,18 @@ import { scrollToTop } from 'component/common/util'; import { formatUnknownError } from 'utils/formatUnknownError'; import { usePageTitle } from 'hooks/usePageTitle'; import { GO_BACK } from 'constants/navigate'; +import { useApiTokens } from '../../../../hooks/api/getters/useApiTokens/useApiTokens'; const pageTitle = 'Create API token'; -export const CreateApiToken = () => { +interface ICreateApiTokenProps { + modal?: boolean; + project?: string; +} +export const CreateApiToken = ({ + modal = false, + project, +}: ICreateApiTokenProps) => { const { setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); const navigate = useNavigate(); @@ -36,9 +44,10 @@ export const CreateApiToken = () => { isValid, errors, clearErrors, - } = useApiTokenForm(); + } = useApiTokenForm(project); const { createToken, loading } = useApiTokensApi(); + const { refetch } = useApiTokens(); usePageTitle(pageTitle); @@ -63,7 +72,8 @@ export const CreateApiToken = () => { const closeConfirm = () => { setShowConfirm(false); - navigate('/admin/api'); + refetch(); + navigate(GO_BACK); }; const formatApiCode = () => { @@ -83,6 +93,7 @@ export const CreateApiToken = () => { { { const navigate = useNavigate(); + const project = useOptionalPathParam('projectId'); + + const to = Boolean(project) ? 'create' : '/admin/api/create-token'; + return ( navigate('/admin/api/create-token')} + onClick={() => navigate(to)} data-testid={CREATE_API_TOKEN_BUTTON} permission={CREATE_API_TOKEN} maxWidth="700px"