mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Various Text, button state, and test changes
This commit is contained in:
parent
634ec91acc
commit
4aea2df986
5 changed files with 71 additions and 59 deletions
|
|
@ -17,6 +17,8 @@ import EntityUsageExportModal from "./EntityUsageExportModal";
|
|||
// Mock utilities that format/export data so tests stay fast and deterministic
|
||||
vi.mock("./utils", () => {
|
||||
return {
|
||||
handleExportCSV: vi.fn(),
|
||||
handleExportJSON: vi.fn(),
|
||||
generateExportData: vi.fn(() => [{ Date: "2025-10-01" }]),
|
||||
generateMetadata: vi.fn(() => ({ meta: true })),
|
||||
};
|
||||
|
|
@ -66,11 +68,11 @@ describe("EntityUsageExportModal", () => {
|
|||
it("renders default state and exports CSV (daily) successfully", async () => {
|
||||
/**
|
||||
* Tests the happy path: user opens modal and exports with defaults.
|
||||
* Verifies that generateExportData is called with 'daily' scope
|
||||
* Verifies that handleExportCSV is called with correct parameters
|
||||
* and modal closes after export completes.
|
||||
*/
|
||||
const user = userEvent.setup();
|
||||
const { generateExportData } = await import("./utils");
|
||||
const { handleExportCSV } = await import("./utils");
|
||||
|
||||
const { getByRole } = render(<EntityUsageExportModal {...baseProps} />);
|
||||
|
||||
|
|
@ -80,10 +82,8 @@ describe("EntityUsageExportModal", () => {
|
|||
// Click export
|
||||
await user.click(getByRole("button", { name: /Export CSV/i }));
|
||||
|
||||
// Verifies export pipeline was invoked with default scope 'daily'
|
||||
expect(generateExportData).toHaveBeenCalled();
|
||||
const callArgs = (generateExportData as any).mock.calls[0];
|
||||
expect(callArgs[1]).toBe("daily");
|
||||
// Verifies export function was invoked with correct parameters
|
||||
expect(handleExportCSV).toHaveBeenCalledWith(baseProps.spendData, "daily", "Tag", "tag");
|
||||
|
||||
// Modal closes after export
|
||||
expect(baseProps.onClose).toHaveBeenCalled();
|
||||
|
|
@ -92,11 +92,11 @@ describe("EntityUsageExportModal", () => {
|
|||
it("exports with 'day-by-day by tag and model' scope when selected", async () => {
|
||||
/**
|
||||
* Tests that user can change export type (scope).
|
||||
* Verifies generateExportData receives 'daily_with_models' scope
|
||||
* Verifies handleExportCSV receives 'daily_with_models' scope
|
||||
* when the second radio option is selected.
|
||||
*/
|
||||
const user = userEvent.setup();
|
||||
const { generateExportData } = await import("./utils");
|
||||
const { handleExportCSV } = await import("./utils");
|
||||
|
||||
const { getByText, getByRole } = render(<EntityUsageExportModal {...baseProps} />);
|
||||
|
||||
|
|
@ -109,9 +109,7 @@ describe("EntityUsageExportModal", () => {
|
|||
await user.click(exportBtn);
|
||||
|
||||
// Ensure the selected scope flowed through
|
||||
expect(generateExportData).toHaveBeenCalled();
|
||||
const callArgs = (generateExportData as any).mock.calls.at(-1);
|
||||
expect(callArgs[1]).toBe("daily_with_models");
|
||||
expect(handleExportCSV).toHaveBeenCalledWith(baseProps.spendData, "daily_with_models", "Tag", "tag");
|
||||
|
||||
// Modal closes after export
|
||||
expect(baseProps.onClose).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import React, { useState } from "react";
|
||||
import { Button } from "@tremor/react";
|
||||
import { Modal } from "antd";
|
||||
import Papa from "papaparse";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import ExportSummary from "./ExportSummary";
|
||||
import ExportTypeSelector from "./ExportTypeSelector";
|
||||
import ExportFormatSelector from "./ExportFormatSelector";
|
||||
import { generateExportData, generateMetadata } from "./utils";
|
||||
import { handleExportCSV, handleExportJSON } from "./utils";
|
||||
import type { EntityUsageExportModalProps, ExportFormat, ExportScope } from "./types";
|
||||
|
||||
const EntityUsageExportModal: React.FC<EntityUsageExportModalProps> = ({
|
||||
|
|
@ -25,50 +24,15 @@ const EntityUsageExportModal: React.FC<EntityUsageExportModalProps> = ({
|
|||
const entityLabel = entityType.charAt(0).toUpperCase() + entityType.slice(1);
|
||||
const modalTitle = customTitle || `Export ${entityLabel} Usage`;
|
||||
|
||||
const handleExportCSV = () => {
|
||||
const data = generateExportData(spendData, exportScope, entityLabel);
|
||||
const csv = Papa.unparse(data);
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.csv`;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleExportJSON = () => {
|
||||
const data = generateExportData(spendData, exportScope, entityLabel);
|
||||
const metadata = generateMetadata(entityType, dateRange, selectedFilters, exportScope, spendData);
|
||||
const exportObject = {
|
||||
metadata,
|
||||
data,
|
||||
};
|
||||
const jsonString = JSON.stringify(exportObject, null, 2);
|
||||
const blob = new Blob([jsonString], { type: "application/json" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.json`;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleExport = async (format?: ExportFormat) => {
|
||||
const formatToUse = format || exportFormat;
|
||||
setIsExporting(true);
|
||||
try {
|
||||
if (formatToUse === "csv") {
|
||||
handleExportCSV();
|
||||
handleExportCSV(spendData, exportScope, entityLabel, entityType);
|
||||
NotificationsManager.success(`${entityLabel} usage data exported successfully as CSV`);
|
||||
} else {
|
||||
handleExportJSON();
|
||||
handleExportJSON(spendData, exportScope, entityLabel, entityType, dateRange, selectedFilters);
|
||||
NotificationsManager.success(`${entityLabel} usage data exported successfully as JSON`);
|
||||
}
|
||||
onClose();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import Papa from "papaparse";
|
||||
import type { EntitySpendData, EntityBreakdown, ExportMetadata, ExportScope } from "./types";
|
||||
import type { DateRangePickerValue } from "@tremor/react";
|
||||
|
||||
export const getEntityBreakdown = (spendData: EntitySpendData): EntityBreakdown[] => {
|
||||
const entitySpend: { [key: string]: EntityBreakdown } = {};
|
||||
|
|
@ -138,7 +140,7 @@ export const generateExportData = (
|
|||
|
||||
export const generateMetadata = (
|
||||
entityType: "tag" | "team" | "organization",
|
||||
dateRange: { from?: Date; to?: Date },
|
||||
dateRange: DateRangePickerValue,
|
||||
selectedFilters: string[],
|
||||
exportScope: ExportScope,
|
||||
spendData: EntitySpendData,
|
||||
|
|
@ -159,3 +161,50 @@ export const generateMetadata = (
|
|||
total_tokens: spendData.metadata.total_tokens,
|
||||
},
|
||||
});
|
||||
|
||||
export const handleExportCSV = (
|
||||
spendData: EntitySpendData,
|
||||
exportScope: ExportScope,
|
||||
entityLabel: string,
|
||||
entityType: "tag" | "team" | "organization",
|
||||
): void => {
|
||||
const data = generateExportData(spendData, exportScope, entityLabel);
|
||||
const csv = Papa.unparse(data);
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.csv`;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const handleExportJSON = (
|
||||
spendData: EntitySpendData,
|
||||
exportScope: ExportScope,
|
||||
entityLabel: string,
|
||||
entityType: "tag" | "team" | "organization",
|
||||
dateRange: DateRangePickerValue,
|
||||
selectedFilters: string[],
|
||||
): void => {
|
||||
const data = generateExportData(spendData, exportScope, entityLabel);
|
||||
const metadata = generateMetadata(entityType, dateRange, selectedFilters, exportScope, spendData);
|
||||
const exportObject = {
|
||||
metadata,
|
||||
data,
|
||||
};
|
||||
const jsonString = JSON.stringify(exportObject, null, 2);
|
||||
const blob = new Blob([jsonString], { type: "application/json" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
const fileName = `${entityType}_usage_${exportScope}_${new Date().toISOString().split("T")[0]}.json`;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -82,31 +82,31 @@ export const QuickActions: React.FC<QuickActionsProps> = ({ onSelectAll, onUnsel
|
|||
</Tooltip>
|
||||
</div>
|
||||
<Button
|
||||
type="default"
|
||||
danger
|
||||
color="danger"
|
||||
variant="outlined"
|
||||
onClick={onUnselectAll}
|
||||
disabled={!hasSelectedEntities}
|
||||
icon={<CloseOutlined />}
|
||||
className="border-gray-300 hover:text-red-600 hover:border-red-300"
|
||||
>
|
||||
Unselect All
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Button
|
||||
type="default"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={() => onSelectAll("MASK")}
|
||||
className="flex items-center justify-center h-10 border-blue-200 hover:border-blue-300 hover:text-blue-700 bg-blue-50 hover:bg-blue-100 text-blue-600"
|
||||
className="h-10"
|
||||
block
|
||||
icon={<EyeInvisibleOutlined />}
|
||||
>
|
||||
Select All & Mask
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
danger
|
||||
color="danger"
|
||||
variant="outlined"
|
||||
onClick={() => onSelectAll("BLOCK")}
|
||||
className="flex items-center justify-center h-10 border-red-200 hover:border-red-300 hover:text-red-700 bg-red-50 hover:bg-red-100 text-red-600"
|
||||
className="h-10 hover:bg-red-100"
|
||||
block
|
||||
icon={<StopOutlined />}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
getRowCanExpand={() => false}
|
||||
isLoading={isLoadingServers}
|
||||
noDataMessage="No MCP servers configured"
|
||||
loadingMessage="🚅 Loading MCP servers..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue