From 0cdfa8e5faf61f09485df18651a905e012a6b955 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 28 Jan 2026 15:39:32 -0800 Subject: [PATCH] Adding Error message search to ui spend logs --- .../src/components/molecules/filter.test.tsx | 127 ++++++++++++++++++ .../src/components/molecules/filter.tsx | 1 + .../src/components/networking.tsx | 2 + .../src/components/view_logs/index.tsx | 5 + .../components/view_logs/log_filter_logic.tsx | 6 +- 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 ui/litellm-dashboard/src/components/molecules/filter.test.tsx diff --git a/ui/litellm-dashboard/src/components/molecules/filter.test.tsx b/ui/litellm-dashboard/src/components/molecules/filter.test.tsx new file mode 100644 index 00000000000..bd06d110e95 --- /dev/null +++ b/ui/litellm-dashboard/src/components/molecules/filter.test.tsx @@ -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( + , + ); + expect(screen.getByRole("button", { name: "Filters" })).toBeInTheDocument(); + }); + + it("should display custom button label", () => { + renderWithProviders( + , + ); + expect(screen.getByRole("button", { name: "Custom Filters" })).toBeInTheDocument(); + }); + + it("should call onResetFilters when reset button is clicked", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + 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( + , + ); + + 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( + , + ); + + 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" }); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/molecules/filter.tsx b/ui/litellm-dashboard/src/components/molecules/filter.tsx index d7c40ae0399..6c46392410d 100644 --- a/ui/litellm-dashboard/src/components/molecules/filter.tsx +++ b/ui/litellm-dashboard/src/components/molecules/filter.tsx @@ -130,6 +130,7 @@ const FilterComponent: React.FC = ({ "User ID", "End User", "Error Code", + "Error Message", "Key Hash", "Model", ]; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index b24f8eaddcc..5cc5216e132 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -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) { diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index e25925f13b8..22af41f6d08 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -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 diff --git a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx index f56e72df496..0e875bc4fd1 100644 --- a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx @@ -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], );