diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts index a9dea73435..5b9a9ab133 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -11,6 +11,10 @@ test.describe("User Info View", () => { test("should display user info when clicking on user ID", async ({ page, }) => { + // Wait for loading state to disappear + await page.waitForSelector('text="🚅 Loading users..."', { + state: "hidden", + }); // Wait for users table to load await page.waitForSelector("table"); diff --git a/ui/litellm-dashboard/src/components/view_users.tsx b/ui/litellm-dashboard/src/components/view_users.tsx index 98a830695c..80bd77cadc 100644 --- a/ui/litellm-dashboard/src/components/view_users.tsx +++ b/ui/litellm-dashboard/src/components/view_users.tsx @@ -27,6 +27,7 @@ import SSOSettings from "./SSOSettings"; import { columns } from "./view_users/columns"; import { UserDataTable } from "./view_users/table"; import { UserInfo } from "./view_users/types"; +import { Skeleton } from "antd"; const { Text, Title } = Typography; @@ -277,14 +278,6 @@ const ViewUserDashboard: React.FC = ({ accessToken, toke }); const possibleUIRoles = userRolesQuery.data; - if (userListQuery.isLoading) { - return
Loading...
; - } - - if (!accessToken || !token || !userRole || !userID) { - return
Loading...
; - } - const tableColumns = columns( possibleUIRoles, (user) => { @@ -300,21 +293,31 @@ const ViewUserDashboard: React.FC = ({ accessToken, toke
- + {userListQuery.isLoading ? ( + <> + + + + + ) : userID && accessToken ? ( + <> + - + - {selectionMode && ( - - )} + {selectionMode && ( + + )} + + ) : null}
@@ -358,12 +361,18 @@ const ViewUserDashboard: React.FC = ({ accessToken, toke - + {!userID || !userRole || !accessToken ? ( +
+ +
+ ) : ( + + )}
diff --git a/ui/litellm-dashboard/src/components/view_users/table.test.tsx b/ui/litellm-dashboard/src/components/view_users/table.test.tsx index 278a42e896..8ef887932c 100644 --- a/ui/litellm-dashboard/src/components/view_users/table.test.tsx +++ b/ui/litellm-dashboard/src/components/view_users/table.test.tsx @@ -94,4 +94,88 @@ describe("UserDataTable", () => { expect(onSortChange).toHaveBeenCalledWith("user_email", "desc"); }); + + it("should show skeleton loaders when isLoading is true", () => { + const filters = { + email: "", + user_id: "", + user_role: "", + sso_user_id: "", + team: "", + model: "", + min_spend: null, + max_spend: null, + sort_by: "", + sort_order: "asc" as const, + }; + + const updateFilters = vi.fn(); + + render( + , + ); + + expect(screen.queryByText(/Showing/i)).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /Previous/i })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /Next/i })).not.toBeInTheDocument(); + }); + + it("should show actual content when isLoading is false", () => { + const filters = { + email: "", + user_id: "", + user_role: "", + sso_user_id: "", + team: "", + model: "", + min_spend: null, + max_spend: null, + sort_by: "", + sort_order: "asc" as const, + }; + + const updateFilters = vi.fn(); + + render( + , + ); + + expect(screen.getByText(/Showing/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Previous/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Next/i })).toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/components/view_users/table.tsx b/ui/litellm-dashboard/src/components/view_users/table.tsx index f20eb2a6d1..4f8dfa5809 100644 --- a/ui/litellm-dashboard/src/components/view_users/table.tsx +++ b/ui/litellm-dashboard/src/components/view_users/table.tsx @@ -2,6 +2,7 @@ import { ColumnDef, flexRender, getCoreRowModel, SortingState, useReactTable } f import React from "react"; import { Table, TableHead, TableHeaderCell, TableBody, TableRow, TableCell, Select, SelectItem } from "@tremor/react"; import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline"; +import { Skeleton } from "antd"; import { UserInfo } from "./types"; import UserInfoView from "./user_info_view"; import { columns as createColumns } from "./columns"; @@ -348,40 +349,53 @@ export function UserDataTable({ {/* Results Count and Pagination */}
- - Showing{" "} - {userListResponse && userListResponse.users && userListResponse.users.length > 0 - ? (userListResponse.page - 1) * userListResponse.page_size + 1 - : 0}{" "} - -{" "} - {userListResponse && userListResponse.users - ? Math.min(userListResponse.page * userListResponse.page_size, userListResponse.total) - : 0}{" "} - of {userListResponse ? userListResponse.total : 0} results - + {isLoading ? ( + + ) : ( + + Showing{" "} + {userListResponse && userListResponse.users && userListResponse.users.length > 0 + ? (userListResponse.page - 1) * userListResponse.page_size + 1 + : 0}{" "} + -{" "} + {userListResponse && userListResponse.users + ? Math.min(userListResponse.page * userListResponse.page_size, userListResponse.total) + : 0}{" "} + of {userListResponse ? userListResponse.total : 0} results + + )} {/* Pagination Buttons */}
- - + {isLoading ? ( + <> + + + + ) : ( + <> + + + + )}