From 8aa812e0f5c80a5bc535cde0974f9f7c280f800a Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:31:42 +0200 Subject: [PATCH] feat: Orphaned tokens - new API tokens list icon (#7693) Moving warning icon from "projects" column, to left icon. --- .../ApiTokenIcon/ApiTokenIcon.test.tsx | 46 +++++++++++++++ .../apiToken/ApiTokenIcon/ApiTokenIcon.tsx | 46 +++++++++++++++ .../ProjectsList/ProjectsList.test.tsx | 57 +------------------ .../apiToken/ProjectsList/ProjectsList.tsx | 37 +----------- .../common/ApiTokenTable/useApiTokenTable.tsx | 8 +-- 5 files changed, 99 insertions(+), 95 deletions(-) create mode 100644 frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.test.tsx create mode 100644 frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.tsx diff --git a/frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.test.tsx b/frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.test.tsx new file mode 100644 index 0000000000..3f0e7f924d --- /dev/null +++ b/frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.test.tsx @@ -0,0 +1,46 @@ +import { render } from 'utils/testRenderer'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ApiTokenIcon } from './ApiTokenIcon'; + +describe('ApiTokenIcon', () => { + it('should show warning icon if it is an orphaned token', async () => { + render( + , + ); + + const errorIcon = await screen.findByTestId('orphaned-token-icon'); + expect(errorIcon).toBeInTheDocument(); + }); + + it('should show tooltip with warning message if it is an orphaned token', async () => { + const user = userEvent.setup(); + render( + , + ); + + const errorIcon = await screen.findByTestId('orphaned-token-icon'); + 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( + , + ); + + const errorIcon = await screen.queryByTestId('orphaned-token-icon'); + expect(errorIcon).toBeNull(); + }); + + it('should not show warning for true wildcard tokens', async () => { + render( + , + ); + + const errorIcon = await screen.queryByTestId('orphaned-token-icon'); + expect(errorIcon).toBeNull(); + }); +}); diff --git a/frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.tsx b/frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.tsx new file mode 100644 index 0000000000..ad82509c87 --- /dev/null +++ b/frontend/src/component/admin/apiToken/ApiTokenIcon/ApiTokenIcon.tsx @@ -0,0 +1,46 @@ +import type { FC } from 'react'; +import KeyIcon from '@mui/icons-material/Key'; +import WarningIcon from '@mui/icons-material/WarningAmber'; +import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; +import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; + +interface IApiTokenIconProps { + project?: string; + projects?: string | string[]; + secret?: string; +} + +export const ApiTokenIcon: FC = ({ secret }) => { + 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; + + if (isOrphanedToken) { + return ( + + 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. +

+ } + placement='bottom-start' + arrow + > + + + } + /> + ); + } + + return } />; +}; diff --git a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx index c2b717834c..396e179d46 100644 --- a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx +++ b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.test.tsx @@ -1,7 +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'; +import { ProjectsList } from './ProjectsList'; describe('ProjectsList', () => { it('should prioritize new "projects" array over deprecated "project"', async () => { @@ -56,58 +55,4 @@ describe('ProjectsList', () => { expect(container.textContent).toEqual('*'); }); - - describe('orphaned tokens', () => { - it('should show warning icon if it is an orphaned token', async () => { - render( - , - ); - - 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( - , - ); - - 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( - , - ); - - const errorIcon = await screen.queryByTestId('ErrorIcon'); - expect(errorIcon).toBeNull(); - }); - - it('should not show warning for wildcard tokens', async () => { - render( - , - ); - - const errorIcon = await screen.queryByTestId('ErrorIcon'); - expect(errorIcon).toBeNull(); - }); - }); }); diff --git a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx index 4be5daae7c..7e802018d9 100644 --- a/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx +++ b/frontend/src/component/admin/apiToken/ProjectsList/ProjectsList.tsx @@ -5,9 +5,7 @@ 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 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', @@ -17,12 +15,6 @@ 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', @@ -31,14 +23,9 @@ const StyledContainer = styled('div')({ interface IProjectsListProps { project?: string; projects?: string | string[]; - secret?: string; } -export const ProjectsList: FC = ({ - projects, - project, - secret, -}) => { +export const ProjectsList: FC = ({ projects, project }) => { const { searchQuery } = useSearchHighlightContext(); const projectsList = @@ -85,35 +72,15 @@ export const ProjectsList: FC = ({ return ; } - 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 ( - 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.' - ) - } + title='ALL current and future projects.' placement='bottom' arrow > * - } - /> diff --git a/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx b/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx index 4012414daf..48df548ead 100644 --- a/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx +++ b/frontend/src/component/common/ApiTokenTable/useApiTokenTable.tsx @@ -2,7 +2,6 @@ import { useMemo } from 'react'; import type { IApiToken } from 'hooks/api/getters/useApiTokens/useApiTokens'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; -import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; import { useTable, @@ -12,7 +11,7 @@ import { } from 'react-table'; import { sortTypes } from 'utils/sortTypes'; import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList'; -import Key from '@mui/icons-material/Key'; +import { ApiTokenIcon } from 'component/admin/apiToken/ApiTokenIcon/ApiTokenIcon'; export const useApiTokenTable = ( tokens: IApiToken[], @@ -27,7 +26,9 @@ export const useApiTokenTable = ( return [ { id: 'Icon', - Cell: () => } />, + Cell: (props: any) => ( + + ), disableSortBy: true, disableGlobalFilter: true, width: 50, @@ -61,7 +62,6 @@ export const useApiTokenTable = ( ), width: 160,