From 74ba586a1c3fdd6d089245490db07a5fbc9115ba Mon Sep 17 00:00:00 2001 From: moe-berri Date: Sun, 8 Nov 2026 15:12:24 +0800 Subject: [PATCH] fix(alerts): preserve offsets when large records shrink a page --- .../Issues/Issues.integration.test.tsx | 39 +++++++++++++++++++ .../src/components/Issues/IssuesInbox.tsx | 9 +++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/components/Issues/Issues.integration.test.tsx b/ui/litellm-dashboard/src/components/Issues/Issues.integration.test.tsx index 7933b3edbe2..61a1fb5778e 100644 --- a/ui/litellm-dashboard/src/components/Issues/Issues.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/Issues/Issues.integration.test.tsx @@ -30,6 +30,7 @@ let refreshBarrier: Promise | undefined; let holdReads = false; let failList = false; let byteLimitedPage = false; +let mixedPageSizes = false; let failActivity = false; let staleStatus = false; let empty = false; @@ -88,6 +89,16 @@ function listResponse(query: URLSearchParams) { })); return json({ data, total: 6, limit: 2, offset }); } + if (mixedPageSizes) { + const limit = Math.min(Number(query.get("limit") ?? 25), offset < 25 ? 25 : 2); + const data = Array.from({ length: Math.min(limit, 30 - offset) }, (_, index) => ({ + ...SPEND_ISSUE, + id: `mixed-${offset + index}`, + scope_label: `Mixed key ${offset + index}`, + })); + const page = { data, total: 30, limit, offset }; + return json(page); + } const emptyPage = { data: [], total: 0, limit: 25, offset }; if (empty) return json(emptyPage); const next = { ...SPEND_ISSUE, id: "second-page", scope_label: "Second key", title: "Second key spend increased" }; @@ -119,6 +130,7 @@ describe("unified issue investigations", () => { holdReads = false; failList = false; byteLimitedPage = false; + mixedPageSizes = false; failActivity = false; staleStatus = false; empty = false; @@ -197,6 +209,33 @@ describe("unified issue investigations", () => { expect(calls.some((call) => call.path === "/issues" && call.query.get("offset") === "4")).toBe(true); }); + it.each([false, true])("preserves the exact offset when later pages shrink (deep link %s)", async (deepLink) => { + mixedPageSizes = true; + const onUrlUpdate = vi.fn(); + renderWithProviders(, { searchParams: deepLink ? "page=2" : "", onUrlUpdate }); + if (!deepLink) { + await screen.findByRole("heading", { name: "Spend on Mixed key 0 rose to 20× normal" }); + await userEvent.click(screen.getByRole("button", { name: "Go to next page" })); + } + await waitFor(() => + expect( + calls + .filter((call) => call.path === "/issues") + .at(-1) + ?.query.get("limit"), + ).toBe("1"), + ); + expect(await screen.findByRole("heading", { name: "Spend on Mixed key 25 rose to 20× normal" })).toBeVisible(); + expect(screen.queryByRole("heading", { name: "Spend on Mixed key 24 rose to 20× normal" })).not.toBeInTheDocument(); + expect(onUrlUpdate.mock.calls.at(-1)?.[0].queryString).toContain("page=26"); + await userEvent.click(screen.getByRole("button", { name: "Go to next page" })); + expect(await screen.findByRole("heading", { name: "Spend on Mixed key 26 rose to 20× normal" })).toBeVisible(); + await userEvent.click(screen.getByRole("button", { name: "Go to previous page" })); + expect(await screen.findByRole("heading", { name: "Spend on Mixed key 25 rose to 20× normal" })).toBeVisible(); + await userEvent.click(screen.getByRole("button", { name: "Go to first page" })); + expect(await screen.findByRole("heading", { name: "Spend on Mixed key 0 rose to 20× normal" })).toBeVisible(); + }); + it("shows retirement and key spend in one server-paginated inbox with issue destinations", async () => { const user = userEvent.setup(); renderWithProviders(); diff --git a/ui/litellm-dashboard/src/components/Issues/IssuesInbox.tsx b/ui/litellm-dashboard/src/components/Issues/IssuesInbox.tsx index 6cd4bfe67ad..cbd8834352b 100644 --- a/ui/litellm-dashboard/src/components/Issues/IssuesInbox.tsx +++ b/ui/litellm-dashboard/src/components/Issues/IssuesInbox.tsx @@ -71,10 +71,11 @@ export default function IssuesInbox() { const { onPaginationChange } = table; useEffect(() => { if (query.data && query.data.limit < pageSize) { - onPaginationChange({ - pageIndex: Math.floor((pageIndex * pageSize) / query.data.limit), - pageSize: query.data.limit, - }); + const offset = pageIndex * pageSize; + const limit = query.data.limit; + const alignedSize = + Array.from({ length: limit }, (_, index) => limit - index).find((size) => offset % size === 0) ?? 1; + onPaginationChange({ pageIndex: offset / alignedSize, pageSize: alignedSize }); } else if (query.data && pageIndex > lastPage) onPaginationChange({ pageIndex: lastPage, pageSize }); }, [query.data, pageIndex, pageSize, lastPage, onPaginationChange]); const changeStatus = (next: IssueStatus) => {