diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx index a5c2c446383..d72515b8e4f 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx @@ -99,7 +99,18 @@ vi.mock("antd", async () => { } (Alert as any).displayName = "AntdAlert"; - return { Select, Alert }; + function Badge(props: any) { + const { count, color, children, ...rest } = props; + return React.createElement( + "div", + { ...rest, "data-testid": "antd-badge", "data-color": color }, + count && React.createElement("span", { "data-testid": "antd-badge-count" }, count), + children, + ); + } + (Badge as any).displayName = "AntdBadge"; + + return { Select, Alert, Badge }; }); vi.mock("@ant-design/icons", async () => { diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx index 7d909db8c37..7f4f98cbc51 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx @@ -27,7 +27,7 @@ import { Text, Title, } from "@tremor/react"; -import { Alert } from "antd"; +import { Alert, Badge } from "antd"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents"; @@ -419,11 +419,13 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
- setUsageView(value)} - isAdmin={all_admin_roles.includes(userRole || "")} - /> + + setUsageView(value)} + isAdmin={all_admin_roles.includes(userRole || "")} + /> +
{/* Your Usage Panel */} diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx index c94a44d4a46..ed837cf038c 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx @@ -6,21 +6,63 @@ vi.mock("antd", async () => { const React = await import("react"); function Select(props: any) { - const { value, onChange, options, ...rest } = props; + const { value, onChange, options, optionRender, labelRender, ...rest } = props; + const selectedOption = options?.find((opt: any) => opt.value === value); + const renderedLabel = labelRender ? labelRender({ value, label: selectedOption?.label }) : selectedOption?.label; + + const optionElements = options?.map((opt: any) => { + const rendered = optionRender ? optionRender({ value: opt.value, label: opt.label }) : opt.label; + return React.createElement("option", { key: opt.value, value: opt.value }, opt.label); + }); + + const optionRenderOutputs = options + ?.map((opt: any) => { + if (optionRender) { + const rendered = optionRender({ value: opt.value, label: opt.label }); + return React.createElement( + "div", + { + key: `option-render-${opt.value}`, + "data-testid": `option-render-${opt.value}`, + style: { display: "none" }, + }, + rendered, + ); + } + return null; + }) + .filter(Boolean); + return React.createElement( - "select", - { - ...rest, - value, - onChange: (e: any) => onChange?.(e.target.value), - role: "combobox", - }, - options?.map((opt: any) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label)), + React.Fragment, + null, + React.createElement( + "select", + { + ...rest, + value, + onChange: (e: any) => onChange?.(e.target.value), + role: "combobox", + }, + optionElements, + ), + ...(optionRenderOutputs || []), ); } (Select as any).displayName = "AntdSelect"; - return { Select }; + function Badge(props: any) { + const { count, color, children, ...rest } = props; + return React.createElement( + "span", + { ...rest, "data-testid": "antd-badge", "data-color": color }, + count && React.createElement("span", { "data-testid": "antd-badge-count" }, count), + children, + ); + } + (Badge as any).displayName = "AntdBadge"; + + return { Select, Badge }; }); vi.mock("@ant-design/icons", async () => { @@ -67,4 +109,13 @@ describe("UsageViewSelect", () => { expect(mockOnChange).toHaveBeenCalledWith("team"); }); + + it("should render badge when option has badgeText", () => { + render(); + + const badge = screen.getByTestId("antd-badge"); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveAttribute("data-color", "blue"); + expect(screen.getByTestId("antd-badge-count")).toHaveTextContent("New"); + }); }); diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx index 4f65a69f951..6b8347b6fbd 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx @@ -8,7 +8,7 @@ import { TagsOutlined, TeamOutlined, } from "@ant-design/icons"; -import { Select } from "antd"; +import { Badge, Select } from "antd"; import React from "react"; export type UsageOption = "global" | "organization" | "team" | "customer" | "tag" | "agent" | "user-agent-activity"; export interface UsageViewSelectProps { @@ -29,6 +29,7 @@ interface OptionConfig { showForNonAdmin?: string; descriptionForAdmin?: string; descriptionForNonAdmin?: string; + badgeText?: string; } const OPTIONS: OptionConfig[] = [ { @@ -37,8 +38,8 @@ const OPTIONS: OptionConfig[] = [ showForAdmin: "Global Usage", showForNonAdmin: "Your Usage", description: "View usage across all resources", - descriptionForAdmin: "View usage across all resources and users", - descriptionForNonAdmin: "View your personal usage statistics", + descriptionForAdmin: "View usage across all resources", + descriptionForNonAdmin: "View your usage", icon: , }, { @@ -48,7 +49,7 @@ const OPTIONS: OptionConfig[] = [ showForNonAdmin: "Your Organization Usage", description: "View organization-level usage", descriptionForAdmin: "View usage across all organizations", - descriptionForNonAdmin: "View your organization's usage statistics", + descriptionForNonAdmin: "View your organization's usage", icon: , }, { @@ -77,6 +78,7 @@ const OPTIONS: OptionConfig[] = [ description: "View usage by AI agents", icon: , adminOnly: true, + badgeText: "New", }, { value: "user-agent-activity", @@ -114,6 +116,7 @@ export const UsageViewSelect: React.FC = ({ label, description: desc, icon: option.icon, + badgeText: option.badgeText, }; }); }; @@ -134,7 +137,7 @@ export const UsageViewSelect: React.FC = ({