From 60e7726116493314e08e1a160e9344d650b6b46d Mon Sep 17 00:00:00 2001 From: Yujong Lee Date: Sun, 4 Oct 2026 13:54:19 -0700 Subject: [PATCH] fix(ui): always show the Lens Settings tab The tab only rendered once GET /lens succeeded for a configuring session, so a slow or failing list, or a read-only session, silently hid it. Settings now always renders, and the worker section shows only where it can be used Co-Authored-By: Claude Opus 5.5 --- .../src/components/lens/LensModeSwitch.tsx | 6 +-- .../lens/LensWorkspace.integration.test.tsx | 38 ++++++++++++------- .../src/components/lens/LensWorkspace.tsx | 31 ++++++++------- .../components/lens/settings/LensSettings.tsx | 22 ++++++----- 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/ui/litellm-dashboard/src/components/lens/LensModeSwitch.tsx b/ui/litellm-dashboard/src/components/lens/LensModeSwitch.tsx index 0cfa9e47bce..16bea129f86 100644 --- a/ui/litellm-dashboard/src/components/lens/LensModeSwitch.tsx +++ b/ui/litellm-dashboard/src/components/lens/LensModeSwitch.tsx @@ -57,15 +57,15 @@ export function LensModeSwitch({ setup?: string; }) { const connected = useWorkerConnected(workers); - const settingsTitle = connected ? "Worker connected" : "Connect worker"; - const tabs = Object.entries(LENS_TABS).filter(([view]) => view !== "settings" || workers); + const workerTitle = connected ? "Worker connected" : "Connect worker"; + const settingsTitle = workers === null ? LENS_TABS.settings : workerTitle; return (
- {tabs.map(([view, label]) => { + {Object.entries(LENS_TABS).map(([view, label]) => { const Icon = MODE_ICONS[view as LensTab]; const workerDisconnected = view === "settings" && workers !== null && !connected; return ( diff --git a/ui/litellm-dashboard/src/components/lens/LensWorkspace.integration.test.tsx b/ui/litellm-dashboard/src/components/lens/LensWorkspace.integration.test.tsx index ce99bbae283..d9a5b5826f0 100644 --- a/ui/litellm-dashboard/src/components/lens/LensWorkspace.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/lens/LensWorkspace.integration.test.tsx @@ -280,8 +280,8 @@ describe("Lens interactive demo", () => { }); renderWithProviders(, { onUrlUpdate }); const tabs = within(screen.getByRole("tablist", { name: "Lens" })); - const settings = await tabs.findByRole("tab", { name: "Settings" }); - expect(settings).toHaveAttribute("title", "Worker connected"); + const settings = tabs.getByRole("tab", { name: "Settings" }); + await waitFor(() => expect(settings).toHaveAttribute("title", "Worker connected")); await user.click(settings); await expectUrl(onUrlUpdate, (url) => expect(url.get("tab")).toBe("settings")); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); @@ -357,7 +357,7 @@ describe("Lens interactive demo", () => { onUrlUpdate, }); const panel = within(await screen.findByRole("region", { name: "Settings" })); - await user.click(panel.getByText("Advanced options")); + await user.click(await panel.findByText("Advanced options")); await user.click(panel.getByRole("switch", { name: "Use an existing virtual key" })); await user.click(panel.getByRole("combobox", { name: "Charge analysis to" })); await user.click(await screen.findByRole("option", { name: "Analysis" })); @@ -378,18 +378,28 @@ describe("Lens interactive demo", () => { expect(await screen.findByRole("region", { name: "New investigation" })).toBeVisible(); }); - it("hides the Settings tab for read-only sessions", async () => { - renderWithProviders(); - expect(await screen.findByRole("tablist", { name: "Lens" })).toBeVisible(); - await waitFor(() => expect(network).toHaveBeenCalled()); - expect(screen.queryByRole("tab", { name: "Settings" })).not.toBeInTheDocument(); - }); - - it("sends read-only sessions following a Settings link to the default tab", async () => { + it("opens Settings for read-only sessions without the worker controls", async () => { renderWithProviders(, { searchParams: "?tab=settings", }); - expect(await screen.findByRole("tab", { name: "Traces", selected: true })).toBeVisible(); + expect(await screen.findByRole("tab", { name: "Settings", selected: true })).toBeVisible(); + const panel = within(screen.getByRole("region", { name: "Settings" })); + expect(await panel.findByRole("status")).toHaveTextContent("Tracing is not enabled"); + expect(panel.getByRole("switch", { name: "Show the introduction on each new session" })).toBeVisible(); + expect(panel.queryByRole("heading", { name: "Analysis worker" })).not.toBeInTheDocument(); + }); + + it("shows the Settings tab before /lens answers and when it fails", async () => { + network.mockImplementation(async (input) => + requestPath(input) === "/lens" + ? Response.json({ detail: "boom" }, { status: 500 }) + : Response.json({ data: [], traces: true, requests: false }), + ); + renderWithProviders(); + const tabs = within(screen.getByRole("tablist", { name: "Lens" })); + expect(tabs.getByRole("tab", { name: "Settings" })).toHaveAttribute("title", "Settings"); + await waitFor(() => expect(network.mock.calls.some(([input]) => requestPath(input) === "/lens")).toBe(true)); + expect(tabs.getByRole("tab", { name: "Settings" })).toBeVisible(); }); it("turns the worker health dot off once heartbeats expire even when polling returns unchanged data", async () => { @@ -412,8 +422,8 @@ describe("Lens interactive demo", () => { }); renderWithProviders(); const tabs = within(screen.getByRole("tablist", { name: "Lens" })); - const settings = await tabs.findByRole("tab", { name: "Settings" }); - expect(settings).toHaveAttribute("title", "Worker connected"); + const settings = tabs.getByRole("tab", { name: "Settings" }); + await waitFor(() => expect(settings).toHaveAttribute("title", "Worker connected")); await vi.advanceTimersByTimeAsync(130000); await waitFor(() => expect(settings).toHaveAttribute("title", "Connect worker")); } finally { diff --git a/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx b/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx index ccd2f665564..9fa1c1df8d6 100644 --- a/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx +++ b/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx @@ -86,7 +86,7 @@ function LensContent({ userRole, readOnly }: Omit const isAdmin = isProxyAdminRole(userRole); const canConfigure = canViewInvestigations && !readOnly; const defaultTab = lensId ? "investigations" : "traces"; - const activeTab = tab === "settings" && !canConfigure ? defaultTab : tab ?? defaultTab; + const activeTab = tab ?? defaultTab; const intro = useLensIntro({ demo, settingUp }); const { activity, list } = useLensOverview( canViewInvestigations, @@ -192,21 +192,20 @@ function LensContent({ userRole, readOnly }: Omit )} - {workers && list && ( - - - New investigation - - ) : undefined - } - onOpenTraces={() => setTab("traces")} - /> - - )} + + + New investigation + + ) : undefined + } + onOpenTraces={() => setTab("traces")} + /> +
void; }) { return (
- - - - + {tracingEnabled !== undefined && } + {workers && ( + + + + )}
);