mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-28 00:06:53 +01:00
feat: show orphaned API tokens (#7569)
Add a visual indication that a token was scoped to projects that have been deleted.
This commit is contained in:
parent
b9c3d101ba
commit
d440d3230a
@ -1,5 +1,6 @@
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList';
|
||||
|
||||
describe('ProjectsList', () => {
|
||||
@ -55,4 +56,58 @@ describe('ProjectsList', () => {
|
||||
|
||||
expect(container.textContent).toEqual('*');
|
||||
});
|
||||
|
||||
describe('orphaned tokens', () => {
|
||||
it('should show warning icon if it is an orphaned token', async () => {
|
||||
render(
|
||||
<ProjectsList
|
||||
projects={[]}
|
||||
secret='test:development.be7536c3a160ff15e3a92da45de531dd54bc1ae15d8455c0476f086b'
|
||||
/>,
|
||||
);
|
||||
|
||||
const errorIcon = await screen.findByTestId('ErrorIcon');
|
||||
expect(errorIcon).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show tooltip with warning message if it is an orphaned token', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<ProjectsList
|
||||
projects={[]}
|
||||
secret='test:development.be7536c3a160ff15e3a92da45de531dd54bc1ae15d8455c0476f086b'
|
||||
/>,
|
||||
);
|
||||
|
||||
const errorIcon = await screen.findByTestId('ErrorIcon');
|
||||
user.hover(errorIcon);
|
||||
|
||||
const tooltip = await screen.findByRole('tooltip');
|
||||
expect(tooltip).toHaveTextContent(/orphaned token/);
|
||||
});
|
||||
|
||||
it('should not show warning icon if token is in v1 format', async () => {
|
||||
render(
|
||||
<ProjectsList
|
||||
projects={[]}
|
||||
secret='be44368985f7fb3237c584ef86f3d6bdada42ddbd63a019d26955178'
|
||||
/>,
|
||||
);
|
||||
|
||||
const errorIcon = await screen.queryByTestId('ErrorIcon');
|
||||
expect(errorIcon).toBeNull();
|
||||
});
|
||||
|
||||
it('should not show warning for wildcard tokens', async () => {
|
||||
render(
|
||||
<ProjectsList
|
||||
projects={[]}
|
||||
secret='*:development.be7536c3a160ff15e3a92da45de531dd54bc1ae15d8455c0476f086b'
|
||||
/>,
|
||||
);
|
||||
|
||||
const errorIcon = await screen.queryByTestId('ErrorIcon');
|
||||
expect(errorIcon).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { Fragment, type FC } from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
import { Highlighter } from 'component/common/Highlighter/Highlighter';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { Fragment, type FC } from 'react';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
textDecoration: 'none',
|
||||
@ -15,12 +17,28 @@ const StyledLink = styled(Link)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledErrorIcon = styled(ErrorIcon)(({ theme }) => ({
|
||||
color: theme.palette.error.main,
|
||||
marginBottom: theme.spacing(0.5),
|
||||
marginLeft: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
const StyledContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
interface IProjectsListProps {
|
||||
project?: string;
|
||||
projects?: string | string[];
|
||||
secret?: string;
|
||||
}
|
||||
|
||||
export const ProjectsList: FC<IProjectsListProps> = ({ projects, project }) => {
|
||||
export const ProjectsList: FC<IProjectsListProps> = ({
|
||||
projects,
|
||||
project,
|
||||
secret,
|
||||
}) => {
|
||||
const { searchQuery } = useSearchHighlightContext();
|
||||
|
||||
const projectsList =
|
||||
@ -67,16 +85,36 @@ export const ProjectsList: FC<IProjectsListProps> = ({ projects, project }) => {
|
||||
return <LinkCell to={`/projects/${item}`} title={item} />;
|
||||
}
|
||||
|
||||
const tokenFormat = secret?.includes(':') ? 'v2' : 'v1'; // see https://docs.getunleash.io/reference/api-tokens-and-client-keys#format
|
||||
const isWildcardToken = secret?.startsWith('*:');
|
||||
const isOrphanedToken = tokenFormat === 'v2' && !isWildcardToken;
|
||||
|
||||
return (
|
||||
<TextCell>
|
||||
<HtmlTooltip
|
||||
title='ALL current and future projects'
|
||||
title={
|
||||
isOrphanedToken ? (
|
||||
<>
|
||||
This is an orphaned token. All of its original
|
||||
projects have been deleted and it now has access to
|
||||
all current and future projects. You should stop
|
||||
using this token and delete it. It will lose access
|
||||
to all projects at a later date.
|
||||
</>
|
||||
) : (
|
||||
'ALL current and future projects.'
|
||||
)
|
||||
}
|
||||
placement='bottom'
|
||||
arrow
|
||||
>
|
||||
<span>
|
||||
<StyledContainer>
|
||||
<Highlighter search={searchQuery}>*</Highlighter>
|
||||
</span>
|
||||
<ConditionallyRender
|
||||
condition={isOrphanedToken}
|
||||
show={<StyledErrorIcon aria-label='Orphaned token' />}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</HtmlTooltip>
|
||||
</TextCell>
|
||||
);
|
||||
|
@ -61,6 +61,7 @@ export const useApiTokenTable = (
|
||||
<ProjectsList
|
||||
project={props.row.original.project}
|
||||
projects={props.row.original.projects}
|
||||
secret={props.row.original.secret}
|
||||
/>
|
||||
),
|
||||
width: 160,
|
||||
|
Loading…
Reference in New Issue
Block a user