[Feature] UI - Usage: Allow Filtering by User#21351
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds user-level filtering to the Usage page. Backend endpoints (
Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/internal_user_endpoints.py | Adds user_id query param to both daily activity endpoints with admin/non-admin authorization logic. However, the new 403 HTTPExceptions are caught by the outer except Exception handler and re-raised as 500 errors, masking the intended authorization response. |
| tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py | Adds tests for non-admin permission enforcement and admin global view. Tests are mock-only (no real network calls). However, they don't assert the HTTP status code, and even acknowledge the 403-to-500 bug in a comment without treating it as a failure. |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.ts | New useInfiniteUsers hook using @tanstack/react-query infinite query. Clean implementation with proper admin-role gating and pagination support. |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.test.ts | Comprehensive mock-only tests for the useInfiniteUsers hook covering pagination, search, auth gating, error handling, and all admin roles. No real network calls. |
| ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx | Adds admin-only user selector dropdown with debounced search and infinite-scroll pagination. Correctly computes effectiveUserId at call time for non-admin users. Properly passes userId through to API calls. |
| ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx | Adds extensive tests for user selector rendering, non-admin behavior, aggregated endpoint fallback, deduplication, model view toggles, and more. All mocked, no real network calls. |
| ui/litellm-dashboard/src/components/networking.tsx | Adds userId parameter to userDailyActivityCall and userDailyActivityAggregatedCall. Correctly uses extraQueryParams for the paginated endpoint and direct queryParams.append for the aggregated endpoint, both properly handling null values. |
Sequence Diagram
sequenceDiagram
participant UI as Admin Dashboard
participant Hook as useInfiniteUsers
participant Net as networking.tsx
participant API as /user/daily/activity
participant Auth as _user_has_admin_view
participant DB as get_daily_activity
UI->>Hook: Select user from dropdown
Hook->>Net: userListCall(page, search)
Net-->>Hook: UserListResponse (paginated)
Hook-->>UI: user options
UI->>Net: userDailyActivityAggregatedCall(dates, userId)
Net->>API: GET /user/daily/activity/aggregated
API->>Auth: check admin status
Auth-->>API: true (admin)
API->>DB: get_daily_activity_aggregated(entity_id)
DB-->>API: SpendAnalyticsPaginatedResponse
API-->>Net: 200 with data
Net-->>UI: render filtered charts
Note over UI,API: Non-admin flow
UI->>Net: userDailyActivityAggregatedCall(dates, ownId)
Net->>API: GET /user/daily/activity/aggregated
API->>Auth: check admin status
Auth-->>API: false (non-admin)
API->>API: Validate user_id matches caller
API->>DB: get_daily_activity_aggregated(entity_id)
DB-->>API: SpendAnalyticsPaginatedResponse
API-->>Net: 200 with data
Last reviewed commit: 0d2aac6
tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py
Outdated
Show resolved
Hide resolved
Additional Comments (2)
The new The same issue exists in the aggregated endpoint at lines 2096-2103. To fix, add an
Same issue as the paginated endpoint above: the new 403 |
- Fix HTTPException swallowed by broad except block in get_user_daily_activity and get_user_daily_activity_aggregated: re-raise HTTPException before the generic handler so 403 status codes propagate correctly - Add status_code assertions in non-admin access tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@greptile |
Greptile SummaryThis PR adds user-level filtering to the global Usage page. On the backend, both
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/internal_user_endpoints.py | Adds user_id query param to both daily activity endpoints with admin/non-admin authorization logic. Introduces a backward-incompatible change: non-admins now get 403 when omitting user_id, whereas before they automatically got their own data. |
| tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py | Adds tests for non-admin permission enforcement and admin global view. Tests are mock-only (no real network calls) and properly verify the 403 responses and correct forwarding of parameters. Missing a test for the non-admin happy path (providing own user_id). |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.ts | New useInfiniteUsers hook using useInfiniteQuery with proper pagination, admin-only gating, and search support. Clean implementation following existing codebase patterns. |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.test.ts | Thorough test suite covering pagination, search, auth gating, admin role variations, and error handling. All tests are mock-only with no real network calls. |
| ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx | Adds admin-only user selector with debounced search and infinite scroll. The selectedUserId state initialization may be incorrect on first render due to async auth state, though it's partially mitigated by the effectiveUserId logic in the fetch callback. |
| ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx | Extensive test additions covering admin user selector rendering, option formatting, non-admin behavior, aggregated endpoint fallback, pagination, and various UI features. All tests are properly mocked. |
| ui/litellm-dashboard/src/components/networking.tsx | Adds userId parameter to userDailyActivityCall and userDailyActivityAggregatedCall. Both correctly handle null by skipping the query param. Consistent with existing patterns in the file. |
Sequence Diagram
sequenceDiagram
participant UI as UsagePageView
participant Hook as useInfiniteUsers
participant Net as networking.tsx
participant API as Backend Endpoints
Note over UI: Admin selects a user from dropdown
UI->>Hook: Fetch paginated user list (debounced search)
Hook->>Net: userListCall(accessToken, page, pageSize, searchEmail)
Net->>API: GET /user/list
API-->>Net: UserListResponse (paginated)
Net-->>Hook: users[]
Hook-->>UI: userOptions for Select dropdown
Note over UI: Admin picks user or clears selection
UI->>Net: userDailyActivityAggregatedCall(token, start, end, userId)
Net->>API: GET /user/daily/activity/aggregated?user_id=...
API->>API: Check is_admin via _user_has_admin_view()
alt Admin with user_id
API->>API: entity_id = user_id (filter by user)
else Admin without user_id
API->>API: entity_id = None (global view)
else Non-admin with own user_id
API->>API: entity_id = user_id
else Non-admin without user_id or wrong user_id
API-->>Net: 403 Forbidden
end
API-->>Net: SpendAnalyticsPaginatedResponse
Net-->>UI: Render spend charts
Last reviewed commit: 310cca1
ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx
Show resolved
Hide resolved
- Default user_id to caller's own ID for non-admins instead of 403 when omitted, preserving backward compatibility for API consumers - Apply same fix to aggregated endpoint - Update test to verify defaulting behavior instead of expecting 403 - Add useEffect to sync selectedUserId when auth state settles in UsagePageView to handle async auth initialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@greptile |
Greptile SummaryThis PR adds user-level filtering to the global Usage page. On the backend, both On the frontend, a new
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/internal_user_endpoints.py | Adds user_id query param to both /user/daily/activity and /user/daily/activity/aggregated. Admin logic is correct (None = global, value = filter). Non-admin logic correctly defaults to own user_id when omitted and raises 403 for other users. except HTTPException: raise properly added to prevent 403 being swallowed by the generic handler. |
| tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py | Good test coverage: tests non-admin 403 for viewing other users, non-admin default to own user_id, and admin global view on aggregated endpoint. Tests use mocks properly and don't make real network calls. Missing newline at end of file. |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.ts | Clean infinite query hook for paginated user list. Properly gates query behind admin role check and accessToken presence. Uses react-query's useInfiniteQuery with correct pagination logic. |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.test.ts | Thorough test suite covering pagination, search, auth gating for all admin roles, error handling, and edge cases (empty search string). All mocked, no real network calls. |
| ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx | Adds admin-only user selector dropdown with debounced search and infinite scroll. Uses antd Select (not deprecated Tremor). Correctly syncs selectedUserId for non-admins via useEffect. effectiveUserId logic properly separates admin/non-admin behavior. |
| ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.test.tsx | Extensive new tests for user selector, non-admin behavior, fallback pagination, and other UI features. Missing mock for @tanstack/react-pacer/debouncer which is used by the component but mocked in similar test files (PaginatedModelSelect). |
| ui/litellm-dashboard/src/components/networking.tsx | Minimal changes: adds optional userId parameter to userDailyActivityCall and userDailyActivityAggregatedCall. Null values are properly skipped by existing appendDailyActivityQueryParam logic and the explicit if (userId) guard. |
Sequence Diagram
sequenceDiagram
participant UI as UsagePageView
participant Hook as useInfiniteUsers
participant Net as networking.tsx
participant API as Backend Endpoints
Note over UI: Admin selects user from dropdown
UI->>Hook: useInfiniteUsers(50, searchEmail)
Hook->>Net: userListCall(accessToken, null, page, pageSize, searchEmail)
Net->>API: GET /user/list?page=1&page_size=50
API-->>Net: UserListResponse
Net-->>Hook: paginated users
Hook-->>UI: user options for dropdown
Note over UI: Admin picks a user (or clears for global)
UI->>UI: setSelectedUserId(userId | null)
UI->>Net: userDailyActivityAggregatedCall(token, start, end, userId)
Net->>API: GET /user/daily/activity/aggregated?user_id=xxx
API->>API: _user_has_admin_view() → true
API->>API: entity_id = user_id (or null for global)
API-->>Net: SpendAnalyticsPaginatedResponse
Net-->>UI: spend data
Note over UI: Non-admin user (no dropdown shown)
UI->>UI: effectiveUserId = userID (from auth)
UI->>Net: userDailyActivityAggregatedCall(token, start, end, userID)
Net->>API: GET /user/daily/activity/aggregated?user_id=own-id
API->>API: _user_has_admin_view() → false
API->>API: Verify user_id matches caller
API-->>Net: SpendAnalyticsPaginatedResponse
Net-->>UI: own spend data
Last reviewed commit: df5e8d0
Additional Comments (1)
Add a mock similar to |
Relevant issues
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unitCI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
✅ Test
🆕 New Feature
Changes
The global Usage page previously showed aggregate spend across all users with no way to drill down by user. This PR adds a
user_idquery parameter to both/user/daily/activityand/user/daily/activity/aggregatedendpoints so admins can filter spend data by a specific user. Non-admin users are now required to provide their ownuser_idand are forbidden from viewing other users' data or the global view.On the UI side, admins see a searchable user selector dropdown (with debounced search and infinite-scroll pagination via a new
useInfiniteUsershook) next to the "Project Spend" header in the global usage tab. Selecting a user filters the spend charts to that user; clearing the selection returns to the global view.Backend:
user_idoptional query param toget_user_daily_activityandget_user_daily_activity_aggregateduser_id=Nonegives global view, providing a value filters by that useruser_id; viewing other users or omitting it returns 403Frontend:
useInfiniteUsershook for paginated user list fetchingSelectdropdown in global usage view for user filtering (admin-only)userDailyActivityCallanduserDailyActivityAggregatedCallnow accept and forwarduserIdTests:
useInfiniteUsershook (pagination, search, auth gating) andUsagePageViewcomponent (user selector rendering, filtering behavior)Screenshots