diff --git a/ui/litellm-dashboard/src/components/view_logs/columns.tsx b/ui/litellm-dashboard/src/components/view_logs/columns.tsx index 1265b8449de..310316d205e 100644 --- a/ui/litellm-dashboard/src/components/view_logs/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/columns.tsx @@ -119,11 +119,13 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] ) : "Time", accessorKey: "startTime", + size: 200, cell: (info: any) => , }, { header: "Type", id: "type", + size: 90, cell: (info: any) => { const row = info.row.original; const sessionCount = row.session_total_count || 1; @@ -168,6 +170,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Status", accessorKey: "metadata.status", + size: 100, cell: (info: any) => { const status = info.getValue() || "Success"; const isSuccess = status.toLowerCase() !== "failure"; @@ -186,6 +189,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Session ID", accessorKey: "session_id", + size: 160, cell: (info: any) => { const value = String(info.getValue() || ""); const onSessionClick = info.row.original.onSessionClick; @@ -207,6 +211,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Request ID", accessorKey: "request_id", + size: 110, cell: (info: any) => ( {String(info.getValue() || "")} @@ -226,6 +231,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] ) : "Cost", accessorKey: "spend", + size: 110, cell: (info: any) => { const row = info.row.original; const mcpCount = row.mcp_tool_call_count || 0; @@ -258,6 +264,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] ) : "Duration (s)", accessorKey: "request_duration_ms", + size: 90, cell: (info: any) => { const ms = info.getValue(); if (ms == null) return -; @@ -282,6 +289,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] ) : "TTFT (s)", accessorKey: "completionStartTime", + size: 80, cell: (info: any) => { const row = info.row.original; const completionStartTime = info.getValue(); @@ -301,6 +309,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Team Name", accessorKey: "metadata.user_api_key_team_alias", + size: 150, cell: (info: any) => ( {String(info.getValue() || "-")} @@ -310,6 +319,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Key Hash", accessorKey: "metadata.user_api_key", + size: 110, cell: (info: any) => { const value = String(info.getValue() || "-"); const onKeyHashClick = info.row.original.onKeyHashClick; @@ -329,6 +339,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Key Alias", accessorKey: "metadata.user_api_key_alias", + size: 150, cell: (info: any) => ( {String(info.getValue() || "-")} @@ -348,6 +359,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] ) : "Model", accessorKey: "model", + size: 200, cell: (info: any) => { const row = info.row.original; const provider = row.custom_llm_provider; @@ -385,6 +397,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] ) : "Tokens", accessorKey: "total_tokens", + size: 140, cell: (info: any) => { const row = info.row.original; return ( @@ -400,6 +413,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Internal User", accessorKey: "user", + size: 150, cell: (info: any) => ( {String(info.getValue() || "-")} @@ -409,6 +423,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "End User", accessorKey: "end_user", + size: 140, cell: (info: any) => ( {String(info.getValue() || "-")} @@ -419,6 +434,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef[] { header: "Tags", accessorKey: "request_tags", + size: 150, cell: (info: any) => { const tags = info.getValue(); if (!tags || Object.keys(tags).length === 0) return "-"; diff --git a/ui/litellm-dashboard/src/components/view_logs/table.test.tsx b/ui/litellm-dashboard/src/components/view_logs/table.test.tsx new file mode 100644 index 00000000000..da9bcef1455 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/table.test.tsx @@ -0,0 +1,46 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { DataTable } from "./table"; + +type Row = { request_id: string; a: string; b: string }; + +const data: Row[] = [{ request_id: "r1", a: "alpha", b: "beta" }]; + +const sizedColumns: ColumnDef[] = [ + { header: "A", accessorKey: "a", size: 120 }, + { header: "B", accessorKey: "b", size: 80 }, +]; + +const unsizedColumns: ColumnDef[] = [ + { header: "A", accessorKey: "a" }, + { header: "B", accessorKey: "b" }, +]; + +describe("DataTable column sizing", () => { + it("min-widths the table to the column total and sizes every cell when columns declare sizes", () => { + render(); + + const table = screen.getByRole("table"); + expect(table.style.minWidth).toBe("200px"); + expect(table.style.width).toBe(""); + + const headers = screen.getAllByRole("columnheader"); + expect(headers.map((h) => h.style.width)).toEqual(["120px", "80px"]); + + const cells = screen.getAllByRole("cell"); + expect(cells.map((c) => c.style.width)).toEqual(["120px", "80px"]); + }); + + it("leaves cells unsized and keeps the fluid table when no column declares a size", () => { + render(); + + const table = screen.getByRole("table"); + expect(table.style.width).toBe(""); + expect(table.style.minWidth).toBe("400px"); + + for (const cell of [...screen.getAllByRole("columnheader"), ...screen.getAllByRole("cell")]) { + expect(cell.style.width).toBe(""); + } + }); +}); diff --git a/ui/litellm-dashboard/src/components/view_logs/table.tsx b/ui/litellm-dashboard/src/components/view_logs/table.tsx index 6aa349513d5..4510cc9a1f0 100644 --- a/ui/litellm-dashboard/src/components/view_logs/table.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/table.tsx @@ -41,6 +41,7 @@ export function DataTable({ enableSorting = false, }: DataTableProps) { const supportsExpansion = !!(renderSubComponent || renderChildRows) && !!getRowCanExpand; + const hasExplicitColumnSizes = columns.some((column) => column.size !== undefined); const [sorting, setSorting] = useState([]); const table = useReactTable({ @@ -63,9 +64,14 @@ export function DataTable({ ...(supportsExpansion && { getExpandedRowModel: getExpandedRowModel() }), }); + const tableClassName = hasExplicitColumnSizes + ? "[&_td]:py-0.5 [&_th]:py-1 [&_table]:table-fixed" + : "[&_td]:py-0.5 [&_th]:py-1 table-fixed w-full box-border"; + const tableStyle = hasExplicitColumnSizes ? { minWidth: table.getCenterTotalSize() } : { minWidth: "400px" }; + return (
- +
{table.getHeaderGroups().map((headerGroup) => ( @@ -77,6 +83,7 @@ export function DataTable({ {header.isPlaceholder ? null : ( @@ -112,7 +119,11 @@ export function DataTable({ onClick={() => onRowClick?.(row.original)} > {row.getVisibleCells().map((cell) => ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))}