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 265103b5ccc..a5c2c446383 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx @@ -1,11 +1,11 @@ -import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"; -import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest"; -import NewUsagePage from "./UsagePageView"; +import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents"; +import { useCustomers } from "@/app/(dashboard)/hooks/customers/useCustomers"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { Organization } from "../../networking"; import * as networking from "../../networking"; -import { useCustomers } from "@/app/(dashboard)/hooks/customers/useCustomers"; -import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents"; -import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import NewUsagePage from "./UsagePageView"; // Polyfill ResizeObserver for test environment beforeAll(() => { @@ -69,6 +69,60 @@ vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: vi.fn(), })); +vi.mock("antd", async () => { + const React = await import("react"); + + function Select(props: any) { + const { value, onChange, options, ...rest } = props; + 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)), + ); + } + (Select as any).displayName = "AntdSelect"; + + function Alert(props: any) { + const { message, description, type, closable, onClose, ...rest } = props; + return React.createElement( + "div", + { ...rest, "data-testid": "antd-alert", "data-type": type }, + message && React.createElement("div", null, message), + description && React.createElement("div", null, description), + closable && React.createElement("button", { onClick: onClose, "aria-label": "Close" }, "×"), + ); + } + (Alert as any).displayName = "AntdAlert"; + + return { Select, Alert }; +}); + +vi.mock("@ant-design/icons", async () => { + const React = await import("react"); + + function Icon() { + return React.createElement("span"); + } + + return { + GlobalOutlined: Icon, + BankOutlined: Icon, + TeamOutlined: Icon, + ShoppingCartOutlined: Icon, + TagsOutlined: Icon, + RobotOutlined: Icon, + LineChartOutlined: Icon, + BarChartOutlined: Icon, + ClockCircleOutlined: Icon, + CalendarOutlined: Icon, + }; +}); + describe("NewUsage", () => { const mockUserDailyActivityAggregatedCall = vi.mocked(networking.userDailyActivityAggregatedCall); const mockTagListCall = vi.mocked(networking.tagListCall); @@ -301,20 +355,20 @@ describe("NewUsage", () => { expect(screen.getByText("Top Virtual Keys")).toBeInTheDocument(); }); - it("should switch between tabs correctly", async () => { + it("should switch between usage views correctly", async () => { render(); await waitFor(() => { expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled(); }); - // Default tab should show Global Usage (for admin) + // Default view should show Global Usage (for admin) expect(screen.getByText("Daily Spend")).toBeInTheDocument(); - // Switch to Team Usage tab - const teamUsageTab = screen.getByText("Team Usage"); + // Switch to Team Usage view + const usageSelect = screen.getByRole("combobox"); act(() => { - fireEvent.click(teamUsageTab); + fireEvent.change(usageSelect, { target: { value: "team" } }); }); // Should render EntityUsage component @@ -323,10 +377,9 @@ describe("NewUsage", () => { expect(entityUsageElements.length).toBeGreaterThan(0); }); - // Switch to Tag Usage tab (admin only) - const tagUsageTab = screen.getByText("Tag Usage"); + // Switch to Tag Usage view (admin only) act(() => { - fireEvent.click(tagUsageTab); + fireEvent.change(usageSelect, { target: { value: "tag" } }); }); // Should still render EntityUsage component for tags @@ -336,16 +389,16 @@ describe("NewUsage", () => { }); }); - it("should show organization usage banner and tab for admins", async () => { + it("should show organization usage banner and view for admins", async () => { render(); await waitFor(() => { expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled(); }); - const organizationTab = screen.getByText("Organization Usage"); + const usageSelect = screen.getByRole("combobox"); act(() => { - fireEvent.click(organizationTab); + fireEvent.change(usageSelect, { target: { value: "organization" } }); }); await waitFor(() => { @@ -355,7 +408,7 @@ describe("NewUsage", () => { }); }); - it("should show customer usage tab for admins", async () => { + it("should show customer usage view for admins", async () => { mockUseCustomers.mockReturnValue({ data: mockCustomers, isLoading: false, @@ -368,9 +421,9 @@ describe("NewUsage", () => { expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled(); }); - const customerTab = screen.getByText("Customer Usage"); + const usageSelect = screen.getByRole("combobox"); act(() => { - fireEvent.click(customerTab); + fireEvent.change(usageSelect, { target: { value: "customer" } }); }); await waitFor(() => { @@ -379,7 +432,7 @@ describe("NewUsage", () => { }); }); - it("should show agent usage tab for admins", async () => { + it("should show agent usage view for admins", async () => { mockUseAgents.mockReturnValue({ data: { agents: mockAgents }, isLoading: false, @@ -392,9 +445,9 @@ describe("NewUsage", () => { expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled(); }); - const agentTab = screen.getByText("Agent Usage"); + const usageSelect = screen.getByRole("combobox"); act(() => { - fireEvent.click(agentTab); + fireEvent.change(usageSelect, { target: { value: "agent" } }); }); await waitFor(() => { diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx index d52e9d7a94b..7d909db8c37 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx @@ -30,13 +30,14 @@ import { import { Alert } from "antd"; import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents"; import { useCustomers } from "@/app/(dashboard)/hooks/customers/useCustomers"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import { Button } from "@tremor/react"; import { all_admin_roles } from "../../../utils/roles"; import { ActivityMetrics, processActivityData } from "../../activity_metrics"; import CloudZeroExportModal from "../../cloudzero_export_modal"; -import EntityUsage, { EntityList } from "./EntityUsage/EntityUsage"; import EntityUsageExportModal from "../../EntityUsageExport"; import { Team } from "../../key_team_helpers/key_list"; import { Organization, tagListCall, userDailyActivityAggregatedCall, userDailyActivityCall } from "../../networking"; @@ -44,13 +45,13 @@ import { getProviderLogoAndName } from "../../provider_info_helpers"; import AdvancedDatePicker from "../../shared/advanced_date_picker"; import { ChartLoader } from "../../shared/chart_loader"; import { Tag } from "../../tag_management/types"; -import TopKeyView from "./EntityUsage/TopKeyView"; -import { DailyData, KeyMetricWithMetadata, MetricWithMetadata } from "../types"; -import { valueFormatterSpend } from "../utils/value_formatters"; import UserAgentActivity from "../../user_agent_activity"; import ViewUserSpend from "../../view_user_spend"; -import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents"; -import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { DailyData, KeyMetricWithMetadata, MetricWithMetadata } from "../types"; +import { valueFormatterSpend } from "../utils/value_formatters"; +import EntityUsage, { EntityList } from "./EntityUsage/EntityUsage"; +import TopKeyView from "./EntityUsage/TopKeyView"; +import { UsageOption, UsageViewSelect } from "./UsageViewSelect/UsageViewSelect"; interface UsagePageProps { teams: Team[]; @@ -86,7 +87,8 @@ const UsagePage: React.FC = ({ teams, organizations }) => { const [isGlobalExportModalOpen, setIsGlobalExportModalOpen] = useState(false); const [showOrganizationBanner, setShowOrganizationBanner] = useState(true); const [showCustomerBanner, setShowCustomerBanner] = useState(true); - + const [usageView, setUsageView] = useState("global"); + const [showAgentBanner, setShowAgentBanner] = useState(true); const getAllTags = async () => { if (!accessToken) { return; @@ -416,431 +418,433 @@ const UsagePage: React.FC = ({ teams, organizations }) => { {/* Global Date Picker and Tabs - Single Row */}
- -
- - {all_admin_roles.includes(userRole || "") ? Global Usage : Your Usage} - {all_admin_roles.includes(userRole || "") ? ( - Organization Usage - ) : ( - Your Organization Usage - )} - Team Usage - {all_admin_roles.includes(userRole || "") ? Customer Usage : <>} - {all_admin_roles.includes(userRole || "") ? Tag Usage : <>} - {all_admin_roles.includes(userRole || "") ? Agent Usage : <>} - {all_admin_roles.includes(userRole || "") ? User Agent Activity : <>} - - -
- - {/* Your Usage Panel */} - - -
- - Cost - Model Activity - Key Activity - MCP Server Activity - -
+ {/* Your Usage Panel */} + {usageView === "global" && ( + +
+ + Cost + Model Activity + Key Activity + MCP Server Activity + + +
+ + {/* Cost Panel */} + + + {/* Total Spend Card */} + + + Project Spend{" "} + {dateValue.from && dateValue.to && ( + <> + {dateValue.from.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: dateValue.from.getFullYear() !== dateValue.to.getFullYear() ? "numeric" : undefined, + })} + {" - "} + {dateValue.to.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + })} + + )} + + + + + + + + Usage Metrics + + + Total Requests + + {userSpendData.metadata?.total_api_requests?.toLocaleString() || 0} + + + + Successful Requests + + {userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0} + + + + Failed Requests + + {userSpendData.metadata?.total_failed_requests?.toLocaleString() || 0} + + + + Total Tokens + + {userSpendData.metadata?.total_tokens?.toLocaleString() || 0} + + + + Average Cost per Request + + $ + {formatNumberWithCommas( + (totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1), + 4, + )} + + + + + + + {/* Daily Spend Chart */} + + + Daily Spend + {loading ? ( + + ) : ( + new Date(a.date).getTime() - new Date(b.date).getTime(), + )} + index="date" + categories={["metrics.spend"]} + colors={["cyan"]} + valueFormatter={valueFormatterSpend} + yAxisWidth={100} + showLegend={false} + customTooltip={({ payload, active }) => { + if (!active || !payload?.[0]) return null; + const data = payload[0].payload; + return ( +
+

{data.date}

+

+ Spend: ${formatNumberWithCommas(data.metrics.spend, 2)} +

+

Requests: {data.metrics.api_requests}

+

Successful: {data.metrics.successful_requests}

+

Failed: {data.metrics.failed_requests}

+

Tokens: {data.metrics.total_tokens}

+
+ ); + }} /> - - )} - > - Export Data - -
- - {/* Cost Panel */} - - - {/* Total Spend Card */} - - - Project Spend{" "} - {dateValue.from && dateValue.to && ( - <> - {dateValue.from.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: - dateValue.from.getFullYear() !== dateValue.to.getFullYear() ? "numeric" : undefined, - })} - {" - "} - {dateValue.to.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - })} - - )} - + )} + + + {/* Top API Keys */} + + + Top Virtual Keys + + + - - + {/* Top Models */} + + +
+ {modelViewType === "groups" ? "Top Public Model Names" : "Top Litellm Models"} +
+ + +
+
+ {loading ? ( + + ) : ( + { + if (!active || !payload?.[0]) return null; + const data = payload[0].payload; + return ( +
+

{data.key}

+

Spend: ${formatNumberWithCommas(data.spend, 2)}

+

Total Requests: {data.requests.toLocaleString()}

+

+ Successful: {data.successful_requests.toLocaleString()} +

+

Failed: {data.failed_requests.toLocaleString()}

+

Tokens: {data.tokens.toLocaleString()}

+
+ ); + }} + /> + )} +
+ - - - Usage Metrics - - - Total Requests - - {userSpendData.metadata?.total_api_requests?.toLocaleString() || 0} - - - - Successful Requests - - {userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0} - - - - Failed Requests - - {userSpendData.metadata?.total_failed_requests?.toLocaleString() || 0} - - - - Total Tokens - - {userSpendData.metadata?.total_tokens?.toLocaleString() || 0} - - - - Average Cost per Request - - $ - {formatNumberWithCommas( - (totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1), - 4, - )} - - - - - - - {/* Daily Spend Chart */} - - - Daily Spend - {loading ? ( - - ) : ( - new Date(a.date).getTime() - new Date(b.date).getTime(), - )} - index="date" - categories={["metrics.spend"]} - colors={["cyan"]} - valueFormatter={valueFormatterSpend} - yAxisWidth={100} - showLegend={false} - customTooltip={({ payload, active }) => { - if (!active || !payload?.[0]) return null; - const data = payload[0].payload; - return ( -
-

{data.date}

-

- Spend: ${formatNumberWithCommas(data.metrics.spend, 2)} -

-

Requests: {data.metrics.api_requests}

-

Successful: {data.metrics.successful_requests}

-

Failed: {data.metrics.failed_requests}

-

Tokens: {data.metrics.total_tokens}

-
- ); - }} - /> - )} -
- - {/* Top API Keys */} - - - Top Virtual Keys - - - - - {/* Top Models */} - - -
- - {modelViewType === "groups" ? "Top Public Model Names" : "Top Litellm Models"} - -
- - -
-
- {loading ? ( - - ) : ( - + +
+ Spend by Provider +
+ {loading ? ( + + ) : ( + + + `$${formatNumberWithCommas(value, 2)}`} colors={["cyan"]} - valueFormatter={valueFormatterSpend} - layout="vertical" - yAxisWidth={200} - showLegend={false} - customTooltip={({ payload, active }) => { - if (!active || !payload?.[0]) return null; - const data = payload[0].payload; - return ( -
-

{data.key}

-

Spend: ${formatNumberWithCommas(data.spend, 2)}

-

Total Requests: {data.requests.toLocaleString()}

-

- Successful: {data.successful_requests.toLocaleString()} -

-

Failed: {data.failed_requests.toLocaleString()}

-

Tokens: {data.tokens.toLocaleString()}

-
- ); - }} /> - )} -
- - - {/* Spend by Provider */} - - -
- Spend by Provider -
- {loading ? ( - - ) : ( - - - `$${formatNumberWithCommas(value, 2)}`} - colors={["cyan"]} - /> - - - - - - Provider - Spend - Successful - Failed - Tokens + + +
+ + + Provider + Spend + Successful + Failed + Tokens + + + + {getProviderSpend() + .filter((provider) => provider.spend > 0) + .map((provider) => ( + + +
+ {provider.provider && ( + {`${provider.provider} { + const target = e.target as HTMLImageElement; + const parent = target.parentElement; + if (parent) { + const fallbackDiv = document.createElement("div"); + fallbackDiv.className = + "w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs"; + fallbackDiv.textContent = provider.provider?.charAt(0) || "-"; + parent.replaceChild(fallbackDiv, target); + } + }} + /> + )} + {provider.provider} +
+
+ ${formatNumberWithCommas(provider.spend, 2)} + + {provider.successful_requests.toLocaleString()} + + + {provider.failed_requests.toLocaleString()} + + {provider.tokens.toLocaleString()}
- - - {getProviderSpend() - .filter((provider) => provider.spend > 0) - .map((provider) => ( - - -
- {provider.provider && ( - {`${provider.provider} { - const target = e.target as HTMLImageElement; - const parent = target.parentElement; - if (parent) { - const fallbackDiv = document.createElement("div"); - fallbackDiv.className = - "w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs"; - fallbackDiv.textContent = provider.provider?.charAt(0) || "-"; - parent.replaceChild(fallbackDiv, target); - } - }} - /> - )} - {provider.provider} -
-
- ${formatNumberWithCommas(provider.spend, 2)} - - {provider.successful_requests.toLocaleString()} - - - {provider.failed_requests.toLocaleString()} - - {provider.tokens.toLocaleString()} -
- ))} -
-
- -
- )} -
- + ))} + + + +
+ )} + + - {/* Usage Metrics */} - -
+ {/* Usage Metrics */} + + - {/* Activity Panel */} - - - - - - - - - -
- - + {/* Activity Panel */} + + + + + + + + + + + + )} + {/* Organization Usage Panel */} - {/* Organization Usage Panel */} - - {showOrganizationBanner && ( - setShowOrganizationBanner(false)} - className="mb-5" - /> - )} - ({ - label: organization.organization_alias, - value: organization.organization_id, - })) || null - } - premiumUser={premiumUser} + {usageView === "organization" && ( + <> + {showOrganizationBanner && ( + setShowOrganizationBanner(false)} + className="mb-5" /> - + )} + ({ + label: organization.organization_alias, + value: organization.organization_id, + })) || null + } + premiumUser={premiumUser} + /> + + )} - {/* Team Usage Panel */} - - ({ - label: team.team_alias, - value: team.team_id, - })) || null - } - premiumUser={premiumUser} - dateValue={dateValue} - /> - + {/* Team Usage Panel */} + {usageView === "team" && ( + ({ + label: team.team_alias, + value: team.team_id, + })) || null + } + premiumUser={premiumUser} + dateValue={dateValue} + /> + )} - {/* Customer Usage Panel */} - - {showCustomerBanner && ( - setShowCustomerBanner(false)} - className="mb-5" - /> - )} - ({ - label: customer.alias || customer.user_id, - value: customer.user_id, - })) || null - } - premiumUser={premiumUser} - dateValue={dateValue} + {/* Customer Usage Panel */} + {usageView === "customer" && ( + <> + {showCustomerBanner && ( + setShowCustomerBanner(false)} + className="mb-5" /> - - {/* Tag Usage Panel */} - - ({ + label: customer.alias || customer.user_id, + value: customer.user_id, + })) || null + } + premiumUser={premiumUser} + dateValue={dateValue} + /> + + )} + {/* Tag Usage Panel */} + {usageView === "tag" && ( + + )} + {usageView === "agent" && ( + <> + {showAgentBanner && ( + setShowAgentBanner(false)} + className="mb-5" /> - - - ({ label: agent.agent_name, value: agent.agent_id })) || null - } - premiumUser={premiumUser} - dateValue={dateValue} - /> - - {/* User Agent Activity Panel */} - - - - - + )} + ({ label: agent.agent_name, value: agent.agent_id })) || null + } + premiumUser={premiumUser} + dateValue={dateValue} + />{" "} + + )} + {/* User Agent Activity Panel */} + {usageView === "user-agent-activity" && ( + + )}
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 new file mode 100644 index 00000000000..c94a44d4a46 --- /dev/null +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx @@ -0,0 +1,70 @@ +import { act, fireEvent, render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { UsageViewSelect } from "./UsageViewSelect"; + +vi.mock("antd", async () => { + const React = await import("react"); + + function Select(props: any) { + const { value, onChange, options, ...rest } = props; + 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)), + ); + } + (Select as any).displayName = "AntdSelect"; + + return { Select }; +}); + +vi.mock("@ant-design/icons", async () => { + const React = await import("react"); + + function Icon(props: any) { + return React.createElement("span", { "data-testid": "antd-icon" }); + } + + return { + GlobalOutlined: Icon, + BankOutlined: Icon, + TeamOutlined: Icon, + ShoppingCartOutlined: Icon, + TagsOutlined: Icon, + RobotOutlined: Icon, + LineChartOutlined: Icon, + BarChartOutlined: Icon, + }; +}); + +describe("UsageViewSelect", () => { + const mockOnChange = vi.fn(); + + beforeEach(() => { + mockOnChange.mockClear(); + }); + + it("should render", () => { + render(); + + expect(screen.getByText("Usage View")).toBeInTheDocument(); + expect(screen.getByText("Select the usage data you want to view")).toBeInTheDocument(); + expect(screen.getByRole("combobox")).toBeInTheDocument(); + }); + + it("should call onChange when value changes", () => { + render(); + + const select = screen.getByRole("combobox"); + act(() => { + fireEvent.change(select, { target: { value: "team" } }); + }); + + expect(mockOnChange).toHaveBeenCalledWith("team"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx new file mode 100644 index 00000000000..4f65a69f951 --- /dev/null +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx @@ -0,0 +1,171 @@ +import { + BankOutlined, + BarChartOutlined, + GlobalOutlined, + LineChartOutlined, + RobotOutlined, + ShoppingCartOutlined, + TagsOutlined, + TeamOutlined, +} from "@ant-design/icons"; +import { Select } from "antd"; +import React from "react"; +export type UsageOption = "global" | "organization" | "team" | "customer" | "tag" | "agent" | "user-agent-activity"; +export interface UsageViewSelectProps { + value: UsageOption; + onChange: (value: UsageOption) => void; + isAdmin: boolean; + title?: string; + description?: string; + "data-id"?: string; +} +interface OptionConfig { + value: UsageOption; + label: string; + description: string; + icon: React.ReactNode; + adminOnly?: boolean; + showForAdmin?: string; + showForNonAdmin?: string; + descriptionForAdmin?: string; + descriptionForNonAdmin?: string; +} +const OPTIONS: OptionConfig[] = [ + { + value: "global", + label: "Global Usage", + 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", + icon: , + }, + { + value: "organization", + label: "Organization Usage", + showForAdmin: "Organization Usage", + showForNonAdmin: "Your Organization Usage", + description: "View organization-level usage", + descriptionForAdmin: "View usage across all organizations", + descriptionForNonAdmin: "View your organization's usage statistics", + icon: , + }, + { + value: "team", + label: "Team Usage", + description: "View usage by team", + icon: , + }, + { + value: "customer", + label: "Customer Usage", + description: "View usage by customer accounts", + icon: , + adminOnly: true, + }, + { + value: "tag", + label: "Tag Usage", + description: "View usage grouped by tags", + icon: , + adminOnly: true, + }, + { + value: "agent", + label: "Agent Usage (A2A)", + description: "View usage by AI agents", + icon: , + adminOnly: true, + }, + { + value: "user-agent-activity", + label: "User Agent Activity", + description: "View detailed user agent activity logs", + icon: , + adminOnly: true, + }, +]; +export const UsageViewSelect: React.FC = ({ + value, + onChange, + isAdmin, + title = "Usage View", + description = "Select the usage data you want to view", + "data-id": dataId, +}) => { + const getFilteredOptions = () => { + return OPTIONS.filter((option) => { + if (option.adminOnly && !isAdmin) { + return false; + } + return true; + }).map((option) => { + let label = option.label; + let desc = option.description; + if (option.showForAdmin && option.showForNonAdmin) { + label = isAdmin ? option.showForAdmin : option.showForNonAdmin; + } + if (option.descriptionForAdmin && option.descriptionForNonAdmin) { + desc = isAdmin ? option.descriptionForAdmin : option.descriptionForNonAdmin; + } + return { + value: option.value, + label, + description: desc, + icon: option.icon, + }; + }); + }; + const filteredOptions = getFilteredOptions(); + return ( +
+
+
+
+ +
+
+

{title}

+

{description}

+
+
+
+