Merge pull request #38782 from BerriAI/litellm_/logs-reopen-shadcn-migration-2c9526

fix(ui): restore the reopen control for the log drawer's trace sidebar
This commit is contained in:
yuneng-jiang 2026-08-29 12:38:26 -07:00 committed by GitHub
commit d435a62ce6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 152 additions and 31 deletions

View file

@ -148,6 +148,32 @@ test.describe("Logs page", () => {
});
});
test("the trace sidebar collapses and expands again", async ({ page, request }) => {
const prompt = `logs-sidebar-prompt-${uniqueSuffix()}`;
const requestId = await sendChatCompletion(request, {
model: CHAT_MODEL_A,
prompt,
});
await waitForSpendLog(request, requestId);
const row = await openLogsForRequest(page, requestId);
await row.click();
const drawer = page.getByRole("dialog").first();
await expect(drawer.getByText("Request & Response")).toBeVisible({ timeout: 20_000 });
const toggle = drawer.getByLabel("Collapse trace sidebar");
await expect(toggle).toBeVisible({ timeout: 10_000 });
await toggle.click();
const expandToggle = drawer.getByLabel("Expand trace sidebar");
await expect(expandToggle).toBeVisible({ timeout: 10_000 });
await expandToggle.click({ timeout: 10_000 });
await expect(drawer.getByLabel("Collapse trace sidebar")).toBeVisible({ timeout: 10_000 });
await expect(drawer.getByText(prompt, { exact: false })).toBeVisible({ timeout: 10_000 });
});
test("the JSON view exposes Request and Response tabs", async ({ page, request }) => {
const prompt = `logs-json-prompt-${uniqueSuffix()}`;
const requestId = await sendChatCompletion(request, {

View file

@ -0,0 +1,67 @@
import { screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { render } from "../../../../tests/test-utils";
import type { LogEntry } from "../columns";
import { DrawerHeader } from "./DrawerHeader";
const logEntry = (overrides: Partial<LogEntry>): LogEntry =>
({
request_id: "170d64ea-69f0-431a-be72-332f8f78c18a",
api_key: "key-1",
team_id: "team-1",
model: "gpt-4o",
model_id: "model-1",
custom_llm_provider: "openai",
call_type: "acompletion",
spend: 0.01,
total_tokens: 10,
prompt_tokens: 5,
completion_tokens: 5,
startTime: "2026-07-07T09:50:13Z",
endTime: "2026-07-07T09:50:14Z",
cache_hit: "false",
messages: [],
response: {},
...overrides,
}) as LogEntry;
const renderHeader = (log: LogEntry, isSidebarCollapsed: boolean) =>
render(
<DrawerHeader
log={log}
onClose={vi.fn()}
onPrevious={vi.fn()}
onNext={vi.fn()}
isSidebarCollapsed={isSidebarCollapsed}
onToggleSidebar={vi.fn()}
statusLabel="Failure"
statusColor="error"
environment="default"
/>,
);
const expandToggle = () => screen.getByLabelText("Expand trace sidebar");
describe("DrawerHeader sidebar toggle", () => {
it("stays out of the header while the sidebar owns it", () => {
renderHeader(logEntry({}), false);
expect(screen.queryByLabelText("Expand trace sidebar")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Collapse trace sidebar")).not.toBeInTheDocument();
});
it("shares the model row once the sidebar is collapsed", () => {
renderHeader(logEntry({}), true);
const row = expandToggle().parentElement as HTMLElement;
expect(within(row).getByText("gpt-4o")).toBeInTheDocument();
});
it("falls back to the request id row when the log names no model", () => {
renderHeader(logEntry({ model: "", custom_llm_provider: "" }), true);
const row = expandToggle().parentElement as HTMLElement;
expect(within(row).getByText("170d64ea-69f0-431a-be72-332f8f78c18a")).toBeInTheDocument();
});
});

View file

@ -7,6 +7,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
import { LogEntry } from "../columns";
import { AutoRouterTag } from "@/components/shared/table_cells";
import { ClassifyTag } from "./ClassifyTag";
import { SidebarToggle } from "./SidebarToggle";
import { getProviderLogoAndName } from "../../provider_info_helpers";
import {
DRAWER_HEADER_PADDING,
@ -26,6 +27,8 @@ interface DrawerHeaderProps {
statusLabel: string;
statusColor: "error" | "success";
environment: string;
isSidebarCollapsed: boolean;
onToggleSidebar: () => void;
}
/**
@ -40,9 +43,13 @@ export function DrawerHeader({
statusLabel,
statusColor,
environment,
isSidebarCollapsed,
onToggleSidebar,
}: DrawerHeaderProps) {
const provider = log.custom_llm_provider || "";
const providerInfo = provider ? getProviderLogoAndName(provider) : null;
const showToggleWithProvider = isSidebarCollapsed && Boolean(providerInfo || log.model);
const showToggleWithRequestId = isSidebarCollapsed && !showToggleWithProvider;
return (
<div
@ -56,18 +63,28 @@ export function DrawerHeader({
}}
>
{/* Row 0: Model + Provider with Logo */}
<ModelProviderSection
model={log.model}
modelGroup={log.model_group}
internalCallOrigin={log.metadata?.internal_call_origin}
providerLogo={providerInfo?.logo}
providerName={providerInfo?.displayName}
/>
<div className="flex items-center gap-2" style={{ marginBottom: SPACING_MEDIUM }}>
{showToggleWithProvider && <SidebarToggle isCollapsed onToggle={onToggleSidebar} />}
<ModelProviderSection
model={log.model}
modelGroup={log.model_group}
internalCallOrigin={log.metadata?.internal_call_origin}
providerLogo={providerInfo?.logo}
providerName={providerInfo?.displayName}
/>
</div>
{/* Row 1: Request ID + Actions */}
<div
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: SPACING_MEDIUM }}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 4,
marginBottom: SPACING_MEDIUM,
}}
>
{showToggleWithRequestId && <SidebarToggle isCollapsed onToggle={onToggleSidebar} />}
<RequestIdSection requestId={log.request_id} />
<NavigationSection onPrevious={onPrevious} onNext={onNext} onClose={onClose} />
</div>
@ -95,7 +112,7 @@ function ModelProviderSection({
providerName?: string;
}) {
return (
<div className="flex items-center gap-2" style={{ marginBottom: SPACING_MEDIUM }}>
<div className="flex min-w-0 items-center gap-2">
{providerLogo && (
<img
src={providerLogo}

View file

@ -1,6 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { Bot, Check, ChevronLeft, ChevronRight, Copy, Sparkles, Wrench } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Bot, Check, Copy, Sparkles, Wrench } from "lucide-react";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { LogEntry } from "../columns";
@ -9,6 +8,7 @@ import { AGENT_CALL_TYPES, MCP_CALL_TYPES } from "../constants";
import { getEventDisplayName } from "../utils";
import { ClassifyTag } from "./ClassifyTag";
import { DrawerHeader } from "./DrawerHeader";
import { SidebarToggle } from "./SidebarToggle";
import { useKeyboardNavigation } from "./useKeyboardNavigation";
import { LogDetailContent, GuardrailJumpLink } from "./LogDetailContent";
import { sessionSpendLogsCall } from "../../networking";
@ -313,26 +313,12 @@ export function LogDetailsDrawer({
{logEntry?.request_id ? `Request ${logEntry.request_id} details` : "Request details"}
</SheetTitle>
<div style={{ height: "100%" }} className="flex relative">
{!isSidebarCollapsed ? (
<Button
variant="ghost"
size="icon-sm"
onClick={() => setIsSidebarCollapsed(true)}
className="absolute top-2 left-2 z-raised bg-card! border! border-border! rounded-md!"
aria-label="Collapse trace sidebar"
>
<ChevronLeft className="size-4" />
</Button>
) : (
<Button
variant="ghost"
size="icon-sm"
onClick={() => setIsSidebarCollapsed(false)}
className="absolute top-2 left-2 z-raised bg-card! border! border-border! rounded-md!"
aria-label="Expand trace sidebar"
>
<ChevronRight className="size-4" />
</Button>
{!isSidebarCollapsed && (
<SidebarToggle
isCollapsed={false}
onToggle={() => setIsSidebarCollapsed(true)}
className="absolute top-2 left-2 z-raised"
/>
)}
{!isSidebarCollapsed && (
<div className="border-r border-border bg-muted flex flex-col" style={{ width: SIDEBAR_WIDTH_PX }}>
@ -466,6 +452,8 @@ export function LogDetailsDrawer({
<DrawerHeader
log={currentLog}
onClose={onClose}
isSidebarCollapsed={isSidebarCollapsed}
onToggleSidebar={() => setIsSidebarCollapsed((collapsed) => !collapsed)}
onPrevious={selectPreviousLog}
onNext={selectNextLog}
statusLabel={statusLabel}

View file

@ -0,0 +1,23 @@
import { ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/cva.config";
export interface SidebarToggleProps {
isCollapsed: boolean;
onToggle: () => void;
className?: string;
}
export function SidebarToggle({ isCollapsed, onToggle, className }: SidebarToggleProps) {
return (
<Button
variant="ghost"
size="icon-sm"
onClick={onToggle}
className={cn("shrink-0 bg-card! border! border-border! rounded-md!", className)}
aria-label={isCollapsed ? "Expand trace sidebar" : "Collapse trace sidebar"}
>
{isCollapsed ? <ChevronLeft className="size-4" /> : <ChevronRight className="size-4" />}
</Button>
);
}