From c0e681863acef16b043630faf070744ab86c173c Mon Sep 17 00:00:00 2001 From: TJ Date: Wed, 26 Aug 2026 12:00:47 -0700 Subject: [PATCH] feat(site): show disabled state on AI provider rows (#28558) Updates the AI providers list to make the disabled state visible on the row itself. Disabled providers show a `Disabled` badge beside the name, a muted avatar, and disabled text for the name and base URL. The `Enabled` badge is removed from the status column, which now holds only issue badges (`Not supported in Agents` and `Warning`). Also fixes tooltip inconsistency in the status column: the `Not supported in Agents` badge used a native `title` attribute while the warning used the shared Tooltip component, so they looked different. Both now use the shared tooltip, with a max width so long warnings wrap instead of rendering as one long line. Storybook interaction coverage: new `Disabled`/`Enabled` stories, and `NotSupportedInAgents` now hovers the badge and asserts the shared tooltip content. --- Created by Coder Agents on behalf of @tracyjohnsonux. --- .../ProvidersPage/ProvidersPageView.tsx | 4 +- .../components/ProviderRow.stories.tsx | 49 +++++++++++++------ .../ProvidersPage/components/ProviderRow.tsx | 48 +++++++++++++----- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx b/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx index dabc4090ea..9b2e2deac5 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx +++ b/site/src/pages/AISettingsPage/ProvidersPage/ProvidersPageView.tsx @@ -103,7 +103,9 @@ const ProvidersPageView: React.FC = ({ Name Base URL - Status + + Status + diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.stories.tsx b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.stories.tsx index b6a3fb7211..0dda38a2f7 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.stories.tsx +++ b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.stories.tsx @@ -23,12 +23,17 @@ const meta: Meta = { }, decorators: [ (Story) => ( - +
- Name - Base URL - Status + Name + Base URL + + Status + + + Open provider + @@ -77,19 +82,36 @@ export const NotSupportedInAgents: Story = { args: { provider: MockAIProviderCopilot, }, - play: async ({ canvasElement }) => { + play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); const badge = canvas.getByRole("button", { name: "Not supported in Agents", }); await expect(badge).toBeInTheDocument(); + await userEvent.hover(badge); - const tooltip = await within(canvasElement.ownerDocument.body).findByRole( - "tooltip", - ); + const tooltip = await within(document.body).findByRole("tooltip"); await expect(tooltip).toHaveTextContent( "This provider works with the AI Gateway Proxy but Coder Agents can't use it.", ); + + // Activation must not navigate the row. + badge.focus(); + await userEvent.keyboard("{Enter}"); + await userEvent.keyboard(" "); + await userEvent.click(badge); + await expect(args.onClick).not.toHaveBeenCalled(); + }, +}; + +export const Disabled: Story = { + args: { + provider: { ...MockAIProviderOpenAI, enabled: false }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(canvas.getByText("Disabled")).toBeInTheDocument(); + await expect(canvas.getByText("OpenAI")).toBeInTheDocument(); }, }; @@ -103,6 +125,7 @@ export const SupportedHasNoAgentsLabel: Story = { await expect( canvas.queryByText("Not supported in Agents"), ).not.toBeInTheDocument(); + await expect(canvas.queryByText("Disabled")).not.toBeInTheDocument(); }, }; @@ -120,19 +143,15 @@ export const WithHostnameCollisionWarning: Story = { }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); - const badge = canvas.getByLabelText(/^Warning: Hostname/); + const badge = canvas.getByRole("button", { name: /^Warning:/ }); await expect(badge).toBeInTheDocument(); - await expect(badge).toHaveAttribute( - "aria-label", + await expect(badge).toHaveAccessibleName( expect.stringContaining("api.openai.com"), ); - await expect(badge).toHaveAttribute("tabIndex", "0"); // Hover shows the tooltip with the warning text. await userEvent.hover(badge); - const tooltip = await within(canvasElement.ownerDocument.body).findByRole( - "tooltip", - ); + const tooltip = await within(document.body).findByRole("tooltip"); await expect(tooltip).toHaveTextContent("api.openai.com"); // Keyboard and mouse activation must not navigate the row. diff --git a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx index 6a889dab7a..2b02d37a42 100644 --- a/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx +++ b/site/src/pages/AISettingsPage/ProvidersPage/components/ProviderRow.tsx @@ -13,6 +13,7 @@ import { TooltipTrigger, } from "#/components/Tooltip/Tooltip"; import { useClickableTableRow } from "#/hooks/useClickableTableRow"; +import { cn } from "#/utils/cn"; import { ProviderIcon } from "./ProviderIcon"; import { getProviderDisplayType } from "./providerFormApiMap"; @@ -29,6 +30,7 @@ export const ProviderRow: React.FC = ({ onClick: () => onClick?.(), }); const displayName = provider.display_name || provider.name; + const disabled = !provider.enabled; // Stop activation from bubbling to a parent `useClickableTableRow` // row, which navigates on click, Enter (onKeyDown), and Space @@ -42,11 +44,27 @@ export const ProviderRow: React.FC = ({ + + {displayName} + + {disabled && ( + + Disabled + + )} + + } avatar={ = ({ {provider.base_url} @@ -66,19 +87,17 @@ export const ProviderRow: React.FC = ({
- {provider.enabled && Enabled} {AgentsUnsupportedProviderTypes.some((t) => t === provider.type) && ( - Not supported in Agents + @@ -91,20 +110,25 @@ export const ProviderRow: React.FC = ({ - Warning + {provider.status.warnings.map((warning) => ( -

{warning}

+

+ {warning} +

))}