From ba2534a203ef03aa6af8796a8b973965b87f5ce9 Mon Sep 17 00:00:00 2001 From: Michael Myers Date: Thu, 12 Dec 2024 11:34:53 -0600 Subject: [PATCH] Always show Bots page with conditional permissions error This will make the bots page always visible to users. If they do not have permissions, they will see the empty state as well as an error of what permissions are missing. --- .../teleport/src/Bots/List/Bots.test.tsx | 27 ++++++++++++++++--- web/packages/teleport/src/Bots/List/Bots.tsx | 25 ++++++++++++----- .../src/Bots/List/EmptyState/EmptyState.tsx | 12 +++++++-- web/packages/teleport/src/features.tsx | 4 +-- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/web/packages/teleport/src/Bots/List/Bots.test.tsx b/web/packages/teleport/src/Bots/List/Bots.test.tsx index 508d8a6d977d2..4a411cf53063f 100644 --- a/web/packages/teleport/src/Bots/List/Bots.test.tsx +++ b/web/packages/teleport/src/Bots/List/Bots.test.tsx @@ -22,13 +22,20 @@ import { render, screen, userEvent, waitFor } from 'design/utils/testing'; import api from 'teleport/services/api'; import { botsApiResponseFixture } from 'teleport/Bots/fixtures'; -import { createTeleportContext } from 'teleport/mocks/contexts'; +import { + allAccessAcl, + createTeleportContext, + noAccess, +} from 'teleport/mocks/contexts'; import { ContextProvider } from 'teleport/index'; +import TeleportContext from 'teleport/teleportContext'; import { Bots } from './Bots'; -function renderWithContext(element) { - const ctx = createTeleportContext(); +function renderWithContext(element, ctx?: TeleportContext) { + if (!ctx) { + ctx = createTeleportContext(); + } return render( {element} @@ -57,6 +64,20 @@ test('shows empty state when bots are empty', async () => { }); }); +test('shows missing permissions error if user lacks permissions to list', async () => { + jest.spyOn(api, 'get').mockResolvedValue({ items: [] }); + const ctx = createTeleportContext(); + ctx.storeUser.setState({ acl: { ...allAccessAcl, bots: noAccess } }); + renderWithContext(, ctx); + + await waitFor(() => { + expect(screen.getByTestId('bots-empty-state')).toBeInTheDocument(); + }); + expect( + screen.getByText(/You do not have permission to access Bots/i) + ).toBeInTheDocument(); +}); + test('calls edit endpoint', async () => { jest .spyOn(api, 'get') diff --git a/web/packages/teleport/src/Bots/List/Bots.tsx b/web/packages/teleport/src/Bots/List/Bots.tsx index 04348997df457..ed9e42bfc019f 100644 --- a/web/packages/teleport/src/Bots/List/Bots.tsx +++ b/web/packages/teleport/src/Bots/List/Bots.tsx @@ -45,12 +45,15 @@ export function Bots() { const ctx = useTeleport(); const flags = ctx.getFeatureFlags(); const hasAddBotPermissions = flags.addBots; + const canListBots = flags.listBots; const [bots, setBots] = useState([]); const [selectedBot, setSelectedBot] = useState(); const [selectedRoles, setSelectedRoles] = useState(); const { attempt: crudAttempt, run: crudRun } = useAttemptNext(); - const { attempt: fetchAttempt, run: fetchRun } = useAttemptNext('processing'); + const { attempt: fetchAttempt, run: fetchRun } = useAttemptNext( + canListBots ? 'processing' : 'success' + ); useEffect(() => { const signal = new AbortController(); @@ -60,15 +63,17 @@ export function Bots() { return await fetchBots(signal, flags); } - fetchRun(() => - bots(signal.signal).then(botRes => { - setBots(botRes.bots); - }) - ); + if (canListBots) { + fetchRun(() => + bots(signal.signal).then(botRes => { + setBots(botRes.bots); + }) + ); + } return () => { signal.abort(); }; - }, [ctx, fetchRun]); + }, [ctx, fetchRun, canListBots]); async function fetchRoleNames(search: string): Promise { const flags = ctx.getFeatureFlags(); @@ -122,6 +127,12 @@ export function Bots() { if (fetchAttempt.status === 'success' && bots.length === 0) { return ( + {!canListBots && ( + + You do not have permission to access Bots. Missing role permissions:{' '} + bot.list + + )} ); diff --git a/web/packages/teleport/src/Bots/List/EmptyState/EmptyState.tsx b/web/packages/teleport/src/Bots/List/EmptyState/EmptyState.tsx index f0b05642020b5..67cf8bc7bd9c9 100644 --- a/web/packages/teleport/src/Bots/List/EmptyState/EmptyState.tsx +++ b/web/packages/teleport/src/Bots/List/EmptyState/EmptyState.tsx @@ -209,12 +209,20 @@ export const BotTiles = () => { {integrationsTop.map(integration => ( - + ))} {integrationsBottom.map(integration => ( - + ))} diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index 7e698622162e5..75a4ca095191d 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -216,8 +216,8 @@ export class FeatureBots implements TeleportFeature { component: Bots, }; - hasAccess(flags: FeatureFlags) { - return flags.listBots; + hasAccess() { + return true; } navigationItem = {