From 546640227463d3af563a796022fc8d49626401c1 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Fri, 31 Jul 2026 16:36:32 -0700 Subject: [PATCH] fix(ui): keep the session view open when selecting a log inside it (#35399) Opening a session from the logs table stored no ?session_id (row clicks called openLog, which deletes it), so session mode was derived from the clicked row's session_total_count. Rows fetched by the session drawer come from /spend/logs/session/ui, which does not enrich that field, so selecting any log inside the session view swapped in an unenriched row and collapsed the drawer to a single-log Trace view Row clicks on a multi-call session's row now call openSession, and selectLog writes ?session_id when the session view is active, so session mode is anchored in the URL instead of derived from row data --- .../view_logs/RequestLogsPanel.test.tsx | 35 +++++++++++++++++++ .../components/view_logs/RequestLogsPanel.tsx | 12 ++++--- .../components/view_logs/logDetailRouting.ts | 7 ++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx index 49870e51c2b..6a66e845675 100644 --- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx @@ -403,6 +403,41 @@ describe("RequestLogsPanel", () => { expect(drawer()).toHaveAttribute("data-session-id", "sess-1"); }); }); + + it("clicking a multi-call session's row writes ?session_id= alongside ?log_id=", async () => { + const user = userEvent.setup(); + respondWith([ + logEntry({ request_id: "req-llm", call_type: "acompletion", session_id: "sess-1", session_total_count: 3 }), + logEntry({ request_id: "req-llm-2", call_type: "acompletion", session_id: "sess-1", session_total_count: 3 }), + ]); + renderWithProviders(); + + await waitFor(() => expect(row("req-llm")).not.toBeNull()); + await user.click(row("req-llm") as HTMLElement); + + const params = new URLSearchParams(window.location.search); + expect(params.get("session_id")).toBe("sess-1"); + expect(params.get("log_id")).toBe("req-llm"); + await waitFor(() => expect(drawer()).toHaveAttribute("data-session-id", "sess-1")); + }); + + it("selecting another log while a session view is open keeps the session open", async () => { + const user = userEvent.setup(); + window.history.replaceState(null, "", "/logs/?log_id=req-llm"); + respondWith([ + logEntry({ request_id: "req-llm", call_type: "acompletion", session_id: "sess-1", session_total_count: 3 }), + logEntry({ request_id: "req-unenriched" }), + ]); + renderWithProviders(); + + await waitFor(() => expect(drawer()).toHaveAttribute("data-session-id", "sess-1")); + + await user.click(screen.getByRole("button", { name: "select-next-log" })); + + await waitFor(() => expect(drawer()).toHaveAttribute("data-log-id", "req-unenriched")); + expect(new URLSearchParams(window.location.search).get("session_id")).toBe("sess-1"); + expect(drawer()).toHaveAttribute("data-session-id", "sess-1"); + }); }); describe("live tail", () => { diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx index 06c8ca26a7e..aa3fa32bdc8 100644 --- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx @@ -235,9 +235,13 @@ export default function RequestLogsPanel({ accessToken, token, userRole, userID, const handleRowClick = useCallback( (log: LogEntry) => { setSelectedLog(log); - openLog(log.request_id); + if (log.session_id && (log.session_total_count || 1) > 1) { + openSession(log.session_id, log.request_id); + } else { + openLog(log.request_id); + } }, - [openLog], + [openLog, openSession], ); const handleSessionClick = useCallback( @@ -253,9 +257,9 @@ export default function RequestLogsPanel({ accessToken, token, userRole, userID, const handleSelectLog = useCallback( (log: LogEntry) => { setSelectedLog(log); - selectLog(log.request_id); + selectLog(log.request_id, displaySessionId); }, - [selectLog], + [selectLog, displaySessionId], ); const handleKeyHashClick = useCallback((keyHash: string) => { diff --git a/ui/litellm-dashboard/src/components/view_logs/logDetailRouting.ts b/ui/litellm-dashboard/src/components/view_logs/logDetailRouting.ts index 5b311c94627..b37a4604585 100644 --- a/ui/litellm-dashboard/src/components/view_logs/logDetailRouting.ts +++ b/ui/litellm-dashboard/src/components/view_logs/logDetailRouting.ts @@ -11,7 +11,7 @@ export interface LogDetailRouting { sessionId: string | null; openLog: (requestId: string) => void; openSession: (sessionId: string, requestId: string | null) => void; - selectLog: (requestId: string) => void; + selectLog: (requestId: string, sessionId?: string | null) => void; close: () => void; } @@ -36,9 +36,12 @@ export function useLogDetailRouting(): LogDetailRouting { }); }, []); - const selectLog = useCallback((requestId: string) => { + const selectLog = useCallback((requestId: string, sessionId?: string | null) => { navigateWithParams((params) => { params.set(LOG_ID_QUERY_PARAM, requestId); + if (sessionId) { + params.set(SESSION_ID_QUERY_PARAM, sessionId); + } }, "replace"); }, []);