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 = {