mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
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:
commit
d435a62ce6
5 changed files with 152 additions and 31 deletions
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue