diff --git a/web/packages/teleport/src/Bots/Details/BotDetails.test.tsx b/web/packages/teleport/src/Bots/Details/BotDetails.test.tsx index abf2867a2f278..dfe63fef0f215 100644 --- a/web/packages/teleport/src/Bots/Details/BotDetails.test.tsx +++ b/web/packages/teleport/src/Bots/Details/BotDetails.test.tsx @@ -251,6 +251,42 @@ describe('BotDetails', () => { ).toBeInTheDocument(); }); + describe('should show bot join tokens empty message', () => { + it('when an empty list is returned', async () => { + withFetchSuccess(); + withFetchJoinTokensSuccess({ tokens: [] }); + withFetchInstancesSuccess(); + withListLocksSuccess(); + renderComponent(); + await waitForLoadingBot(); + await waitForLoadingTokens(); + + const panel = screen + .getByRole('heading', { name: 'Join Tokens' }) + .closest('section'); + expect(panel).toBeInTheDocument(); + + expect(within(panel!).getByText('No join tokens')).toBeInTheDocument(); + }); + + it('when null is returned', async () => { + withFetchSuccess(); + withFetchJoinTokensSuccess({ tokens: null }); + withFetchInstancesSuccess(); + withListLocksSuccess(); + renderComponent(); + await waitForLoadingBot(); + await waitForLoadingTokens(); + + const panel = screen + .getByRole('heading', { name: 'Join Tokens' }) + .closest('section'); + expect(panel).toBeInTheDocument(); + + expect(within(panel!).getByText('No join tokens')).toBeInTheDocument(); + }); + }); + it('should show bot instances', async () => { withFetchSuccess(); withFetchJoinTokensSuccess(); @@ -728,8 +764,11 @@ const withFetchSuccess = () => { server.use(getBotSuccess()); }; -const withFetchJoinTokensSuccess = () => { - server.use(listV2TokensSuccess()); +const withFetchJoinTokensSuccess = (options?: { + hasNextPage?: boolean; + tokens?: string[] | null; +}) => { + server.use(listV2TokensSuccess(options)); }; const withFetchJoinTokensMfaError = () => { diff --git a/web/packages/teleport/src/Bots/Details/BotDetails.tsx b/web/packages/teleport/src/Bots/Details/BotDetails.tsx index 2657732802467..f9a6885a52eb1 100644 --- a/web/packages/teleport/src/Bots/Details/BotDetails.tsx +++ b/web/packages/teleport/src/Bots/Details/BotDetails.tsx @@ -522,7 +522,7 @@ function JoinTokens(props: { botName: string; onViewAllClicked: () => void }) { {isSuccess ? ( <> - {data.items.length ? ( + {data.items?.length ? ( {data.items .toSorted((a, b) => a.safeName.localeCompare(b.safeName)) diff --git a/web/packages/teleport/src/services/joinToken/consts.ts b/web/packages/teleport/src/services/joinToken/consts.ts index a01c4cb1d63e3..3fe96cdc0e8f0 100644 --- a/web/packages/teleport/src/services/joinToken/consts.ts +++ b/web/packages/teleport/src/services/joinToken/consts.ts @@ -29,6 +29,10 @@ export function validateListJoinTokensResponse( return false; } + if (!data.items) { + return true; + } + if (!Array.isArray(data.items)) { return false; } diff --git a/web/packages/teleport/src/services/joinToken/types.ts b/web/packages/teleport/src/services/joinToken/types.ts index a4fdc7ba5fad2..36c281992872b 100644 --- a/web/packages/teleport/src/services/joinToken/types.ts +++ b/web/packages/teleport/src/services/joinToken/types.ts @@ -195,6 +195,6 @@ export type JoinTokenRequest = { }; export type ListJoinTokensResponse = { - items: JoinToken[]; + items?: JoinToken[] | null; next_page_token?: string; }; diff --git a/web/packages/teleport/src/test/helpers/tokens.ts b/web/packages/teleport/src/test/helpers/tokens.ts index 6505560be9de3..69820da484600 100644 --- a/web/packages/teleport/src/test/helpers/tokens.ts +++ b/web/packages/teleport/src/test/helpers/tokens.ts @@ -42,36 +42,39 @@ export const listV2TokensError = ( export const listV2TokensSuccess = (options?: { hasNextPage?: boolean; - tokens?: string[]; + tokens?: string[] | null; }) => { const { hasNextPage = false, tokens } = options ?? {}; return http.get(cfg.api.joinToken.listV2, () => { return HttpResponse.json( { - items: ( - tokens ?? [ - 'token', - 'ec2', - 'iam', - 'github', - 'circleci', - 'kubernetes', - 'azure', - 'gitlab', - 'gcp', - 'spacelift', - 'tpm', - 'terraform_cloud', - 'bitbucket', - 'oracle', - 'azure_devops', - 'bound_keypair', - ] - ).map(method => ({ - id: `token-${method}`, - safeName: method, - method: method, - })), + items: + tokens === null + ? null + : ( + tokens ?? [ + 'token', + 'ec2', + 'iam', + 'github', + 'circleci', + 'kubernetes', + 'azure', + 'gitlab', + 'gcp', + 'spacelift', + 'tpm', + 'terraform_cloud', + 'bitbucket', + 'oracle', + 'azure_devops', + 'bound_keypair', + ] + ).map(method => ({ + id: `token-${method}`, + safeName: method, + method: method, + })), next_page_token: hasNextPage ? 'yes' : undefined, }, { status: 200 }