From 7f2e12cd103dbe618f8da9c1c93e9bc365750f2d Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 29 Feb 2024 16:30:12 -0800 Subject: [PATCH] fix: enable end user tracking on ui --- litellm/proxy/proxy_server.py | 22 ++++++ litellm/proxy/utils.py | 20 +++++ .../src/components/networking.tsx | 40 ++++++++-- .../src/components/view_users.tsx | 73 ++++++++++++++----- 4 files changed, 130 insertions(+), 25 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 544eb77315..b5bf0b4189 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -4113,6 +4113,28 @@ async def global_spend_keys( return response +@router.get( + "/global/spend/end_users", + tags=["Budget & Spend Tracking"], + dependencies=[Depends(user_api_key_auth)], +) +async def global_spend_end_users(): + """ + [BETA] This is a beta endpoint. It will change. + + Use this to get the top 'n' keys with the highest spend, ordered by spend. + """ + global prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail={"error": "No db connected"}) + sql_query = f"""SELECT * FROM "Last30dTopEndUsersSpend";""" + + response = await prisma_client.db.query_raw(query=sql_query) + + return response + + @router.get( "/global/spend/models", tags=["Budget & Spend Tracking"], diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 76fc21242b..c7213d465c 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -593,6 +593,26 @@ class PrismaClient: print("Last30dModelsBySpend Created!") # noqa + try: + await self.db.query_raw( + """SELECT 1 FROM "Last30dTopEndUsersSpend" LIMIT 1""" + ) + print("Last30dTopEndUsersSpend Exists!") # noqa + except Exception as e: + sql_query = """ + CREATE VIEW "Last30dTopEndUsersSpend" AS + SELECT end_user, SUM(spend) AS total_spend + FROM "LiteLLM_SpendLogs" + WHERE end_user <> '' AND end_user <> user + AND "startTime" >= CURRENT_DATE - INTERVAL '30 days' + GROUP BY end_user + ORDER BY total_spend DESC + LIMIT 100; + """ + await self.db.execute_raw(query=sql_query) + + print("Last30dTopEndUsersSpend Created!") # noqa + return @backoff.on_exception( diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 0588ba34b7..b64ec7e684 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -404,13 +404,13 @@ export const adminTopKeysCall = async (accessToken: String) => { } }; -export const adminTopModelsCall = async (accessToken: String) => { +export const adminTopEndUsersCall = async (accessToken: String) => { try { let url = proxyBaseUrl - ? `${proxyBaseUrl}/global/spend/models?limit=5` - : `/global/spend/models?limit=5`; + ? `${proxyBaseUrl}/global/spend/end_users` + : `/global/spend/end_users`; - message.info("Making spend models request"); + message.info("Making top end users request"); const response = await fetch(url, { method: "GET", headers: { @@ -426,7 +426,37 @@ export const adminTopModelsCall = async (accessToken: String) => { const data = await response.json(); console.log(data); - message.success("Spend Logs received"); + message.success("Top End users received"); + return data; + } catch (error) { + console.error("Failed to create key:", error); + throw error; + } +}; + +export const adminTopModelsCall = async (accessToken: String) => { + try { + let url = proxyBaseUrl + ? `${proxyBaseUrl}/global/spend/models?limit=5` + : `/global/spend/models?limit=5`; + + message.info("Making top models request"); + const response = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + const errorData = await response.text(); + message.error(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log(data); + message.success("Top Models received"); return data; } catch (error) { console.error("Failed to create key:", error); diff --git a/ui/litellm-dashboard/src/components/view_users.tsx b/ui/litellm-dashboard/src/components/view_users.tsx index 98cddae982..763176a308 100644 --- a/ui/litellm-dashboard/src/components/view_users.tsx +++ b/ui/litellm-dashboard/src/components/view_users.tsx @@ -8,6 +8,9 @@ import { TableRow, TableCell, TableBody, + Tab, + TabGroup, + TabList, Metric, Grid, } from "@tremor/react"; @@ -29,8 +32,9 @@ const ViewUserDashboard: React.FC = ({ userRole, userID, }) => { - const [userData, setuserData] = useState(null); - const [pendingRequests, setPendingRequests] = useState([]); + const [userData, setUserData] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const defaultPageSize = 25; useEffect(() => { if (!accessToken || !token || !userRole || !userID) { @@ -46,7 +50,7 @@ const ViewUserDashboard: React.FC = ({ true ); console.log("user data response:", userDataResponse); - setuserData(userDataResponse); + setUserData(userDataResponse); } catch (error) { console.error("There was an error fetching the model data", error); } @@ -65,11 +69,49 @@ const ViewUserDashboard: React.FC = ({ return
Loading...
; } + function renderPagination() { + if (!userData) return null; + + const totalPages = Math.ceil(userData.length / defaultPageSize); + const startItem = (currentPage - 1) * defaultPageSize + 1; + const endItem = Math.min(currentPage * defaultPageSize, userData.length); + + return ( +
+
+ Showing {startItem} – {endItem} of {userData.length} +
+
+ + +
+
+ ); + } + return (
+ + + Key Owners + End-Users + + @@ -93,34 +135,25 @@ const ViewUserDashboard: React.FC = ({ {userData.map((user: any) => ( + {user.user_id} - {user.user_id} + {user.user_role ? user.user_role : "app_owner"} - - {user.user_role ? user.user_role : "app_user"} - + {user.models && user.models.length > 0 + ? user.models + : "All Models"} + {user.spend ? user.spend : 0} - - {user.models && user.models.length > 0 - ? user.models - : "All Models"} - - - - {user.spend ? user.spend : 0} - - - - {user.max_budget ? user.max_budget : "Unlimited"} - + {user.max_budget ? user.max_budget : "Unlimited"} ))}
+ {renderPagination()}
);