Skip to content

fix(charts): synthesize query_context on chart import (#33615) - #43303

Open
eugeneo17 wants to merge 39 commits into
apache:masterfrom
CloudGeometry:change/33615-chart-data-query-context
Open

eugeneo17 wants to merge 39 commits into
apache:masterfrom
CloudGeometry:change/33615-chart-data-query-context

Conversation

@eugeneo17

Copy link
Copy Markdown

SUMMARY

Fixes #33615.

Charts imported as part of a dashboard ZIP are persisted with
Slice.query_context = NULL — the v1 importer never generates one. The Chart
Data read path requires it, so the first GET /api/v1/chart/{pk}/data/ returns
400 "Chart has no query context saved. Please save the chart again." until each
chart is manually re-opened and saved in Explore. That workaround does not scale
(the reporter has ~100 imported charts).

Root cause: import_chart (superset/commands/chart/importers/v1/utils.py) copies
the exported chart config verbatim; migrate_chart only rewrites an existing
query_context and never synthesizes one,a
query_context yields a NULL column, which ChartDataRestApi.get_data
(superset/charts/data/api.py) rejects with a 400.

Fix. import_chart now derives and persists a query_context when the imported
chart lacks one. Design decisions:

  • Non-destructive / idempotent — an existing query_context is never
    overwritten (re-importing is safe).
  • Authorization-preserving — the datasext is taken
    only from the importer-resolved datasource id/type, never from the chart
    params, so it names the same real datasource the read path authorizes (no
    RLS/authz bypass).
  • Honest-fail — charts with no derivabing to
    query, or a datasource-less viz such as markup/handlebars/divider) are left
    NULL rather than given a fabricated/in
  • Resilient — a per-chart derivation error is logged and skipped, never aborting
    the import bundle.
  • The chart-data read path is unchanged.

A superset charts backfill-query-context es charts that were imported before this fix and still have a NULL query_context`.

**Design note / open question for maintainontext mapping is per-plugin frontend logic (buildQuery.ts), so a pure-backend derivation is only approximate for complex viz types. This branch also includes an optional full-fidelity path that runs the the backend via an embedded V8 (py_mini_racer) over a generated bundle, with byte-for-byte parity across 29 viz types; import_chart` uses it when available and falls back to
the pure Python derivation otherwise. Shiptime
dependency + the JS bundle is a packaging decision that likely warrants a SIP — I'm
happy to split it into a separate PR/SIP and keep this PR to the dependency-free
synthesis if preferred.

BEFORE/AFTER SCREENSHOTS OR ANIMATED G

API behavior (no UI change):

  • Before: import a dashboard ZIP → GET /api/v1/chart/{pk}/data/ →
    400 {"message": "Chart has no query context saved. Please save the chart again."}
  • After: same import → `GET /api/v1/chquery results,
    with no manual re-save.

TESTING INSTRUCTIONS

New/updated tests:

# Unit (helper + importer)
pytest tests/unit_tests/charts/commands/imder_test.py \
       tests/unit_tests/charts/commands/importers/v1/import_test.py

# Full-fidelity parity (backend V8 output == frontend buildQuery, per viz type)
pytest tests/unit_tests/charts/commands/importers/v1/query_context_parity_test.py

# Integration (needs a metadata DB + example data)
pytest tests/integration_tests/charts/commands_tests.py::TestImportChartsCommand
pytest tests/integration_tests/charts/datataApi

Verified locally: unit 27 passed; parity 29/29 (byte-identical across all covered
viz types); TestImportChartsCommand 6/6; T existing
chart-data tests are kept unmodified as a no-regression guard). Manual: import a
dashboard ZIP whose charts have no query_context, then GET /api/v1/chart/{id}/data/
returns rows instead of a 400.

### ADDITIONAL INFORMATION

- [x] Has associated issue: Fixes #33615
- [ ] Required feature flags:
- [ ] Changes UI
- [ ] Includes DB Migration (follow approval process in SIP-59 (https://github.com/apache/superset/issues/13351))
  - [ ] Migration is atomic, supports rollback & is backwards-compatible
  - [ ] Confirm DB migration upgrade and downgrade tested
  - [ ] Runtime estimates and downtime expectations provided
- [x] Introduces new feature or API: superset charts backfill-query-context CLI; optional py_mini_racer runtime dependency for the full-fidelity path (see Design note)
- [ ] Removes existing feature or API

eugeneo17 and others added 2 commits August 17, 2026 19:10
Charts imported from a dashboard ZIP persisted with query_context = NULL, so
GET /api/v1/chart/{pk}/data/ returned HTTP 400 "Chart has no query context
saved. Please save the chart again." until each chart was manually re-opened
in Explore — infeasible for bulk imports (~100 charts).

import_chart now derives and persists a query_context from the chart's params
+ importer-resolved datasource when one is absent, via a new pure helper
build_query_context_config. Properties:

- Non-destructive/idempotent: an existing query_context is never overwritten.
- Authz/RLS-preserving: the datasource is taken from the importer-resolved id
  only, never from params, so the synthesized context names the same real
  datasource the read path authorizes.
- Honest-fail: non-derivable charts (no datasource, nothing to query, or a
  datasource-less viz such as markup/handlebars/divider) are left NULL rather
  than given a fabricated/invalid context.
- Resilient: a per-chart derivation error is logged and skipped, never
  aborting the import bundle.
- The chart-data read path (charts/data/api.py) is unchanged.

Also logs a per-import summary of synthesized/preserved/non-derivable charts.

Tests: new unit suite for the helper (derivation, the non-derivable decision
table, datasource-from-argument, adhoc-filter translation, idempotency) plus
import round-trip and chart-data API coverage; the existing chart-data API
tests are kept unmodified as a no-regression guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Eugene Olkhovski <eugeneo@cloudgeometry.com>
… for apache#33615

Follow-up to the targeted import-time synthesis. For perfect fidelity across all
chart types, run the REAL frontend `buildQuery` on the backend via an embedded V8
(py_mini_racer) instead of the approximate generic Python derivation — the
form_data -> query_context mapping is per-plugin JS, so reusing it is the only way
to match the frontend exactly (per the upstream discussion on this issue).

- superset-frontend/src/backend-querycontext: a DOM-free entry that bundles each
  plugin's default `buildQuery` directly (generated REGISTRY covering 29 viz keys)
  and exposes `generateQueryContext(vizType, formData)`. Built with esbuild
  (`npm run build:backend-querycontext`); the bundle is a generated artifact
  (gitignored).
- superset/commands/chart/query_context_generator.py: a lazy, thread-guarded
  MiniRacer singleton that loads the bundle (with minimal browser-global shims)
  and runs it. `generate()` returns None (never raises) when the engine/bundle is
  unavailable or the viz is unsupported, so callers fall back cleanly.
- import_chart now tries the V8 generator first (authoritative) and falls back to
  the pure `build_query_context_config`. The datasource is still taken only from
  the importer-resolved id/type (authz/RLS preserved); synthesis stays
  non-destructive/idempotent and never aborts the bundle.
- `superset charts backfill-query-context [--dry-run]`: re-encode charts that were
  imported before the fix and still have a NULL query_context.
- Parity harness: `query_context_parity_test.py` asserts the backend engine output
  is byte-identical to the frontend `buildQuery` golden for every registered viz
  type (29/29).

Shipping py_mini_racer as a runtime dependency plus the JS bundle is a packaging
decision intended for upstream (SIP) discussion; the change degrades gracefully to
the pure-Python synthesis when the engine is absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Eugene Olkhovski <eugeneo@cloudgeometry.com>
@dosubot dosubot Bot added api:charts Related to the REST endpoints of charts change:backend Requires changing the backend viz:charts:import Related to importing charts labels Aug 18, 2026
@netlify

netlify Bot commented Aug 18, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 1f83506
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6acf8ca34a6d93930f0d00a7
😎 Deploy Preview https://deploy-preview-43303--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.

Comment thread superset-frontend/scripts/gen-qc-fixtures.mjs
@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. The generator script currently only writes new files and does not clean up stale fixtures, which can lead to parity test failures when visualization types are removed or renamed. To resolve this, you should clear the target fixture directories before generating the new set.

Here is a concise implementation to add to superset-frontend/scripts/gen-qc-fixtures.mjs (or the relevant generator script) before the generation logic:

import { rmSync } from 'fs';

// ... existing setup ...

// Clear existing fixtures to prevent stale files
[fixtureDir1, fixtureDir2].forEach(dir => {
  rmSync(dir, { recursive: true, force: true });
  mkdirSync(dir, { recursive: true });
});

// ... proceed with generation ...

I have checked the available PR context, and there are no other review comments to address. Would you like me to perform any other tasks related to this PR?

superset-frontend/scripts/gen-qc-fixtures.mjs

import { rmSync } from 'fs';

// Clear existing fixtures to prevent stale files
[fixtureDir1, fixtureDir2].forEach(dir => {
  rmSync(dir, { recursive: true, force: true });
  mkdirSync(dir, { recursive: true });
});

Comment thread superset-frontend/scripts/gen-qc-registry.mjs
Comment on lines +56 to +57
const raw = generateQueryContext(vizType, JSON.stringify(formData));
const result = JSON.parse(raw);

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.

Suggestion: This test creates its expected output by invoking the same generateQueryContext implementation that the backend uses, then writes that output as the golden. Consequently, a wrong buildQuery mapping or incorrect query-context implementation can produce matching goldens and leave the test green; it only checks serialization/runtime consistency, not frontend/backend parity. The expected fixtures need to be produced by an independent frontend execution or independently maintained expected values. [incomplete implementation]

Severity Level: Minor 🧹
- ⚠️ Parity CI can miss incorrect registry mappings.
- ⚠️ Frontend/backend query-context divergence may reach imports.
- ⚠️ Golden files provide consistency, not independent correctness.

Use CodeAnt Skill

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-frontend/src/backend-querycontext/parity.test.ts
**Line:** 56:57
**Comment:**
	*Incomplete Implementation: This test creates its expected output by invoking the same `generateQueryContext` implementation that the backend uses, then writes that output as the golden. Consequently, a wrong `buildQuery` mapping or incorrect query-context implementation can produce matching goldens and leave the test green; it only checks serialization/runtime consistency, not frontend/backend parity. The expected fixtures need to be produced by an independent frontend execution or independently maintained expected values.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Acknowledged, and it is noted in the test docstring: the JS golden guards runtime and serialization consistency, and the actual frontend/backend parity is enforced by the Python parity test that asserts the pure-Python fallback matches these goldens. Seeding goldens from an independent frontend execution is reasonable hardening, but I would prefer to track it separately. OK to keep the two-sided setup for this PR?

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.

✅ Customized review instruction saved!

Instruction:

For frontend/backend parity tests, do not require independently generated JavaScript goldens when a separate parity test validates the Python implementation against those goldens; consistency and cross-implementation validation may be split across tests.

Applied to:

  • **/test/**
  • **/tests/**
  • **/*test*.ts

💡 To manage or update this instruction, visit: CodeAnt AI Settings

Comment thread superset/cli/charts.py
Comment thread superset/commands/chart/importers/v1/__init__.py Outdated
Comment thread superset/commands/chart/importers/v1/utils.py Outdated
Comment on lines +81 to +89
# else: unmappable SIMPLE filter — dropped, no crash (RISK-T05).
elif expression_type == "SQL":
sql_expression = adhoc_filter.get("sqlExpression")
if sql_expression:
clause = (adhoc_filter.get("clause") or "WHERE").upper()
if clause == "HAVING":
having_expressions.append(sql_expression)
else:
where_expressions.append(sql_expression)

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.

Suggestion: Malformed or unsupported adhoc filters are silently omitted while a query context is still generated. This means a saved chart with an unmappable filter can be imported with a broader query that returns rows the chart's form data intended to exclude. Treat an unmappable filter as non-derivable, or preserve it in a form the query processor understands, rather than silently dropping it. [logic error]

Severity Level: Major ⚠️
- ⚠️ Imported charts can display unintended rows.
- ⚠️ Saved query context no longer matches chart filters.

Use CodeAnt Skill

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/commands/chart/query_context_builder.py
**Line:** 81:89
**Comment:**
	*Logic Error: Malformed or unsupported adhoc filters are silently omitted while a query context is still generated. This means a saved chart with an unmappable filter can be imported with a broader query that returns rows the chart's form data intended to exclude. Treat an unmappable filter as non-derivable, or preserve it in a form the query processor understands, rather than silently dropping it.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed—dropping an unmappable filter while still persisting a context turns an invalid imported chart into a broader query than its form data requested. Could the fallback return non-derivable when any filter cannot be preserved?

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.

Agreed. The fallback should be conservative: if any adhoc_filter cannot be translated, it must return None so the chart remains non-derivable rather than persisting a broader query.

A minimal fix is to validate the filters before calling the shared splitter:

def _translate_adhoc_filters(
    adhoc_filters: list[Any] | None,
) -> tuple[list[dict[str, Any]], str, str] | None:
    sanitized: list[dict[str, Any]] = []

    for filter_ in adhoc_filters or []:
        if not isinstance(filter_, dict):
            return None

        expression_type = filter_.get("expressionType")
        if expression_type == "SIMPLE":
            if not filter_.get("subject") or not filter_.get("operator"):
                return None
        elif expression_type == "SQL":
            if not isinstance(filter_.get("sqlExpression"), str):
                return None
        else:
            return None

        sanitized.append(filter_)

    form_data: dict[str, Any] = {"adhoc_filters": sanitized}
    split_adhoc_filters_into_base_filters(form_data)

    return (
        form_data.get("filters") or [],
        form_data.get("where") or "",
        form_data.get("having") or "",
    )

Then propagate the result in build_query_context_config:

translated = _translate_adhoc_filters(params.get("adhoc_filters", []))
if translated is None:
    return None

filters, where, having = translated

The existing tests expecting malformed filters to be silently dropped should be updated to assert None; add coverage for unknown expression types and malformed SIMPLE/SQL filters. The V8 path remains authoritative, while the Python fallback now fails honestly whenever it cannot preserve the chart’s filters.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. My inclination is to fail closed: return non-derivable (None) when any filter cannot be preserved, since a 400 is more honest than a silently broadened result set. That does mean one malformed adhoc filter drops the whole synthesized context. If you agree I will implement it with a test that an unmappable WHERE yields None.

Comment thread superset/commands/chart/query_context_builder.py Outdated

@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 #c84a60

Actionable Suggestions - 6
  • superset/commands/chart/importers/v1/utils.py - 1
    • TypeError on string params in query_context synthesis · Line 96-96
  • superset/cli/charts.py - 2
  • superset/commands/chart/query_context_generator.py - 2
  • superset-frontend/scripts/gen-qc-registry.mjs - 1
    • Regex excludes constructor-arg plugins · Line 41-43
Additional Suggestions - 6
  • superset/cli/charts.py - 1
    • Truthiness check for numeric id · Line 101-102
      Line 101 assigns `datasource_id` from `Column(Integer)` — a falsy zero would bypass the V8/generator attempt and fall through to `build_query_context_config`. While `datasource_id=0` is a rare edge case for saved charts, using explicit `None` check (`datasource_id is not None`) makes the guard unambiguous.
  • superset/commands/chart/importers/v1/utils.py - 2
    • Magic string in datasource_type default · Line 99-99
      Line 99 defaults `datasource_type` to `"table"` with no inline comment. This magic string controls which datasource type is synthesized when the imported config omits it. Callers and maintainers may not infer that `"table"` is the intentional supported-only value versus an accidental default. A brief comment noting the constraint improves maintainability.
    • Catching broad Exception instead of specific · Line 130-130
      The bare `except Exception:` catches all exceptions, including those that may not be appropriate to swallow silently. Consider catching specific exceptions that are expected from the query context generation logic.
  • superset-frontend/scripts/build-backend-querycontext.mjs - 1
    • Inconsistent Node.js ESM import style · Line 35-35
      The import at line 35 uses bare `'fs'` while other scripts in `scripts/` (gen-qc-fixtures.mjs, gen-qc-goldens.mjs) use the explicit `node:fs` protocol. Aligning import style improves consistency and follows the modern Node.js ESM convention.
  • superset-frontend/scripts/gen-qc-fixtures.mjs - 1
    • Dead fallback in VIZ_TYPES · Line 35-35
      The fallback `|| globalThis.SUPERSET_QC_VIZ_TYPES` will never be taken because `mod.exports.VIZ_TYPES` is always set by the bundled CJS code from `registry.generated.ts`. The fallback only makes sense if the bundle used default-only exports, which never happens here.
  • tests/integration_tests/charts/data/api_tests.py - 1
    • Mutable class attribute needs ClassVar annotation · Line 1055-1055
      Mutable class attribute `_GENDERS_PARAMS` at line 1055-1062 should be annotated with `typing.ClassVar` to indicate it's a class-level mutable default.
Filtered by Review Rules

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

  • superset-frontend/src/backend-querycontext/registry.generated.ts - 1
    • Type safety: `any` erases buildQuery signatures · Line 42-42
Review Details
  • Files reviewed - 17 · Commit Range: 87463b5..407d0e1
    • superset-frontend/scripts/build-backend-querycontext.mjs
    • superset-frontend/scripts/gen-qc-fixtures.mjs
    • superset-frontend/scripts/gen-qc-goldens.mjs
    • superset-frontend/scripts/gen-qc-registry.mjs
    • superset-frontend/src/backend-querycontext/entry.ts
    • superset-frontend/src/backend-querycontext/parity.test.ts
    • superset-frontend/src/backend-querycontext/registry.generated.ts
    • superset/cli/charts.py
    • superset/commands/chart/importers/v1/__init__.py
    • superset/commands/chart/importers/v1/utils.py
    • superset/commands/chart/query_context_builder.py
    • superset/commands/chart/query_context_generator.py
    • tests/integration_tests/charts/commands_tests.py
    • tests/integration_tests/charts/data/api_tests.py
    • tests/unit_tests/charts/commands/importers/v1/import_test.py
    • tests/unit_tests/charts/commands/importers/v1/query_context_builder_test.py
    • tests/unit_tests/charts/commands/importers/v1/query_context_parity_test.py
  • Files skipped - 61
    • .gitignore - Reason: Filter setting
    • superset-frontend/package.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/big_number.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/big_number_total.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/box_plot.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/bubble_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_area.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_bar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_line.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_scatter.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_smooth.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_step.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/funnel.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/gauge_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/graph_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/handlebars.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/heatmap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/histogram_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/mixed_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/pie.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/pivot_table_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/pop_kpi.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/radar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/sankey_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/sunburst_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/table.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/tree_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/treemap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/waterfall.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/word_cloud.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/big_number.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/big_number_total.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/box_plot.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/bubble_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_area.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_bar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_line.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_scatter.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_smooth.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_step.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/funnel.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/gauge_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/graph_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/handlebars.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/heatmap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/histogram_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/mixed_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/pie.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/pivot_table_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/pop_kpi.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/radar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/sankey_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/sunburst_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/table.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/tree_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/treemap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/waterfall.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/word_cloud.json - Reason: Filter setting
    • superset/commands/chart/_bundles/README.md - Reason: Filter setting
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ 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

Comment thread superset/commands/chart/importers/v1/utils.py Outdated
Comment thread superset/cli/charts.py
Comment on lines +34 to +142
@charts.command("backfill-query-context")
@with_appcontext
@click.option(
"--dry-run",
is_flag=True,
default=False,
help="Report what would change without writing.",
)
@click.option(
"--viz-type",
"viz_types",
multiple=True,
help="Restrict to these viz types (repeatable). Default: all.",
)
@click.option(
"--batch-size",
type=int,
default=200,
show_default=True,
help="Commit every N updated charts.",
)
def backfill_query_context(
dry_run: bool, viz_types: tuple[str, ...], batch_size: int
) -> None:
"""
Backfill a synthesized ``query_context`` on saved charts that have none.

Repairs charts imported before the import-time synthesis landed (issue
#33615): each chart with ``query_context IS NULL`` gets a context derived
from its ``params`` + datasource — authoritatively via the frontend
``buildQuery`` (V8) when available, else the pure-Python generic derivation.
Non-derivable charts are left untouched (never a fabricated context).
"""
# Imported lazily so the module imports cleanly without an app context.
from superset.commands.chart.query_context_builder import (
build_query_context_config,
)
from superset.commands.chart.query_context_generator import (
get_query_context_generator,
)
from superset.extensions import db
from superset.models.slice import Slice
from superset.utils import json

generator = get_query_context_generator()

# `enable_eagerloads(False)` is required for `yield_per`: Slice has eager
# (joined) collection relationships that otherwise raise
# "Can't use yield_per with eager loaders that require uniquing/buffering".
query = (
db.session.query(Slice)
.filter(Slice.query_context.is_(None))
.enable_eagerloads(False)
)
if viz_types:
query = query.filter(Slice.viz_type.in_(viz_types))

updated = 0
non_derivable = 0
errors = 0
pending = 0

for chart in query.yield_per(batch_size):
try:
params = json.loads(chart.params) if chart.params else {}
if not isinstance(params, dict):
params = {}
datasource_id = chart.datasource_id
datasource_type = chart.datasource_type or "table"

context = None
if datasource_id:
js_params = {
**params,
"datasource": f"{datasource_id}__{datasource_type}",
}
context = generator.generate(chart.viz_type, js_params)
if context is None:
context = build_query_context_config(
params, chart.viz_type, datasource_id, datasource_type
)

if context is None:
non_derivable += 1
continue

updated += 1
if dry_run:
continue

chart.query_context = json.dumps(context)
pending += 1
if pending >= batch_size:
db.session.commit()
pending = 0
except Exception as ex: # pylint: disable=broad-except
errors += 1
logger.warning(
"backfill-query-context: chart id=%s failed: %s", chart.id, ex
)

if not dry_run and pending:
db.session.commit()

prefix = "[dry-run] would update" if dry_run else "updated"
click.echo(
f"backfill-query-context: {prefix} {updated}, "
f"non-derivable (left null) {non_derivable}, errors {errors}."
)

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.

Missing test coverage for CLI command

The backfill-query-context command has zero test coverage — no unit or integration tests were found in the test suite. Other CLI commands in cli_tests.py use app.test_cli_runner() for coverage. A test should verify dry-run output, batch commits, and non-derivable chart handling.

Catching generic Exception is blind

Catching broad Exception may hide important errors. Catch specific exceptions or log and re-raise to avoid silent failures.

Code Review Run #c84a60


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

  • Yes, avoid them

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fair. The backfill-query-context command has no tests yet; a proper one needs an app context and a DB session (existing charts with and without a query_context), so I would add it as an integration test rather than a unit test. Planning it as a follow-up unless you would rather it block here.

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.

Adding an integration test is a solid approach for verifying the CLI command, as it allows you to properly mock or set up the application context and database session. Since this command interacts directly with the database and relies on the app context, an integration test is more appropriate than a unit test to ensure the backfill-query-context logic behaves correctly in a real-world scenario. Proceeding with this as a follow-up is reasonable, provided it is tracked to ensure the coverage gap is addressed.

Comment thread superset/commands/chart/query_context_generator.py
Comment thread superset/commands/chart/query_context_generator.py
Comment thread superset-frontend/scripts/gen-qc-registry.mjs
The master merge into this branch left tests/.../import_test.py scrambled
(test bodies shuffled, undefined names admin/config/original_id) and a stray
blank line in the v1 importers __init__.py. Reconstruct import_test.py from
master's soft-delete suite plus the three query_context synthesis tests, and
clear the remaining lint/type failures so CI is green:

- ruff: TID251 (use superset.utils.json), E501, I001, and C901 — extract
  helpers _synthesize_query_context_if_absent (import_chart) and
  _derive_query_context (backfill CLI) to cut cyclomatic complexity.
- pylint: annotate the two batched CLI commits with
  consider-using-transaction (streaming backfill, not a single unit of work).
- mypy: guard the Optional SqlaTable datasource before .database.
- oxlint (frontend): fix no-new-func, prefer-template, no-plusplus,
  no-unused-vars and jest consistent-test-it in the gen-qc scripts and
  parity.test.ts.
- RAT license check: add Apache headers to the three gen-qc *.mjs scripts.
- oxfmt: format the generated registry/entry and the query_context fixtures.
- .gitignore: restore entries dropped by the merge and ignore the generated
  _bundles/*.js artifact (documented as not committed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@eugeneo17 eugeneo17 changed the title Change/33615 chart data query context fix(charts): synthesize query_context on chart import (#33615) Aug 19, 2026
@bito-code-review

bito-code-review Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #654874

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset-frontend/src/backend-querycontext/registry.generated.ts - 1
    • Generated file manual edit · Line 43-71
      This file is generated by `scripts/gen-qc-registry.mjs` (line 15-16 header). The generator explicitly produces quoted keys (line 189: `body += ' '${viz}':...`). Manual edits create a permanent divergence that will be overwritten on every future regeneration. Run the generator instead.
Filtered by Review Rules

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

  • superset/commands/chart/query_context_generator.py - 1
Review Details
  • Files reviewed - 16 · Commit Range: 407d0e1..a269f58
    • superset-frontend/scripts/gen-qc-fixtures.mjs
    • superset-frontend/scripts/gen-qc-goldens.mjs
    • superset-frontend/scripts/gen-qc-registry.mjs
    • superset-frontend/src/backend-querycontext/entry.ts
    • superset-frontend/src/backend-querycontext/parity.test.ts
    • superset-frontend/src/backend-querycontext/registry.generated.ts
    • superset/cli/charts.py
    • superset/commands/chart/importers/v1/__init__.py
    • superset/commands/chart/importers/v1/utils.py
    • superset/commands/chart/query_context_builder.py
    • superset/commands/chart/query_context_generator.py
    • tests/integration_tests/charts/commands_tests.py
    • tests/integration_tests/charts/data/api_tests.py
    • tests/unit_tests/charts/commands/importers/v1/import_test.py
    • tests/unit_tests/charts/commands/importers/v1/query_context_builder_test.py
    • tests/unit_tests/charts/commands/importers/v1/query_context_parity_test.py
  • Files skipped - 59
    • .gitignore - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/big_number.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/big_number_total.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/box_plot.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/bubble_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_area.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_bar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_line.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_scatter.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_smooth.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/echarts_timeseries_step.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/funnel.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/gauge_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/graph_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/handlebars.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/heatmap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/histogram_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/mixed_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/pie.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/pivot_table_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/pop_kpi.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/radar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/sankey_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/sunburst_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/table.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/tree_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/treemap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/waterfall.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/expected/word_cloud.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/big_number.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/big_number_total.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/box_plot.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/bubble_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_area.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_bar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_line.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_scatter.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_smooth.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/echarts_timeseries_step.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/funnel.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/gauge_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/graph_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/handlebars.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/heatmap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/histogram_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/mixed_timeseries.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/pie.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/pivot_table_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/pop_kpi.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/radar.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/sankey_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/sunburst_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/table.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/tree_chart.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/treemap_v2.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/waterfall.json - Reason: Filter setting
    • superset-frontend/src/backend-querycontext/__fixtures__/formdata/word_cloud.json - Reason: Filter setting
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ 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

@codecov

codecov Bot commented Aug 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.20472% with 96 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.09%. Comparing base (ffee0a8) to head (1f83506).
⚠️ Report is 53 commits behind head on master.

Files with missing lines Patch % Lines
superset/commands/chart/query_context_generator.py 46.66% 38 Missing and 2 partials ⚠️
superset/commands/chart/query_context_builder.py 58.82% 14 Missing and 7 partials ⚠️
superset/cli/charts.py 70.14% 11 Missing and 9 partials ⚠️
superset/commands/chart/importers/v1/utils.py 72.72% 5 Missing and 4 partials ⚠️
...uperset-frontend/src/backend-querycontext/entry.ts 75.00% 3 Missing ⚠️
superset/commands/chart/importers/v1/__init__.py 78.57% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #43303      +/-   ##
==========================================
+ Coverage   65.02%   65.09%   +0.07%     
==========================================
  Files        3039     3045       +6     
  Lines      198382   198865     +483     
  Branches    46213    46389     +176     
==========================================
+ Hits       128992   129447     +455     
- Misses      66489    66492       +3     
- Partials     2901     2926      +25     
Flag Coverage Δ
hive 34.93% <12.91%> (-0.05%) ⬇️
javascript 79.00% <78.57%> (+0.10%) ⬆️
mysql 53.03% <61.25%> (+0.03%) ⬆️
postgres 53.03% <61.25%> (+0.03%) ⬆️
presto 36.69% <12.91%> (-0.06%) ⬇️
python 53.32% <61.25%> (+0.03%) ⬆️
sqlite 52.79% <61.25%> (+0.03%) ⬆️

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.

The query_context-synthesis rework left `_import` calling `import_chart` twice on the same config dict. The first call serializes `config["params"]` to a JSON string; the second call then passes that string to `filter_chart_annotations`, which does `params.get(...)` and raises AttributeError: 'str' object has no attribute 'get', surfacing as HTTP 500 on chart/dashboard import (test-postgres/-sqlite/-mysql).

Collapse to a single call that passes `default_viewers`, deriving the per-chart synthesis counters from its result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #c44232

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: a269f58..90b3b81
    • superset/commands/chart/importers/v1/__init__.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

Comment thread superset-frontend/scripts/gen-qc-registry.mjs Outdated
Comment thread superset/commands/chart/query_context_builder.py
Comment thread superset-frontend/package.json
Comment thread superset/commands/chart/query_context_builder.py Outdated
@bito-code-review

bito-code-review Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #0f7a9e

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 2317c21..0030f83
    • tests/integration_tests/cli_tests.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 an incremental AI Review.

  • /review full - 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

eugeneo17 and others added 2 commits September 30, 2026 08:33
…3615)

A master merge added the echarts Candlestick chart's buildQuery, so
registry.generated.ts drifted and lint-frontend's freshness check failed.
Re-ran `npm run gen:backend-querycontext-registry`; the only change is the new
`candlestick` mapping and the deterministic renumbering that follows it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #210cde

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 0030f83..ec5972a
    • superset-frontend/src/backend-querycontext/registry.generated.ts
  • Files skipped - 0
  • Tools
    • Eslint (Linter) - ✔︎ 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 an incremental AI Review.

  • /review full - 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

@rusackas

rusackas commented Oct 1, 2026

Copy link
Copy Markdown
Member

Running CI 🤞

Not sure if @sadpandajoe's comments are addressed or not. Please feel free to reply/resolve, but let us know if/when this is ready for another review pass, if CI passes.

if not metrics and not columns:
return None

translated = _translate_adhoc_filters(params.get("adhoc_filters", []))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When V8 is unavailable, a chart with where: "region = 'EMEA'" persists extras.where: "", and an extra_filters region restriction is also dropped, so the saved-chart data endpoint returns a broader result set than the chart defines. Could the fallback preserve these supported filter sources, or leave the chart non-derivable when it cannot?

# misclassified as non-derivable (#33615: Big Number left with a NULL context).
if not metrics and params.get("metric"):
metrics = [params["metric"]]
# `groupby` is the deprecated alias of `columns`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With the optional V8 runtime absent, a pivot_table_v2 chart using groupbyRows and groupbyColumns but no generic columns/groupby is saved as a single aggregate with columns: [], losing both pivot axes. Could unsupported visualization-specific shapes remain non-derivable rather than persisting a context that returns the wrong data?

}

return {
"datasource": {"id": datasource_id, "type": datasource_type},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This fallback omits form_data, which QueryContextFactory reads to enable server pagination; a saved table with server_pagination: true is therefore reconstructed with pagination disabled and its requested row limit is clamped to the lower SQL_MAX_ROW cap. Could the persisted context retain the necessary form data with destination-local identity, and cover this through the query-context schema?

// here instead of silently dropping the entry.
const EXPLICIT_BUILD_QUERY = {
chord: 'plugins/plugin-chart-chord/src/buildQuery.ts',
cartodiagram: 'plugins/plugin-chart-cartodiagram/src/plugin/buildQuery.ts',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Cartodiagram's builder calls getChartBuildQueryRegistry().get(selectedChart.viz_type), but this standalone bundle never registers the imported builders in that singleton, so selecting a Pie chart throws and returns __error__ despite this mapping. Could the bundle initialize the nested-builder registry and test Cartodiagram through the generated entry without test-side registration?

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Checking back in since my last comment, since you've now gotten a fresh round from @sadpandajoe (posted this morning, plus one from the 29th) that's still open. I traced two of these myself against the current head rather than just relaying them:

_derive_orderby really does fall back to the raw sort_by_metric boolean as the sort column when timeseries_limit_metric is absent, so a synthesized context can end up ordering by true instead of the chart's actual metric.

And the pure-Python fallback builder (query_context_builder.py) never reads extra_filters at all, only adhoc_filters. The fail-closed logic in _translate_adhoc_filters is good, but it doesn't cover this case, so a chart scoped by a dashboard-level extra_filters restriction gets a broader, unscoped context when the optional V8 runtime isn't installed. That's the same "query a broader row set than the chart defines" risk the fail-closed design elsewhere in this file is explicitly trying to avoid.

The other four open threads (CLI backfill not stripping slice_id/not propagating cache_timeout, the pivot_table fallback losing both axes, server-pagination tables losing their row cap, Cartodiagram's standalone bundle not registering builders) all read as real to me too, though I didn't trace each one personally the way I did the two above.

Separate from the line-level stuff: the PR's own description flags the py_mini_racer/embedded-V8 dependency and bundle as maybe warranting a SIP rather than landing in this PR. I don't think that's something to resolve in review comments, that's a call for the list, not a thread. Worth raising there before this goes further regardless of how the correctness findings shake out.

Holding off on approving until the fallback-path bugs get a look, since those are the ones with real data-correctness consequences rather than just missing coverage.

@rusackas

rusackas commented Oct 2, 2026

Copy link
Copy Markdown
Member

Thanks @eugeneo17, CI looks green now. Before another pass, the adhoc-filter fail-closed change and the sort_by_metric / fallback where and extra_filters cases from @sadpandajoe still look open to me. Could you reply or resolve each thread once it's handled?

I'd also lean toward splitting the V8 bundle and mini-racer dependency into its own PR/SIP, as you offered. That would let the dependency-free synthesis land on its own.

@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 2, 2026
@github-actions github-actions Bot removed the requires:rebase Requires rebasing on top of current master label Oct 2, 2026
filters, where, having = translated

query_object = {
"time_range": params.get("time_range", " : "),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For a chart with an adhoc TEMPORAL_RANGE filter and no top-level time_range (including charts migrated by _migrate_temporal_filter), this default makes QueryContextFactory overwrite the filter’s January-only comparator with " : ", querying all dates instead. Could an absent time_range remain null or omitted so the factory can infer it from the temporal filter?

Comment thread Dockerfile
# (DEV_MODE=false); without it the chart importer falls back to the pure-Python
# generic derivation. Must come after `COPY superset superset` so it is not
# clobbered by the source tree (which never contains the generated bundle).
COPY --from=superset-node /app/superset/commands/chart/_bundles/query_context_bundle.j[s] superset/commands/chart/_bundles/query_context_bundle.js

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With DEV_MODE=true, the node stage skips the build and never creates /app/superset/commands/chart/_bundles, so this COPY fails with "lstat .../_bundles: no such file or directory"; the optional glob only tolerates a missing file when its parent exists. Could that directory be created in the node stage even when frontend compilation is skipped?

sort_metric = params.get("timeseries_limit_metric") or params.get("sort_by_metric")
if sort_metric:
return [[sort_metric, order_asc]]
return []

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A raw table with order_by_cols: ['["revenue", false]'] and row_limit: 1 gets orderby: [], so its saved-chart data read returns an arbitrary row instead of the highest-revenue row when V8 is unavailable. Could this preserve the raw-table ordering, as orderby_from_form_data and the frontend do?

"columns": columns,
"metrics": metrics,
"orderby": _derive_orderby(params),
"annotation_layers": [],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This discards annotation layers that the importer has already resolved to destination-local IDs, so a chart with a native annotation layer gets an empty annotation_data response when V8 is unavailable. Could the fallback preserve the resolved layers, or leave such a chart non-derivable?

"row_limit": params.get("row_limit"),
"timeseries_limit": 0,
"order_desc": params.get("order_desc", True),
"url_params": {},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A chart with url_params: {"region": "EMEA"} over a virtual dataset using url_param("region", "APAC") loses its stored default here, so saved-chart reads query APAC instead of EMEA when V8 is unavailable. Could the synthesized query retain the chart’s URL-parameter mapping?

@@ -538,71 +710,3 @@ def test_import_tag_logic_for_charts(session_with_schema: Session):
.all()
)
assert len(associated_tags) == 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Removing test_import_tag_savepoint_keeps_session_usable drops the regression for a tag association failing during SAVEPOINT flush while a second tag still imports; the remaining test only covers successful imports and the disabled flag. Could that regression remain, including its assertions that only the successful tag is reported and the session is still queryable?

if not metrics and params.get("metric"):
metrics = [params["metric"]]
# `groupby` is the deprecated alias of `columns`.
columns = params.get("columns") or params.get("groupby") or []

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Without the V8 bundle, any chart that has a metric or groupby gets a context saved, but that context ignores viz-specific semantics. For example, the bundled Big_Number_with_Trendline.yaml has x_axis: order_date and metric: count. The fallback ignores x_axis and never sets is_timeseries, so the saved-chart data read returns a single aggregate instead of the monthly trendline series. A big_number with aggregation: LAST_VALUE returns the period total instead of the last bucket. Pie with threshold_for_other is missing the contribution post-processing, so the "Other" grouping never happens. Because the context is persisted, installing the V8 extra later does not repair it: both synthesis and the backfill only act on a missing context. Could the fallback return non-derivable for viz types or shapes whose semantics it cannot reproduce (time axis, aggregation, post-processing), and keep the generic mapping for the shapes it does cover?

Comment thread Dockerfile
# (DEV_MODE=false); without it the chart importer falls back to the pure-Python
# generic derivation. Must come after `COPY superset superset` so it is not
# clobbered by the source tree (which never contains the generated bundle).
COPY --from=superset-node /app/superset/commands/chart/_bundles/query_context_bundle.j[s] superset/commands/chart/_bundles/query_context_bundle.js

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This copies the V8 bundle into the image, but nothing in the image installs the querycontext extra: the lean stage runs uv pip install -e . --no-deps and the default stage installs only .[postgres,mysql,fastmcp]. _ensure_ctx() therefore fails its py_mini_racer import in every published image, so the bundle is never used and imports silently use the approximate Python fallback. Is the intent to also install .[querycontext] in the image, or should the bundle copy be dropped until the runtime dependency ships?

}
return acc;
}
const files = walk(PLUGINS);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Discovery only walks plugins/, but TimeTable is registered in MainPreset with its builder under src/visualizations/TimeTable/config/buildQuery.ts, so time_table never appears in the generated registry even with V8 available. Importing a TimeTable chart with metrics: ["count"] and a daily granularity_sqla falls through to the Python fallback, which omits is_timeseries, so the saved-chart read returns an aggregate with no time series. Could the generator also scan src/visualizations/ (or the registry check fail when a MainPreset plugin with a buildQuery has no entry)?

"row_limit": 100,
"series_columns": ["gender"],
"series_limit": 0,
"order_desc": true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This golden has no group_others_when_limit_reached, but buildQueryObject.ts always emits group_others_when_limit_reached: false, and none of the goldens contain it. parity.test.ts rewrites these files on every run rather than comparing them, and the Python parity test is skipped in CI (no querycontext extra, no bundle). So the committed goldens are already stale and nothing detects it; once someone enables the Python parity test, actual == expected fails for word_cloud. Could the goldens be regenerated at this head and the Python parity test run in a CI job that has the bundle and extra installed?

try:
# --- RED anchor: produced payload constructs a real QueryContext ---
QueryContextFactory().create(**payload)
except Exception as exc: # pragma: no cover - environment-dependent

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The broad except Exception turns any failure of QueryContextFactory().create(**payload) into pytest.skip. The test uses datasource id 1 with no resolvable datasource, so a real payload-shape error (a TypeError from an unexpected kwarg, for instance) skips instead of failing, and the "real end-to-end validity check" can never go red. Could the test create a datasource with the unit-test session fixture and let construction errors fail, asserting the resolved datasource and the query fields?

# synthesized (#33615 review).
if had_query_context.get(str(config["uuid"])):
n_preserved += 1
elif config.get("query_context"):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

import_chart() rebinds its local config to the deep-copied result of migrate_chart() and sets query_context on that copy, but import_charts() yields the caller's original config. So config.get("query_context") here is falsy for every chart that did not already have one, and the summary logs successfully synthesized charts as non-derivable (0 queryable, N non-derivable). Could the count be based on the persisted chart.query_context instead?

}

@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_synthesized_query_context_returns_data(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These four new read-path tests sit inside TestGetChartDataApi, which is still decorated with the class-level @pytest.mark.skip (line 1212). None of them run, even with a configured database, so a synthesized query_context that the GET /data/ endpoint rejects with a 400 would still pass the importer persistence assertions. Could they move to a class or module that actually executes, so the 200-with-rows and denies-unauthorized-datasource assertions are exercised?

return JSON.stringify({ __unsupported__: true, viz_type: vizType });
}
const formData = JSON.parse(formDataJson);
const queryContext = fn(formData);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The stored params go straight into the plugin buildQuery, without the defaults Explore applies to form data first. For a box_plot chart whose params have metrics and dimensions but no whiskerOptions, boxplotOperator returns undefined, so the persisted query_context has post_processing: [] (the committed expected/box_plot.json golden shows the same). The chart would then return raw rows instead of box statistics on its first /data/ read. Should defaults be merged into the form data here before calling the builder?

const require = createRequire(import.meta.url);
const FEX = 'src/backend-querycontext/__fixtures__';

const res = await build({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This esbuild call has no loader map, but build-backend-querycontext.mjs needed .css/.svg/font/image entries mapped to empty because the entry.ts barrel imports transitively pull in theme and UI assets. As written, running node scripts/gen-qc-goldens.mjs (and gen-qc-fixtures.mjs:31, which has the same config) should fail while bundling, so the goldens and fixtures can't be refreshed when a buildQuery changes. Could these scripts share the production script's loader config?

)
# --- RED anchor: derivable → context; non-derivable → NULL (FR-003) ---
assert derivable_chart.query_context is not None
assert markup_chart.query_context is None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The markup chart simply yields no derivation (it returns None), so this passes even if the per-chart except in _synthesize_query_context_if_absent is removed. The bundle-isolation guarantee (one chart's synthesis raising must not abort the import) is never exercised. Could this add a chart whose synthesis actually raises, for example adhoc_filters: 123 on a big_number_total, and assert the import still succeeds, both charts persist, and only the failing one keeps a NULL query_context?

query_context = json.loads(chart.query_context)
# --- RED anchor: datasource taken from resolved id, not params (RISK-T02) ---
assert query_context["datasource"] == {"id": 1, "type": "table"}
assert query_context["queries"][0]["metrics"] == ["count"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test only checks queries[0] against a chart that falls back to the Python derivation when the V8 runtime isn't installed, and the other synthesis tests force the generator to return None. No import test has the generator return a real payload, so an importer that rewrote or dropped authoritative output (for example a mixed_timeseries with two queries) would still pass. Could one test mock generate() to return a two-query context and assert import_chart persists it unchanged without calling the fallback?

The backfill_po.py run re-flagged the already-confirmed "Markup type"
translation as a machine-translated "#, fuzzy" guess in fi/ko/th, which
downgraded a confirmed translation and tripped check_translation_regression
(translated -> fuzzy). The msgstr is unchanged from master; only the stray
backfill comment and fuzzy flag are removed, restoring the entry to its
confirmed state and clearing the babel-extract regression check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added i18n Namespace | Anything related to localization i18n:korean Translation related to Korean language requires:rebase Requires rebasing on top of current master labels Oct 14, 2026
Comment thread superset/cli/charts.py
}
context = generator.generate(chart.viz_type, js_params)
if context is None:
context = build_query_context_config(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The import path re-injects the chart's cache_timeout as custom_cache_timeout (_synthesize_query_context_if_absent), but this backfill path doesn't. A chart with cache_timeout=-1 (caching disabled) on a dataset with a positive timeout gets a backfilled context with no override, and the saved-chart data read then falls back to the datasource timeout and starts caching it. Should the backfill apply the same cache-timeout carry-over, ideally via a shared helper so the two paths can't drift?

const removed = [];
for (const dir of [`${FEX}/formdata`, `${FEX}/expected`]) {
for (const f of readdirSync(dir)) {
if (f.endsWith('.json') && !keep.has(f)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This reconcile deletes the fixtures of every viz type that isn't in covered, and covered excludes anything whose builder now throws or returns __error__. If a still-registered builder such as table regresses and returns an error, regenerating removes its formdata/expected files, and the parity tests (which iterate the remaining files) pass without it. Should pruning be limited to viz types no longer in the registry, with a still-registered type that stops producing a context failing the generator?

) as get_generator:
get_generator.return_value.generate.return_value = None
runner = current_app.test_cli_runner()
response = runner.invoke(superset.cli.charts.backfill_query_context, ())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This only exercises a single eligible chart with the default batch size, so the loop runs exactly one batch. The id-snapshot paging and per-batch commit that replaced the yield_per streaming (which broke on PostgreSQL once a backfill spanned more than one batch) aren't covered. Could the test create three charts with a NULL context, invoke the command with --batch-size 1, and assert all three end up with a persisted context?

Resolve conflicts in fi/ko/th messages.po: keep master's new backfill
entry ("Marks charts created through the MCP geographic contract ...")
and the now-confirmed (non-fuzzy) "Markup type" translation. Both sides
agree "Markup type" is no longer fuzzy, so no translation regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot removed i18n Namespace | Anything related to localization i18n:korean Translation related to Korean language requires:rebase Requires rebasing on top of current master labels Oct 14, 2026
@eugeneo17

Copy link
Copy Markdown
Author

Thanks @rusackas for the review, and good to hear CI is green.

All three threads are valid and I will close them here:

  1. Fail-closed adhoc filters: already in the current revision. If a filter cannot be mapped exactly, the builder synthesizes no context, so an unmappable filter can no longer widen the query. I will reply there and resolve once you confirm the direction.
  2. sort_by_metric: it is a boolean toggle, not a sort value, so I will emit orderby only when it is truthy.
  3. where and extra_filters: dropping them widens the result set on read, so I will carry where into extras and keep extra_filters.

On splitting out V8 and mini-racer: reasonable idea, but I would keep it in this PR for now, because:

  • The V8 bundle is the reference output, so without it beside the Python synthesis there is nothing to judge the approximation against, which is what these threads are about.
  • The open fixes are small and local to query_context_builder.py, so settling them here is faster than splitting mid-review and rebuilding context in two PRs.
  • The threads span both the synthesis and the bundle, so splitting now would scatter one discussion across two PRs.
  • The dependency is already optional behind an extra, so it does not force itself on anyone and there is no urgency to extract it before the behavior is settled.

Proposal: I resolve the three correctn PR unified for this pass. If you stillprefer the split, happy to discuss.

Thanks again.

return []


def build_query_context_config(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a second Python form-data-to-query mapping, and it drops roles that superset.common.form_data_query_context.build_query_context_from_form_data already handles. For example, a World Map chart with entity: country, metric: count, secondary_metric: revenue gets columns=[] and metrics=[count] here, so the backfilled/imported chart returns a single aggregate row instead of per-country rows with both metrics (that viz isn't covered by the V8 path, so this fallback is what runs). Could this reuse or extend the shared helper instead of maintaining another mapping?

return None
if not isinstance(result, dict):
return None
if any(sentinel in result for sentinel in _FALLBACK_SENTINELS):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The sentinel guard here is the only thing keeping {"__unsupported__": true} / {"__error__": ...} from being persisted as a non-NULL query_context, but every importer test mocks generate() to return None, so the guard is never exercised. If it were removed, the tests would stay green while charts got an invalid context that 400s on the data endpoint and is skipped by the backfill (it only targets NULL). Could you add a unit test where the mocked _ctx.call returns each sentinel and asserts generate() returns None (and import falls back to the Python derivation)?

@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 16, 2026

This branch has not been deployed

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

Labels

api:charts Related to the REST endpoints of charts change:backend Requires changing the backend dependencies:npm github_actions Pull requests that update GitHub Actions code requires:rebase Requires rebasing on top of current master size/XXL viz:charts:import Related to importing charts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to get chart data through this API: api/v1/chart/{pk}/data/

4 participants