Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/litellm-dashboard/eslint-metrics.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@typescript-eslint/no-explicit-any": 1990,
"@typescript-eslint/no-explicit-any": 1988,
"complexity": 128,
"max-depth": 59,
"no-console": 15
Expand Down
5 changes: 0 additions & 5 deletions ui/litellm-dashboard/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2077,11 +2077,6 @@
"count": 1
}
},
"src/components/view_logs/table.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/components/view_user_spend.tsx": {
"react-hooks/set-state-in-effect": {
"count": 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import { TagUsage } from "../../types";

interface TopKeyViewProps {
topKeys: any[];

Check warning on line 14 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
teams: any[] | null;

Check warning on line 15 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
showTags?: boolean;
topKeysLimit: number;
setTopKeysLimit: (limit: number) => void;
Expand All @@ -22,7 +22,7 @@
const { accessToken, userRole, userId: userID, premiumUser } = useAuthorized();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [selectedKey, setSelectedKey] = useState<string | null>(null);
const [keyData, setKeyData] = useState<any | undefined>(undefined);

Check warning on line 25 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
const [viewMode, setViewMode] = useState<"chart" | "table">("table");
const [expandedTags, setExpandedTags] = useState<Set<string>>(new Set());

Expand All @@ -38,7 +38,7 @@
});
};

const handleKeyClick = async (item: any) => {

Check warning on line 41 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
if (!accessToken) return;

try {
Expand Down Expand Up @@ -83,7 +83,7 @@
{
header: "Key ID",
accessorKey: "api_key",
cell: (info: any) => (

Check warning on line 86 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
<div className="overflow-hidden">
<Tooltip title={info.getValue() as string}>
<Button
Expand All @@ -101,14 +101,14 @@
{
header: "Key Alias",
accessorKey: "key_alias",
cell: (info: any) => info.getValue() || "-",

Check warning on line 104 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
},
];

const tagsColumn = {
header: "Tags",
accessorKey: "tags",
cell: (info: any) => {

Check warning on line 111 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
const tags = info.getValue() as TagUsage[] | undefined;
const apiKey = info.row.original.api_key;
const isExpanded = expandedTags.has(apiKey);
Expand Down Expand Up @@ -164,7 +164,8 @@
const spendColumn = {
header: "Spend (USD)",
accessorKey: "spend",
meta: { numeric: true },
cell: (info: any) => {

Check warning on line 168 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
const value = info.getValue();
return value > 0 && value < 0.01 ? "<$0.01" : `$${formatNumberWithCommas(value, 2)}`;
},
Expand Down Expand Up @@ -247,13 +248,7 @@
</div>
) : (
<div className="border rounded-lg overflow-hidden max-h-[600px] overflow-y-auto">
<DataTable
columns={columns}
data={topKeys}
renderSubComponent={() => <></>}
getRowCanExpand={() => false}
isLoading={false}
/>
<DataTable columns={columns} data={topKeys} isLoading={false} />
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
{
header: "Model",
accessorKey: "key",
cell: (info: any) => info.getValue() || "-",

Check warning on line 28 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopModelView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
},
{
header: "Spend (USD)",
accessorKey: "spend",
meta: { numeric: true },
cell: (info: any) => {

Check warning on line 34 in ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopModelView.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
const value = info.getValue();
return `$${formatNumberWithCommas(value, 2)}`;
},
Expand All @@ -38,16 +39,19 @@
{
header: "Successful",
accessorKey: "successful_requests",
meta: { numeric: true },
cell: (info: any) => <span className="text-green-600">{info.getValue()?.toLocaleString() || 0}</span>,
},
{
header: "Failed",
accessorKey: "failed_requests",
meta: { numeric: true },
cell: (info: any) => <span className="text-red-600">{info.getValue()?.toLocaleString() || 0}</span>,
},
{
header: "Tokens",
accessorKey: "tokens",
meta: { numeric: true },
cell: (info: any) => info.getValue()?.toLocaleString() || 0,
},
];
Expand Down Expand Up @@ -99,13 +103,7 @@
</div>
) : (
<div className="border rounded-lg overflow-hidden max-h-[600px] overflow-y-auto">
<DataTable
columns={columns}
data={processedTopModels}
renderSubComponent={() => <></>}
getRowCanExpand={() => false}
isLoading={false}
/>
<DataTable columns={columns} data={processedTopModels} isLoading={false} />
</div>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,6 @@ export function MCPToolsetsTab({ accessToken, userRole }: MCPToolsetsTabProps) {
<DataTable
data={toolsets}
columns={columns}
renderSubComponent={() => <div />}
getRowCanExpand={() => false}
isLoading={isLoading}
noDataMessage="No toolsets yet. Click 'New Toolset' to create one."
loadingMessage="Loading toolsets..."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,6 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({
<DataTable
data={generalSettings}
columns={columns}
renderSubComponent={() => <div></div>}
getRowCanExpand={() => false}
isLoading={false}
noDataMessage="No pass-through endpoints configured"
/>
Expand Down
10 changes: 7 additions & 3 deletions ui/litellm-dashboard/src/components/view_logs/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,14 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
: "Cost",
accessorKey: "spend",
size: 110,
meta: { numeric: true },
cell: (info: any) => {
const row = info.row.original;
const mcpCount = row.mcp_tool_call_count || 0;
const mcpSpend = row.mcp_tool_call_spend || 0;

return (
<div className="flex flex-col">
<div className="flex flex-col items-end">
<Tooltip title={`$${String(info.getValue() || 0)}`}>
<span>{getSpendString(info.getValue() || 0)}</span>
</Tooltip>
Expand All @@ -263,13 +264,14 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
)
: "Duration (s)",
accessorKey: "request_duration_ms",
meta: { numeric: true },
cell: (info: any) => {
const ms = info.getValue();
if (ms == null) return <span>-</span>;
const seconds = (ms / 1000).toFixed(2);
return (
<Tooltip title={`${ms}ms`}>
<span className="max-w-[15ch] truncate block">{seconds}</span>
<span className="max-w-[15ch] truncate inline-block">{seconds}</span>
</Tooltip>
);
},
Expand All @@ -287,6 +289,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
)
: "TTFT (s)",
accessorKey: "completionStartTime",
meta: { numeric: true },
cell: (info: any) => {
const row = info.row.original;
const completionStartTime = info.getValue();
Expand All @@ -298,7 +301,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
const ttftSeconds = (ttftMs / 1000).toFixed(2);
return (
<Tooltip title={`${ttftMs}ms`}>
<span className="max-w-[15ch] truncate block">{ttftSeconds}</span>
<span className="max-w-[15ch] truncate inline-block">{ttftSeconds}</span>
</Tooltip>
);
},
Expand Down Expand Up @@ -395,6 +398,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
: "Tokens",
accessorKey: "total_tokens",
size: 140,
meta: { numeric: true },
cell: (info: any) => {
const row = info.row.original;
return (
Expand Down
1 change: 1 addition & 0 deletions ui/litellm-dashboard/src/components/view_logs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export default function SpendLogsTable({ accessToken, token, userRole, userID, p
<DataTable
columns={columns}
data={deferredData}
getRowId={(row) => row.request_id}
onRowClick={handleRowClick}
isLoading={isLogsLoading}
/>
Expand Down
81 changes: 69 additions & 12 deletions ui/litellm-dashboard/src/components/view_logs/table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,35 @@ describe("DataTable states", () => {
expect(screen.getByText("Nothing here")).toBeInTheDocument();
});

it("falls back to generic loading and empty defaults", () => {
const { rerender } = render(<DataTable data={data} columns={unsizedColumns} isLoading />);
expect(screen.getByText("Loading...")).toBeInTheDocument();

rerender(<DataTable data={[]} columns={unsizedColumns} />);
expect(screen.getByText("No results")).toBeInTheDocument();
});

it("suppresses the primitive's row hover on loading, empty, and expansion placeholder rows", async () => {
const user = userEvent.setup();
const { rerender } = render(<DataTable data={data} columns={unsizedColumns} isLoading />);
expect(screen.getByText("Loading...").closest("tr")).toHaveClass("hover:bg-transparent");

rerender(<DataTable data={[]} columns={unsizedColumns} />);
expect(screen.getByText("No results").closest("tr")).toHaveClass("hover:bg-transparent");

rerender(
<DataTable
data={data}
columns={[expanderColumn, ...unsizedColumns]}
getRowCanExpand={() => true}
renderSubComponent={({ row }) => <div>details for {row.original.request_id}</div>}
/>,
);
await user.click(screen.getByRole("button", { name: "expand r1" }));
expect(screen.getByText("details for r1").closest("tr")).toHaveClass("hover:bg-transparent");
expect(screen.getByText("alpha").closest("tr")).not.toHaveClass("hover:bg-transparent");
});

it("renders row data through plain TanStack column defs, including custom cell renderers", () => {
const columns: ColumnDef<Row>[] = [
{ header: "A", accessorKey: "a" },
Expand All @@ -84,6 +113,29 @@ describe("DataTable states", () => {
expect(screen.getByText("alpha")).toBeInTheDocument();
expect(screen.getByText("custom:beta")).toBeInTheDocument();
});

it("clips the table to the rounded wrapper so the header band cannot bleed past the corners", () => {
const { container } = render(<DataTable data={data} columns={unsizedColumns} />);

const wrapper = container.firstElementChild;
expect(wrapper).toHaveClass("rounded-lg", "overflow-hidden");
});

it("right-aligns headers and cells with tabular figures for numeric meta columns", () => {
const columns: ColumnDef<Row>[] = [
{ header: "A", accessorKey: "a" },
{ header: "B", accessorKey: "b", meta: { numeric: true } },
];
render(<DataTable data={data} columns={columns} />);

const headers = screen.getAllByRole("columnheader");
expect(headers[1].querySelector("div")).toHaveClass("justify-end");
expect(headers[0].querySelector("div")).not.toHaveClass("justify-end");

const cells = screen.getAllByRole("cell");
expect(cells[1]).toHaveClass("text-right", "tabular-nums");
expect(cells[0]).not.toHaveClass("text-right");
});
});

describe("DataTable row interaction", () => {
Expand Down Expand Up @@ -129,28 +181,33 @@ describe("DataTable expansion", () => {
expect(screen.queryByText("details for r1")).not.toBeInTheDocument();
});

it("renders child rows as sibling table rows (child-rows path)", async () => {
it("keeps expansion attached to the same row through data reorders when getRowId is injected", async () => {
const user = userEvent.setup();
render(
const { rerender } = render(
<DataTable
data={rows}
columns={[expanderColumn, ...unsizedColumns]}
getRowId={(row) => row.request_id}
getRowCanExpand={() => true}
renderChildRows={({ row }) => (
<tr>
<td colSpan={3}>child of {row.original.request_id}</td>
</tr>
)}
renderSubComponent={({ row }) => <div>details for {row.original.request_id}</div>}
/>,
);

expect(screen.queryByText("child of r2")).not.toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "expand r1" }));
expect(screen.getByText("details for r1")).toBeInTheDocument();

await user.click(screen.getByRole("button", { name: "expand r2" }));
rerender(
<DataTable
data={[...rows].reverse()}
columns={[expanderColumn, ...unsizedColumns]}
getRowId={(row) => row.request_id}
getRowCanExpand={() => true}
renderSubComponent={({ row }) => <div>details for {row.original.request_id}</div>}
/>,
);

const childCell = screen.getByText("child of r2");
expect(childCell.closest("tr")).not.toBeNull();
expect(within(screen.getByRole("table")).getByText("child of r2")).toBeInTheDocument();
expect(screen.getByText("details for r1")).toBeInTheDocument();
expect(screen.queryByText("details for r2")).not.toBeInTheDocument();
});

it("does not expand rows when getRowCanExpand is missing even if a renderer is provided", () => {
Expand Down
Loading
Loading