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],
);