Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions ui/litellm-dashboard/src/components/molecules/filter.test.tsx
Original file line number Diff line number Diff line change
@@ -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" });
});
});
});
1 change: 1 addition & 0 deletions ui/litellm-dashboard/src/components/molecules/filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
"User ID",
"End User",
"Error Code",
"Error Message",
"Key Hash",
"Model",
];
Expand Down
2 changes: 2 additions & 0 deletions ui/litellm-dashboard/src/components/networking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2764,6 +2764,7 @@ export const uiSpendLogsCall = async (
model?: string,
keyAlias?: string,
error_code?: string,
error_message?: string,
) => {
try {
// Construct base URL
Expand All @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions ui/litellm-dashboard/src/components/view_logs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,6 +56,7 @@ export function useLogFilterLogic({
[FILTER_KEYS.STATUS]: "",
[FILTER_KEYS.KEY_ALIAS]: "",
[FILTER_KEYS.ERROR_CODE]: "",
[FILTER_KEYS.ERROR_MESSAGE]: "",
}),
[],
);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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],
);
Expand Down
Loading