UI - Users page - Enable global sorting (allows finding users with highest spend) (#10211)

* fix(view_users.tsx): add time tracking logic to debounce search - prevent new queries from being overwritten by previous ones

* fix(internal_user_endpoints.py): add sort functionality to user list endpoint

* feat(internal_user_endpoints.py): support sort by on `/user/list`

* fix(view_users.tsx): enable global sorting

allows finding user with highest spend

* feat(view_users.tsx): support filtering by sso user id

* test(search_users.spec.ts): add tests to ensure filtering works

* test: add more unit testing
This commit is contained in:
Krish Dholakia 2025-04-22 19:59:53 -07:00 committed by Christian Owusu
parent 7c8a9e216b
commit aba37e9f56
6 changed files with 287 additions and 17 deletions

View file

@ -902,6 +902,42 @@ async def get_user_key_counts(
return result
def _validate_sort_params(
sort_by: Optional[str], sort_order: str
) -> Optional[Dict[str, str]]:
order_by: Dict[str, str] = {}
if sort_by is None:
return None
# Validate sort_by is a valid column
valid_columns = [
"user_id",
"user_email",
"created_at",
"spend",
"user_alias",
"user_role",
]
if sort_by not in valid_columns:
raise HTTPException(
status_code=400,
detail={
"error": f"Invalid sort column. Must be one of: {', '.join(valid_columns)}"
},
)
# Validate sort_order
if sort_order.lower() not in ["asc", "desc"]:
raise HTTPException(
status_code=400,
detail={"error": "Invalid sort order. Must be 'asc' or 'desc'"},
)
order_by[sort_by] = sort_order.lower()
return order_by
@router.get(
"/user/list",
tags=["Internal User management"],
@ -915,6 +951,9 @@ async def get_users(
user_ids: Optional[str] = fastapi.Query(
default=None, description="Get list of users by user_ids"
),
sso_user_ids: Optional[str] = fastapi.Query(
default=None, description="Get list of users by sso_user_id"
),
user_email: Optional[str] = fastapi.Query(
default=None, description="Filter users by partial email match"
),
@ -925,9 +964,16 @@ async def get_users(
page_size: int = fastapi.Query(
default=25, ge=1, le=100, description="Number of items per page"
),
sort_by: Optional[str] = fastapi.Query(
default=None,
description="Column to sort by (e.g. 'user_id', 'user_email', 'created_at', 'spend')",
),
sort_order: str = fastapi.Query(
default="asc", description="Sort order ('asc' or 'desc')"
),
):
"""
Get a paginated list of users with filtering options.
Get a paginated list of users with filtering and sorting options.
Parameters:
role: Optional[str]
@ -938,6 +984,8 @@ async def get_users(
- internal_user_viewer
user_ids: Optional[str]
Get list of users by user_ids. Comma separated list of user_ids.
sso_ids: Optional[str]
Get list of users by sso_ids. Comma separated list of sso_ids.
user_email: Optional[str]
Filter users by partial email match
team: Optional[str]
@ -946,9 +994,10 @@ async def get_users(
The page number to return
page_size: int
The number of items per page
Returns:
UserListResponse with filtered and paginated users
sort_by: Optional[str]
Column to sort by (e.g. 'user_id', 'user_email', 'created_at', 'spend')
sort_order: Optional[str]
Sort order ('asc' or 'desc')
"""
from litellm.proxy.proxy_server import prisma_client
@ -984,13 +1033,25 @@ async def get_users(
"has": team # Array contains for string arrays in Prisma
}
if sso_user_ids is not None and isinstance(sso_user_ids, str):
sso_id_list = [sid.strip() for sid in sso_user_ids.split(",") if sid.strip()]
where_conditions["sso_user_id"] = {
"in": sso_id_list,
}
## Filter any none fastapi.Query params - e.g. where_conditions: {'user_email': {'contains': Query(None), 'mode': 'insensitive'}, 'teams': {'has': Query(None)}}
where_conditions = {k: v for k, v in where_conditions.items() if v is not None}
# Build order_by conditions
order_by: Optional[Dict[str, str]] = _validate_sort_params(sort_by, sort_order)
users = await prisma_client.db.litellm_usertable.find_many(
where=where_conditions,
skip=skip,
take=page_size,
order={"created_at": "desc"},
order=order_by
if order_by
else {"created_at": "desc"}, # Default to created_at desc if no sort specified
)
# Get total count of user rows