Merge pull request #31426 from BerriAI/litellm_/cranky-hamilton-21b5d0

fix(ui): stop Request Logs page from overflowing horizontally and size its columns
This commit is contained in:
yuneng-jiang 2026-06-30 10:23:38 -07:00 committed by GitHub
commit f8a2ea7378
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 74 additions and 4 deletions

View file

@ -126,7 +126,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
<div className="mt-2">
<SidebarProvider setPage={navigateToPage} defaultSelectedKey={page} sidebarCollapsed={sidebarCollapsed} />
</div>
<main className="flex-1">{children}</main>
<main className="flex-1 min-w-0">{children}</main>
</>
)}
</div>

View file

@ -119,11 +119,13 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
)
: "Time",
accessorKey: "startTime",
size: 200,
cell: (info: any) => <TimeCell utcTime={info.getValue()} />,
},
{
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<LogEntry>[]
{
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<LogEntry>[]
{
header: "Session ID",
accessorKey: "session_id",
size: 120,
cell: (info: any) => {
const value = String(info.getValue() || "");
const onSessionClick = info.row.original.onSessionClick;
@ -226,6 +230,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
)
: "Cost",
accessorKey: "spend",
size: 110,
cell: (info: any) => {
const row = info.row.original;
const mcpCount = row.mcp_tool_call_count || 0;
@ -301,6 +306,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
{
header: "Team Name",
accessorKey: "metadata.user_api_key_team_alias",
size: 150,
cell: (info: any) => (
<Tooltip title={String(info.getValue() || "-")}>
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
@ -310,6 +316,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
{
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 +336,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
{
header: "Key Alias",
accessorKey: "metadata.user_api_key_alias",
size: 150,
cell: (info: any) => (
<Tooltip title={String(info.getValue() || "-")}>
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
@ -348,6 +356,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
)
: "Model",
accessorKey: "model",
size: 200,
cell: (info: any) => {
const row = info.row.original;
const provider = row.custom_llm_provider;
@ -385,6 +394,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
)
: "Tokens",
accessorKey: "total_tokens",
size: 140,
cell: (info: any) => {
const row = info.row.original;
return (
@ -400,6 +410,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
{
header: "Internal User",
accessorKey: "user",
size: 150,
cell: (info: any) => (
<Tooltip title={String(info.getValue() || "-")}>
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
@ -409,6 +420,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
{
header: "End User",
accessorKey: "end_user",
size: 140,
cell: (info: any) => (
<Tooltip title={String(info.getValue() || "-")}>
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
@ -419,6 +431,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
{
header: "Tags",
accessorKey: "request_tags",
size: 150,
cell: (info: any) => {
const tags = info.getValue();
if (!tags || Object.keys(tags).length === 0) return "-";

View file

@ -234,7 +234,7 @@ export default function SpendLogsTable({ accessToken, token, userRole, userID, p
};
return (
<div className="w-full max-w-screen p-6 overflow-x-hidden box-border">
<div className="w-full p-6 overflow-x-hidden box-border">
<TabGroup defaultIndex={0} onIndexChange={(index) => setActiveTab(index === 0 ? "request logs" : "audit logs")}>
<TabList>
<Tab>Request Logs</Tab>

View file

@ -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<Row>[] = [
{ header: "A", accessorKey: "a", size: 120 },
{ header: "B", accessorKey: "b", size: 80 },
];
const unsizedColumns: ColumnDef<Row>[] = [
{ 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(<DataTable data={data} columns={sizedColumns} />);
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(<DataTable data={data} columns={unsizedColumns} />);
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("");
}
});
});

View file

@ -41,6 +41,7 @@ export function DataTable<TData, TValue>({
enableSorting = false,
}: DataTableProps<TData, TValue>) {
const supportsExpansion = !!(renderSubComponent || renderChildRows) && !!getRowCanExpand;
const hasExplicitColumnSizes = columns.some((column) => column.size !== undefined);
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable<TData>({
@ -63,9 +64,14 @@ export function DataTable<TData, TValue>({
...(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 (
<div className="rounded-lg custom-border overflow-x-auto w-full max-w-full box-border">
<Table className="[&_td]:py-0.5 [&_th]:py-1 table-fixed w-full box-border" style={{ minWidth: "400px" }}>
<Table className={tableClassName} style={tableStyle}>
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
@ -77,6 +83,7 @@ export function DataTable<TData, TValue>({
<TableHeaderCell
key={header.id}
className={`py-1 h-8 ${canSort ? "cursor-pointer select-none hover:bg-gray-50" : ""}`}
style={hasExplicitColumnSizes ? { width: header.getSize() } : undefined}
onClick={canSort ? header.column.getToggleSortingHandler() : undefined}
>
{header.isPlaceholder ? null : (
@ -112,7 +119,11 @@ export function DataTable<TData, TValue>({
onClick={() => onRowClick?.(row.original)}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
<TableCell
key={cell.id}
className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap"
style={hasExplicitColumnSizes ? { width: cell.column.getSize() } : undefined}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}