mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-13 11:07:39 +00:00
Spend logs spend column enhancement
This commit is contained in:
@@ -424,7 +424,7 @@ const NewUsagePage: React.FC<NewUsagePageProps> = ({
|
||||
<div className="flex items-end justify-between gap-6 mb-6">
|
||||
<div className="flex-1">
|
||||
<TabGroup>
|
||||
<div className="flex items-end justify-start gap-6 mb-6">
|
||||
<div className="flex items-end justify-between gap-6 mb-6 w-full">
|
||||
<TabList variant="solid">
|
||||
{all_admin_roles.includes(userRole || "") ? <Tab>Global Usage</Tab> : <Tab>Your Usage</Tab>}
|
||||
{all_admin_roles.includes(userRole || "") ? (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getSpendString } from "@/utils/dataUtils";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Badge, Button } from "@tremor/react";
|
||||
import { Tooltip } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { getProviderLogoAndName } from "../provider_info_helpers";
|
||||
import { Tooltip } from "antd";
|
||||
import { TimeCell } from "./time_cell";
|
||||
import { Button, Badge } from "@tremor/react";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
|
||||
// Helper to get the appropriate logo URL
|
||||
const getLogoUrl = (row: LogEntry, provider: string) => {
|
||||
@@ -145,7 +145,11 @@ export const columns: ColumnDef<LogEntry>[] = [
|
||||
{
|
||||
header: "Cost",
|
||||
accessorKey: "spend",
|
||||
cell: (info: any) => <span>${formatNumberWithCommas(info.getValue() || 0, 6)}</span>,
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={`$${String(info.getValue() || 0)} `}>
|
||||
<span>{getSpendString(info.getValue() || 0)}</span>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Duration (s)",
|
||||
|
||||
+42
-10
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
||||
import { copyToClipboard, formatNumberWithCommas, updateExistingKeys } from "../../src/utils/dataUtils";
|
||||
import { copyToClipboard, formatNumberWithCommas, getSpendString, updateExistingKeys } from "./dataUtils";
|
||||
|
||||
// Mock NotificationsManager
|
||||
vi.mock("../../src/components/molecules/notifications_manager", () => ({
|
||||
@@ -10,7 +10,7 @@ vi.mock("../../src/components/molecules/notifications_manager", () => ({
|
||||
}));
|
||||
|
||||
// Import the mocked module
|
||||
import NotificationsManager from "../../src/components/molecules/notifications_manager";
|
||||
import NotificationsManager from "../components/molecules/notifications_manager";
|
||||
const mockNotificationsManager = vi.mocked(NotificationsManager);
|
||||
|
||||
describe("dataUtils", () => {
|
||||
@@ -71,9 +71,41 @@ describe("dataUtils", () => {
|
||||
expect(formatNumberWithCommas(undefined)).toBe("-");
|
||||
});
|
||||
|
||||
it("should handle zero", () => {
|
||||
expect(formatNumberWithCommas(0)).toBe("0");
|
||||
expect(formatNumberWithCommas(0, 2)).toBe("0.00");
|
||||
it("should handle zero and non-finite values", () => {
|
||||
expect(formatNumberWithCommas(0)).toBe("-");
|
||||
expect(formatNumberWithCommas(0, 2)).toBe("-");
|
||||
expect(formatNumberWithCommas(Infinity)).toBe("-");
|
||||
expect(formatNumberWithCommas(Number.NaN)).toBe("-");
|
||||
});
|
||||
|
||||
it("should abbreviate large numbers", () => {
|
||||
expect(formatNumberWithCommas(1_234_567, 1, true)).toBe("1.2M");
|
||||
expect(formatNumberWithCommas(12_345, 2, true)).toBe("12.35K");
|
||||
expect(formatNumberWithCommas(-1_200, 2, true)).toBe("-1.20K");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSpendString", () => {
|
||||
it("should return '-' for null, undefined, zero, or non-finite", () => {
|
||||
expect(getSpendString(null)).toBe("-");
|
||||
expect(getSpendString(undefined)).toBe("-");
|
||||
expect(getSpendString(0)).toBe("-");
|
||||
expect(getSpendString(Number.NaN)).toBe("-");
|
||||
});
|
||||
|
||||
it("should format spend with dollar sign", () => {
|
||||
expect(getSpendString(1234.5, 2)).toBe("$1,234.50");
|
||||
expect(getSpendString(-2500, 0)).toBe("$-2,500");
|
||||
});
|
||||
|
||||
it("should return threshold string for very small values", () => {
|
||||
expect(getSpendString(0.0000004, 6)).toBe("< $0.000001");
|
||||
expect(getSpendString(-0.0000004, 6)).toBe("< $0.000001");
|
||||
});
|
||||
|
||||
it("should respect custom decimals", () => {
|
||||
expect(getSpendString(0.01234, 3)).toBe("$0.012");
|
||||
expect(getSpendString(999.9999, 1)).toBe("$1,000.0");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,7 +150,7 @@ describe("dataUtils", () => {
|
||||
// Mock DOM methods
|
||||
const mockTextArea = {
|
||||
value: "",
|
||||
style: {},
|
||||
style: {} as Record<string, string>,
|
||||
setAttribute: vi.fn(),
|
||||
focus: vi.fn(),
|
||||
select: vi.fn(),
|
||||
@@ -149,7 +181,7 @@ describe("dataUtils", () => {
|
||||
// Mock DOM methods
|
||||
const mockTextArea = {
|
||||
value: "",
|
||||
style: {},
|
||||
style: {} as Record<string, string>,
|
||||
setAttribute: vi.fn(),
|
||||
focus: vi.fn(),
|
||||
select: vi.fn(),
|
||||
@@ -171,7 +203,7 @@ describe("dataUtils", () => {
|
||||
it("should set textarea properties correctly", async () => {
|
||||
const mockTextArea = {
|
||||
value: "",
|
||||
style: {},
|
||||
style: {} as Record<string, string>,
|
||||
setAttribute: vi.fn(),
|
||||
focus: vi.fn(),
|
||||
select: vi.fn(),
|
||||
@@ -213,7 +245,7 @@ describe("dataUtils", () => {
|
||||
it("should clean up textarea element after successful copy", async () => {
|
||||
const mockTextArea = {
|
||||
value: "",
|
||||
style: {},
|
||||
style: {} as Record<string, string>,
|
||||
setAttribute: vi.fn(),
|
||||
focus: vi.fn(),
|
||||
select: vi.fn(),
|
||||
@@ -237,7 +269,7 @@ describe("dataUtils", () => {
|
||||
document.execCommand = vi.fn().mockReturnValue(true);
|
||||
const mockTextArea = {
|
||||
value: "",
|
||||
style: {},
|
||||
style: {} as Record<string, string>,
|
||||
setAttribute: vi.fn(),
|
||||
focus: vi.fn(),
|
||||
select: vi.fn(),
|
||||
@@ -17,7 +17,7 @@ export const formatNumberWithCommas = (
|
||||
decimals: number = 0,
|
||||
abbreviate: boolean = false,
|
||||
): string => {
|
||||
if (value === null || value === undefined || !Number.isFinite(value)) {
|
||||
if (value === null || value === undefined || !Number.isFinite(value) || value === 0) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
@@ -46,6 +46,22 @@ export const formatNumberWithCommas = (
|
||||
return `${sign}${scaled.toLocaleString("en-US", opts)}${suffix}`;
|
||||
};
|
||||
|
||||
export const getSpendString = (value: number | null | undefined, decimals: number = 6): string => {
|
||||
if (value === null || value === undefined || !Number.isFinite(value) || value === 0) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const formatted = formatNumberWithCommas(value, decimals);
|
||||
const numericFormatted = Number(formatted.replace(/,/g, ""));
|
||||
|
||||
if (numericFormatted === 0) {
|
||||
const threshold = (1 / 10 ** decimals).toFixed(decimals);
|
||||
return `< $${threshold}`;
|
||||
}
|
||||
|
||||
return `$${formatted}`;
|
||||
};
|
||||
|
||||
export const copyToClipboard = async (
|
||||
text: string | null | undefined,
|
||||
messageText: string = "Copied to clipboard",
|
||||
|
||||
Reference in New Issue
Block a user