mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
Merge pull request #40644 from BerriAI/litellm_logs_last_page_jump
fix(ui): jump straight to the last Request Logs page instead of advancing one page
This commit is contained in:
commit
d70e64d973
5 changed files with 322 additions and 33 deletions
|
|
@ -2925,6 +2925,32 @@ async def _fetch_session_representatives(
|
|||
return [rep_by_key[key] for key in session_keys if key in rep_by_key] # mutable-ok: rows are enriched in place
|
||||
|
||||
|
||||
async def _count_grouped_sessions(
|
||||
prisma_client: "PrismaClient",
|
||||
where_clause: str,
|
||||
sql_params: Sequence[object],
|
||||
next_param_index: int,
|
||||
) -> tuple[int, bool]:
|
||||
"""Count the sessions matching the filter, returning ``(total, total_is_capped)`` bounded by the count cap."""
|
||||
count_query: Final = f"""
|
||||
SELECT COUNT(*) AS total_count
|
||||
FROM (
|
||||
SELECT 1
|
||||
FROM "LiteLLM_SpendLogs"
|
||||
WHERE {where_clause}
|
||||
GROUP BY {_SESSION_GROUP_KEY_SQL}
|
||||
LIMIT ${next_param_index}
|
||||
) AS bounded_sessions
|
||||
"""
|
||||
count_rows: Final[Sequence[_SpendLogsCountRow]] = await _query_raw(
|
||||
prisma_client, count_query, *sql_params, SPEND_LOGS_PAGINATION_COUNT_CAP + 1
|
||||
)
|
||||
raw_total: Final = int(count_rows[0]["total_count"]) if count_rows else 0
|
||||
return (
|
||||
(SPEND_LOGS_PAGINATION_COUNT_CAP, True) if raw_total > SPEND_LOGS_PAGINATION_COUNT_CAP else (raw_total, False)
|
||||
)
|
||||
|
||||
|
||||
async def _ui_session_grouped_spend_logs(
|
||||
prisma_client: "PrismaClient",
|
||||
sql_conditions: Sequence[str],
|
||||
|
|
@ -2944,11 +2970,19 @@ async def _ui_session_grouped_spend_logs(
|
|||
next ``page_size`` sessions ordered by ``(MAX(startTime), session_key,
|
||||
api_key)``, resumed from the ``session_cursor`` keyset
|
||||
``'<last_activity>|<api_key>|<session_key>'`` instead of an OFFSET, so
|
||||
page depth does not degrade the query plan. Each session is represented
|
||||
page depth does not degrade the query plan. A request for ``page > 1``
|
||||
without a cursor (the UI jumping straight to the last page, or back to a
|
||||
page it never walked through) falls back to ``OFFSET (page - 1) *
|
||||
page_size``, trimmed to the end of the ``SPEND_LOGS_PAGINATION_COUNT_CAP``
|
||||
window the capped ``total`` promises, so a page never runs past that total
|
||||
and one starting at or past it returns no rows without a query. Each session is represented
|
||||
by its newest non-MCP row, enriched by ``_build_ui_spend_logs_response``
|
||||
exactly like the flat listing, and the response carries
|
||||
``next_session_cursor`` / ``has_more`` while ``total`` counts sessions
|
||||
(capped like the flat total).
|
||||
(capped like the flat total). A page that runs out of sessions while still
|
||||
holding some is itself the end of the list, so its ``total`` is
|
||||
``offset + len(page)`` and the grouped count query is skipped; a page that
|
||||
starts past the end says nothing about the total, so that one is counted.
|
||||
"""
|
||||
where_clause: Final = " AND ".join(sql_conditions) if sql_conditions else "TRUE"
|
||||
cmp_op: Final = "<" if sort_desc else ">"
|
||||
|
|
@ -2963,6 +2997,10 @@ async def _ui_session_grouped_spend_logs(
|
|||
)
|
||||
cursor_params: Final[tuple[object, ...]] = cursor if cursor else ()
|
||||
limit_index: Final = next_param_index + len(cursor_params)
|
||||
offset: Final = (page - 1) * page_size if cursor is None else 0
|
||||
page_limit: Final = min(page_size, SPEND_LOGS_PAGINATION_COUNT_CAP - offset)
|
||||
offset_params: Final[tuple[int, ...]] = (offset,) if offset and page_limit > 0 else ()
|
||||
offset_clause: Final = f"OFFSET ${limit_index + 1}" if offset_params else ""
|
||||
|
||||
page_query: Final = f"""
|
||||
SELECT {_SESSION_KEY_EXPR} AS session_key,
|
||||
|
|
@ -2973,36 +3011,29 @@ async def _ui_session_grouped_spend_logs(
|
|||
GROUP BY {_SESSION_GROUP_KEY_SQL}
|
||||
{having_clause}
|
||||
ORDER BY MAX("startTime") {direction}, {_SESSION_KEY_EXPR} {direction}, api_key {direction}
|
||||
LIMIT ${limit_index}
|
||||
LIMIT ${limit_index} {offset_clause}
|
||||
"""
|
||||
page_rows: Final[Sequence[_SessionPageRow]] = await _query_raw(
|
||||
prisma_client, page_query, *sql_params, *cursor_params, page_size + 1
|
||||
page_rows: Final[Sequence[_SessionPageRow]] = (
|
||||
()
|
||||
if page_limit <= 0
|
||||
else await _query_raw(prisma_client, page_query, *sql_params, *cursor_params, page_limit + 1, *offset_params)
|
||||
)
|
||||
|
||||
has_more: Final = len(page_rows) > page_size
|
||||
visible_rows: Final = page_rows[:page_size]
|
||||
has_more: Final = len(page_rows) > page_limit
|
||||
visible_rows: Final = page_rows[:page_limit]
|
||||
next_cursor: Final = (
|
||||
f"{visible_rows[-1]['last_activity']}|{visible_rows[-1]['api_key']}|{visible_rows[-1]['session_key']}"
|
||||
if has_more and visible_rows
|
||||
else None
|
||||
)
|
||||
|
||||
count_query: Final = f"""
|
||||
SELECT COUNT(*) AS total_count
|
||||
FROM (
|
||||
SELECT 1
|
||||
FROM "LiteLLM_SpendLogs"
|
||||
WHERE {where_clause}
|
||||
GROUP BY {_SESSION_GROUP_KEY_SQL}
|
||||
LIMIT ${next_param_index}
|
||||
) AS bounded_sessions
|
||||
"""
|
||||
count_rows: Final[Sequence[_SpendLogsCountRow]] = await _query_raw(
|
||||
prisma_client, count_query, *sql_params, SPEND_LOGS_PAGINATION_COUNT_CAP + 1
|
||||
page_starts_inside_the_list: Final = offset == 0 or len(page_rows) > 0
|
||||
page_ends_the_list: Final = cursor is None and page_limit > 0 and not has_more and page_starts_inside_the_list
|
||||
total_records, total_is_capped = (
|
||||
(offset + len(page_rows), False)
|
||||
if page_ends_the_list
|
||||
else await _count_grouped_sessions(prisma_client, where_clause, sql_params, next_param_index)
|
||||
)
|
||||
raw_total: Final = int(count_rows[0]["total_count"]) if count_rows else 0
|
||||
total_is_capped: Final = raw_total > SPEND_LOGS_PAGINATION_COUNT_CAP
|
||||
total_records: Final = SPEND_LOGS_PAGINATION_COUNT_CAP if total_is_capped else raw_total
|
||||
|
||||
session_keys: Final = tuple((row["session_key"], row["api_key"]) for row in visible_rows)
|
||||
data: Final[list[dict[str, object]]] = ( # mutable-ok: _build_ui_spend_logs_response writes onto each row
|
||||
|
|
|
|||
|
|
@ -6629,6 +6629,30 @@ def _session_page_row(session_key, last_activity):
|
|||
return {"session_key": session_key, "api_key": "hashed-key", "last_activity": last_activity}
|
||||
|
||||
|
||||
def _session_grouped_paginating_prisma(sessions, counted_total=None):
|
||||
"""Mock prisma serving the grouped page query out of ``sessions``, honoring the LIMIT and OFFSET it asks for."""
|
||||
|
||||
async def mock_query_raw(sql_query, *params):
|
||||
if "COUNT(*) AS total_count" in sql_query:
|
||||
return [{"total_count": min(len(sessions) if counted_total is None else counted_total, params[-1])}]
|
||||
if "DISTINCT ON" in sql_query:
|
||||
return [_session_representative_row(f"req-{session_key}", session_key) for session_key in params[-2]]
|
||||
if "COALESCE(SUM(spend)" in sql_query:
|
||||
return []
|
||||
bounds = re.search(r"LIMIT \$(\d+)(?: OFFSET \$(\d+))?", sql_query)
|
||||
limit = params[int(bounds.group(1)) - 1]
|
||||
offset = params[int(bounds.group(2)) - 1] if bounds.group(2) else 0
|
||||
return [
|
||||
_session_page_row(session_key, last_activity)
|
||||
for session_key, last_activity in sessions[offset : offset + limit]
|
||||
]
|
||||
|
||||
mock_prisma = MagicMock()
|
||||
mock_prisma.db = MagicMock()
|
||||
mock_prisma.db.query_raw = AsyncMock(side_effect=mock_query_raw)
|
||||
return mock_prisma
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_first_page(client, monkeypatch):
|
||||
"""One row per (session, api_key), session-count total, and a keyset cursor for the next page."""
|
||||
|
|
@ -6742,6 +6766,203 @@ async def test_ui_view_spend_logs_group_by_session_cursor_page(client, monkeypat
|
|||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_jumps_to_page_without_cursor(client, monkeypatch):
|
||||
"""page > 1 with no session_cursor (the UI's last-page jump) serves the sessions that page starts at."""
|
||||
sessions = tuple((f"sess-{index:02d}", f"2026-08-29 10:{59 - index:02d}:00") for index in range(60))
|
||||
mock_prisma = _session_grouped_paginating_prisma(sessions)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.spend_management_endpoints._is_admin_view_safe",
|
||||
lambda user_api_key_dict: True,
|
||||
)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
try:
|
||||
start_date, end_date = _default_date_range()
|
||||
response = client.get(
|
||||
"/spend/logs/ui",
|
||||
params={
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"group_by_session": "true",
|
||||
"page": 3,
|
||||
"page_size": 25,
|
||||
},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
data = response.json()
|
||||
assert data["page"] == 3
|
||||
assert data["total"] == 60
|
||||
assert data["has_more"] is False
|
||||
assert data["next_session_cursor"] is None
|
||||
assert [row["request_id"] for row in data["data"]] == [f"req-sess-{index:02d}" for index in range(50, 60)]
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_short_page_totals_itself(client, monkeypatch):
|
||||
"""A page that runs out of sessions is the end of the list, so the total comes from it and nothing is counted."""
|
||||
sessions = tuple((f"sess-{index:02d}", f"2026-08-29 10:{59 - index:02d}:00") for index in range(10))
|
||||
mock_prisma = _session_grouped_paginating_prisma(sessions, counted_total=999)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.spend_management_endpoints._is_admin_view_safe",
|
||||
lambda user_api_key_dict: True,
|
||||
)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
try:
|
||||
start_date, end_date = _default_date_range()
|
||||
response = client.get(
|
||||
"/spend/logs/ui",
|
||||
params={
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"group_by_session": "true",
|
||||
"page": 1,
|
||||
"page_size": 25,
|
||||
},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
data = response.json()
|
||||
assert data["total"] == 10, "the count query's 999 would have won if it had been asked"
|
||||
assert data["total_is_capped"] is False
|
||||
assert data["total_pages"] == 1
|
||||
assert len(data["data"]) == 10
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_page_past_the_end_keeps_the_real_total(client, monkeypatch):
|
||||
"""An empty page past the last one says nothing about the total, so it is counted rather than inferred."""
|
||||
sessions = tuple((f"sess-{index:02d}", f"2026-08-29 10:{59 - index:02d}:00") for index in range(100))
|
||||
mock_prisma = _session_grouped_paginating_prisma(sessions)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.spend_management_endpoints._is_admin_view_safe",
|
||||
lambda user_api_key_dict: True,
|
||||
)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
try:
|
||||
start_date, end_date = _default_date_range()
|
||||
response = client.get(
|
||||
"/spend/logs/ui",
|
||||
params={
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"group_by_session": "true",
|
||||
"page": 4,
|
||||
"page_size": 50,
|
||||
},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
data = response.json()
|
||||
assert data["data"] == []
|
||||
assert data["total"] == 100, "the empty page's offset is not a total"
|
||||
assert data["total_pages"] == 2
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_page_past_count_cap_is_empty(client, monkeypatch):
|
||||
"""The last page inside the capped total still lists sessions; the page after it is empty and costs no query."""
|
||||
cap = spend_management_endpoints.SPEND_LOGS_PAGINATION_COUNT_CAP
|
||||
sessions = tuple((f"sess-{index:06d}", "2026-08-29 10:00:00") for index in range(cap + 50))
|
||||
mock_prisma = _session_grouped_paginating_prisma(sessions)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.spend_management_endpoints._is_admin_view_safe",
|
||||
lambda user_api_key_dict: True,
|
||||
)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
try:
|
||||
start_date, end_date = _default_date_range()
|
||||
params = {
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"group_by_session": "true",
|
||||
"page_size": 25,
|
||||
}
|
||||
last_page = client.get(
|
||||
"/spend/logs/ui",
|
||||
params={**params, "page": cap // 25},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
assert last_page.status_code == 200, last_page.text
|
||||
last_page_data = last_page.json()
|
||||
assert last_page_data["total"] == cap
|
||||
assert last_page_data["total_is_capped"] is True
|
||||
assert last_page_data["data"][0]["request_id"] == f"req-sess-{cap - 25:06d}"
|
||||
assert len(last_page_data["data"]) == 25
|
||||
|
||||
mock_prisma.db.query_raw.reset_mock()
|
||||
past_cap = client.get(
|
||||
"/spend/logs/ui",
|
||||
params={**params, "page": cap // 25 + 1},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
assert past_cap.status_code == 200, past_cap.text
|
||||
past_cap_data = past_cap.json()
|
||||
assert past_cap_data["data"] == []
|
||||
assert past_cap_data["has_more"] is False
|
||||
assert past_cap_data["total"] == cap
|
||||
assert mock_prisma.db.query_raw.await_count == 1, "only the bounded count query runs past the capped window"
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_last_page_stops_at_the_capped_total(client, monkeypatch):
|
||||
"""A page size that does not divide the cap still ends the last page at the capped total it reports."""
|
||||
cap = spend_management_endpoints.SPEND_LOGS_PAGINATION_COUNT_CAP
|
||||
sessions = tuple((f"sess-{index:06d}", "2026-08-29 10:00:00") for index in range(cap + 50))
|
||||
mock_prisma = _session_grouped_paginating_prisma(sessions)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.spend_management_endpoints._is_admin_view_safe",
|
||||
lambda user_api_key_dict: True,
|
||||
)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
try:
|
||||
start_date, end_date = _default_date_range()
|
||||
response = client.get(
|
||||
"/spend/logs/ui",
|
||||
params={
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"group_by_session": "true",
|
||||
"page": cap // 7 + 1,
|
||||
"page_size": 7,
|
||||
},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
data = response.json()
|
||||
assert data["total"] == cap
|
||||
assert [row["request_id"] for row in data["data"]] == [
|
||||
f"req-sess-{index:06d}" for index in range(cap - cap % 7, cap)
|
||||
]
|
||||
assert data["has_more"] is True
|
||||
assert data["next_session_cursor"] is not None
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_offset_for_non_starttime_sort(
|
||||
client, monkeypatch
|
||||
|
|
|
|||
|
|
@ -528,8 +528,8 @@ async def test_spend_logs_ui_group_by_session_paginates_sessions(monkeypatch):
|
|||
|
||||
group_key = "COALESCE(NULLIF(session_id, ''), request_id), api_key"
|
||||
session_rows = [
|
||||
{"session_key": "req-1", "api_key": "k", "last_activity": "2026-02-16 10:00:00"},
|
||||
{"session_key": "req-2", "api_key": "k", "last_activity": "2026-02-16 09:00:00"},
|
||||
{"session_key": f"req-{index}", "api_key": "k", "last_activity": f"2026-02-16 10:{59 - index:02d}:00"}
|
||||
for index in range(51)
|
||||
]
|
||||
representative_rows = [
|
||||
{"request_id": "req-1", "api_key": "k", "metadata": "{}", "session_id": None},
|
||||
|
|
@ -538,7 +538,7 @@ async def test_spend_logs_ui_group_by_session_paginates_sessions(monkeypatch):
|
|||
|
||||
async def mock_query_raw(sql_query, *params):
|
||||
if "COUNT(*) AS total_count" in sql_query:
|
||||
return [{"total_count": 12}]
|
||||
return [{"total_count": 60}]
|
||||
if "DISTINCT ON" in sql_query:
|
||||
return representative_rows
|
||||
return session_rows
|
||||
|
|
@ -590,11 +590,11 @@ async def test_spend_logs_ui_group_by_session_paginates_sessions(monkeypatch):
|
|||
assert "COUNT(*) OVER ()" not in rep_sql
|
||||
|
||||
assert [row["request_id"] for row in response["data"]] == ["req-1", "req-2"]
|
||||
assert response["total"] == 12
|
||||
assert response["total"] == 60
|
||||
assert response["total_is_capped"] is False
|
||||
assert response["total_pages"] == 1
|
||||
assert response["has_more"] is False
|
||||
assert response["next_session_cursor"] is None
|
||||
assert response["total_pages"] == 2
|
||||
assert response["has_more"] is True
|
||||
assert response["next_session_cursor"] == "2026-02-16 10:10:00|k|req-49"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -258,6 +258,44 @@ describe("RequestLogsPanel", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("jumps straight to the last page without a cursor when the last-page button is clicked", async () => {
|
||||
const firstPage = Array.from({ length: 25 }, (_, index) => logEntry({ request_id: `req-${index}` }));
|
||||
const lastPage = Array.from({ length: 10 }, (_, index) => logEntry({ request_id: `req-last-${index}` }));
|
||||
vi.mocked(uiSpendLogsCall).mockImplementation(async ({ page }) =>
|
||||
page === 3
|
||||
? {
|
||||
data: lastPage,
|
||||
total: 60,
|
||||
page: 3,
|
||||
page_size: 25,
|
||||
total_pages: 3,
|
||||
next_session_cursor: null,
|
||||
has_more: false,
|
||||
}
|
||||
: {
|
||||
data: firstPage,
|
||||
total: 60,
|
||||
page: 1,
|
||||
page_size: 25,
|
||||
total_pages: 3,
|
||||
next_session_cursor: "2026-07-07 09:50:13|key-1|sess-1",
|
||||
has_more: true,
|
||||
},
|
||||
);
|
||||
renderPanel();
|
||||
|
||||
await waitFor(() => expect(row("req-0")).not.toBeNull());
|
||||
expect(screen.getByTestId("pagination-page")).toHaveTextContent("Page 1 of 3");
|
||||
fireEvent.click(screen.getByTestId("pagination-last"));
|
||||
|
||||
await waitFor(() => expect(row("req-last-0")).not.toBeNull());
|
||||
expect(lastCall()?.page).toBe(3);
|
||||
expect(lastCall()?.params?.session_cursor).toBeUndefined();
|
||||
expect(screen.getByTestId("pagination-page")).toHaveTextContent("Page 3 of 3");
|
||||
expect(screen.getByTestId("pagination-range")).toHaveTextContent("Showing 51-60 of 60");
|
||||
expect(vi.mocked(uiSpendLogsCall).mock.calls.filter(([options]) => options.page === 2)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("drops the cursor and returns to the first page when a filter changes", async () => {
|
||||
const firstPage = Array.from({ length: 50 }, (_, index) => logEntry({ request_id: `req-${index}` }));
|
||||
vi.mocked(uiSpendLogsCall).mockResolvedValue({
|
||||
|
|
|
|||
|
|
@ -209,15 +209,14 @@ export default function RequestLogsPanel({ accessToken, token, userRole, userID,
|
|||
setPagination({ ...requested, pageIndex: 0 });
|
||||
return;
|
||||
}
|
||||
if (requested.pageIndex <= pagination.pageIndex) {
|
||||
if (requested.pageIndex !== pagination.pageIndex + 1) {
|
||||
setPagination(requested);
|
||||
return;
|
||||
}
|
||||
const nextCursor = filteredLogs.next_session_cursor;
|
||||
if (!nextCursor || logsQuery.isPlaceholderData) return;
|
||||
const nextPageIndex = pagination.pageIndex + 1;
|
||||
setSessionCursors((previous) => ({ ...previous, [nextPageIndex]: nextCursor }));
|
||||
setPagination({ ...requested, pageIndex: nextPageIndex });
|
||||
setSessionCursors((previous) => ({ ...previous, [requested.pageIndex]: nextCursor }));
|
||||
setPagination(requested);
|
||||
},
|
||||
[usesSessionCursor, pagination, filteredLogs.next_session_cursor, logsQuery.isPlaceholderData],
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue