mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(ui): use single-select for user filter and add tests
The user entity type's backend endpoint only accepts a single user_id, so the filter now uses single-select mode instead of multi-select. Added tests for the new user entity type in EntityUsage and UsageViewSelect. Updated CLAUDE.md and AGENTS.md with guidance on UI/backend contract consistency and test coverage for new entity types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
92fc41c614
commit
574be2c4c9
6 changed files with 38 additions and 3 deletions
|
|
@ -174,6 +174,8 @@ When opening issues or pull requests, follow these templates:
|
|||
3. **Rate Limits**: Respect provider rate limits in tests
|
||||
4. **Memory Usage**: Be mindful of memory usage in streaming scenarios
|
||||
5. **Dependencies**: Keep dependencies minimal and well-justified
|
||||
6. **UI/Backend Contract Mismatch**: When adding a new entity type to the UI, always check whether the backend endpoint accepts a single value or an array. Match the UI control accordingly (single-select vs. multi-select) to avoid silently dropping user selections
|
||||
7. **Missing Tests for New Entity Types**: When adding a new entity type (e.g., in `EntityUsage`, `UsageViewSelect`), always add corresponding tests in the existing test files and update any icon/component mocks
|
||||
|
||||
## HELPFUL RESOURCES
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ LiteLLM is a unified interface for 100+ LLM providers with two main components:
|
|||
- Integration tests for each provider in `tests/llm_translation/`
|
||||
- Proxy tests in `tests/proxy_unit_tests/`
|
||||
- Load tests in `tests/load_tests/`
|
||||
- **Always add tests when adding new entity types or features** — if the existing test file covers other entity types, add corresponding tests for the new one
|
||||
|
||||
### UI / Backend Consistency
|
||||
- When wiring a new UI entity type to an existing backend endpoint, verify the backend API contract (single value vs. array, required vs. optional params) and ensure the UI controls match — e.g., use a single-select dropdown when the backend accepts a single value, not a multi-select
|
||||
|
||||
### Database Migrations
|
||||
- Prisma handles schema migrations
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface UsageExportHeaderProps {
|
|||
selectedFilters?: string[];
|
||||
onFiltersChange?: (filters: string[]) => void;
|
||||
filterOptions?: Array<{ label: string; value: string }>;
|
||||
filterMode?: "multiple" | "single";
|
||||
customTitle?: string;
|
||||
compactLayout?: boolean;
|
||||
teams?: Team[];
|
||||
|
|
@ -32,6 +33,7 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
|
|||
selectedFilters = [],
|
||||
onFiltersChange,
|
||||
filterOptions = [],
|
||||
filterMode = "multiple",
|
||||
customTitle,
|
||||
compactLayout = false,
|
||||
teams = [],
|
||||
|
|
@ -59,11 +61,17 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
|
|||
<div>
|
||||
{filterLabel && <Text className="mb-2">{filterLabel}</Text>}
|
||||
<Select
|
||||
mode="multiple"
|
||||
mode={filterMode === "single" ? undefined : "multiple"}
|
||||
style={{ width: "100%" }}
|
||||
placeholder={filterPlaceholder}
|
||||
value={selectedFilters}
|
||||
onChange={onFiltersChange}
|
||||
value={filterMode === "single" ? (selectedFilters[0] ?? undefined) : selectedFilters}
|
||||
onChange={(value: any) => {
|
||||
if (filterMode === "single") {
|
||||
onFiltersChange?.(value ? [value] : []);
|
||||
} else {
|
||||
onFiltersChange?.(value);
|
||||
}
|
||||
}}
|
||||
options={filterOptions}
|
||||
allowClear
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ vi.mock("../../../networking", () => ({
|
|||
organizationDailyActivityCall: vi.fn(),
|
||||
customerDailyActivityCall: vi.fn(),
|
||||
agentDailyActivityCall: vi.fn(),
|
||||
userDailyActivityCall: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the child components to simplify testing
|
||||
|
|
@ -58,6 +59,7 @@ describe("EntityUsage", () => {
|
|||
const mockOrganizationDailyActivityCall = vi.mocked(networking.organizationDailyActivityCall);
|
||||
const mockCustomerDailyActivityCall = vi.mocked(networking.customerDailyActivityCall);
|
||||
const mockAgentDailyActivityCall = vi.mocked(networking.agentDailyActivityCall);
|
||||
const mockUserDailyActivityCall = vi.mocked(networking.userDailyActivityCall);
|
||||
|
||||
const mockSpendData = {
|
||||
results: [
|
||||
|
|
@ -146,11 +148,13 @@ describe("EntityUsage", () => {
|
|||
mockOrganizationDailyActivityCall.mockClear();
|
||||
mockCustomerDailyActivityCall.mockClear();
|
||||
mockAgentDailyActivityCall.mockClear();
|
||||
mockUserDailyActivityCall.mockClear();
|
||||
mockTagDailyActivityCall.mockResolvedValue(mockSpendData);
|
||||
mockTeamDailyActivityCall.mockResolvedValue(mockSpendData);
|
||||
mockOrganizationDailyActivityCall.mockResolvedValue(mockSpendData);
|
||||
mockCustomerDailyActivityCall.mockResolvedValue(mockSpendData);
|
||||
mockAgentDailyActivityCall.mockResolvedValue(mockSpendData);
|
||||
mockUserDailyActivityCall.mockResolvedValue(mockSpendData);
|
||||
});
|
||||
|
||||
it("should render with tag entity type and display spend metrics", async () => {
|
||||
|
|
@ -232,6 +236,21 @@ describe("EntityUsage", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should render with user entity type and call user API", async () => {
|
||||
render(<EntityUsage {...defaultProps} entityType="user" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUserDailyActivityCall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.getByText("User Spend Overview")).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
const spendElements = screen.getAllByText("$100.50");
|
||||
expect(spendElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should switch between tabs", async () => {
|
||||
render(<EntityUsage {...defaultProps} />);
|
||||
|
||||
|
|
|
|||
|
|
@ -401,6 +401,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
selectedFilters={selectedTags}
|
||||
onFiltersChange={setSelectedTags}
|
||||
filterOptions={getAllTags() || undefined}
|
||||
filterMode={entityType === "user" ? "single" : "multiple"}
|
||||
teams={teams || []}
|
||||
/>
|
||||
<TabGroup>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ vi.mock("@ant-design/icons", async () => {
|
|||
ShoppingCartOutlined: Icon,
|
||||
TagsOutlined: Icon,
|
||||
RobotOutlined: Icon,
|
||||
UserOutlined: Icon,
|
||||
LineChartOutlined: Icon,
|
||||
BarChartOutlined: Icon,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue