Skip to content

Commit

Permalink
feat: add input for api token in playground (#5130)
Browse files Browse the repository at this point in the history
Adds a token input in playground.

In the case of tokens that span multiple projects ie
`[]:development.etc` it will look into the token definitions to find the
token and get the projects

Otherwise it will try to parse the project and environment from the
token itself (without checking for it being a valid token)

Also, does not support admin tokens `*:*.etc`

Closes #
[1-1507](https://linear.app/unleash/issue/1-1507/create-a-token-input-in-the-playground-form)
<img width="1661" alt="Screenshot 2023-10-23 at 16 38 11"
src="https://github.com/Unleash/unleash/assets/104830839/f2d4fb6e-962f-4cc1-b5e4-817fd2de18ff">
<img width="1673" alt="Screenshot 2023-10-23 at 16 38 33"
src="https://github.com/Unleash/unleash/assets/104830839/27645955-d651-41e6-be02-4381c4f00551">

<img width="1377" alt="Screenshot 2023-10-25 at 17 06 43"
src="https://github.com/Unleash/unleash/assets/104830839/c7638366-3634-4521-af65-4f68a4f3b330">

---------

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
  • Loading branch information
andreas-unleash committed Oct 25, 2023
1 parent de540e0 commit 8e3863a
Show file tree
Hide file tree
Showing 6 changed files with 411 additions and 45 deletions.
Expand Up @@ -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),
Expand All @@ -34,6 +34,7 @@ export const AdvancedPlayground: VFC<{
projects: string[];
environments: string[];
context?: string;
token?: string;
} = { projects: [], environments: [] };
const { value, setValue } = createLocalStorage(
'AdvancedPlayground:v1',
Expand All @@ -49,6 +50,7 @@ export const AdvancedPlayground: VFC<{
value.environments,
);
const [projects, setProjects] = useState<string[]>(value.projects);
const [token, setToken] = useState<string | undefined>(value.token);
const [context, setContext] = useState<string | undefined>(value.context);
const [results, setResults] = useState<
AdvancedPlaygroundResponseSchema | undefined
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -249,6 +261,8 @@ export const AdvancedPlayground: VFC<{
availableEnvironments={availableEnvironments}
projects={projects}
environments={environments}
token={token}
setToken={setToken}
setProjects={setProjects}
setEnvironments={setEnvironments}
/>
Expand Down
@@ -0,0 +1,150 @@
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { render } from 'utils/testRenderer';
import { fireEvent, screen, within } 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.964a287e1b728cb5f4f3e0120df92cb5',
projects: ['default', 'MyProject'],
},
],
},
'get',
200,
);
});

const Component = () => {
const [environments, setEnvironments] = useState<string[]>([]);
const [projects, setProjects] = useState<string[]>([]);
const [token, setToken] = useState<string>();

const availableEnvironments = ['development', 'production'];

return (
<PlaygroundConnectionFieldset
environments={environments}
projects={projects}
token={token}
setToken={setToken}
setEnvironments={setEnvironments}
setProjects={setProjects}
availableEnvironments={availableEnvironments}
/>
);
};

test('should parse project and environment from token input', async () => {
render(<Component />);

const tokenInput = await screen.findByLabelText('Api token');
fireEvent.change(tokenInput, {
target: {
value: 'default:development.964a287e1b728cb5f4f3e0120df92cb5',
},
});

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();
await within(projectAutocomplete).findByText('Default');
await within(environmentAutocomplete).findByText('development');
});

test('should load projects from token definition if project is []', async () => {
render(<Component />);

const tokenInput = await screen.findByLabelText('Api token');
fireEvent.change(tokenInput, {
target: { value: '[]:development.964a287e1b728cb5f4f3e0120df92cb5' },
});

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();
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(<Component />);

const tokenInput = await screen.findByLabelText('Api token');
fireEvent.change(tokenInput, {
target: { value: '*:*.964a287e1b728cb5f4f3e0120df92cb5' },
});

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();
await screen.findByText('Admin tokens are not supported in the playground');
});

0 comments on commit 8e3863a

Please sign in to comment.