Fixes #33190: Stop Mode report pagination on unseen-report exhaustion - #33189
Fixes #33190: Stop Mode report pagination on unseen-report exhaustion#33189harshsoni2024 wants to merge 2 commits into
Conversation
Report pagination terminated on `len(reports) < REPORTS_PAGE_SIZE`, which made two assumptions about Mode's API that it does not guarantee. If a page ever holds fewer than 30 records, the first page satisfies the condition and the rest of the space is dropped with no warning - reproducing the missing-reports symptom this change set exists to fix. If a space holds an exact multiple of 30 records and Mode clamps the out-of-range page back to the last one, the repeated-page guard raised and killed the whole source, dropping every dashboard of every space. Pagination now stops once a page carries no report that was not already seen in that space, which terminates on an empty page, on a clamped repeat, and on an ignored page parameter, without assuming how many records a full page holds. Reports are de-duplicated by token, falling back to the record itself when Mode omits one. Ports ad466b8 from the 1.13 backport so both branches carry the same pagination contract. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
✅ PR checks passedThe linked issue has a description and all required Shipping project fields set. Thanks! |
There was a problem hiding this comment.
🟡 Changes recommended
A critical comment remains unresolved because repeated non-empty pages can silently cause partial ingestion without warning.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes Mode report pagination to prevent report loss and failures caused by undocumented page-size behavior.
Changes:
- Tracks unseen reports per space and handles repeated pages safely.
- Adds regression tests for short, overlapping, and repeated pages.
- Removes obsolete type-check suppressions.
File summaries
| File | Summary |
|---|---|
ingestion/tests/unit/source/dashboard/mode/test_client.py |
Adds pagination and de-duplication regression coverage. |
ingestion/src/metadata/ingestion/source/dashboard/mode/client.py |
Implements unseen-report pagination termination. |
ingestion/.basedpyright/baseline.json |
Removes obsolete diagnostics. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Thanks for the review, The trigger also needs token-less reports, which the connector can't process I'd rather not add a page cap — a |
Code Review ✅ Approved 1 resolved / 1 findingsHardens Mode report pagination to stop on unseen-report exhaustion instead of relying on page-size heuristics, fixing silent truncation when pages hold fewer than 30 records and hard aborts when out-of-range pages are clamped. All 42 tests pass with 100% coverage of changed lines. Consider adding a defensive maximum-page cap as a backstop in case Mode returns full pages of genuinely distinct records indefinitely, since the old page-size heuristic previously bounded the walk implicitly. ✅ 1 resolved✅ Edge Case: No absolute page cap on the pagination loop
OptionsDisplay: compact → Counting what did not apply, without listing it. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|
✅ Playwright Results — workflow succeededValidated commit ✅ 107 passed · ❌ 0 failed · 🟡 2 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky PerformanceBlocking targets: ✅ met · Optimization targets: 🟡 in progress Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting. 🕒 Full workflow signal wall (to summary) 51m 15s ⏱️ Max setup 4m 29s · max shard execution 15m 13s · max shard-job elapsed before upload 21m 18s · reporting 6s 🌐 235.44 requests/attempt · 1.79 app boots/UI scenario · 0.00% common-shard skew Optimization targets still in progress:
🟡 2 flaky test(s) (passed on retry)
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |



Describe your changes:
Fixes #33190
Hardens the pagination added for #22559; the defects below were found in review
of the 1.13 backport, so they get their own issue rather than reopening a closed one.
#32533 paginated Mode report discovery so that reports past the first page of a
space are ingested. Its termination condition —
len(reports) < REPORTS_PAGE_SIZEwith
REPORTS_PAGE_SIZE = 30— makes two assumptions about Mode's API that theAPI does not guarantee, both of which lose reports. Review on the 1.13 backport
(#33147) raised them; this is the same fix on
mainso the two branches carryone pagination contract.
Silent truncation. If a report page ever holds fewer than 30 records, the
first page satisfies
len(reports) < 30, the walk stops, and the remainder ofthat space is dropped with no warning and no error — reproducing the very
missing-reports symptom #22559 was filed about. The page size is undocumented
and is not pinned with a
per_pageparameter, so a server-side default changesilently regresses the connector.
Hard abort. A guard added during review raised
RuntimeErrorwhen a pagerepeated. Many paginated REST APIs clamp an out-of-range
pageto the last pageand re-return it instead of returning an empty list. A space holding an exact
multiple of 30 reports therefore takes the
page += 1branch, receives the samepage back, and raises.
fetch_all_reportsis called fromget_dashboards_listwithout a guard, so that exception kills the whole Mode source and drops every
dashboard of every space — a benign input destroying an entire ingestion run.
Pagination now stops once a page carries no report unseen in that space.
Type of change:
High-level design:
One condition replaces both heuristics:
It terminates on every case the old code handled and the two it mishandled:
pageparameter ignored entirelyPage size is no longer consulted, so
REPORTS_PAGE_SIZEis removed rather thanre-tuned — the failure mode is structurally gone, not calibrated against a
guessed constant.
_report_keyde-duplicates by report token and falls back to a sorted JSON dumpof the record when Mode omits one, so token-less pages neither collide with each
other nor loop forever.
Cost: one extra request per space, always — the terminal empty or repeated
page. The old code already paid that whenever a space's report count was an
exact multiple of 30.
seen_reportsis a per-space working set, reset eachspace and bounded by that space's report count, which is the same order as the
all_reportsaccumulator that already existed.Alternative considered: following
_links.nextfrom Mode's HAL responsewould be contract-driven rather than heuristic, and the client already reads
_linksforshareandreport_viz_web. It is the better long-term shape, butconfirming the pagination link's presence needs a live Mode Business workspace,
which is unavailable here. The unseen-report condition needs no knowledge of the
response envelope, so it is correct without that confirmation and does not block
moving to
_links.nextlater.ingestion/.basedpyright/baseline.jsondrops three suppressed diagnostics thatthe removed code carried (9 → 6 for
mode/client.py). Verified structurallythat no other file key changed: 809 keys before and after, none added or
removed.
Tests:
Use cases covered
stopping after page one
walk and keeps its reports, instead of aborting the source
pageparameter the API ignores terminates instead of looping foreverfilterQueryParamstill fails fast with a clear messageUnit tests
ingestion/tests/unit/source/dashboard/mode/test_client.py— rewrittenaround the new contract, 6 → 8 tests.
test_fetch_all_reports_rejects_repeated_full_pageis deleted because it asserted the removed
RuntimeError;test_..._requests_page_after_exactly_thirty_resultsis folded into themulti-space test, which now asserts the terminal page request per space.
test_fetch_all_reports_keeps_paging_after_a_page_smaller_than_thirty(two25-record pages return all 50; the old code returned 25) and
test_fetch_all_reports_stops_when_a_page_repeats(a clamped repeat returns30 reports instead of raising).
(17), plus
burstiq(10) andqlikcloud(4) run alongside so any test-modulename collision would surface.
client.py: 100% — all 22 changed/added lines areexecuted; cross-referenced
--cov-report=term-missingagainst the diff's lineranges.
Backend integration tests
Ingestion integration tests
unavailable, so the Mode HTTP boundary stays covered with fixtures. The change
is deliberately shaped to need no knowledge of the response envelope beyond
_embedded.reports, which the connector already depended on.Playwright (UI) tests
Manual testing performed
Commands run on this branch, with their actual results:
pytest tests/unit/source/dashboard/mode/ tests/unit/topology/dashboard/test_mode.py tests/unit/source/database/burstiq tests/unit/source/dashboard/qlikcloud— 42 passedpytest ... --cov=metadata.ingestion.source.dashboard.mode --cov-report=term-missing— used to confirm every changed line inclient.pyis coveredruff check ... --config pyproject.toml— no issuespython scripts/check_ruff_suppressions.py --check— exit 0ruff format --check ... --config pyproject.toml— already formattedbasedpyright src/metadata/ingestion/source/dashboard/mode/client.py— 0 errors, 0 warnings, 0 notesNot performed: a live Mode ingestion run, for lack of workspace credentials.
UI screen recording / screenshots:
Not applicable — no UI changes.
Checklist:
Fixes <issue-number>: <short explanation>Fixes #<issue-number>above.pagination contract and why it cannot rely on page size is documented on
fetch_all_reports.