Skip to content

Add transparent cursor-based pagination for eventsearch, filteredeven… - #24

Merged
jimmyday12 merged 3 commits into
mainfrom
ams-7144-cursor-pagination
Jun 3, 2026
Merged

Add transparent cursor-based pagination for eventsearch, filteredeven…#24
jimmyday12 merged 3 commits into
mainfrom
ams-7144-cursor-pagination

Conversation

@jimmyday12

@jimmyday12 jimmyday12 commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds automatic, transparent cursor-based pagination to the three AMS endpoints that support it. No breaking changes — existing user code works without modification.

  • sb_get_event() (eventsearch / filteredeventsearch) now fetches all pages automatically
  • sb_sync_event() (synchronise) now fetches all pages, accumulating deleted_event_id across pages and taking new_sync_time from the last page
  • All other endpoints (profilesearch, usersearch, groupmembers, listgroups) are unchanged

Changes

  • export_body.R: inject paginate = TRUE and conditional cursor into request bodies; synchronise uses a nested pagination object, eventsearch/filteredeventsearch use top-level fields
  • export_extract.R: add .extract_cursor() to pull nextCursor / pagination.cursor from responses
  • export_flatten.R: filter to the events array before gather_array() to avoid a tidyjson crash on the sibling nextCursor string key
  • export_handler.R: add .paginate_export() loop and .combine_paginated_pages() to reconstruct sb_df attributes across pages

Known server-side limitation

The eventsearch/filteredeventsearch cursors use a strict > comparison that orphans ~1 event per page boundary. This is a server-side bug tracked in AMS-7144 — our implementation is correct per the documented protocol. synchronise is unaffected.

Test plan

  • Existing testthat suite passes (4 pre-existing network failures unrelated to this change)
  • sb_get_event() returns same row count as non-paginated baseline
  • sb_sync_event() returns correct new_sync_time attribute and accumulates deleted_event_id across pages
  • class(result) includes "sb_df" on paginated results

🤖 Generated with Claude Code
EOF

Summary by CodeRabbit

  • New Features
    • Cursor-based pagination for export endpoints so large exports are fetched across pages and returned as a single combined result.
  • Refactor
    • Export flow transparently iterates pages, suppresses per-page messages, emits one final success message, and preserves/merges export metadata (including sync time and deleted IDs) across pages.
    • Improved response flattening to handle event arrays and top-level cursors correctly.
  • Tests
    • Integration tests updated to reflect revised test data schema.

Review Change Stack

…tsearch, and synchronise

All three AMS endpoints that support cursor-based pagination now
automatically fetch all pages and return a single combined tibble.
No API-breaking changes — existing user code works without modification.

Key changes:
- export_body.R: inject `paginate = TRUE` and conditional cursor into
  request bodies; synchronise uses a nested `pagination` object while
  eventsearch/filteredeventsearch use top-level fields
- export_extract.R: add .extract_cursor() to pull nextCursor / pagination.cursor
  from responses; uses check_type = FALSE for AMS's missing Content-Type header
- export_flatten.R: filter to the `events` array before gather_array() to
  avoid a tidyjson crash on the sibling `nextCursor` string key
- export_handler.R: add .paginate_export() loop, .combine_paginated_pages()
  to reconstruct sb_df attributes across pages, and branch .export_handler()
  to use the new loop for paginating endpoints only

Documents the known server-side eventsearch/filteredeventsearch cursor bug
(strict > comparison orphans ~1 event per page boundary) — tracked in AMS-7144.
The synchronise endpoint is unaffected; our implementation is correct per protocol.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d61ca21f-f41d-485f-ade4-2e4056c21653

📥 Commits

Reviewing files that changed from the base of the PR and between 9c1b01a and 05a443c.

📒 Files selected for processing (1)
  • R/export_handler.R
🚧 Files skipped from review as they are similar to previous changes (1)
  • R/export_handler.R

📝 Walkthrough

Walkthrough

Adds cursor-based pagination across export flows: body builders accept cursors, a new extractor reads endpoint-specific cursors, flattening handles event arrays correctly, the handler paginates and combines pages, and integration tests are updated to new test columns.

Changes

Export pagination implementation

