Skip to content

feat(reports): structured chart-holder diagnostics for capture logging - #42816

Open
eschutho wants to merge 2 commits into
masterfrom
report-diagnostics-parity
Open

feat(reports): structured chart-holder diagnostics for capture logging#42816
eschutho wants to merge 2 commits into
masterfrom
report-diagnostics-parity

Conversation

@eschutho

@eschutho eschutho commented Aug 5, 2026

Copy link
Copy Markdown
Member

SUMMARY

Follow-up to #42624, logging-only. Enriches the report readiness/capture log lines with a structured per-state chart-holder breakdown, proven out in production while diagnosing blank-report incidents (the same investigations that produced #42624).

What's added:

  • ChartHolderDiagnostics (report_execution.py): structured counts separating capture readiness from semantic successrendered/empty/error/virtualized/unready per capture, with TERMINAL_CHART_HOLDER_STATES and an explicit CHART_HOLDER_SEMANTIC_POLICY = deliver_terminal_errors_with_warning.
  • Enriched report_readiness_poll / _ready / _terminal lines on both the Playwright and Selenium paths: the existing mounted/ready counts gain the full per-state breakdown and semantic_success.
  • report_readiness_tile: per-tile readiness diagnostics during tiled capture. Each holder's strongest observed terminal state is aggregated across tiles, so the final ready line reports true whole-dashboard counts even though virtualization means any single viewport only sees a fraction of the charts.
  • report_semantic_status: a WARNING emitted whenever capture readiness is satisfied but the artifact contains terminal chart errors — delivery is not semantic completeness, and this makes that distinction operator-visible and alertable, per tile and per capture.
  • Restores the explicit zero-holder diagnostic on the no-context (thumbnail) path.

Example of what the tile lines look like in production (52-chart dashboard, 6 tiles — note virtualized_holders showing why per-viewport counts can't be summed naively):

report_readiness_tile ... expected_holders=52 mounted_holders=52 ready_holders=52 rendered_holders=10 empty_holders=0 error_holders=0 virtualized_holders=42 unready_holders=0 semantic_success=True tile=1/6 ...
report_readiness_ready ... expected_holders=52 mounted_holders=52 ready_holders=52 rendered_holders=52 ... semantic_success=True

No behavior changes: readiness predicates, budgets, and delivery are untouched; this only changes what gets logged. Log call style stays lazy %-formatting throughout.

TESTING INSTRUCTIONS

  • Unit tests pin the ChartHolderDiagnostics counts (terminal-error separation, unready counting, zero-holder non-success) and the enriched line formats on the poll/ready/terminal/tile paths.
  • Full tests/unit_tests/utils/ + commands/report/ + tasks/ scope: 1345 passing; ruff and mypy clean.

ADDITIONAL INFORMATION

… logging

Enriches the report readiness/capture log lines with the per-state
holder breakdown proven out in production:

- ChartHolderDiagnostics (report_execution.py): structured counts
  separating capture readiness from semantic success, with
  TERMINAL_CHART_HOLDER_STATES and the explicit
  CHART_HOLDER_SEMANTIC_POLICY=deliver_terminal_errors_with_warning.
- report_readiness_poll/_ready/_terminal now carry rendered/empty/
  error/virtualized/unready counts and semantic_success on both the
  Playwright and Selenium paths.
- report_readiness_tile: per-tile readiness diagnostics during tiled
  capture, with each holder's strongest observed terminal state
  aggregated across tiles for the final ready line.
- report_semantic_status: a WARNING whenever capture readiness is
  satisfied but the artifact contains terminal chart errors — delivery
  is not semantic completeness, and this makes the distinction
  operator-visible (and alertable) per tile and per capture.
- Restores the explicit zero-holder thumbnail diagnostic on the
  no-context path.

Logging-only: no readiness predicate, budget, or delivery behavior
changes. Unit tests pin the diagnostics counts and the enriched line
formats.

Co-Authored-By: Claude <noreply@anthropic.com>
@dosubot dosubot Bot added the alert-reports Namespace | Anything related to the Alert & Reports feature label Aug 5, 2026
@eschutho
eschutho requested a review from rebenitez1802 August 5, 2026 22:21

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Agent Run #6153b4

Actionable Suggestions - 2
  • tests/unit_tests/utils/webdriver_test.py - 2
Additional Suggestions - 1
  • superset/utils/screenshot_utils.py - 1
    • Missing holder state recording on zero-holders timeout · Line 472-472
      The zero-holders timeout path retrieves `holder_states` at line 472 but never calls `_record_visible_holder_states()`, so `observed_holder_states` misses any chart errors or empty states from this timeout. The final diagnostic at line 854 will therefore use `holder_states` directly instead of the per-chart progressive state that `_record_visible_holder_states` builds. Add the missing call alongside the already-invoked `ChartHolderDiagnostics`.
Review Details
  • Files reviewed - 6 · Commit Range: 652f61e..652f61e
    • superset/utils/report_execution.py
    • superset/utils/screenshot_utils.py
    • superset/utils/webdriver.py
    • tests/unit_tests/utils/test_report_execution.py
    • tests/unit_tests/utils/test_screenshot_utils.py
    • tests/unit_tests/utils/webdriver_test.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

assert warning_call.args[9] == [{"chartId": "42", "state": "nothing_mounted"}]
assert warning_call.args[9] == 1 # unready holders
assert warning_call.args[16] == [{"chartId": "42", "state": "nothing_mounted"}]
assert warning_call.args[17] == [{"chartId": "42", "state": "nothing_mounted"}]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Out-of-bounds array access

Array access warning_call.args[17] will raise IndexError at runtime because the new logger.warning call in webdriver.py only passes 17 arguments (indices 0–16).

Code Review Run #6153b4


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not a bug: call.args includes the format string at index 0, so a logger.warning with 17 format arguments has indices 1–17 and args[17] is the final states payload. These exact assertions run green in this PR's suite (and have on two other branches carrying the same test).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The suggestion to avoid this type of review comment is noted. Based on the provided context, the assertion that warning_call.args[17] is out-of-bounds appears to be a false positive, as the test suite confirms the argument index is valid for the current implementation.

assert failure_args[9] == diagnostics
assert failure_args[10] == diagnostics
assert failure_args[16] == diagnostics
assert failure_args[17] == diagnostics

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Out-of-bounds array access

Array access failure_args[17] will raise IndexError at runtime because the logger.warning call only has 17 arguments (indices 0–16).

Code Review Run #6153b4


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same as the sibling comment: call.args[0] is the format string, so 17 format args occupy indices 1–17 and failure_args[17] is in bounds. The test passes in this PR's CI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The suggestion is based on an incorrect assumption about the number of arguments passed to the logger. Since the format string is the first argument, the subsequent 17 arguments are correctly accessed at indices 1 through 17, making the array access valid in this context.

tests/unit_tests/utils/webdriver_test.py

failure_args = mock_logger.warning.call_args.args
        assert failure_args[16] == diagnostics
        assert failure_args[17] == diagnostics

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 23.88060% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.72%. Comparing base (32e4e3c) to head (652f61e).

Files with missing lines Patch % Lines
superset/utils/screenshot_utils.py 0.00% 29 Missing ⚠️
superset/utils/webdriver.py 0.00% 15 Missing ⚠️
superset/utils/report_execution.py 69.56% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42816      +/-   ##
==========================================
- Coverage   65.73%   65.72%   -0.02%     
==========================================
  Files        2843     2843              
  Lines      162670   162723      +53     
  Branches    37255    37267      +12     
==========================================
+ Hits       106932   106945      +13     
- Misses      53646    53685      +39     
- Partials     2092     2093       +1     
Flag Coverage Δ
hive 37.95% <23.88%> (-0.01%) ⬇️
mysql 57.77% <23.88%> (-0.02%) ⬇️
postgres 57.81% <23.88%> (-0.03%) ⬇️
presto 39.86% <23.88%> (-0.01%) ⬇️
python 59.18% <23.88%> (-0.03%) ⬇️
sqlite 57.45% <23.88%> (-0.02%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review of the chart-holder diagnostics change. Posting the one blocking item as an apply-able suggestion, plus the two should-fix items inline. The design is sound and the security model is intact — the new lines add only integer counts, a boolean, and a constant policy string; url and the raw states list were already logged on the base terminal paths, and every format string is a literal with %-args. A few Low nits (semantic_success vs its docstring on virtualized holders, the final report_readiness_ready data-source switch, per-tile/thumbnail log levels, and brittle positional-index test asserts) are left out here — glad to add them if useful.

Comment thread superset/utils/screenshot_utils.py Outdated
else:
tile_elapsed = time.monotonic() - tile_wait_start
if report_execution_context:
holder_states = page.evaluate(FIND_CHART_HOLDER_STATES_JS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 High — Unguarded per-tile page.evaluate can fail an otherwise-successful capture

This runs on the readiness-succeeded branch with no local guard — the enclosing try only catches PlaywrightTimeout. A generic error here (Playwright "execution context was destroyed" on a navigation race, or a render-process crash on a large dashboard — exactly the case the tiled path exists to serve) propagates to except Exception at line 927, where readiness_timeout is False, so it logs "Tiled screenshot failed" and return None — discarding the tiles already captured and failing a report that would otherwise deliver (allow_partial_fallback=False for reports). The twin evaluate at line 842 is wrapped for exactly this reason (# diagnostics must not discard valid tiles); this one isn't.

Suggested change
holder_states = page.evaluate(FIND_CHART_HOLDER_STATES_JS)
try:
holder_states = page.evaluate(FIND_CHART_HOLDER_STATES_JS)
if not isinstance(holder_states, list):
holder_states = []
except Exception: # noqa: BLE001 # diagnostics must not discard valid tiles
logger.warning(
"Unable to collect per-tile chart-holder diagnostics%s",
context_suffix,
exc_info=True,
)
holder_states = []

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed and fixed in 52f16c59bd — this was the best catch of the review. The per-tile evaluate now carries the same guard as the final block, and test_per_tile_diagnostics_failure_does_not_discard_capture pins the regression: the diagnostics evaluate raising a non-timeout error ("Execution context was destroyed") on all three tiles still yields a successful combined capture with a warning per tile.

)
elapsed, remaining = _deadline_values()
logger.info(
"report_readiness_tile url=%s expected_holders=%s "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium — The enriched fields (the point of this PR) are effectively untested

Every test that drives an enriched log line uses an all-zero-terminal scenario (a single waiting_on_database/nothing_mounted holder), so the rendered/empty/error/virtualized/semantic_success field→slot mapping across the ~9 logger calls is never asserted with non-zero values — swap error_holders and rendered_holders in any emitter and the suite still passes. The cross-tile aggregation, the report_semantic_status branch, and the Selenium enriched lines have zero assertions; notably, no test would have caught the High above. Add one log-path test per emitter driving a mixed set (e.g. 2 rendered + 1 empty + 1 error) that asserts the formatted substrings, using the warning_args[0] % warning_args[1:] pattern already adopted in test_screenshot_utils.py.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair — added in 52f16c59bd: test_tile_line_reports_mixed_holder_states drives 2 rendered + 1 empty + 1 error + 1 unready through the per-tile emitter and asserts every field substring in the rendered message (a swap between any two counters now fails), plus test_final_semantic_status_fires_exactly_once_for_error_holders covers the semantic-status branch. I stopped short of one-per-emitter across all ~9 call sites — the counters all come from the same ChartHolderDiagnostics.from_holder_states, which has its own mixed-state unit tests, so the residual risk is per-emitter arg-ordering; the two highest-traffic emitters (tile + final) are now pinned and the rest share the pattern. Happy to extend to the Selenium pair if you want it.

Comment thread superset/utils/screenshot_utils.py Outdated
f"{remaining:.2f}" if remaining is not None else None,
context_suffix,
)
if diagnostics.error_holders:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Mediumreport_semantic_status WARNING is duplicative and noisy for a delivered condition

This if diagnostics.error_holders: fires the WARNING per-tile here and again in the final block (line 885), plus the webdriver paths — so one persistent error chart spanning 3 tiles emits ~4 WARNINGs per run, multiplied across scheduled runs, for an artifact the policy still delivers. Operators alerting on WARNING from this module get paged for customer-side chart errors, which can mask real faults. Consider emitting the semantic-status line once per capture (final only), and possibly at INFO given delivery still succeeds.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed and changed in 52f16c59bd, with one nuance: rather than demoting to INFO, the per-tile WARNING is removed entirely (the report_readiness_tile INFO line already carries error_holders per tile, so it was pure duplication) and the final-block WARNING stays a WARNING. Rationale for keeping the final one warm: deliver_terminal_errors_with_warning is the policy — the deliverable went out with broken content, and the single WARNING per capture is the operator-visible half of that contract. One error chart now produces exactly one WARNING per run instead of N+1; test_final_semantic_status_fires_exactly_once_for_error_holders pins it.

…e per capture

Review fixes:
- Wrap the per-tile diagnostics evaluate on the readiness-succeeded path
  (same guard as the final block: diagnostics must not discard valid
  tiles). A navigation race or renderer crash during the diagnostics
  call no longer fails a capture whose tiles are already good.
- Emit report_semantic_status once per capture (final block only); the
  per-tile report_readiness_tile INFO lines already carry error_holders,
  and one error chart spanning N tiles was producing N+1 WARNINGs.
- Mixed-state tests pin the field-to-slot mapping of the enriched lines
  (2 rendered + 1 empty + 1 error + 1 unready), the once-per-capture
  semantic status, and the diagnostics-failure regression.

Co-Authored-By: Claude <noreply@anthropic.com>
@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 52f16c5
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a74f2841fbe9400081a7ee7
😎 Deploy Preview https://deploy-preview-42816--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

bito-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #61dd56

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 652f61e..52f16c5
    • superset/utils/screenshot_utils.py
    • tests/unit_tests/utils/test_screenshot_utils.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

alert-reports Namespace | Anything related to the Alert & Reports feature preset-io size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants