From d14072ff74fc377aad0f9c292733222580191033 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Tue, 7 Feb 2023 13:01:45 +0200 Subject: [PATCH] show api access screen in projet settings (#3056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: andreas-unleash ## About the changes Closes # [1-612](https://linear.app/unleash/issue/1-612/first-iteration-of-project-token-management) ### Important files ## Discussion points --------- Signed-off-by: andreas-unleash --- .../apiToken/ApiTokenTable/ApiTokenTable.tsx | 34 ++++++++++++++++-- .../ProjectSettings/ProjectApiAccess.tsx | 35 +++++++++++++++++++ .../ProjectSettings/ProjectSettings.tsx | 14 ++++++++ frontend/src/interfaces/uiConfig.ts | 1 + .../__snapshots__/create-config.test.ts.snap | 2 ++ src/lib/types/experimental.ts | 4 +++ 6 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess.tsx diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx index 5a149e7886b..4f833dd96a0 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx @@ -28,12 +28,38 @@ import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColum import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; const hiddenColumnsSmall = ['Icon', 'createdAt']; +const hiddenColumnsCompact = ['Icon', 'project', 'seenAt']; -export const ApiTokenTable = () => { +interface IApiTokenTableProps { + compact?: boolean; + filterForProject?: string; +} +export const ApiTokenTable = ({ + compact = false, + filterForProject, +}: IApiTokenTableProps) => { const { tokens, loading } = useApiTokens(); const initialState = useMemo(() => ({ sortBy: [{ id: 'createdAt' }] }), []); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + const filteredTokens = useMemo(() => { + if (Boolean(filterForProject)) { + return tokens.filter(token => { + if (token.projects) { + if (token.projects?.length > 1) return false; + if ( + token.projects?.length === 1 && + token.projects[0] === filterForProject + ) + return true; + } + + return token.project === filterForProject; + }); + } + return tokens; + }, [tokens, filterForProject]); + const { getTableProps, getTableBodyProps, @@ -46,7 +72,7 @@ export const ApiTokenTable = () => { } = useTable( { columns: COLUMNS as any, - data: tokens as any, + data: filteredTokens as any, initialState, sortTypes, autoResetHiddenColumns: false, @@ -62,6 +88,10 @@ export const ApiTokenTable = () => { condition: isSmallScreen, columns: hiddenColumnsSmall, }, + { + condition: compact, + columns: hiddenColumnsCompact, + }, ], setHiddenColumns, COLUMNS diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess.tsx new file mode 100644 index 00000000000..815ee3c90a6 --- /dev/null +++ b/frontend/src/component/project/Project/ProjectSettings/ProjectApiAccess.tsx @@ -0,0 +1,35 @@ +import { useContext } from 'react'; +import { PageContent } from 'component/common/PageContent/PageContent'; +import { Alert } from '@mui/material'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import AccessContext from 'contexts/AccessContext'; +import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions'; +import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import { usePageTitle } from 'hooks/usePageTitle'; +import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject'; +import { ApiTokenTable } from '../../../admin/apiToken/ApiTokenTable/ApiTokenTable'; + +export const ProjectApiAccess = () => { + const projectId = useRequiredPathParam('projectId'); + const projectName = useProjectNameOrId(projectId); + const { hasAccess } = useContext(AccessContext); + + usePageTitle(`Project api access – ${projectName}`); + + if (!hasAccess(UPDATE_PROJECT, projectId)) { + return ( + }> + + You need project owner or admin permissions to access this + section. + + + ); + } + + return ( +
+ +
+ ); +}; diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx index 56aec71fd64..972f48370c5 100644 --- a/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx @@ -9,10 +9,14 @@ import { ITab, VerticalTabs } from 'component/common/VerticalTabs/VerticalTabs'; import { ProjectAccess } from 'component/project/ProjectAccess/ProjectAccess'; import ProjectEnvironmentList from 'component/project/ProjectEnvironment/ProjectEnvironment'; import { ChangeRequestConfiguration } from './ChangeRequestConfiguration/ChangeRequestConfiguration'; +import { ProjectApiAccess } from './ProjectApiAccess'; +import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; export const ProjectSettings = () => { const location = useLocation(); const navigate = useNavigate(); + const { uiConfig } = useUiConfig(); + const { showProjectApiAccess } = uiConfig.flags; const tabs: ITab[] = [ { @@ -29,6 +33,13 @@ export const ProjectSettings = () => { }, ]; + if (Boolean(showProjectApiAccess)) { + tabs.push({ + id: 'api-access', + label: 'API access', + }); + } + const onChange = (tab: ITab) => { navigate(tab.id); }; @@ -53,6 +64,9 @@ export const ProjectSettings = () => { path="change-requests/*" element={} /> + {Boolean(showProjectApiAccess) && ( + } /> + )} } diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index fd137937946..d13b90af451 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -48,6 +48,7 @@ export interface IFlags { newProjectOverview?: boolean; caseInsensitiveInOperators?: boolean; crOnVariants?: boolean; + showProjectApiAccess?: boolean; } export interface IVersionInfo { diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index f5135eccb23..f459f90b446 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -83,6 +83,7 @@ exports[`should create default config 1`] = ` "proxyReturnAllToggles": false, "responseTimeWithAppName": false, "serviceAccounts": false, + "showProjectApiAccess": false, "variantsPerEnvironment": false, }, }, @@ -104,6 +105,7 @@ exports[`should create default config 1`] = ` "proxyReturnAllToggles": false, "responseTimeWithAppName": false, "serviceAccounts": false, + "showProjectApiAccess": false, "variantsPerEnvironment": false, }, "externalResolver": { diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 6f537164a3c..4a0dd55c13c 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -66,6 +66,10 @@ const flags = { process.env.UNLEASH_EXPERIMENTAL_CR_ON_VARIANTS, false, ), + showProjectApiAccess: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_PROJECT_API_ACCESS, + false, + ), }; export const defaultExperimentalOptions: IExperimentalOptions = {