mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Onboarding connect api token generation (#8054)
This commit is contained in:
		
							parent
							
								
									29af716952
								
							
						
					
					
						commit
						6a51a0b14a
					
				@ -5,12 +5,20 @@ import {
 | 
			
		||||
    styled,
 | 
			
		||||
    type Theme,
 | 
			
		||||
    Typography,
 | 
			
		||||
    useMediaQuery,
 | 
			
		||||
    useTheme,
 | 
			
		||||
} from '@mui/material';
 | 
			
		||||
import { SingleSelectConfigButton } from '../common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton';
 | 
			
		||||
import EnvironmentsIcon from '@mui/icons-material/CloudCircle';
 | 
			
		||||
import { useEffect, useState } from 'react';
 | 
			
		||||
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
 | 
			
		||||
import CodeIcon from '@mui/icons-material/Code';
 | 
			
		||||
import { useProjectApiTokens } from 'hooks/api/getters/useProjectApiTokens/useProjectApiTokens';
 | 
			
		||||
import { ArcherContainer, ArcherElement } from 'react-archer';
 | 
			
		||||
import useProjectApiTokensApi from 'hooks/api/actions/useProjectApiTokensApi/useProjectApiTokensApi';
 | 
			
		||||
import { formatUnknownError } from 'utils/formatUnknownError';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import { parseToken } from './parseToken';
 | 
			
		||||
 | 
			
		||||
interface IConnectSDKDialogProps {
 | 
			
		||||
    open: boolean;
 | 
			
		||||
@ -19,10 +27,11 @@ interface IConnectSDKDialogProps {
 | 
			
		||||
    environments: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ConceptsDefinitions = styled('div')(({ theme }) => ({
 | 
			
		||||
const ConceptsDefinitionsWrapper = styled('div')(({ theme }) => ({
 | 
			
		||||
    backgroundColor: theme.palette.background.sidebar,
 | 
			
		||||
    padding: theme.spacing(8),
 | 
			
		||||
    flexBasis: '30%',
 | 
			
		||||
    padding: theme.spacing(6),
 | 
			
		||||
    flex: 0,
 | 
			
		||||
    minWidth: '400px',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const IconStyle = ({ theme }: { theme: Theme }) => ({
 | 
			
		||||
@ -59,7 +68,7 @@ export const APIKeyGeneration = styled('div')(({ theme }) => ({
 | 
			
		||||
    backgroundColor: theme.palette.background.paper,
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    flexBasis: '70%',
 | 
			
		||||
    flex: 1,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const SpacedContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
@ -106,15 +115,198 @@ const NextStepSectionSpacedContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    padding: theme.spacing(3, 8, 3, 8),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const SecretExplanation = styled('div')(({ theme }) => ({
 | 
			
		||||
    backgroundColor: theme.palette.background.elevation1,
 | 
			
		||||
    borderRadius: theme.shape.borderRadius,
 | 
			
		||||
    padding: theme.spacing(3),
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    alignItems: 'center',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const SecretExplanationDescription = styled('div')(({ theme }) => ({
 | 
			
		||||
    backgroundColor: theme.palette.primary.contrastText,
 | 
			
		||||
    borderRadius: theme.shape.borderRadius,
 | 
			
		||||
    padding: theme.spacing(2),
 | 
			
		||||
    flex: 1,
 | 
			
		||||
    color: theme.palette.text.secondary,
 | 
			
		||||
    fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const ConceptsDefinitions = () => (
 | 
			
		||||
    <ConceptsDefinitionsWrapper>
 | 
			
		||||
        <ConceptItem>
 | 
			
		||||
            <StyledProjectIcon />
 | 
			
		||||
            <Box>
 | 
			
		||||
                <ConceptSummary>Flags live in projects</ConceptSummary>
 | 
			
		||||
                <ConceptDetails>
 | 
			
		||||
                    Projects are containers for feature flags. When you create a
 | 
			
		||||
                    feature flag it will belong to the project you create it in.
 | 
			
		||||
                </ConceptDetails>
 | 
			
		||||
            </Box>
 | 
			
		||||
        </ConceptItem>
 | 
			
		||||
        <ConceptItem>
 | 
			
		||||
            <StyledEnvironmentsIcon />
 | 
			
		||||
            <Box>
 | 
			
		||||
                <ConceptSummary>
 | 
			
		||||
                    Flags have configuration in environments
 | 
			
		||||
                </ConceptSummary>
 | 
			
		||||
                <ConceptDetails>
 | 
			
		||||
                    In Unleash you can have multiple environments. Each feature
 | 
			
		||||
                    flag will have different configuration in every environment.
 | 
			
		||||
                </ConceptDetails>
 | 
			
		||||
            </Box>
 | 
			
		||||
        </ConceptItem>
 | 
			
		||||
        <ConceptItem>
 | 
			
		||||
            <StyledCodeIcon />
 | 
			
		||||
            <Box>
 | 
			
		||||
                <ConceptSummary>
 | 
			
		||||
                    SDKs connect to Unleash to retrieve configuration
 | 
			
		||||
                </ConceptSummary>
 | 
			
		||||
                <ConceptDetails>
 | 
			
		||||
                    When you connect an SDK to Unleash it will use the API key
 | 
			
		||||
                    to deduce which feature flags to retrieve and from which
 | 
			
		||||
                    environment to retrieve configuration.
 | 
			
		||||
                </ConceptDetails>
 | 
			
		||||
            </Box>
 | 
			
		||||
        </ConceptItem>
 | 
			
		||||
    </ConceptsDefinitionsWrapper>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const TokenExplanationBox = styled(Box)(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    gap: theme.spacing(2),
 | 
			
		||||
    alignItems: 'flex-start',
 | 
			
		||||
    marginTop: theme.spacing(8),
 | 
			
		||||
    flexWrap: 'wrap',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const TokenExplanation = ({
 | 
			
		||||
    project,
 | 
			
		||||
    environment,
 | 
			
		||||
    secret,
 | 
			
		||||
}: { project: string; environment: string; secret: string }) => {
 | 
			
		||||
    const theme = useTheme();
 | 
			
		||||
    const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'));
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ArcherContainer
 | 
			
		||||
            strokeColor={theme.palette.secondary.border}
 | 
			
		||||
            endMarker={false}
 | 
			
		||||
            lineStyle='curve'
 | 
			
		||||
        >
 | 
			
		||||
            <SecretExplanation>
 | 
			
		||||
                <Box sx={{ wordBreak: 'break-all' }}>
 | 
			
		||||
                    <ArcherElement id='project'>
 | 
			
		||||
                        <span>{project}</span>
 | 
			
		||||
                    </ArcherElement>
 | 
			
		||||
                    :
 | 
			
		||||
                    <ArcherElement id='environment'>
 | 
			
		||||
                        <span>{environment}</span>
 | 
			
		||||
                    </ArcherElement>
 | 
			
		||||
                    .
 | 
			
		||||
                    <ArcherElement id='secret'>
 | 
			
		||||
                        <span>{secret}</span>
 | 
			
		||||
                    </ArcherElement>
 | 
			
		||||
                </Box>
 | 
			
		||||
 | 
			
		||||
                {isLargeScreen ? (
 | 
			
		||||
                    <TokenExplanationBox>
 | 
			
		||||
                        <ArcherElement
 | 
			
		||||
                            id='project-description'
 | 
			
		||||
                            relations={[
 | 
			
		||||
                                {
 | 
			
		||||
                                    targetId: 'project',
 | 
			
		||||
                                    targetAnchor: 'bottom',
 | 
			
		||||
                                    sourceAnchor: 'top',
 | 
			
		||||
                                },
 | 
			
		||||
                            ]}
 | 
			
		||||
                        >
 | 
			
		||||
                            <SecretExplanationDescription>
 | 
			
		||||
                                The project this API key will retrieve feature
 | 
			
		||||
                                flags from
 | 
			
		||||
                            </SecretExplanationDescription>
 | 
			
		||||
                        </ArcherElement>
 | 
			
		||||
                        <ArcherElement
 | 
			
		||||
                            id='environment-description'
 | 
			
		||||
                            relations={[
 | 
			
		||||
                                {
 | 
			
		||||
                                    targetId: 'environment',
 | 
			
		||||
                                    targetAnchor: 'bottom',
 | 
			
		||||
                                    sourceAnchor: 'top',
 | 
			
		||||
                                },
 | 
			
		||||
                            ]}
 | 
			
		||||
                        >
 | 
			
		||||
                            <SecretExplanationDescription>
 | 
			
		||||
                                The environment the API key will retrieve
 | 
			
		||||
                                feature flag configuration from
 | 
			
		||||
                            </SecretExplanationDescription>
 | 
			
		||||
                        </ArcherElement>
 | 
			
		||||
                        <ArcherElement
 | 
			
		||||
                            id='secreat-description'
 | 
			
		||||
                            relations={[
 | 
			
		||||
                                {
 | 
			
		||||
                                    targetId: 'secret',
 | 
			
		||||
                                    targetAnchor: 'bottom',
 | 
			
		||||
                                    sourceAnchor: 'top',
 | 
			
		||||
                                },
 | 
			
		||||
                            ]}
 | 
			
		||||
                        >
 | 
			
		||||
                            <SecretExplanationDescription>
 | 
			
		||||
                                The API key secret
 | 
			
		||||
                            </SecretExplanationDescription>
 | 
			
		||||
                        </ArcherElement>
 | 
			
		||||
                    </TokenExplanationBox>
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </SecretExplanation>
 | 
			
		||||
        </ArcherContainer>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ChooseEnvironment = ({
 | 
			
		||||
    environments,
 | 
			
		||||
    onSelect,
 | 
			
		||||
    currentEnvironment,
 | 
			
		||||
}: {
 | 
			
		||||
    environments: string[];
 | 
			
		||||
    currentEnvironment: string;
 | 
			
		||||
    onSelect: (env: string) => void;
 | 
			
		||||
}) => {
 | 
			
		||||
    const longestEnv = Math.max(
 | 
			
		||||
        ...environments.map((environment) => environment.length),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <SingleSelectConfigButton
 | 
			
		||||
            tooltip={{ header: '' }}
 | 
			
		||||
            description='Select the environment where API key will be created'
 | 
			
		||||
            options={environments.map((environment) => ({
 | 
			
		||||
                label: environment,
 | 
			
		||||
                value: environment,
 | 
			
		||||
            }))}
 | 
			
		||||
            onChange={(value: any) => {
 | 
			
		||||
                onSelect(value);
 | 
			
		||||
            }}
 | 
			
		||||
            button={{
 | 
			
		||||
                label: currentEnvironment,
 | 
			
		||||
                icon: <EnvironmentsIcon />,
 | 
			
		||||
                labelWidth: `${longestEnv + 5}ch`,
 | 
			
		||||
            }}
 | 
			
		||||
            search={{
 | 
			
		||||
                label: 'Filter project mode options',
 | 
			
		||||
                placeholder: 'Select project mode',
 | 
			
		||||
            }}
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ConnectSDKDialog = ({
 | 
			
		||||
    open,
 | 
			
		||||
    onClose,
 | 
			
		||||
    environments,
 | 
			
		||||
    project,
 | 
			
		||||
}: IConnectSDKDialogProps) => {
 | 
			
		||||
    const [environment, setEnvironment] = useState('');
 | 
			
		||||
    const longestEnv = Math.max(
 | 
			
		||||
        ...environments.map((environment) => environment.length),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (environments.length > 0) {
 | 
			
		||||
@ -122,6 +314,34 @@ export const ConnectSDKDialog = ({
 | 
			
		||||
        }
 | 
			
		||||
    }, [JSON.stringify(environments)]);
 | 
			
		||||
 | 
			
		||||
    const { tokens, refetch: refreshTokens } = useProjectApiTokens(project);
 | 
			
		||||
    const { createToken, loading: creatingToken } = useProjectApiTokensApi();
 | 
			
		||||
    const currentEnvironmentToken = tokens.find(
 | 
			
		||||
        (token) => token.environment === environment,
 | 
			
		||||
    );
 | 
			
		||||
    const parsedToken = parseToken(currentEnvironmentToken?.secret);
 | 
			
		||||
 | 
			
		||||
    const theme = useTheme();
 | 
			
		||||
    const isLargeScreen = useMediaQuery(theme.breakpoints.up('lg'));
 | 
			
		||||
    const { setToastApiError } = useToast();
 | 
			
		||||
 | 
			
		||||
    const generateAPIKey = async () => {
 | 
			
		||||
        try {
 | 
			
		||||
            await createToken(
 | 
			
		||||
                {
 | 
			
		||||
                    environment,
 | 
			
		||||
                    type: 'CLIENT',
 | 
			
		||||
                    projects: [project],
 | 
			
		||||
                    username: 'onboarding-api-key',
 | 
			
		||||
                },
 | 
			
		||||
                project,
 | 
			
		||||
            );
 | 
			
		||||
            refreshTokens();
 | 
			
		||||
        } catch (error: unknown) {
 | 
			
		||||
            setToastApiError(formatUnknownError(error));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledDialog open={open} onClose={onClose}>
 | 
			
		||||
            <Box sx={{ display: 'flex' }}>
 | 
			
		||||
@ -130,49 +350,54 @@ export const ConnectSDKDialog = ({
 | 
			
		||||
                        <Typography variant='h2'>
 | 
			
		||||
                            Connect an SDK to Unleash
 | 
			
		||||
                        </Typography>
 | 
			
		||||
                        <Box>
 | 
			
		||||
                        <Box sx={{ mt: 4 }}>
 | 
			
		||||
                            <SectionHeader>Environment</SectionHeader>
 | 
			
		||||
                            <SectionDescription>
 | 
			
		||||
                                The environment SDK will connect to in order to
 | 
			
		||||
                                retrieve configuration.
 | 
			
		||||
                            </SectionDescription>
 | 
			
		||||
                            {environments.length > 0 ? (
 | 
			
		||||
                                <SingleSelectConfigButton
 | 
			
		||||
                                    tooltip={{ header: '' }}
 | 
			
		||||
                                    description='Select the environment where API key will be created'
 | 
			
		||||
                                    options={environments.map(
 | 
			
		||||
                                        (environment) => ({
 | 
			
		||||
                                            label: environment,
 | 
			
		||||
                                            value: environment,
 | 
			
		||||
                                        }),
 | 
			
		||||
                                    )}
 | 
			
		||||
                                    onChange={(value: any) => {
 | 
			
		||||
                                        setEnvironment(value);
 | 
			
		||||
                                    }}
 | 
			
		||||
                                    button={{
 | 
			
		||||
                                        label: environment,
 | 
			
		||||
                                        icon: <EnvironmentsIcon />,
 | 
			
		||||
                                        labelWidth: `${longestEnv + 5}ch`,
 | 
			
		||||
                                    }}
 | 
			
		||||
                                    search={{
 | 
			
		||||
                                        label: 'Filter project mode options',
 | 
			
		||||
                                        placeholder: 'Select project mode',
 | 
			
		||||
                                    }}
 | 
			
		||||
                                <ChooseEnvironment
 | 
			
		||||
                                    environments={environments}
 | 
			
		||||
                                    currentEnvironment={environment}
 | 
			
		||||
                                    onSelect={setEnvironment}
 | 
			
		||||
                                />
 | 
			
		||||
                            ) : null}
 | 
			
		||||
                        </Box>
 | 
			
		||||
 | 
			
		||||
                        <Box sx={{ mt: 3 }}>
 | 
			
		||||
                            <SectionHeader>API Key</SectionHeader>
 | 
			
		||||
                            <SectionDescription>
 | 
			
		||||
                                You currently have no active API keys for this
 | 
			
		||||
                                project/environment combination. You'll need to
 | 
			
		||||
                                generate and API key in order to proceed with
 | 
			
		||||
                                connecting your SDK.
 | 
			
		||||
                            </SectionDescription>
 | 
			
		||||
                            <Button variant='contained'>
 | 
			
		||||
                                Generate API Key
 | 
			
		||||
                            </Button>
 | 
			
		||||
                            {parsedToken ? (
 | 
			
		||||
                                <SectionDescription>
 | 
			
		||||
                                    Here is your generated API key. We will use
 | 
			
		||||
                                    it to connect to the{' '}
 | 
			
		||||
                                    <b>{parsedToken.project}</b> project in the{' '}
 | 
			
		||||
                                    <b>{parsedToken.environment}</b>{' '}
 | 
			
		||||
                                    environment.
 | 
			
		||||
                                </SectionDescription>
 | 
			
		||||
                            ) : (
 | 
			
		||||
                                <SectionDescription>
 | 
			
		||||
                                    You currently have no active API keys for
 | 
			
		||||
                                    this project/environment combination. You'll
 | 
			
		||||
                                    need to generate and API key in order to
 | 
			
		||||
                                    proceed with connecting your SDK.
 | 
			
		||||
                                </SectionDescription>
 | 
			
		||||
                            )}
 | 
			
		||||
                            {parsedToken ? (
 | 
			
		||||
                                <TokenExplanation
 | 
			
		||||
                                    project={parsedToken.project}
 | 
			
		||||
                                    environment={parsedToken.environment}
 | 
			
		||||
                                    secret={parsedToken.secret}
 | 
			
		||||
                                />
 | 
			
		||||
                            ) : (
 | 
			
		||||
                                <Button
 | 
			
		||||
                                    variant='contained'
 | 
			
		||||
                                    disabled={creatingToken}
 | 
			
		||||
                                    onClick={generateAPIKey}
 | 
			
		||||
                                >
 | 
			
		||||
                                    Generate API Key
 | 
			
		||||
                                </Button>
 | 
			
		||||
                            )}
 | 
			
		||||
                        </Box>
 | 
			
		||||
                    </SpacedContainer>
 | 
			
		||||
 | 
			
		||||
@ -185,49 +410,8 @@ export const ConnectSDKDialog = ({
 | 
			
		||||
                        </NextStepSectionSpacedContainer>
 | 
			
		||||
                    </NextStepSection>
 | 
			
		||||
                </APIKeyGeneration>
 | 
			
		||||
                <ConceptsDefinitions>
 | 
			
		||||
                    <ConceptItem>
 | 
			
		||||
                        <StyledProjectIcon />
 | 
			
		||||
                        <Box>
 | 
			
		||||
                            <ConceptSummary>
 | 
			
		||||
                                Flags live in projects
 | 
			
		||||
                            </ConceptSummary>
 | 
			
		||||
                            <ConceptDetails>
 | 
			
		||||
                                Projects are containers for feature flags. When
 | 
			
		||||
                                you create a feature flag it will belong to the
 | 
			
		||||
                                project you create it in.
 | 
			
		||||
                            </ConceptDetails>
 | 
			
		||||
                        </Box>
 | 
			
		||||
                    </ConceptItem>
 | 
			
		||||
                    <ConceptItem>
 | 
			
		||||
                        <StyledEnvironmentsIcon />
 | 
			
		||||
                        <Box>
 | 
			
		||||
                            <ConceptSummary>
 | 
			
		||||
                                Flags have configuration in environments
 | 
			
		||||
                            </ConceptSummary>
 | 
			
		||||
                            <ConceptDetails>
 | 
			
		||||
                                In Unleash you can have multiple environments.
 | 
			
		||||
                                Each feature flag will have different
 | 
			
		||||
                                configuration in every environment.
 | 
			
		||||
                            </ConceptDetails>
 | 
			
		||||
                        </Box>
 | 
			
		||||
                    </ConceptItem>
 | 
			
		||||
                    <ConceptItem>
 | 
			
		||||
                        <StyledCodeIcon />
 | 
			
		||||
                        <Box>
 | 
			
		||||
                            <ConceptSummary>
 | 
			
		||||
                                SDKs connect to Unleash to retrieve
 | 
			
		||||
                                configuration
 | 
			
		||||
                            </ConceptSummary>
 | 
			
		||||
                            <ConceptDetails>
 | 
			
		||||
                                When you connect an SDK to Unleash it will use
 | 
			
		||||
                                the API key to deduce which feature flags to
 | 
			
		||||
                                retrieve and from which environment to retrieve
 | 
			
		||||
                                configuration.
 | 
			
		||||
                            </ConceptDetails>
 | 
			
		||||
                        </Box>
 | 
			
		||||
                    </ConceptItem>
 | 
			
		||||
                </ConceptsDefinitions>
 | 
			
		||||
 | 
			
		||||
                {isLargeScreen ? <ConceptsDefinitions /> : null}
 | 
			
		||||
            </Box>
 | 
			
		||||
        </StyledDialog>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								frontend/src/component/onboarding/parseToken.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								frontend/src/component/onboarding/parseToken.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
import { parseToken } from './parseToken';
 | 
			
		||||
 | 
			
		||||
describe('parseToken', () => {
 | 
			
		||||
    test('should return null if token is undefined', () => {
 | 
			
		||||
        const result = parseToken(undefined);
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return null if token is an empty string', () => {
 | 
			
		||||
        const result = parseToken('');
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return the correct object when a valid token is provided', () => {
 | 
			
		||||
        const token = 'project123:env456.secret789';
 | 
			
		||||
        const result = parseToken(token);
 | 
			
		||||
        expect(result).toEqual({
 | 
			
		||||
            project: 'project123',
 | 
			
		||||
            environment: 'env456',
 | 
			
		||||
            secret: 'secret789',
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return null if the token does not have a colon', () => {
 | 
			
		||||
        const token = 'project123env456.secret789';
 | 
			
		||||
        const result = parseToken(token);
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return null if the token does not have a period', () => {
 | 
			
		||||
        const token = 'project123:env456secret789';
 | 
			
		||||
        const result = parseToken(token);
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return null if the token has an incomplete project part', () => {
 | 
			
		||||
        const token = ':env456.secret789';
 | 
			
		||||
        const result = parseToken(token);
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return null if the token has an incomplete environment part', () => {
 | 
			
		||||
        const token = 'project123:.secret789';
 | 
			
		||||
        const result = parseToken(token);
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return null if the token has an incomplete secret part', () => {
 | 
			
		||||
        const token = 'project123:env456.';
 | 
			
		||||
        const result = parseToken(token);
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return null if the token has extra parts', () => {
 | 
			
		||||
        const token = 'project123:env456.secret789.extra';
 | 
			
		||||
        const result = parseToken(token);
 | 
			
		||||
        expect(result).toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										20
									
								
								frontend/src/component/onboarding/parseToken.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/src/component/onboarding/parseToken.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
export const parseToken = (
 | 
			
		||||
    token?: string,
 | 
			
		||||
): { project: string; environment: string; secret: string } | null => {
 | 
			
		||||
    if (!token) return null;
 | 
			
		||||
 | 
			
		||||
    const [project, rest] = token.split(':', 2);
 | 
			
		||||
    if (!rest) return null;
 | 
			
		||||
 | 
			
		||||
    const [environment, secret, ...extra] = rest.split('.');
 | 
			
		||||
 | 
			
		||||
    if (project && environment && secret && extra.length === 0) {
 | 
			
		||||
        return {
 | 
			
		||||
            project,
 | 
			
		||||
            environment,
 | 
			
		||||
            secret,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
};
 | 
			
		||||
@ -94,7 +94,7 @@ export const theme = {
 | 
			
		||||
            contrastText: colors.grey[50], // Color used for content when primary.main is used as a background
 | 
			
		||||
        },
 | 
			
		||||
        secondary: {
 | 
			
		||||
            // Used for purple badges and puple light elements
 | 
			
		||||
            // Used for purple badges and purple light elements
 | 
			
		||||
            main: colors.purple[800],
 | 
			
		||||
            light: colors.purple[50],
 | 
			
		||||
            dark: colors.purple[900], // Color used for text
 | 
			
		||||
@ -150,7 +150,7 @@ export const theme = {
 | 
			
		||||
            default: colors.grey[50],
 | 
			
		||||
            application: colors.grey[300],
 | 
			
		||||
            sidebar: colors.purple[800],
 | 
			
		||||
            alternative: colors.purple[800], // used on the dark theme to shwitch primary main to a darker shade
 | 
			
		||||
            alternative: colors.purple[800], // used on the dark theme to switch primary main to a darker shade
 | 
			
		||||
            elevation1: colors.grey[100],
 | 
			
		||||
            elevation2: colors.grey[200],
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user