mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge pull request #19963 from BerriAI/litellm_ui_spend_logs_em_search
[Feature] UI - Logs: Adding Error message search to ui spend logs
This commit is contained in:
commit
e796b9eb22
5 changed files with 140 additions and 1 deletions
127
ui/litellm-dashboard/src/components/molecules/filter.test.tsx
Normal file
127
ui/litellm-dashboard/src/components/molecules/filter.test.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import { screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { renderWithProviders } from "../../../tests/test-utils";
|
||||
import FilterComponent, { FilterOption } from "./filter";
|
||||
|
||||
describe("FilterComponent", () => {
|
||||
const mockOnApplyFilters = vi.fn();
|
||||
const mockOnResetFilters = vi.fn();
|
||||
|
||||
const defaultOptions: FilterOption[] = [
|
||||
{
|
||||
name: "teamId",
|
||||
label: "Team ID",
|
||||
options: [
|
||||
{ label: "Team 1", value: "team1" },
|
||||
{ label: "Team 2", value: "team2" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
label: "Status",
|
||||
options: [
|
||||
{ label: "Active", value: "active" },
|
||||
{ label: "Inactive", value: "inactive" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "userId",
|
||||
label: "User ID",
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
renderWithProviders(
|
||||
<FilterComponent
|
||||
options={defaultOptions}
|
||||
onApplyFilters={mockOnApplyFilters}
|
||||
onResetFilters={mockOnResetFilters}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByRole("button", { name: "Filters" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display custom button label", () => {
|
||||
renderWithProviders(
|
||||
<FilterComponent
|
||||
options={defaultOptions}
|
||||
onApplyFilters={mockOnApplyFilters}
|
||||
onResetFilters={mockOnResetFilters}
|
||||
buttonLabel="Custom Filters"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByRole("button", { name: "Custom Filters" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onResetFilters when reset button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<FilterComponent
|
||||
options={defaultOptions}
|
||||
onApplyFilters={mockOnApplyFilters}
|
||||
onResetFilters={mockOnResetFilters}
|
||||
initialValues={{ teamId: "team1", status: "active" }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const resetButton = screen.getByRole("button", { name: "Reset Filters" });
|
||||
await user.click(resetButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnResetFilters).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should render filters in correct order", async () => {
|
||||
const user = userEvent.setup();
|
||||
const options: FilterOption[] = [
|
||||
{ name: "model", label: "Model" },
|
||||
{ name: "teamId", label: "Team ID" },
|
||||
{ name: "status", label: "Status" },
|
||||
{ name: "userId", label: "User ID" },
|
||||
];
|
||||
|
||||
renderWithProviders(
|
||||
<FilterComponent
|
||||
options={options}
|
||||
onApplyFilters={mockOnApplyFilters}
|
||||
onResetFilters={mockOnResetFilters}
|
||||
/>,
|
||||
);
|
||||
|
||||
const filterButton = screen.getByRole("button", { name: "Filters" });
|
||||
await user.click(filterButton);
|
||||
|
||||
await waitFor(() => {
|
||||
const labels = screen.getAllByText(/^(Team ID|Status|User ID|Model)$/);
|
||||
expect(labels[0]).toHaveTextContent("Team ID");
|
||||
expect(labels[1]).toHaveTextContent("Status");
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle input filter changes", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<FilterComponent
|
||||
options={defaultOptions}
|
||||
onApplyFilters={mockOnApplyFilters}
|
||||
onResetFilters={mockOnResetFilters}
|
||||
/>,
|
||||
);
|
||||
|
||||
const filterButton = screen.getByRole("button", { name: "Filters" });
|
||||
await user.click(filterButton);
|
||||
|
||||
const userIdInput = screen.getByPlaceholderText("Enter User ID...");
|
||||
await user.type(userIdInput, "user123");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnApplyFilters).toHaveBeenCalledWith({ userId: "user123" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -130,6 +130,7 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
|
|||
"User ID",
|
||||
"End User",
|
||||
"Error Code",
|
||||
"Error Message",
|
||||
"Key Hash",
|
||||
"Model",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2764,6 +2764,7 @@ export const uiSpendLogsCall = async (
|
|||
model?: string,
|
||||
keyAlias?: string,
|
||||
error_code?: string,
|
||||
error_message?: string,
|
||||
) => {
|
||||
try {
|
||||
// Construct base URL
|
||||
|
|
@ -2784,6 +2785,7 @@ export const uiSpendLogsCall = async (
|
|||
if (model) queryParams.append("model", model);
|
||||
if (keyAlias) queryParams.append("key_alias", keyAlias);
|
||||
if (error_code) queryParams.append("error_code", error_code);
|
||||
if (error_message) queryParams.append("error_message", error_message);
|
||||
// Append query parameters to URL if any exist
|
||||
const queryString = queryParams.toString();
|
||||
if (queryString) {
|
||||
|
|
|
|||
|
|
@ -464,6 +464,11 @@ export default function SpendLogsTable({
|
|||
label: "Key Hash",
|
||||
isSearchable: false,
|
||||
},
|
||||
{
|
||||
name: "Error Message",
|
||||
label: "Error Message",
|
||||
isSearchable: false,
|
||||
},
|
||||
];
|
||||
|
||||
// When a session is selected, render the SessionView component
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const FILTER_KEYS = {
|
|||
STATUS: "Status",
|
||||
KEY_ALIAS: "Key Alias",
|
||||
ERROR_CODE: "Error Code",
|
||||
ERROR_MESSAGE: "Error Message",
|
||||
} as const;
|
||||
|
||||
export type FilterKey = keyof typeof FILTER_KEYS;
|
||||
|
|
@ -55,6 +56,7 @@ export function useLogFilterLogic({
|
|||
[FILTER_KEYS.STATUS]: "",
|
||||
[FILTER_KEYS.KEY_ALIAS]: "",
|
||||
[FILTER_KEYS.ERROR_CODE]: "",
|
||||
[FILTER_KEYS.ERROR_MESSAGE]: "",
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
|
@ -97,6 +99,7 @@ export function useLogFilterLogic({
|
|||
filters[FILTER_KEYS.MODEL] || undefined,
|
||||
filters[FILTER_KEYS.KEY_ALIAS] || undefined,
|
||||
filters[FILTER_KEYS.ERROR_CODE] || undefined,
|
||||
filters[FILTER_KEYS.ERROR_MESSAGE] || undefined,
|
||||
);
|
||||
|
||||
if (currentTimestamp === lastSearchTimestamp.current && response.data) {
|
||||
|
|
@ -137,7 +140,8 @@ export function useLogFilterLogic({
|
|||
filters[FILTER_KEYS.REQUEST_ID] ||
|
||||
filters[FILTER_KEYS.USER_ID] ||
|
||||
filters[FILTER_KEYS.END_USER] ||
|
||||
filters[FILTER_KEYS.ERROR_CODE]
|
||||
filters[FILTER_KEYS.ERROR_CODE] ||
|
||||
filters[FILTER_KEYS.ERROR_MESSAGE]
|
||||
),
|
||||
[filters],
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue