From 769041c1b1a44c18d841055d8b95fdeed1463d6c Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Mon, 23 Oct 2023 16:41:12 +0300 Subject: [PATCH 01/12] feat: add input for api token in playground Signed-off-by: andreas-unleash --- .../Playground/AdvancedPlayground.tsx | 18 ++++- .../PlaygroundConnectionFieldset.tsx | 71 ++++++++++++++++++- .../PlaygroundForm/PlaygroundForm.tsx | 6 ++ 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/frontend/src/component/playground/Playground/AdvancedPlayground.tsx b/frontend/src/component/playground/Playground/AdvancedPlayground.tsx index 6bb35573655d..d0e00bb0630c 100644 --- a/frontend/src/component/playground/Playground/AdvancedPlayground.tsx +++ b/frontend/src/component/playground/Playground/AdvancedPlayground.tsx @@ -17,11 +17,11 @@ import { } from './playground.utils'; import { PlaygroundGuidance } from './PlaygroundGuidance/PlaygroundGuidance'; import { PlaygroundGuidancePopper } from './PlaygroundGuidancePopper/PlaygroundGuidancePopper'; -import Loader from '../../common/Loader/Loader'; +import Loader from 'component/common/Loader/Loader'; import { AdvancedPlaygroundResultsTable } from './AdvancedPlaygroundResultsTable/AdvancedPlaygroundResultsTable'; import { AdvancedPlaygroundResponseSchema } from 'openapi'; import { createLocalStorage } from 'utils/createLocalStorage'; -import { BadRequestError } from '../../../utils/apiUtils'; +import { BadRequestError } from 'utils/apiUtils'; const StyledAlert = styled(Alert)(({ theme }) => ({ marginBottom: theme.spacing(3), @@ -34,6 +34,7 @@ export const AdvancedPlayground: VFC<{ projects: string[]; environments: string[]; context?: string; + token?: string; } = { projects: [], environments: [] }; const { value, setValue } = createLocalStorage( 'AdvancedPlayground:v1', @@ -49,6 +50,7 @@ export const AdvancedPlayground: VFC<{ value.environments, ); const [projects, setProjects] = useState(value.projects); + const [token, setToken] = useState(value.token); const [context, setContext] = useState(value.context); const [results, setResults] = useState< AdvancedPlaygroundResponseSchema | undefined @@ -76,6 +78,7 @@ export const AdvancedPlayground: VFC<{ const environments = resolveEnvironmentsFromUrl(); const projects = resolveProjectsFromUrl(); const context = resolveContextFromUrl(); + const token = resolveTokenFromUrl(); const makePlaygroundRequest = async () => { if (environments && context) { await evaluatePlaygroundContext( @@ -124,6 +127,15 @@ export const AdvancedPlayground: VFC<{ return contextFromUrl; }; + const resolveTokenFromUrl = () => { + let tokenFromUrl = searchParams.get('token'); + if (tokenFromUrl) { + tokenFromUrl = decodeURI(tokenFromUrl); + setToken(tokenFromUrl); + } + return tokenFromUrl; + }; + const evaluatePlaygroundContext = async ( environments: string[] | string, projects: string[] | string, @@ -249,6 +261,8 @@ export const AdvancedPlayground: VFC<{ availableEnvironments={availableEnvironments} projects={projects} environments={environments} + token={token} + setToken={setToken} setProjects={setProjects} setEnvironments={setEnvironments} /> diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx index 9bb4b7de808e..9e9a37aadccf 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx @@ -1,4 +1,4 @@ -import React, { ComponentProps, VFC } from 'react'; +import React, { ComponentProps, useState, VFC } from 'react'; import { Autocomplete, Box, @@ -8,12 +8,21 @@ import { } from '@mui/material'; import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { renderOption } from '../renderOption'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useUiFlag } from 'hooks/useUiFlag'; +import { + IApiToken, + useApiTokens, +} from 'hooks/api/getters/useApiTokens/useApiTokens'; +import Input from 'component/common/Input/Input'; interface IPlaygroundConnectionFieldsetProps { environments: string[]; projects: string[]; + token?: string; setProjects: (projects: string[]) => void; setEnvironments: (environments: string[]) => void; + setToken?: (token: string) => void; availableEnvironments: string[]; } @@ -29,11 +38,16 @@ export const PlaygroundConnectionFieldset: VFC< > = ({ environments, projects, + token, setProjects, setEnvironments, + setToken, availableEnvironments, }) => { const theme = useTheme(); + const playgroundImprovements = useUiFlag('playgroundImprovements'); + const { tokens } = useApiTokens(); + const [tokenError, setTokenError] = useState(); const { projects: availableProjects = [] } = useProjects(); const projectsOptions = [ @@ -102,6 +116,44 @@ export const PlaygroundConnectionFieldset: VFC< environments.includes(id), ); + const onSetToken: ComponentProps['onChange'] = async ( + event, + ) => { + const tempToken = event.target.value; + setToken?.(tempToken); + if (tempToken === '') { + setTokenError(undefined); + return; + } + + const validToken = tokens.find( + (token: IApiToken) => token.secret === tempToken, + ); + if (validToken) { + if (typeof validToken.projects === 'undefined') { + setProjects([allOption.id]); + } + + if (typeof validToken.projects === 'string') { + setProjects([validToken.projects]); + } + + if (Array.isArray(validToken.projects)) { + setProjects(validToken.projects); + } + + if (validToken.environment === '*') { + setEnvironments(availableEnvironments); + } else { + setEnvironments([validToken.environment]); + } + } else { + setTokenError( + 'Invalid token. Please make sure you are using a valid token from this Unleash instance', + ); + } + }; + return ( @@ -130,6 +182,7 @@ export const PlaygroundConnectionFieldset: VFC< size='small' value={envValue} onChange={onEnvironmentsChange} + disabled={Boolean(token)} data-testid={'PLAYGROUND_ENVIRONMENT_SELECT'} /> + + } + /> ); }; diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundForm.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundForm.tsx index 8a01617182fc..e8ffb362ae83 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundForm.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundForm.tsx @@ -9,6 +9,8 @@ interface IPlaygroundFormProps { onSubmit: (event: FormEvent) => void; environments: string | string[]; projects: string[]; + token?: string; + setToken?: React.Dispatch>; setProjects: React.Dispatch>; setEnvironments: React.Dispatch>; context: string | undefined; @@ -20,6 +22,8 @@ export const PlaygroundForm: VFC = ({ environments, onSubmit, projects, + token, + setToken, setProjects, setEnvironments, context, @@ -39,6 +43,8 @@ export const PlaygroundForm: VFC = ({ Array.isArray(environments) ? environments : [environments] } projects={projects} + token={token} + setToken={setToken} setEnvironments={setEnvironments} setProjects={setProjects} availableEnvironments={availableEnvironments.map( From ed8b46995262c4befaef8a7e50e4557e57c50857 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 12:58:06 +0300 Subject: [PATCH 02/12] feat: add tests Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.test.tsx | 132 ++++++++++++++++++ .../PlaygroundConnectionFieldset.tsx | 72 +++++++--- .../playground/Playground/playground.utils.ts | 17 +++ 3 files changed, 199 insertions(+), 22 deletions(-) create mode 100644 frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx new file mode 100644 index 000000000000..bb666a4b025a --- /dev/null +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx @@ -0,0 +1,132 @@ +import { testServerRoute, testServerSetup } from "utils/testServer"; +import { render } from "utils/testRenderer"; +import userEvent from '@testing-library/user-event'; +import { screen } from "@testing-library/react"; +import { PlaygroundConnectionFieldset } from "./PlaygroundConnectionFieldset"; +import {useState} from 'react' + +const server = testServerSetup(); + +const Component = () => { + const [environments, setEnvironments] = useState([]); + const [projects, setProjects] = useState([]); + const [token, setToken] = useState(); + + const availableEnvironments = ['development', 'production']; + + return ( + + ) +} + +beforeEach(() => { + testServerRoute( + server, + 'api/admin/ui-config', + { + flags: { + playgroundImprovements: true, + }, + }, + 'get', + 200, + ); + testServerRoute( + server, + '/api/admin/projects', + { + projects: [{ + id: 'default', + name: 'Default' + }, { + id: 'MyProject', + name: 'MyProject' + }], + }, + 'get', + 200, + ); +}); + +test('should parse project and environment from token input', async () => { + render(); + + const user = userEvent.setup(); + const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); + await user.type( + tokenInput, + 'default:development.964a287e1b728cb5f4f3e0120df92cb5', + ); + + const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); + const environmentInput = screen.getByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + + expect(projectInput).toBeDisabled(); + expect(projectInput).toHaveValue('default'); + expect(environmentInput).toBeDisabled(); + expect(environmentInput).toHaveValue('development'); +}); + +test('should load projects from token definition if project is []', async () => { + testServerRoute( + server, + '/api/admin/api-tokens/*', + { + tokens: [ + { + secret: '[]:development.964a287e1b728cb5f4f3e0120df92cb5', + projects: ['default', 'MyProject'], + }, + ], + }, + 'get', + 200, + ); + + render(); + + const user = userEvent.setup(); + const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); + await user.type( + tokenInput, + '[]:development.964a287e1b728cb5f4f3e0120df92cb5', + ); + + const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); + const environmentInput = screen.getByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + + expect(projectInput).toBeDisabled(); + expect(projectInput).toHaveValue('default, MyProject'); + expect(environmentInput).toBeDisabled(); + expect(environmentInput).toHaveValue('development'); +}); + +test('should show an error when admin token', async () => { + render(); + + const user = userEvent.setup(); + const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); + await user.type(tokenInput, '*:*.964a287e1b728cb5f4f3e0120df92cb5'); + + const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); + const environmentInput = screen.getByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + + expect(projectInput).toBeDisabled(); + expect(environmentInput).toBeDisabled(); + await screen.findByText( + 'Admin tokens are not supported in the playground') +}); diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx index 9e9a37aadccf..4fdb480c7064 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx @@ -11,10 +11,13 @@ import { renderOption } from '../renderOption'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useUiFlag } from 'hooks/useUiFlag'; import { - IApiToken, useApiTokens, } from 'hooks/api/getters/useApiTokens/useApiTokens'; import Input from 'component/common/Input/Input'; +import { + extractProjectEnvironmentFromToken, + validateTokenFormat, +} from '../../playground.utils'; interface IPlaygroundConnectionFieldsetProps { environments: string[]; @@ -110,7 +113,9 @@ export const PlaygroundConnectionFieldset: VFC< }; const isAllProjects = - projects.length === 0 || (projects.length === 1 && projects[0] === '*'); + projects && + (projects.length === 0 || + (projects.length === 1 && projects[0] === '*')); const envValue = environmentOptions.filter(({ id }) => environments.includes(id), @@ -126,31 +131,53 @@ export const PlaygroundConnectionFieldset: VFC< return; } - const validToken = tokens.find( - (token: IApiToken) => token.secret === tempToken, - ); - if (validToken) { - if (typeof validToken.projects === 'undefined') { - setProjects([allOption.id]); - } + try { + validateTokenFormat(tempToken); + setTokenError(undefined); + const [tokenProject, tokenEnvironment] = + extractProjectEnvironmentFromToken(tempToken); + setEnvironments([tokenEnvironment]); + if (tokenProject === '[]') { + const validToken = tokens.find( + ({ secret }) => secret === tempToken, + ); + if (validToken) { + if (typeof validToken.projects === 'undefined') { + setProjects([allOption.id]); + } - if (typeof validToken.projects === 'string') { - setProjects([validToken.projects]); - } + if (typeof validToken.projects === 'string') { + setProjects([validToken.projects]); + } - if (Array.isArray(validToken.projects)) { - setProjects(validToken.projects); + if (Array.isArray(validToken.projects)) { + setProjects(validToken.projects); + } + } else { + setTokenError( + 'Invalid token. Please make sure you are using a valid token from this Unleash instance', + ); + } + return; + } + if (tokenProject === '*') { + setProjects([allOption.id]); + return; } - if (validToken.environment === '*') { - setEnvironments(availableEnvironments); - } else { - setEnvironments([validToken.environment]); + if ( + !projectsOptions + .map((option) => option.id) + .includes(tokenProject) + ) { + setTokenError( + `Invalid token. Project ${tokenProject} does not exist`, + ); } - } else { - setTokenError( - 'Invalid token. Please make sure you are using a valid token from this Unleash instance', - ); + setProjects([tokenProject]); + } catch (e: any) { + setTokenError(e.message); + return; } }; @@ -223,6 +250,7 @@ export const PlaygroundConnectionFieldset: VFC< error={Boolean(tokenError)} errorText={tokenError} placeholder={'Enter your api token'} + data-testid={'PLAYGROUND_TOKEN_INPUT'} /> } /> diff --git a/frontend/src/component/playground/Playground/playground.utils.ts b/frontend/src/component/playground/Playground/playground.utils.ts index 74a47f953312..a7220fdd599e 100644 --- a/frontend/src/component/playground/Playground/playground.utils.ts +++ b/frontend/src/component/playground/Playground/playground.utils.ts @@ -125,3 +125,20 @@ export const normalizeCustomContextProperties = ( return output; }; + +export const validateTokenFormat = (token: string): void => { + const [projectEnvAccess] = token.split('.'); + const [project, environment] = projectEnvAccess.split(':'); + if (!project || !environment) { + throw new Error('Invalid token format'); + } + + if (environment === '*') { + throw new Error('Admin tokens are not supported in the playground'); + } +}; + +export const extractProjectEnvironmentFromToken = (token: string) => { + const [projectEnvAccess] = token.split('.'); + return projectEnvAccess.split(':'); +}; From c5d2dc0cfc68773858afbac7cbcfa2f5b2308bb4 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 13:11:21 +0300 Subject: [PATCH 03/12] feat: add tests Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.test.tsx | 47 ++++++++++--------- .../PlaygroundConnectionFieldset.tsx | 4 +- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx index bb666a4b025a..98f51a5ce158 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx @@ -4,29 +4,10 @@ import userEvent from '@testing-library/user-event'; import { screen } from "@testing-library/react"; import { PlaygroundConnectionFieldset } from "./PlaygroundConnectionFieldset"; import {useState} from 'react' +import { aw } from "../../../../../../build/static/index-1365192c"; const server = testServerSetup(); -const Component = () => { - const [environments, setEnvironments] = useState([]); - const [projects, setProjects] = useState([]); - const [token, setToken] = useState(); - - const availableEnvironments = ['development', 'production']; - - return ( - - ) -} - beforeEach(() => { testServerRoute( server, @@ -56,18 +37,38 @@ beforeEach(() => { ); }); +const Component = () => { + const [environments, setEnvironments] = useState([]); + const [projects, setProjects] = useState([]); + const [token, setToken] = useState(); + + const availableEnvironments = ['development', 'production']; + + return ( + + ) +} + test('should parse project and environment from token input', async () => { render(); const user = userEvent.setup(); - const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); + const tokenInput = await screen.findByTestId('PLAYGROUND_TOKEN_INPUT'); await user.type( tokenInput, 'default:development.964a287e1b728cb5f4f3e0120df92cb5', ); - const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); - const environmentInput = screen.getByTestId( + const projectInput = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); + const environmentInput = await screen.findByTestId( 'PLAYGROUND_ENVIRONMENT_SELECT', ); diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx index 4fdb480c7064..c0fea73843b8 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx @@ -10,9 +10,7 @@ import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { renderOption } from '../renderOption'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useUiFlag } from 'hooks/useUiFlag'; -import { - useApiTokens, -} from 'hooks/api/getters/useApiTokens/useApiTokens'; +import { useApiTokens } from 'hooks/api/getters/useApiTokens/useApiTokens'; import Input from 'component/common/Input/Input'; import { extractProjectEnvironmentFromToken, From 4062c40d2a62d9d6eec23de483feb6761dc8970b Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 13:14:30 +0300 Subject: [PATCH 04/12] feat: add tests Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.test.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx index 98f51a5ce158..7281975d2158 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx @@ -4,14 +4,13 @@ import userEvent from '@testing-library/user-event'; import { screen } from "@testing-library/react"; import { PlaygroundConnectionFieldset } from "./PlaygroundConnectionFieldset"; import {useState} from 'react' -import { aw } from "../../../../../../build/static/index-1365192c"; const server = testServerSetup(); beforeEach(() => { testServerRoute( server, - 'api/admin/ui-config', + '/api/admin/ui-config', { flags: { playgroundImprovements: true, From c6bd4d1814c5845c710e727fb348bb167eb489c0 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 13:50:01 +0300 Subject: [PATCH 05/12] feat: add tests Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.test.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx index 7281975d2158..89d28e9b6f35 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx @@ -8,17 +8,14 @@ import {useState} from 'react' const server = testServerSetup(); beforeEach(() => { - testServerRoute( - server, - '/api/admin/ui-config', - { - flags: { - playgroundImprovements: true, - }, + testServerRoute(server, '/api/admin/ui-config', { + versionInfo: { + current: { oss: 'version', enterprise: 'version' }, }, - 'get', - 200, - ); + flags: { + playgroundImprovements: true, + }, + }); testServerRoute( server, '/api/admin/projects', @@ -34,6 +31,19 @@ beforeEach(() => { 'get', 200, ); + + testServerRoute( + server, + '/api/admin/api-tokens', + { + tokens: [{ + secret: '[]:development.sometoken', + projects: ['default', 'MyProject'] + }], + }, + 'get', + 200, + ); }); const Component = () => { @@ -71,7 +81,7 @@ test('should parse project and environment from token input', async () => { 'PLAYGROUND_ENVIRONMENT_SELECT', ); - expect(projectInput).toBeDisabled(); + // expect(projectInput).toBeDisabled(); expect(projectInput).toHaveValue('default'); expect(environmentInput).toBeDisabled(); expect(environmentInput).toHaveValue('development'); From 4e36d422843ad77ebfffb8ecae56376932c283a0 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 14:09:13 +0300 Subject: [PATCH 06/12] feat: add tooltip Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.test.tsx | 229 +++++++++--------- .../PlaygroundConnectionFieldset.tsx | 103 ++++---- 2 files changed, 169 insertions(+), 163 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx index 89d28e9b6f35..53ace33e86d9 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx @@ -1,142 +1,131 @@ -import { testServerRoute, testServerSetup } from "utils/testServer"; -import { render } from "utils/testRenderer"; +import { testServerRoute, testServerSetup } from 'utils/testServer'; +import { render } from 'utils/testRenderer'; import userEvent from '@testing-library/user-event'; -import { screen } from "@testing-library/react"; -import { PlaygroundConnectionFieldset } from "./PlaygroundConnectionFieldset"; -import {useState} from 'react' +import { screen } from '@testing-library/react'; +import { PlaygroundConnectionFieldset } from './PlaygroundConnectionFieldset'; +import { useState } from 'react'; const server = testServerSetup(); beforeEach(() => { - testServerRoute(server, '/api/admin/ui-config', { - versionInfo: { - current: { oss: 'version', enterprise: 'version' }, - }, - flags: { - playgroundImprovements: true, - }, - }); - testServerRoute( - server, - '/api/admin/projects', - { - projects: [{ - id: 'default', - name: 'Default' - }, { - id: 'MyProject', - name: 'MyProject' - }], - }, - 'get', - 200, - ); - - testServerRoute( - server, - '/api/admin/api-tokens', - { - tokens: [{ - secret: '[]:development.sometoken', - projects: ['default', 'MyProject'] - }], - }, - 'get', - 200, - ); + testServerRoute(server, '/api/admin/ui-config', { + versionInfo: { + current: { oss: 'version', enterprise: 'version' }, + }, + flags: { + playgroundImprovements: true, + }, + }); + testServerRoute( + server, + '/api/admin/projects', + { + projects: [ + { + id: 'default', + name: 'Default', + }, + { + id: 'MyProject', + name: 'MyProject', + }, + ], + }, + 'get', + 200, + ); }); const Component = () => { - const [environments, setEnvironments] = useState([]); - const [projects, setProjects] = useState([]); - const [token, setToken] = useState(); - - const availableEnvironments = ['development', 'production']; - - return ( - - ) -} + const [environments, setEnvironments] = useState([]); + const [projects, setProjects] = useState([]); + const [token, setToken] = useState(); + + const availableEnvironments = ['development', 'production']; + + return ( + + ); +}; test('should parse project and environment from token input', async () => { - render(); - - const user = userEvent.setup(); - const tokenInput = await screen.findByTestId('PLAYGROUND_TOKEN_INPUT'); - await user.type( - tokenInput, - 'default:development.964a287e1b728cb5f4f3e0120df92cb5', - ); - - const projectInput = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); - const environmentInput = await screen.findByTestId( - 'PLAYGROUND_ENVIRONMENT_SELECT', - ); - - // expect(projectInput).toBeDisabled(); - expect(projectInput).toHaveValue('default'); - expect(environmentInput).toBeDisabled(); - expect(environmentInput).toHaveValue('development'); + render(); + + const user = userEvent.setup(); + const tokenInput = await screen.findByTestId('PLAYGROUND_TOKEN_INPUT'); + await user.type( + tokenInput, + 'default:development.964a287e1b728cb5f4f3e0120df92cb5', + ); + + const projectInput = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); + const environmentInput = await screen.findByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + + // expect(projectInput).toBeDisabled(); + expect(projectInput).toHaveValue('default'); + expect(environmentInput).toBeDisabled(); + expect(environmentInput).toHaveValue('development'); }); test('should load projects from token definition if project is []', async () => { - testServerRoute( - server, - '/api/admin/api-tokens/*', - { - tokens: [ + testServerRoute( + server, + '/api/admin/api-tokens/*', { - secret: '[]:development.964a287e1b728cb5f4f3e0120df92cb5', - projects: ['default', 'MyProject'], + tokens: [ + { + secret: '[]:development.964a287e1b728cb5f4f3e0120df92cb5', + projects: ['default', 'MyProject'], + }, + ], }, - ], - }, - 'get', - 200, - ); - - render(); - - const user = userEvent.setup(); - const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); - await user.type( - tokenInput, - '[]:development.964a287e1b728cb5f4f3e0120df92cb5', - ); - - const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); - const environmentInput = screen.getByTestId( - 'PLAYGROUND_ENVIRONMENT_SELECT', - ); - - expect(projectInput).toBeDisabled(); - expect(projectInput).toHaveValue('default, MyProject'); - expect(environmentInput).toBeDisabled(); - expect(environmentInput).toHaveValue('development'); + 'get', + 200, + ); + + render(); + + const user = userEvent.setup(); + const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); + await user.type( + tokenInput, + '[]:development.964a287e1b728cb5f4f3e0120df92cb5', + ); + + const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); + const environmentInput = screen.getByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + + expect(projectInput).toBeDisabled(); + expect(projectInput).toHaveValue('default, MyProject'); + expect(environmentInput).toBeDisabled(); + expect(environmentInput).toHaveValue('development'); }); test('should show an error when admin token', async () => { - render(); + render(); - const user = userEvent.setup(); - const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); - await user.type(tokenInput, '*:*.964a287e1b728cb5f4f3e0120df92cb5'); + const user = userEvent.setup(); + const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); + await user.type(tokenInput, '*:*.964a287e1b728cb5f4f3e0120df92cb5'); - const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); - const environmentInput = screen.getByTestId( - 'PLAYGROUND_ENVIRONMENT_SELECT', - ); + const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); + const environmentInput = screen.getByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); - expect(projectInput).toBeDisabled(); - expect(environmentInput).toBeDisabled(); - await screen.findByText( - 'Admin tokens are not supported in the playground') + expect(projectInput).toBeDisabled(); + expect(environmentInput).toBeDisabled(); + await screen.findByText('Admin tokens are not supported in the playground'); }); diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx index c0fea73843b8..3e469eb56f84 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx @@ -3,6 +3,7 @@ import { Autocomplete, Box, TextField, + Tooltip, Typography, useTheme, } from '@mui/material'; @@ -191,50 +192,66 @@ export const PlaygroundConnectionFieldset: VFC< - ( - - )} - renderOption={renderOption} - getOptionLabel={({ label }) => label} - disableCloseOnSelect={false} - size='small' - value={envValue} - onChange={onEnvironmentsChange} - disabled={Boolean(token)} - data-testid={'PLAYGROUND_ENVIRONMENT_SELECT'} - /> - ( - - )} - renderOption={renderOption} - getOptionLabel={({ label }) => label} - disableCloseOnSelect - size='small' - value={ - isAllProjects - ? allOption - : projectsOptions.filter(({ id }) => - projects.includes(id), - ) + + > + ( + + )} + renderOption={renderOption} + getOptionLabel={({ label }) => label} + disableCloseOnSelect={false} + size='small' + value={envValue} + onChange={onEnvironmentsChange} + disabled={Boolean(token)} + data-testid={'PLAYGROUND_ENVIRONMENT_SELECT'} + /> + + + ( + + )} + renderOption={renderOption} + getOptionLabel={({ label }) => label} + disableCloseOnSelect + size='small' + value={ + isAllProjects + ? allOption + : projectsOptions.filter(({ id }) => + projects.includes(id), + ) + } + onChange={onProjectsChange} + disabled={Boolean(token)} + data-testid={'PLAYGROUND_PROJECT_SELECT'} + /> + Date: Wed, 25 Oct 2023 16:23:51 +0300 Subject: [PATCH 07/12] fix: fix tests Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.test.tsx | 101 ++++++++---------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx index 53ace33e86d9..ceb3d3a783c7 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx @@ -1,7 +1,6 @@ import { testServerRoute, testServerSetup } from 'utils/testServer'; import { render } from 'utils/testRenderer'; -import userEvent from '@testing-library/user-event'; -import { screen } from '@testing-library/react'; +import { fireEvent, screen, within } from "@testing-library/react"; import { PlaygroundConnectionFieldset } from './PlaygroundConnectionFieldset'; import { useState } from 'react'; @@ -34,6 +33,20 @@ beforeEach(() => { 'get', 200, ); + testServerRoute( + server, + '/api/admin/api-tokens', + { + tokens: [ + { + secret: '[]:development.964a287e1b728cb5f4f3e0120df92cb5', + projects: ['default', 'MyProject'], + }, + ], + }, + 'get', + 200, + ); }); const Component = () => { @@ -59,73 +72,53 @@ const Component = () => { test('should parse project and environment from token input', async () => { render(); - const user = userEvent.setup(); - const tokenInput = await screen.findByTestId('PLAYGROUND_TOKEN_INPUT'); - await user.type( - tokenInput, - 'default:development.964a287e1b728cb5f4f3e0120df92cb5', - ); + const tokenInput = await screen.findByLabelText('Api token'); + fireEvent.change(tokenInput, { target: { value: 'default:development.964a287e1b728cb5f4f3e0120df92cb5' } }); - const projectInput = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); - const environmentInput = await screen.findByTestId( - 'PLAYGROUND_ENVIRONMENT_SELECT', - ); + const projectAutocomplete = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); + const projectInput = within(projectAutocomplete).getByRole('combobox') + + const environmentAutocomplete = await screen.findByTestId('PLAYGROUND_ENVIRONMENT_SELECT'); + const environmentInput = within(environmentAutocomplete).getByRole('combobox'); - // expect(projectInput).toBeDisabled(); - expect(projectInput).toHaveValue('default'); - expect(environmentInput).toBeDisabled(); - expect(environmentInput).toHaveValue('development'); + expect(projectInput).toBeDisabled() + expect(environmentInput).toBeDisabled() + await within(projectAutocomplete).findByText('Default'); + await within(environmentAutocomplete).findByText('development'); }); test('should load projects from token definition if project is []', async () => { - testServerRoute( - server, - '/api/admin/api-tokens/*', - { - tokens: [ - { - secret: '[]:development.964a287e1b728cb5f4f3e0120df92cb5', - projects: ['default', 'MyProject'], - }, - ], - }, - 'get', - 200, - ); - render(); - const user = userEvent.setup(); - const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); - await user.type( - tokenInput, - '[]:development.964a287e1b728cb5f4f3e0120df92cb5', - ); + const tokenInput = await screen.findByLabelText('Api token'); + fireEvent.change(tokenInput, { target: { value: '[]:development.964a287e1b728cb5f4f3e0120df92cb5' } }); - const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); - const environmentInput = screen.getByTestId( - 'PLAYGROUND_ENVIRONMENT_SELECT', - ); + const projectAutocomplete = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); + const projectInput = within(projectAutocomplete).getByRole('combobox') + + const environmentAutocomplete = await screen.findByTestId('PLAYGROUND_ENVIRONMENT_SELECT'); + const environmentInput = within(environmentAutocomplete).getByRole('combobox'); - expect(projectInput).toBeDisabled(); - expect(projectInput).toHaveValue('default, MyProject'); - expect(environmentInput).toBeDisabled(); - expect(environmentInput).toHaveValue('development'); + expect(projectInput).toBeDisabled() + expect(environmentInput).toBeDisabled() + await within(projectAutocomplete).findByText('Default'); + await within(projectAutocomplete).findByText('MyProject'); + await within(environmentAutocomplete).findByText('development'); }); test('should show an error when admin token', async () => { render(); - const user = userEvent.setup(); - const tokenInput = screen.getByTestId('PLAYGROUND_TOKEN_INPUT'); - await user.type(tokenInput, '*:*.964a287e1b728cb5f4f3e0120df92cb5'); + const tokenInput = await screen.findByLabelText('Api token'); + fireEvent.change(tokenInput, { target: { value: '*:*.964a287e1b728cb5f4f3e0120df92cb5' } }); - const projectInput = screen.getByTestId('PLAYGROUND_PROJECT_SELECT'); - const environmentInput = screen.getByTestId( - 'PLAYGROUND_ENVIRONMENT_SELECT', - ); + const projectAutocomplete = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); + const projectInput = within(projectAutocomplete).getByRole('combobox') + + const environmentAutocomplete = await screen.findByTestId('PLAYGROUND_ENVIRONMENT_SELECT'); + const environmentInput = within(environmentAutocomplete).getByRole('combobox'); - expect(projectInput).toBeDisabled(); - expect(environmentInput).toBeDisabled(); + expect(projectInput).toBeDisabled() + expect(environmentInput).toBeDisabled() await screen.findByText('Admin tokens are not supported in the playground'); }); From 74e4ca35492565fefeb90cdb6ed8852658cd6be6 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 16:26:11 +0300 Subject: [PATCH 08/12] fix: fmt Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.test.tsx | 94 ++++++++++++------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx index ceb3d3a783c7..cd0242d4e8e0 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.test.tsx @@ -1,6 +1,6 @@ import { testServerRoute, testServerSetup } from 'utils/testServer'; import { render } from 'utils/testRenderer'; -import { fireEvent, screen, within } from "@testing-library/react"; +import { fireEvent, screen, within } from '@testing-library/react'; import { PlaygroundConnectionFieldset } from './PlaygroundConnectionFieldset'; import { useState } from 'react'; @@ -34,18 +34,18 @@ beforeEach(() => { 200, ); testServerRoute( - server, - '/api/admin/api-tokens', - { - tokens: [ - { - secret: '[]:development.964a287e1b728cb5f4f3e0120df92cb5', - projects: ['default', 'MyProject'], - }, - ], - }, - 'get', - 200, + server, + '/api/admin/api-tokens', + { + tokens: [ + { + secret: '[]:development.964a287e1b728cb5f4f3e0120df92cb5', + projects: ['default', 'MyProject'], + }, + ], + }, + 'get', + 200, ); }); @@ -73,16 +73,26 @@ test('should parse project and environment from token input', async () => { render(); const tokenInput = await screen.findByLabelText('Api token'); - fireEvent.change(tokenInput, { target: { value: 'default:development.964a287e1b728cb5f4f3e0120df92cb5' } }); + fireEvent.change(tokenInput, { + target: { + value: 'default:development.964a287e1b728cb5f4f3e0120df92cb5', + }, + }); - const projectAutocomplete = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); - const projectInput = within(projectAutocomplete).getByRole('combobox') + const projectAutocomplete = await screen.findByTestId( + 'PLAYGROUND_PROJECT_SELECT', + ); + const projectInput = within(projectAutocomplete).getByRole('combobox'); - const environmentAutocomplete = await screen.findByTestId('PLAYGROUND_ENVIRONMENT_SELECT'); - const environmentInput = within(environmentAutocomplete).getByRole('combobox'); + const environmentAutocomplete = await screen.findByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + const environmentInput = within(environmentAutocomplete).getByRole( + 'combobox', + ); - expect(projectInput).toBeDisabled() - expect(environmentInput).toBeDisabled() + expect(projectInput).toBeDisabled(); + expect(environmentInput).toBeDisabled(); await within(projectAutocomplete).findByText('Default'); await within(environmentAutocomplete).findByText('development'); }); @@ -91,16 +101,24 @@ test('should load projects from token definition if project is []', async () => render(); const tokenInput = await screen.findByLabelText('Api token'); - fireEvent.change(tokenInput, { target: { value: '[]:development.964a287e1b728cb5f4f3e0120df92cb5' } }); + fireEvent.change(tokenInput, { + target: { value: '[]:development.964a287e1b728cb5f4f3e0120df92cb5' }, + }); - const projectAutocomplete = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); - const projectInput = within(projectAutocomplete).getByRole('combobox') + const projectAutocomplete = await screen.findByTestId( + 'PLAYGROUND_PROJECT_SELECT', + ); + const projectInput = within(projectAutocomplete).getByRole('combobox'); - const environmentAutocomplete = await screen.findByTestId('PLAYGROUND_ENVIRONMENT_SELECT'); - const environmentInput = within(environmentAutocomplete).getByRole('combobox'); + const environmentAutocomplete = await screen.findByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + const environmentInput = within(environmentAutocomplete).getByRole( + 'combobox', + ); - expect(projectInput).toBeDisabled() - expect(environmentInput).toBeDisabled() + expect(projectInput).toBeDisabled(); + expect(environmentInput).toBeDisabled(); await within(projectAutocomplete).findByText('Default'); await within(projectAutocomplete).findByText('MyProject'); await within(environmentAutocomplete).findByText('development'); @@ -110,15 +128,23 @@ test('should show an error when admin token', async () => { render(); const tokenInput = await screen.findByLabelText('Api token'); - fireEvent.change(tokenInput, { target: { value: '*:*.964a287e1b728cb5f4f3e0120df92cb5' } }); + fireEvent.change(tokenInput, { + target: { value: '*:*.964a287e1b728cb5f4f3e0120df92cb5' }, + }); - const projectAutocomplete = await screen.findByTestId('PLAYGROUND_PROJECT_SELECT'); - const projectInput = within(projectAutocomplete).getByRole('combobox') + const projectAutocomplete = await screen.findByTestId( + 'PLAYGROUND_PROJECT_SELECT', + ); + const projectInput = within(projectAutocomplete).getByRole('combobox'); - const environmentAutocomplete = await screen.findByTestId('PLAYGROUND_ENVIRONMENT_SELECT'); - const environmentInput = within(environmentAutocomplete).getByRole('combobox'); + const environmentAutocomplete = await screen.findByTestId( + 'PLAYGROUND_ENVIRONMENT_SELECT', + ); + const environmentInput = within(environmentAutocomplete).getByRole( + 'combobox', + ); - expect(projectInput).toBeDisabled() - expect(environmentInput).toBeDisabled() + expect(projectInput).toBeDisabled(); + expect(environmentInput).toBeDisabled(); await screen.findByText('Admin tokens are not supported in the playground'); }); From b153bfd95d8482f2b22020c70233bca8391a4442 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 16:44:59 +0300 Subject: [PATCH 09/12] fix: fmt Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx index 3e469eb56f84..a50b5b469d99 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx @@ -194,7 +194,7 @@ export const PlaygroundConnectionFieldset: VFC< Date: Wed, 25 Oct 2023 17:23:28 +0300 Subject: [PATCH 10/12] refactor for readability Signed-off-by: andreas-unleash --- .../PlaygroundConnectionFieldset.tsx | 105 +++++++++++------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx index a50b5b469d99..a8be4be04136 100644 --- a/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundForm/PlaygroundConnectionFieldset/PlaygroundConnectionFieldset.tsx @@ -11,7 +11,10 @@ import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { renderOption } from '../renderOption'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useUiFlag } from 'hooks/useUiFlag'; -import { useApiTokens } from 'hooks/api/getters/useApiTokens/useApiTokens'; +import { + IApiToken, + useApiTokens, +} from 'hooks/api/getters/useApiTokens/useApiTokens'; import Input from 'component/common/Input/Input'; import { extractProjectEnvironmentFromToken, @@ -125,61 +128,79 @@ export const PlaygroundConnectionFieldset: VFC< ) => { const tempToken = event.target.value; setToken?.(tempToken); + if (tempToken === '') { - setTokenError(undefined); + resetTokenState(); return; } try { validateTokenFormat(tempToken); setTokenError(undefined); - const [tokenProject, tokenEnvironment] = - extractProjectEnvironmentFromToken(tempToken); - setEnvironments([tokenEnvironment]); - if (tokenProject === '[]') { - const validToken = tokens.find( - ({ secret }) => secret === tempToken, - ); - if (validToken) { - if (typeof validToken.projects === 'undefined') { - setProjects([allOption.id]); - } + processToken(tempToken); + } catch (e: any) { + setTokenError(e.message); + } + }; - if (typeof validToken.projects === 'string') { - setProjects([validToken.projects]); - } + const processToken = (tempToken: string) => { + const [tokenProject, tokenEnvironment] = + extractProjectEnvironmentFromToken(tempToken); + setEnvironments([tokenEnvironment]); - if (Array.isArray(validToken.projects)) { - setProjects(validToken.projects); - } - } else { - setTokenError( - 'Invalid token. Please make sure you are using a valid token from this Unleash instance', - ); - } - return; - } - if (tokenProject === '*') { - setProjects([allOption.id]); - return; - } + switch (tokenProject) { + case '[]': + handleTokenWithSomeProjects(tempToken); + break; + case '*': + handleTokenWithAllProjects(); + break; + default: + handleSpecificProjectToken(tokenProject); + } + }; - if ( - !projectsOptions - .map((option) => option.id) - .includes(tokenProject) - ) { - setTokenError( - `Invalid token. Project ${tokenProject} does not exist`, - ); - } + const updateProjectsBasedOnValidToken = (validToken: IApiToken) => { + if (!validToken.projects || validToken.projects === '*') { + setProjects([allOption.id]); + } else if (typeof validToken.projects === 'string') { + setProjects([validToken.projects]); + } else if (Array.isArray(validToken.projects)) { + setProjects(validToken.projects); + } + }; + + const handleTokenWithSomeProjects = (tempToken: string) => { + const validToken = tokens.find(({ secret }) => secret === tempToken); + if (validToken) { + updateProjectsBasedOnValidToken(validToken); + } else { + setTokenError( + 'Invalid token. Ensure you use a valid token from this Unleash instance.', + ); + } + }; + + const handleTokenWithAllProjects = () => { + setProjects([allOption.id]); + }; + + const handleSpecificProjectToken = (tokenProject: string) => { + if ( + !projectsOptions.map((option) => option.id).includes(tokenProject) + ) { + setTokenError( + `Invalid token. Project ${tokenProject} does not exist.`, + ); + } else { setProjects([tokenProject]); - } catch (e: any) { - setTokenError(e.message); - return; } }; + const resetTokenState = () => { + setTokenError(undefined); + }; + return ( From 4bbf9143e0c08c31b0f307315975f73294fdebe4 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 17:30:40 +0300 Subject: [PATCH 11/12] add tests for validateTokenFormat Signed-off-by: andreas-unleash --- .../Playground/playground.utils.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/frontend/src/component/playground/Playground/playground.utils.test.ts b/frontend/src/component/playground/Playground/playground.utils.test.ts index 16e53201e7db..147a19ac4bd2 100644 --- a/frontend/src/component/playground/Playground/playground.utils.test.ts +++ b/frontend/src/component/playground/Playground/playground.utils.test.ts @@ -1,6 +1,7 @@ import { normalizeCustomContextProperties, NormalizedContextProperties, + validateTokenFormat, } from './playground.utils'; test('should keep standard properties in their place', () => { @@ -76,3 +77,50 @@ test('should add multiple standard properties without breaking custom properties }, }); }); + +describe('validateTokenFormat', () => { + it('should throw an error for invalid token format without colon', () => { + const invalidToken = 'invalidToken'; + expect(() => validateTokenFormat(invalidToken)).toThrow( + 'Invalid token format', + ); + }); + + it('should throw an error for invalid token format without period', () => { + const invalidToken = 'project:environment'; + expect(() => validateTokenFormat(invalidToken)).toThrow( + 'Invalid token format', + ); + }); + + it('should throw an error for tokens with an empty project', () => { + const invalidToken = ':environment.abc123'; + expect(() => validateTokenFormat(invalidToken)).toThrow( + 'Invalid token format', + ); + }); + + it('should throw an error for tokens with an empty environment', () => { + const invalidToken = 'project:.abc123'; + expect(() => validateTokenFormat(invalidToken)).toThrow( + 'Invalid token format', + ); + }); + + it('should throw an error for admin tokens', () => { + const adminToken = 'project:*.abc123'; + expect(() => validateTokenFormat(adminToken)).toThrow( + 'Admin tokens are not supported in the playground', + ); + }); + + it('should not throw an error for valid token formats', () => { + const validToken = 'project:environment.abc123'; + expect(() => validateTokenFormat(validToken)).not.toThrow(); + }); + + it('should not throw an error for valid token format and all projects', () => { + const validToken = '*:environment.abc123'; + expect(() => validateTokenFormat(validToken)).not.toThrow(); + }); +}); From 1e3501e36ee064ffe47cb1702251287f731e251b Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Wed, 25 Oct 2023 17:42:55 +0300 Subject: [PATCH 12/12] add tests for validateTokenFormat Signed-off-by: andreas-unleash --- .../playground/Playground/playground.utils.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/component/playground/Playground/playground.utils.test.ts b/frontend/src/component/playground/Playground/playground.utils.test.ts index 147a19ac4bd2..153fd5a46f28 100644 --- a/frontend/src/component/playground/Playground/playground.utils.test.ts +++ b/frontend/src/component/playground/Playground/playground.utils.test.ts @@ -86,11 +86,9 @@ describe('validateTokenFormat', () => { ); }); - it('should throw an error for invalid token format without period', () => { + it('should not throw an error for invalid token format without period', () => { const invalidToken = 'project:environment'; - expect(() => validateTokenFormat(invalidToken)).toThrow( - 'Invalid token format', - ); + expect(() => validateTokenFormat(invalidToken)).not.toThrow(); }); it('should throw an error for tokens with an empty project', () => {