Layer / File(s) Summary
Request body builders with cursor support
R/export_body.R
.build_export_event_body() and .build_export_synchronise_body() now accept optional cursor; event bodies include body$cursor only when present and synchronise bodies include a nested pagination object. .build_export_body() forwards cursor to pagination-capable builders.
Cursor extraction from API responses
R/export_extract.R
New .extract_cursor() parses response JSON and returns cursor from endpoint-specific fields (nextCursor for eventsearch/filteredeventsearch, pagination$cursor for synchronise), normalizing missing/empty values to NULL.
Flattening paginated response metadata
R/export_flatten.R
.flatten_export_metadata() special-cases eventsearch/filteredeventsearch responses to extract the events array before array-gathering logic runs.
Handler routing and pagination loop
R/export_handler.R
.export_handler() routes paginated endpoints to .paginate_export(), which loops building bodies with evolving cursor, fetches pages, converts pages to tibbles, and stops when cursor is NULL. .combine_paginated_pages() merges pages and reconstructs metadata; .json_to_df_handler() suppresses per-page success messages during pagination.
Integration tests
tests/testthat/test-api_integration.R
Test fixtures and assertions updated to use Number Test and Text Test fields; update/upsert test logic and verifications adjusted accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop through pages, cursor held tight,
Building bodies by day and extracting by night,
Tokens tucked safely, pages combine,
One final message — the export is fine,
I nibble good code and leave no loose twine.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change—adding cursor-based pagination for specific export endpoints—and is specific and relevant to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ams-7144-cursor-pagination

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@R/export_handler.R`:
- Around line 106-133: The pagination repeat loop can spin forever if the API
returns an identical cursor; modify the loop in export_handler.R (the block that
increments page_n, calls .build_export_body/.make_request/.extract_cursor and
appends page_result) to track the previous cursor (e.g. last_cursor) and detect
if cursor == last_cursor (treat as failure), and also enforce a configurable
page ceiling (use an option like arg$option$max_pages or a sensible default) to
break and raise an error if exceeded; on detection, stop further requests and
raise a clear error message indicating non-advancing cursor or max page limit
reached so callers can handle it.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a642cd1b-c119-4f78-9b5b-4e55b5c83a66

📥 Commits

Reviewing files that changed from the base of the PR and between 45b332d and 6914587.

📒 Files selected for processing (4)
  • R/export_body.R
  • R/export_extract.R
  • R/export_flatten.R
  • R/export_handler.R

Comment thread R/export_handler.R

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/testthat/test-api_integration.R (1)

209-214: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen the final value-set assertion to avoid false positives.

Line 213 currently allows passes when expected values are missing (subset check only). Use exact set equality to assert the intended outcome.

Proposed fix
-  expect_true(all(unique_values %in% c(123, 999, 543)))
+  expect_setequal(unique_values, c(123, 999, 543))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/testthat/test-api_integration.R` around lines 209 - 214, The test
currently computes unique_values from check_upsert_results and then uses
expect_true(all(unique_values %in% c(123, 999, 543))) which allows missing
expected values; replace that subset-style assertion with an exact set equality
check (e.g., use testthat::expect_setequal(unique_values, c(123, 999, 543)) or
compare sorted vectors with expect_equal(sort(unique_values), sort(c(123, 999,
543)))) so the test fails if any expected value is missing or any unexpected
value is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/testthat/test-api_integration.R`:
- Around line 209-214: The test currently computes unique_values from
check_upsert_results and then uses expect_true(all(unique_values %in% c(123,
999, 543))) which allows missing expected values; replace that subset-style
assertion with an exact set equality check (e.g., use
testthat::expect_setequal(unique_values, c(123, 999, 543)) or compare sorted
vectors with expect_equal(sort(unique_values), sort(c(123, 999, 543)))) so the
test fails if any expected value is missing or any unexpected value is present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6b46ecd-cced-490e-ad5b-3cabb4a499b4

📥 Commits

Reviewing files that changed from the base of the PR and between 6914587 and 9c1b01a.

📒 Files selected for processing (1)
  • tests/testthat/test-api_integration.R

@rparastw

Copy link
Copy Markdown

Lgtm

@jimmyday12 jimmyday12 assigned rparastw and unassigned rpparas May 27, 2026
@rparastw rparastw removed their assignment May 27, 2026
Add two safety checks to .paginate_export() in response to PR review:

1. Duplicate-cursor detection: if the server returns the same cursor on
   consecutive pages (a stuck loop), abort immediately with a clear error
   rather than looping indefinitely.

2. Page ceiling: abort with a clear error if page_n exceeds max_pages
   (default 1000; overridable via arg$option$max_pages). Prevents runaway
   requests if duplicate-cursor detection is somehow bypassed.

Both error paths call clear_progress_id() first to clean up CLI state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found.

@jimmyday12
jimmyday12 requested a review from rparastw May 27, 2026 03:34
@jimmyday12
jimmyday12 requested a review from rparastw June 2, 2026 00:10
@jimmyday12
jimmyday12 merged commit 7dae122 into main Jun 3, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants