mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
fix(ui): bound Models table page, page size and sort_by read from the URL
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
868d3855ab
commit
99fa38504a
3 changed files with 54 additions and 12 deletions
|
|
@ -317,13 +317,28 @@ describe("AllModelsTab", () => {
|
|||
expect(screen.queryByText(/To access these models/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("falls back to the first page and default size when the URL carries values the server rejects", () => {
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />, { searchParams: { page: "0", page_size: "-5" } });
|
||||
it("clamps a hand-edited page and page size into the range the table supports", () => {
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />, { searchParams: { page: "0", page_size: "5000" } });
|
||||
|
||||
expect(lastModelsInfoCall().page).toBe(1);
|
||||
expect(lastModelsInfoCall().size).toBe(100);
|
||||
});
|
||||
|
||||
it("keeps the default page size when the URL value is not a number", () => {
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />, { searchParams: { page_size: "lots" } });
|
||||
|
||||
expect(lastModelsInfoCall().size).toBe(50);
|
||||
});
|
||||
|
||||
it("ignores a sort_by the table cannot sort by instead of forwarding it to the server", () => {
|
||||
renderWithProviders(<AllModelsTab {...defaultProps} />, {
|
||||
searchParams: { sort_by: "litellm_credential_name", sort_order: "desc" },
|
||||
});
|
||||
|
||||
expect(lastModelsInfoCall().sortBy).toBeUndefined();
|
||||
expect(lastModelsInfoCall().sortOrder).toBeUndefined();
|
||||
});
|
||||
|
||||
it("writes sort changes to the URL with the page cleared", async () => {
|
||||
setModelsInfo([makeRow()], 200);
|
||||
const user = userEvent.setup();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||
import { useDebouncedValue } from "@tanstack/react-pacer/debouncer";
|
||||
import { ColumnFiltersState, functionalUpdate, OnChangeFn, PaginationState, SortingState } from "@tanstack/react-table";
|
||||
import { Info } from "lucide-react";
|
||||
import { parseAsInteger, parseAsString, parseAsStringLiteral, useQueryStates } from "nuqs";
|
||||
import { createParser, parseAsInteger, parseAsString, parseAsStringLiteral, useQueryStates } from "nuqs";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import { useModelsInfo } from "../../hooks/models/useModels";
|
||||
|
|
@ -25,22 +25,39 @@ import {
|
|||
PERSONAL_TEAM_VALUE,
|
||||
WILDCARD_MODEL_GROUP_VALUE,
|
||||
} from "./AllModelsTable";
|
||||
import { ACCESS_GROUPS_COLUMN_ID, MODEL_NAME_COLUMN_ID, toServerSortField } from "./ModelsTableColumns";
|
||||
import {
|
||||
ACCESS_GROUPS_COLUMN_ID,
|
||||
isModelTableSortColumnId,
|
||||
MODEL_NAME_COLUMN_ID,
|
||||
MODEL_TABLE_SORT_COLUMN_IDS,
|
||||
toServerSortField,
|
||||
} from "./ModelsTableColumns";
|
||||
|
||||
const SEARCH_DEBOUNCE_WAIT_MS = 200;
|
||||
const DEFAULT_PAGE_SIZE = 50;
|
||||
const MAX_PAGE_SIZE = 100;
|
||||
const MAX_PAGE = 100_000;
|
||||
|
||||
const MODEL_VIEW_MODES = ["current_team", "all"] as const satisfies readonly ModelViewMode[];
|
||||
|
||||
const boundedInteger = (min: number, max: number, fallback: number) =>
|
||||
createParser({
|
||||
parse: (value: string) => {
|
||||
const parsed = parseAsInteger.parse(value);
|
||||
return parsed === null ? null : Math.min(Math.max(parsed, min), max);
|
||||
},
|
||||
serialize: String,
|
||||
}).withDefault(fallback);
|
||||
|
||||
const TABLE_STATE = {
|
||||
model_search: parseAsString.withDefault(""),
|
||||
view_mode: parseAsStringLiteral(MODEL_VIEW_MODES).withDefault("current_team"),
|
||||
filter_team: parseAsString.withDefault(PERSONAL_TEAM_VALUE),
|
||||
access_group: parseAsString.withDefault(""),
|
||||
sort_by: parseAsString.withDefault(""),
|
||||
sort_by: parseAsStringLiteral(MODEL_TABLE_SORT_COLUMN_IDS),
|
||||
sort_order: parseAsStringLiteral(["asc", "desc"] as const).withDefault("asc"),
|
||||
page: parseAsInteger.withDefault(1),
|
||||
page_size: parseAsInteger.withDefault(DEFAULT_PAGE_SIZE),
|
||||
page: boundedInteger(1, MAX_PAGE, 1),
|
||||
page_size: boundedInteger(1, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE),
|
||||
};
|
||||
|
||||
interface AllModelsTabProps {
|
||||
|
|
@ -72,10 +89,7 @@ const AllModelsTab = ({
|
|||
const selectedTeamValue = tableState.filter_team;
|
||||
const selectedModelAccessGroupFilter = tableState.access_group || null;
|
||||
const pagination = useMemo<PaginationState>(
|
||||
() => ({
|
||||
pageIndex: Math.max(tableState.page, 1) - 1,
|
||||
pageSize: tableState.page_size >= 1 ? tableState.page_size : DEFAULT_PAGE_SIZE,
|
||||
}),
|
||||
() => ({ pageIndex: tableState.page - 1, pageSize: tableState.page_size }),
|
||||
[tableState.page, tableState.page_size],
|
||||
);
|
||||
const sorting = useMemo<SortingState>(
|
||||
|
|
@ -177,7 +191,7 @@ const AllModelsTab = ({
|
|||
const handleSortingChange: OnChangeFn<SortingState> = (updater) => {
|
||||
const active = functionalUpdate(updater, sorting)[0];
|
||||
void setTableState({
|
||||
sort_by: active?.id ?? null,
|
||||
sort_by: active && isModelTableSortColumnId(active.id) ? active.id : null,
|
||||
sort_order: active?.desc ? "desc" : null,
|
||||
page: null,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,19 @@ export const TEAM_ID_COLUMN_ID = "model_info_team_id";
|
|||
export const ACCESS_GROUPS_COLUMN_ID = "model_info_access_groups";
|
||||
export const STATUS_COLUMN_ID = "model_info_db_model";
|
||||
|
||||
export const MODEL_TABLE_SORT_COLUMN_IDS = [
|
||||
MODEL_NAME_COLUMN_ID,
|
||||
CREATED_BY_COLUMN_ID,
|
||||
UPDATED_AT_COLUMN_ID,
|
||||
COSTS_COLUMN_ID,
|
||||
STATUS_COLUMN_ID,
|
||||
] as const;
|
||||
|
||||
export type ModelTableSortColumnId = (typeof MODEL_TABLE_SORT_COLUMN_IDS)[number];
|
||||
|
||||
export const isModelTableSortColumnId = (columnId: string): columnId is ModelTableSortColumnId =>
|
||||
(MODEL_TABLE_SORT_COLUMN_IDS as readonly string[]).includes(columnId);
|
||||
|
||||
const COLUMN_ID_TO_SERVER_SORT_FIELD: Record<string, string> = {
|
||||
[COSTS_COLUMN_ID]: "costs",
|
||||
[STATUS_COLUMN_ID]: "status",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue