Skip to content

fix(mcp): bind Big Number chart to a temporal column for dashboard time filters#41895

Merged
aminghadersohi merged 5 commits into
apache:masterfrom
aminghadersohi:sc-112870-mcp-bignumber-time-filter
Jul 10, 2026
Merged

fix(mcp): bind Big Number chart to a temporal column for dashboard time filters#41895
aminghadersohi merged 5 commits into
apache:masterfrom
aminghadersohi:sc-112870-mcp-bignumber-time-filter

Conversation

@aminghadersohi

Copy link
Copy Markdown
Contributor

SUMMARY

Big Number charts created via the Superset MCP server did not respond to dashboard time-range filters. map_big_number_config() in superset/mcp_service/chart/chart_utils.py never added a TEMPORAL_RANGE adhoc filter (nor set granularity_sqla) for the default big_number_total viz_type (Big Number without a trendline), so the dashboard's native Time Range filter had no column to bind to.

This mirrors the Explore UI's BigNumberTotal control panel, which always exposes an adhoc_filters control (defaulted to a TEMPORAL_RANGE filter on the dataset's main temporal column) even though there's no dedicated time-column control for the total variant.

BEFORE

Big Number charts (without a trendline) created via the MCP server ignored dashboard-level time-range filters — no adhoc filter existed to bind to.

AFTER

map_big_number_config() now binds a TEMPORAL_RANGE adhoc filter using an explicit temporal_column, or falls back to the dataset's main_dttm_col when one isn't specified. This reuses the existing _ensure_temporal_adhoc_filter helper already used by map_xy_config for the same purpose, so both chart types now follow the same pattern for making dashboard time filters effective.

TESTING INSTRUCTIONS

  • Added 4 unit tests to tests/unit_tests/mcp_service/chart/test_big_number_chart.py covering: dataset fallback binding, no-op when dataset has no temporal column, explicit temporal_column without a trendline, and trendline charts getting both granularity_sqla and the adhoc filter.
  • pytest tests/unit_tests/mcp_service/chart/test_big_number_chart.py tests/unit_tests/mcp_service/chart/test_chart_utils.py -q → 204 passed
  • pytest tests/unit_tests/mcp_service/ -q → 2596 passed (no regressions)
  • ruff format --check / ruff check clean on all changed files

ADDITIONAL INFORMATION

  • Has associated tests

…me filters

Big Number charts created via the MCP server did not respond to dashboard
time-range filters. map_big_number_config() never added a TEMPORAL_RANGE
adhoc filter (nor set granularity_sqla) for the default big_number_total
viz_type, so the dashboard's Time Range filter had no column to bind to.

Bind an explicit temporal_column, or fall back to the dataset's
main_dttm_col, and ensure a TEMPORAL_RANGE adhoc filter is present. This
mirrors the existing _ensure_temporal_adhoc_filter pattern already used
by map_xy_config for the same purpose, and matches the Explore UI's
BigNumberTotal control panel, which always exposes an adhoc_filters
control even without a dedicated time-column control.
@aminghadersohi
aminghadersohi marked this pull request as ready for review July 8, 2026 18:39
@aminghadersohi
aminghadersohi requested a review from Copilot July 8, 2026 18:39
@dosubot dosubot Bot added change:backend Requires changing the backend dashboard:native-filters Related to the native filters of the Dashboard viz:charts:bignumber Related to BigNumber charts labels Jul 8, 2026

Copilot AI 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.

Pull request overview

This PR fixes Big Number (specifically big_number_total, i.e. without a trendline) charts created via the MCP service not responding to dashboard-native time range filters by ensuring the generated form_data contains a temporal binding via a TEMPORAL_RANGE adhoc filter (using an explicit temporal_column or falling back to the dataset’s main_dttm_col).

Changes:

  • Add a best-effort dataset lookup helper to retrieve main_dttm_col, and always ensure a TEMPORAL_RANGE adhoc filter is present for Big Number charts when a temporal column can be determined.
  • Wire dataset_id through the Big Number MCP plugin so the mapping layer can fall back to dataset metadata when needed.
  • Add unit tests covering dataset fallback, no-op when no temporal column exists, explicit temporal column handling, and trendline behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
superset/mcp_service/chart/chart_utils.py Adds dataset temporal-column fallback and ensures TEMPORAL_RANGE adhoc filter binding for Big Number charts so dashboard time filters apply.
superset/mcp_service/chart/plugins/big_number.py Passes dataset_id through to the Big Number form-data mapper to enable dataset-based fallback behavior.
tests/unit_tests/mcp_service/chart/test_big_number_chart.py Adds regression/unit tests validating temporal binding behavior for big_number_total and trendline variants.

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 18.51852% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.77%. Comparing base (2c8cdbb) to head (21233a5).
⚠️ Report is 34 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/chart/chart_utils.py 19.23% 21 Missing ⚠️
superset/mcp_service/chart/plugins/big_number.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #41895      +/-   ##
==========================================
+ Coverage   64.59%   64.77%   +0.17%     
==========================================
  Files        2712     2739      +27     
  Lines      151091   152919    +1828     
  Branches    34751    35032     +281     
==========================================
+ Hits        97602    99052    +1450     
- Misses      51663    51967     +304     
- Partials     1826     1900      +74     
Flag Coverage Δ
hive 39.07% <18.51%> (+0.02%) ⬆️
mysql 58.00% <18.51%> (+0.52%) ⬆️
postgres 58.05% <18.51%> (+0.51%) ⬆️
presto 41.04% <18.51%> (+0.48%) ⬆️
python 59.44% <18.51%> (+0.49%) ⬆️
sqlite 57.66% <18.51%> (+0.53%) ⬆️
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.

…dataset lookup

Address code review findings on the Big Number dashboard time-filter fix:

- Guard the new TEMPORAL_RANGE adhoc filter binding with
  is_column_truly_temporal(), matching the check map_xy_config already
  applies to its x-axis. Without this, an explicit non-trendline
  temporal_column that isn't actually a temporal SQL type would get bound
  into a dashboard time filter, which can produce invalid SQL.
- Extract _find_dataset_by_id_or_uuid() and reuse it in
  is_column_truly_temporal(), _resolve_default_x_axis(), and
  _find_dataset_main_dttm_col(), which had all duplicated the same
  numeric-id-vs-uuid dispatch.
- Add a dispatch-level regression test through map_config_to_form_data()
  covering the dataset_id forwarding fix in BigNumberChartPlugin.to_form_data().
Comment thread superset/mcp_service/chart/chart_utils.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged performance issue is correct. The current implementation of map_big_number_config performs redundant dataset lookups when temporal_column is omitted, as both _find_dataset_main_dttm_col and is_column_truly_temporal independently fetch the dataset.

To resolve this, you should fetch the dataset once at the beginning of the function and pass it to the helper functions. Below is the corrected implementation for superset/mcp_service/chart/chart_utils.py:

def map_big_number_config(
    config: BigNumberChartConfig, dataset_id: int | str | None = None
) -> Dict[str, Any]:
    # ... (existing code)
    dataset = _find_dataset_by_id_or_uuid(dataset_id)
    main_dttm_col = dataset.main_dttm_col if dataset else None
    temporal_column = config.temporal_column or main_dttm_col

    if temporal_column and is_column_truly_temporal(temporal_column, dataset_id):
        _ensure_temporal_adhoc_filter(form_data, temporal_column)

    return form_data

Would you like me to check the remaining comments on this PR and implement fixes for them as well?

superset/mcp_service/chart/chart_utils.py

dataset = _find_dataset_by_id_or_uuid(dataset_id)
    main_dttm_col = dataset.main_dttm_col if dataset else None
    temporal_column = config.temporal_column or main_dttm_col

    if temporal_column and is_column_truly_temporal(temporal_column, dataset_id):
        _ensure_temporal_adhoc_filter(form_data, temporal_column)

Address code review comment on apache#41895: map_big_number_config queried
DatasetDAO twice for the same dataset_id when temporal_column was
omitted (once for the main_dttm_col fallback, again inside
is_column_truly_temporal). Extract the fallback + guard into
_resolve_big_number_temporal_column(), which fetches the dataset once
and passes it into is_column_truly_temporal for reuse.
@bito-code-review

bito-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #b98819

Actionable Suggestions - 0
Review Details
  • Files reviewed - 3 · Commit Range: adb7e30..d45c3eb
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/plugins/big_number.py
    • tests/unit_tests/mcp_service/chart/test_big_number_chart.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

@fitzee fitzee 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.

Griffen + Codex Dual Review

Verdict: LGTM with nits — Two-way door, no blocking findings from either reviewer.

Nice fix — this closes a real customer-reported gap (SC-112870) between Explore-UI-created and MCP-created Big Number charts, and does it by extending the existing _ensure_temporal_adhoc_filter pattern already used by map_xy_config, rather than inventing a new mechanism. The 3-commit history shows good responsiveness to review feedback: correctness guard first (is_column_truly_temporal, avoiding invalid SQL from a non-temporal column), then efficiency (removing a redundant DAO lookup) — the right order.

Door classification

Two-way door. Purely additive change to MCP chart form_data generation. No schema/migration, no public API contract break (dataset_id param is backward-compatible, defaults to None), isolated blast radius (only new/re-mapped MCP-created Big Number charts). Simple rollback via revert.

Findings (non-blocking)

MEDIUM — untested guard-suppression branch. The is_column_truly_temporal guard added in the 2nd commit specifically to stop a non-temporal explicit temporal_column from being bound into a TEMPORAL_RANGE filter has no test exercising the "column resolves to non-temporal → suppress filter" branch for big_number_total. test_total_binds_dataset_main_dttm_col_for_dashboard_filters uses mock_dataset.columns = [], which hits the "column not found → default True" branch, not "found a non-temporal column type → False". map_xy_config has a direct analog for this exact scenario (test_non_temporal_x_axis_no_temporal_filter) — worth mirroring here so the guard this PR added stays covered against regressions.

LOW — duplicate id/uuid dataset lookup helper (Griffen + Codex, found independently). The new _find_dataset_by_id_or_uuid() reimplements dispatch-on-isdigit() logic that DatasetDAO's inherited BaseDAO.find_by_id_or_uuid() already provides in a single query. Not a correctness issue today, but it's a second implementation of the same lookup that can silently drift from the DAO's (e.g. around base_filter/visibility semantics). Consider consolidating onto find_by_id_or_uuid() and dropping the new private helper.

LOW — trendline path now does two temporal-type lookups per chart create (Codex). For trendline charts, _resolve_big_number_temporal_column() calls is_column_truly_temporal() (its own DAO lookup, since dataset isn't pre-fetched when temporal_column is explicit), and the pre-existing post_map_validate() calls it again independently. Not a correctness problem, but it's the same class of redundancy commit 3 just fixed for the non-trendline path, newly introduced here for trendline. Worth a quick follow-up to share the result between the two call sites.

LOW — same double-lookup pattern survives, untouched, in map_xy_config (Griffen). _resolve_default_x_axis() fetches the dataset when x-axis is omitted, then is_column_truly_temporal(config.x.name, dataset_id) a few lines later doesn't reuse it — the exact inefficiency just fixed here for Big Number. Non-blocking, but since this PR's own docstring says it "mirrors" map_xy_config's pattern, it'd be a quick consistency win to apply the same dataset= reuse there too.

None of the above block merge — they're follow-up-worthy, not regressions in this diff's core fix. The trendline path is also already protected from invalid SQL by the independent (pre-existing) post_map_validate() check regardless of the redundant-lookup nit above.

…r Big Number

- _find_dataset_by_id_or_uuid now delegates to DatasetDAO.find_by_id_or_uuid
  (already used by the dataset API) instead of reimplementing the id/uuid
  dispatch.
- map_xy_config reuses the dataset fetched while resolving a default x-axis
  instead of re-querying DatasetDAO in is_column_truly_temporal, mirroring
  the dedup already applied to map_big_number_config.
- Added a regression test for Big Number Total suppressing the TEMPORAL_RANGE
  filter when is_column_truly_temporal resolves a real (non-mocked) non-
  temporal column, plus a dataset-reuse regression test for map_xy_config's
  default x-axis path.

Addresses review feedback from @fitzee on PR apache#41895.
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough dual review, @fitzee — really appreciate the "door classification" framing and the concrete repro steps.

Pushed da4f9dc4e5 addressing 3 of the 4 nits:

  • MEDIUM (untested guard-suppression branch): Added test_total_non_temporal_column_skips_temporal_filter, mirroring map_xy_config's test_non_temporal_x_axis_no_temporal_filter. It mocks is_column_truly_temporal to return False for an explicit temporal_column on big_number_total, so the "found a non-temporal column → suppress" branch is now covered (the existing test only hit the "column not found → default True" branch).
  • LOW (duplicate id/uuid helper): _find_dataset_by_id_or_uuid now delegates to DatasetDAO.find_by_id_or_uuid (same method the dataset API already uses) instead of reimplementing the isdigit() dispatch.
  • LOW (map_xy_config double lookup): _resolve_default_x_axis now returns the dataset it fetched while resolving the default x-axis, and map_xy_config passes it into is_column_truly_temporal(..., dataset=...) — same reuse pattern as map_big_number_config. Added a regression test asserting a single DatasetDAO.find_by_id_or_uuid call.

Not fixed here — LOW (trendline path: to_form_data + post_map_validate each call is_column_truly_temporal): left as-is. Sharing that lookup would mean passing a resolved dataset across two separate plugin lifecycle methods (to_form_data then post_map_validate, invoked independently by map_config_to_form_data), which touches the shared plugin interface used by all 7 chart types rather than a local fix. Agreed it's worth doing, but I'd rather scope that as its own follow-up than fold an interface change into this bug fix. Filed for later — happy to pick it up separately if that works.

All 2599 mcp_service unit tests pass, ruff format/ruff check clean.

@netlify

netlify Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit da4f9dc
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a506f1cc3b34d0008636891
😎 Deploy Preview https://deploy-preview-41895--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 Jul 10, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #dfe0ff

Actionable Suggestions - 0
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • tests/unit_tests/mcp_service/chart/test_chart_utils.py - 1
Review Details
  • Files reviewed - 3 · Commit Range: d45c3eb..da4f9dc
    • superset/mcp_service/chart/chart_utils.py
    • tests/unit_tests/mcp_service/chart/test_big_number_chart.py
    • tests/unit_tests/mcp_service/chart/test_chart_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

@richardfogaca richardfogaca 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.

Richard's agent here — reviewed da4f9dc.

BLOCKER

  • superset/mcp_service/chart/chart_utils.py:281 and tests/unit_tests/mcp_service/chart/test_chart_utils.py:964: could we move DatasetDAO and ColumnSpec to module scope? If DatasetDAO genuinely needs to remain local to break a cycle, please document the concrete circular dependency.

MEDIUM

  • superset/mcp_service/chart/schemas.py:1405: could we update the public temporal_column description? It currently says the field is only for the trendline x-axis, while this change also uses it to select dashboard time-filter binding for big_number_total.
  • superset/mcp_service/chart/chart_utils.py:269: could we use the concrete SqlaTable | None type for the new dataset helper, prefetched parameter, and tuple return instead of Any? A type-checking-only import would avoid runtime coupling if needed.

NIT

  • tests/unit_tests/mcp_service/chart/test_big_number_chart.py:385: the all(...call_args_list) assertion looks redundant with the stronger assert_called_once_with("42") immediately below it.

PRAISE

  • Reusing the existing temporal-filter helper keeps Big Number and XY behavior aligned, and the fallback, explicit-column, non-temporal, trendline, and dispatcher cases are well covered.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @richardfogaca — addressed in 21233a5:

BLOCKER — module scope vs. local DatasetDAO/ColumnSpec imports
Kept DatasetDAO local, but documented it: this mirrors the established convention across mcp_service (every DAO/command import in this module — and in most of mcp_service, e.g. chart_helpers.py:64,82,100, chart_helpers.py:498,659) is a function-local import annotated # avoid circular import, so superset.daos.* never gets pulled in at mcp_service module-import time. Added the same # avoid circular import comment to _find_dataset_by_id_or_uuid for consistency (chart_utils.py:281).

ColumnSpec in the test file had no such constraint (tests already import plenty of superset.* at module scope), so that one moved to the top-level import and the two local re-imports were dropped (test_chart_utils.py).

MEDIUM — schemas.py:1405 temporal_column description
Updated to reflect that the field now also drives dashboard time-filter binding for big_number_total, not just the trendline x-axis.

MEDIUM — chart_utils.py:269 concrete type instead of Any
Added a TYPE_CHECKING-guarded SqlaTable import and switched _find_dataset_by_id_or_uuid's return type, is_column_truly_temporal's dataset param, and _resolve_default_x_axis's tuple return to SqlaTable | None — no runtime import added.

NIT — test_big_number_chart.py:385
Removed the redundant all(...call_args_list) assertion; assert_called_once_with("42") already covers it.

ruff format/ruff check clean, pre-commit (incl. mypy) clean, and pytest tests/unit_tests/mcp_service/chart/test_big_number_chart.py tests/unit_tests/mcp_service/chart/test_chart_utils.py -q → 207 passed.

@aminghadersohi
aminghadersohi merged commit bd61e09 into apache:master Jul 10, 2026
59 checks passed
@bito-code-review

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

@sadpandajoe sadpandajoe added the review:checkpoint Last PR reviewed during the daily review standup label Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:backend Requires changing the backend dashboard:native-filters Related to the native filters of the Dashboard review:checkpoint Last PR reviewed during the daily review standup size/L viz:charts:bignumber Related to BigNumber charts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants