From 0aa137190385e7a50504644aab5a7d98d9407f57 Mon Sep 17 00:00:00 2001 From: tanjiro <56165694+NANDINI-star@users.noreply.github.com> Date: Fri, 22 Aug 2025 06:56:49 +0900 Subject: [PATCH] Fix Filter Dropdown UX Issue - Load Initial Options (#13858) * move the filter component to molecules * load initial options --- .../src/components/all_keys_table.tsx | 4 +- .../filter.tsx | 44 ++++++++++++++++++- .../src/components/view_logs/index.tsx | 4 +- 3 files changed, 47 insertions(+), 5 deletions(-) rename ui/litellm-dashboard/src/components/{common_components => molecules}/filter.tsx (75%) diff --git a/ui/litellm-dashboard/src/components/all_keys_table.tsx b/ui/litellm-dashboard/src/components/all_keys_table.tsx index 34116d3f4b1..b65f8911a44 100644 --- a/ui/litellm-dashboard/src/components/all_keys_table.tsx +++ b/ui/litellm-dashboard/src/components/all_keys_table.tsx @@ -7,8 +7,8 @@ import { Button } from "@tremor/react" import KeyInfoView from "./templates/key_info_view" import { Tooltip } from "antd" import { Team, KeyResponse } from "./key_team_helpers/key_list" -import FilterComponent from "./common_components/filter" -import { FilterOption } from "./common_components/filter" +import FilterComponent from "./molecules/filter" +import { FilterOption } from "./molecules/filter" import { keyListCall, Organization, userListCall } from "./networking" import { createTeamSearchFunction } from "./key_team_helpers/team_search_fn" import { createOrgSearchFunction } from "./key_team_helpers/organization_search_fn" diff --git a/ui/litellm-dashboard/src/components/common_components/filter.tsx b/ui/litellm-dashboard/src/components/molecules/filter.tsx similarity index 75% rename from ui/litellm-dashboard/src/components/common_components/filter.tsx rename to ui/litellm-dashboard/src/components/molecules/filter.tsx index 20b11447b21..e52cbb463b9 100644 --- a/ui/litellm-dashboard/src/components/common_components/filter.tsx +++ b/ui/litellm-dashboard/src/components/molecules/filter.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from "react"; +import React, { useState, useCallback, useEffect } from "react"; import { Button, Input, Select } from "antd"; import { FilterIcon } from "@heroicons/react/outline"; import debounce from "lodash/debounce"; @@ -43,6 +43,9 @@ const FilterComponent: React.FC = ({ const [searchInputValueMap, setSearchInputValueMap] = useState<{ [key: string]: string; }>({}); + const [initialOptionsLoaded, setInitialOptionsLoaded] = useState<{ + [key: string]: boolean; + }>({}); const debouncedSearch = useCallback( debounce(async (value: string, option: FilterOption) => { @@ -62,6 +65,36 @@ const FilterComponent: React.FC = ({ [] ); + // Load initial options for searchable filters + const loadInitialOptions = useCallback(async (option: FilterOption) => { + if (!option.isSearchable || !option.searchFn || initialOptionsLoaded[option.name]) return; + + setSearchLoadingMap((prev) => ({ ...prev, [option.name]: true })); + setInitialOptionsLoaded((prev) => ({ ...prev, [option.name]: true })); + + try { + // Load initial options with empty search to get some default results + const results = await option.searchFn(""); + setSearchOptionsMap((prev) => ({ ...prev, [option.name]: results })); + } catch (error) { + console.error("Error loading initial options:", error); + setSearchOptionsMap((prev) => ({ ...prev, [option.name]: [] })); + } finally { + setSearchLoadingMap((prev) => ({ ...prev, [option.name]: false })); + } + }, [initialOptionsLoaded]); + + // Load initial options when filters are shown + useEffect(() => { + if (showFilters) { + options.forEach(option => { + if (option.isSearchable && !initialOptionsLoaded[option.name]) { + loadInitialOptions(option); + } + }); + } + }, [showFilters, options, loadInitialOptions, initialOptionsLoaded]); + const handleFilterChange = (name: string, value: string) => { const newValues = { ...tempValues, @@ -80,6 +113,13 @@ const FilterComponent: React.FC = ({ onResetFilters(); }; + // Handle dropdown open to load initial options + const handleDropdownVisibleChange = (open: boolean, option: FilterOption) => { + if (open && option.isSearchable && !initialOptionsLoaded[option.name]) { + loadInitialOptions(option); + } + }; + // Define the order of filters const orderedFilters = [ "Team ID", @@ -125,6 +165,7 @@ const FilterComponent: React.FC = ({ placeholder={`Search ${option.label || option.name}...`} value={tempValues[option.name] || undefined} onChange={(value) => handleFilterChange(option.name, value)} + onDropdownVisibleChange={(open) => handleDropdownVisibleChange(open, option)} onSearch={(value) => { setSearchInputValueMap((prev) => ({ ...prev, @@ -138,6 +179,7 @@ const FilterComponent: React.FC = ({ loading={searchLoadingMap[option.name]} options={searchOptionsMap[option.name] || []} allowClear + notFoundContent={searchLoadingMap[option.name] ? "Loading..." : "No results found"} /> ) : option.options ? (