Show 'user_email' on key table on UI (#8887)

* refactor(internal_user_endpoints.py): refactor `/user/list` to accept 'user_ids' and use prisma for db calls

enables bulk search from UI

* fix(internal_user_endpoints.py): fix linting errors

* fix(all_keys_table.tsx): show user email on create key table

make it easier for admin to know which key is associated to which user

* docs(internal_user_endpoints.py): improve docstring

* fix: sync schema with main

* fix(columns.tsx): display SSO ID on Internal User Table

make it easy to identify what the SSO ID for a user is

* fix(columns.tsx): add tooltip to header

help user understand what SSO ID means

* style: add more tooltips in the management flows

make it easier to understand what you're seeing

* style(all_keys_table.tsx): replace 'Not Set' with '-'

reduces words on table

* fix(internal_user_endpoints.py): fix user ids check

* test: fix test

* fix(internal_user_endpoints.py): maintain returning key count in `/user/list`
This commit is contained in:
Krish Dholakia 2025-02-27 21:56:14 -08:00 committed by GitHub
parent 475c1d0f99
commit bd2b6bdeb3
8 changed files with 199 additions and 67 deletions

View file

@ -753,6 +753,9 @@ async def get_users(
role: Optional[str] = fastapi.Query(
default=None, description="Filter users by role"
),
user_ids: Optional[str] = fastapi.Query(
default=None, description="Get list of users by user_ids"
),
page: int = fastapi.Query(default=1, ge=1, description="Page number"),
page_size: int = fastapi.Query(
default=25, ge=1, le=100, description="Number of items per page"
@ -770,12 +773,19 @@ async def get_users(
- proxy_admin_viewer
- internal_user
- internal_user_viewer
user_ids: Optional[str]
Get list of users by user_ids. Comma separated list of user_ids.
page: int
The page number to return
page_size: int
The number of items per page
Currently - admin-only endpoint.
Example curl:
```
http://0.0.0.0:4000/user/list?user_ids=default_user_id,693c1a4a-1cc0-4c7c-afe8-b5d2c8d52e17
```
"""
from litellm.proxy.proxy_server import prisma_client
@ -787,49 +797,69 @@ async def get_users(
# Calculate skip and take for pagination
skip = (page - 1) * page_size
take = page_size
# Prepare the query conditions
where_clause = ""
# Build where conditions based on provided parameters
where_conditions: Dict[str, Any] = {}
if role:
where_clause = f"""WHERE "user_role" = '{role}'"""
where_conditions["user_role"] = {
"contains": role,
"mode": "insensitive", # Case-insensitive search
}
# Single optimized SQL query that gets both users and total count
sql_query = f"""
WITH total_users AS (
SELECT COUNT(*) AS total_number_internal_users
FROM "LiteLLM_UserTable"
),
paginated_users AS (
SELECT
u.*,
(
SELECT COUNT(*)
FROM "LiteLLM_VerificationToken" vt
WHERE vt."user_id" = u."user_id"
) AS key_count
FROM "LiteLLM_UserTable" u
{where_clause}
LIMIT {take} OFFSET {skip}
if user_ids and isinstance(user_ids, str):
user_id_list = [uid.strip() for uid in user_ids.split(",") if uid.strip()]
where_conditions["user_id"] = {
"in": user_id_list, # Now passing a list of strings as required by Prisma
}
users: Optional[List[LiteLLM_UserTable]] = (
await prisma_client.db.litellm_usertable.find_many(
where=where_conditions,
skip=skip,
take=page_size,
order={"created_at": "desc"},
)
)
SELECT
(SELECT total_number_internal_users FROM total_users),
*
FROM paginated_users;
"""
# Execute the query
results = await prisma_client.db.query_raw(sql_query)
# Get total count from the first row (if results exist)
total_count = 0
if len(results) > 0:
total_count = results[0].get("total_number_internal_users")
# Get total count of user rows
total_count = await prisma_client.db.litellm_usertable.count(
where=where_conditions # type: ignore
)
# Get key count for each user
if users is not None:
user_keys = await prisma_client.db.litellm_verificationtoken.group_by(
by=["user_id"],
count={"user_id": True},
where={"user_id": {"in": [user.user_id for user in users]}},
)
user_key_counts = {
item["user_id"]: item["_count"]["user_id"] for item in user_keys
}
else:
user_key_counts = {}
verbose_proxy_logger.debug(f"Total count of users: {total_count}")
# Calculate total pages
total_pages = -(-total_count // page_size) # Ceiling division
# Prepare response
user_list: List[LiteLLM_UserTableWithKeyCount] = []
if users is not None:
for user in users:
user_list.append(
LiteLLM_UserTableWithKeyCount(
**user.model_dump(), key_count=user_key_counts.get(user.user_id, 0)
)
) # Return full key object
else:
user_list = []
return {
"users": results,
"users": user_list,
"total": total_count,
"page": page,
"page_size": page_size,