mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-04 07:27:51 +00:00
feat(ui): save default session log sort preference
This commit is contained in:
parent
581b7d1a02
commit
3eee2ac4da
2 changed files with 97 additions and 7 deletions
|
|
@ -1,9 +1,11 @@
|
|||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { LogDetailsDrawer } from "./LogDetailsDrawer";
|
||||
import { sessionSpendLogsCall } from "../../networking";
|
||||
import { LogEntry } from "../types";
|
||||
import { toast } from "@/lib/toast";
|
||||
import * as localStorageUtils from "@/utils/localStorageUtils";
|
||||
import { AutoRouterModelGroupsProvider } from "@/components/shared/table_cells";
|
||||
|
||||
vi.mock("../../networking", () => ({
|
||||
|
|
@ -83,19 +85,78 @@ const sessionLogs = [
|
|||
const renderSessionDrawer = () => {
|
||||
vi.mocked(sessionSpendLogsCall).mockResolvedValue({ data: sessionLogs, total: 4, total_pages: 1 });
|
||||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
const drawer = (open: boolean) => (
|
||||
const drawer = (open: boolean, sessionId = "session-1") => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<LogDetailsDrawer open={open} onClose={() => {}} logEntry={null} sessionId="session-1" accessToken="token" />
|
||||
<LogDetailsDrawer open={open} onClose={() => {}} logEntry={null} sessionId={sessionId} accessToken="token" />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
const { rerender } = render(drawer(true));
|
||||
return { rerender, drawer };
|
||||
const { rerender, unmount } = render(drawer(true));
|
||||
return { rerender, drawer, unmount };
|
||||
};
|
||||
|
||||
const sidebarEventNames = () =>
|
||||
screen.queryAllByText(/^(llm-early|llm-late|tool-early|tool-late)$/).map((el) => el.textContent);
|
||||
|
||||
describe("LogDetailsDrawer session sidebar sorting", () => {
|
||||
beforeEach(() => localStorage.removeItem("litellm_session_logs_default_sort"));
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it("saves a default sort for reopened sessions and a fresh page mount", async () => {
|
||||
const { rerender, drawer, unmount } = renderSessionDrawer();
|
||||
await screen.findByText("llm-early");
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Start time" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Set as default" }));
|
||||
expect(screen.getByRole("button", { name: "Default in this browser" })).toBeDisabled();
|
||||
|
||||
rerender(drawer(false));
|
||||
rerender(drawer(true));
|
||||
await waitFor(() => expect(sidebarEventNames()).toEqual(["llm-early", "tool-early", "llm-late", "tool-late"]));
|
||||
unmount();
|
||||
renderSessionDrawer();
|
||||
await waitFor(() => expect(sidebarEventNames()).toEqual(["llm-early", "tool-early", "llm-late", "tool-late"]));
|
||||
});
|
||||
|
||||
it("keeps a temporary sort separate from the default for the next session", async () => {
|
||||
localStorage.setItem("litellm_session_logs_default_sort", "start_time");
|
||||
const { rerender, drawer } = renderSessionDrawer();
|
||||
await waitFor(() => expect(sidebarEventNames()).toEqual(["llm-early", "tool-early", "llm-late", "tool-late"]));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Duration" }));
|
||||
expect(sidebarEventNames()).toEqual(["tool-early", "llm-late", "llm-early", "tool-late"]);
|
||||
rerender(drawer(true, "session-2"));
|
||||
await waitFor(() => expect(sidebarEventNames()).toEqual(["llm-early", "tool-early", "llm-late", "tool-late"]));
|
||||
});
|
||||
|
||||
it("lets duration become the saved default again", async () => {
|
||||
localStorage.setItem("litellm_session_logs_default_sort", "start_time");
|
||||
const { unmount } = renderSessionDrawer();
|
||||
await screen.findByText("llm-early");
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Duration" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Set as default" }));
|
||||
unmount();
|
||||
renderSessionDrawer();
|
||||
await waitFor(() => expect(sidebarEventNames()).toEqual(["tool-early", "llm-late", "llm-early", "tool-late"]));
|
||||
});
|
||||
|
||||
it("falls back to duration when the stored preference is invalid", async () => {
|
||||
localStorage.setItem("litellm_session_logs_default_sort", "unsupported-mode");
|
||||
renderSessionDrawer();
|
||||
await waitFor(() => expect(sidebarEventNames()).toEqual(["tool-early", "llm-late", "llm-early", "tool-late"]));
|
||||
expect(screen.getByRole("tab", { name: "Duration" })).toHaveAttribute("aria-selected", "true");
|
||||
});
|
||||
|
||||
it("reports a failed save without pretending the default changed", async () => {
|
||||
vi.spyOn(localStorageUtils, "setLocalStorageItem").mockImplementation(() => {});
|
||||
const { rerender, drawer } = renderSessionDrawer();
|
||||
await screen.findByText("llm-early");
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Start time" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Set as default" }));
|
||||
expect(toast.error).toHaveBeenCalledWith("Couldn't save the default sort in this browser");
|
||||
expect(screen.getByRole("button", { name: "Set as default" })).toBeEnabled();
|
||||
rerender(drawer(false));
|
||||
rerender(drawer(true));
|
||||
await waitFor(() => expect(sidebarEventNames()).toEqual(["tool-early", "llm-late", "llm-early", "tool-late"]));
|
||||
});
|
||||
|
||||
it("defaults to duration order, longest call first across LLM and MCP calls", async () => {
|
||||
renderSessionDrawer();
|
||||
await waitFor(() => expect(sidebarEventNames()).toHaveLength(4));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ import { useEffect, useMemo, useState } from "react";
|
|||
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 { Button } from "@/components/ui/button";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { getLocalStorageItem, setLocalStorageItem } from "@/utils/localStorageUtils";
|
||||
import { LogEntry } from "../types";
|
||||
import { AutoRouterIcon, useIsAutoRoutedModelGroup } from "@/components/shared/table_cells";
|
||||
import { AGENT_CALL_TYPES, MCP_CALL_TYPES } from "../constants";
|
||||
|
|
@ -32,6 +35,7 @@ export interface LogDetailsDrawerProps {
|
|||
}
|
||||
|
||||
const SIDEBAR_WIDTH_PX = 224;
|
||||
const SESSION_SORT_PREFERENCE_KEY = "litellm_session_logs_default_sort";
|
||||
|
||||
// Session logs are fetched page-by-page from the paginated backend and
|
||||
// accumulated so the drawer can show the whole session. page_size is the
|
||||
|
|
@ -125,6 +129,7 @@ export function LogDetailsDrawer({
|
|||
const isSessionMode = Boolean(sessionId);
|
||||
const [selectedSessionRequestId, setSelectedSessionRequestId] = useState<string | null>(null);
|
||||
const [sessionSortMode, setSessionSortMode] = useState<SessionLogSortMode>("duration");
|
||||
const [defaultSessionSortMode, setDefaultSessionSortMode] = useState<SessionLogSortMode>("duration");
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
||||
const [copiedLeftPanelId, setCopiedLeftPanelId] = useState(false);
|
||||
|
||||
|
|
@ -222,12 +227,26 @@ export function LogDetailsDrawer({
|
|||
useEffect(() => {
|
||||
if (open) {
|
||||
setIsSidebarCollapsed(false);
|
||||
if (isSessionMode) {
|
||||
const storedMode = getLocalStorageItem(SESSION_SORT_PREFERENCE_KEY);
|
||||
const defaultMode = storedMode === "start_time" ? "start_time" : "duration";
|
||||
setDefaultSessionSortMode(defaultMode);
|
||||
setSessionSortMode(defaultMode);
|
||||
}
|
||||
} else {
|
||||
if (isSessionMode) setSelectedSessionRequestId(null);
|
||||
setSessionSortMode("duration");
|
||||
setCopiedLeftPanelId(false);
|
||||
}
|
||||
}, [open, isSessionMode]);
|
||||
}, [open, isSessionMode, sessionId]);
|
||||
|
||||
const saveDefaultSessionSort = () => {
|
||||
setLocalStorageItem(SESSION_SORT_PREFERENCE_KEY, sessionSortMode);
|
||||
if (getLocalStorageItem(SESSION_SORT_PREFERENCE_KEY) !== sessionSortMode) {
|
||||
toast.error("Couldn't save the default sort in this browser");
|
||||
return;
|
||||
}
|
||||
setDefaultSessionSortMode(sessionSortMode);
|
||||
};
|
||||
|
||||
// Keyboard navigation
|
||||
const { selectNextLog, selectPreviousLog } = useKeyboardNavigation({
|
||||
|
|
@ -402,6 +421,16 @@ export function LogDetailsDrawer({
|
|||
Start time
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="mt-1 w-full text-[11px]"
|
||||
disabled={sessionSortMode === defaultSessionSortMode}
|
||||
onClick={saveDefaultSessionSort}
|
||||
title="Use this order when opening sessions in this browser"
|
||||
>
|
||||
{sessionSortMode === defaultSessionSortMode ? "Default in this browser" : "Set as default"}
|
||||
</Button>
|
||||
</Tabs>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue