Skip to content

feat(gaq): async chart data opt-in per request (async_mode) + auto-enable GTF - #43429

Open
villebro wants to merge 5 commits into
apache:gaq-to-gtffrom
villebro:villebro/gtf-async-mode
Open

feat(gaq): async chart data opt-in per request (async_mode) + auto-enable GTF#43429
villebro wants to merge 5 commits into
apache:gaq-to-gtffrom
villebro:villebro/gtf-async-mode

Conversation

@villebro

Copy link
Copy Markdown
Member

SUMMARY

Step 5 of the GAQ→GTF epic (#43407): make async chart data opt-in per request. After step 4 moved async execution onto the Global Task Framework, this makes async opt-in per request and adds the deployment/per-dashboard controls for it.

Backend

  • async_mode per-request flag on /chart/data. Async runs only when the request sets async_mode (an absent flag is treated as synchronous), so programmatic API clients keep the synchronous HTTP 200 flow. Async still additionally requires GLOBAL_ASYNC_QUERIES, a full JSON result, and caching on. Documented on ChartDataQueryContextSchema (popped in post_load — it's a request flag, not part of the QueryContext). The decision is centralized in ChartDataRestApi._should_run_async.
  • GLOBAL_ASYNC_QUERIES force-enables GLOBAL_TASK_FRAMEWORK at startup (with a log line; a custom feature-flag func can still override), since async chart data runs on GTF.
  • New config GLOBAL_ASYNC_QUERIES_DEFAULT (default True) — a frontend-only policy input for the UI's default async_mode, exposed via bootstrap conf.

Frontend

  • resolveAsyncMode() policy chainGLOBAL_ASYNC_QUERIES feature-flag gate → per-dashboard override → GLOBAL_ASYNC_QUERIES_DEFAULT. Full-JSON chart-data renders inject async_mode accordingly (exports and non-full result types stay synchronous), so the UI stays async by default when the flag is on.
  • Per-dashboard override in the dashboard Properties modal — an "Asynchronous query execution" control (Deployment default / Force enabled / Force disabled), shown only when GLOBAL_ASYNC_QUERIES is enabled, persisted to json_metadata.async_mode, and threaded through exploreJSON so that dashboard's charts honor it. A value set directly in the Advanced JSON editor takes precedence (mirrors refresh_frequency).

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — no change to how charts render for end users. Adds an opt-in control in dashboard properties (visible only when GLOBAL_ASYNC_QUERIES is enabled).

TESTING INSTRUCTIONS

  • With GLOBAL_ASYNC_QUERIES enabled, confirm GLOBAL_TASK_FRAMEWORK is enabled automatically (startup log line) and dashboards/explore load charts async by default.
  • Set GLOBAL_ASYNC_QUERIES_DEFAULT = False; confirm the UI loads synchronously by default, and a dashboard set to "Force enabled" loads its charts async.
  • POST /api/v1/chart/data without async_mode → HTTP 200 (synchronous); with "async_mode": true (flag on, full JSON, caching on) → HTTP 202 with task_ids.

Automated: pytest tests/unit_tests/charts/test_chart_data_api.py tests/unit_tests/feature_flag_test.py tests/unit_tests/initialization_test.py; frontend npm run test -- asyncMode chartActions PropertiesModal.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags: GLOBAL_ASYNC_QUERIES (force-enables GLOBAL_TASK_FRAMEWORK)
  • Changes UI (dashboard Properties: async-mode override)
  • Includes DB Migration
  • Introduces new feature or API (async_mode request flag; GLOBAL_ASYNC_QUERIES_DEFAULT config)
  • Removes existing feature or API

Targets the gaq-to-gtf feature branch (part of #43407), not master.

Async chart-data execution is now opt-in per request:
- /chart/data honors a request-level async_mode flag; an absent flag runs
  synchronously (HTTP 200), so programmatic API clients are unaffected. async
  still requires GLOBAL_ASYNC_QUERIES + full JSON result + caching on.
- schema: document async_mode on ChartDataQueryContextSchema (popped in
  post_load — it's a request flag, not part of the QueryContext).
- GLOBAL_ASYNC_QUERIES force-enables GLOBAL_TASK_FRAMEWORK at startup (async
  runs on GTF), with a log line; a custom feature-flag func can still override.
- new config GLOBAL_ASYNC_QUERIES_DEFAULT (default True) — a frontend-only
  policy input for the UI's default async_mode, exposed via bootstrap conf.
- update integration async tests to send async_mode; add a _should_run_async
  unit test.
Add resolveAsyncMode() policy chain — GLOBAL_ASYNC_QUERIES feature-flag gate →
per-dashboard override → GLOBAL_ASYNC_QUERIES_DEFAULT — and inject async_mode
into full-JSON chart-data renders (v1ChartDataRequest), so the UI stays async by
default when the flag is on (exports and non-full result types stay sync). Thread
the per-dashboard override (json_metadata.async_mode) from dashboard state through
exploreJSON's requestParams. Adds resolveAsyncMode unit tests.
Add an 'Asynchronous query execution' control to the dashboard Properties modal
(Deployment default / Force enabled / Force disabled), shown only when
GLOBAL_ASYNC_QUERIES is enabled and persisted to json_metadata.async_mode. The
value flows through exploreJSON → requestParams → resolveAsyncMode so the
dashboard's charts honor the override; 'Deployment default' clears the key. A
value set directly in the Advanced JSON editor takes precedence (mirrors
refresh_frequency).
@dosubot dosubot Bot added api:charts Related to the REST endpoints of charts change:backend Requires changing the backend change:frontend Requires changing the frontend dashboard:properties Related to the properties of the Dashboard global:async-query Related to Async Queries feature labels Aug 22, 2026
@bito-code-review

bito-code-review Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped - Branch Excluded

Bito didn't auto-review because the source or target branch is excluded from automatic reviews.
No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change the branch exclusion settings here, or contact your Bito workspace admin at evan@preset.io.

@github-actions github-actions Bot added the api Related to the REST API label Aug 22, 2026
@netlify

netlify Bot commented Aug 22, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

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

@villebro

Copy link
Copy Markdown
Member Author

/review

@bito-code-review

bito-code-review Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #17b7e5

Actionable Suggestions - 0
Additional Suggestions - 2
  • superset-frontend/src/dashboard/components/PropertiesModal/sections/index.ts - 1
    • Missing unit test coverage · Line 24-24
      The `AsyncModeSection` component has no unit test file, while other sections (AccessSection, AdvancedSection, BasicInfoSection, StylingSection) all have corresponding test files. Rule [11730] requires comprehensive unit tests for new components covering success paths, error scenarios, and edge cases.
  • superset/utils/feature_flag_manager.py - 1
    • Config dict mutation risk · Line 35-36
      Line 35 assigns `app.config["DEFAULT_FEATURE_FLAGS"]` directly, then line 36 mutates it with `update()`. This shares object identity with the original config dict, risking cross-request contamination if the same Flask app instance serves multiple requests with different configs. Use `deepcopy()` to ensure isolation.
Review Details
  • Files reviewed - 14 · Commit Range: 7f3c8dc..124264b
    • superset-frontend/src/components/Chart/chartAction.ts
    • superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
    • superset-frontend/src/dashboard/components/PropertiesModal/sections/AsyncModeSection.tsx
    • superset-frontend/src/dashboard/components/PropertiesModal/sections/index.ts
    • superset-frontend/src/utils/asyncMode.test.ts
    • superset-frontend/src/utils/asyncMode.ts
    • superset/charts/data/api.py
    • superset/charts/schemas.py
    • superset/config.py
    • superset/utils/feature_flag_manager.py
    • superset/views/base.py
    • tests/integration_tests/charts/data/api_tests.py
    • tests/unit_tests/charts/test_chart_data_api.py
    • tests/unit_tests/feature_flag_test.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ 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

Comment on lines +360 to +368
// Persist the per-dashboard async override (unless set directly in the
// Advanced JSON editor). 'default' clears it so the deployment default applies.
if (jsonMetadataObj.async_mode === undefined) {
if (asyncMode === 'default') {
delete jsonMetadataObj.async_mode;
} else {
jsonMetadataObj.async_mode = asyncMode;
}
}

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: The Advanced JSON editor is initialized from metadata that still contains an existing async_mode, so jsonMetadataObj.async_mode is already defined whenever a dashboard has a prior override. The conditional therefore skips the selector value entirely, preventing users from changing or clearing an existing force_on or force_off setting. Exclude this field from the editor copy or otherwise distinguish an intentional editor edit from the existing value before applying the selector. [incorrect condition logic]

Severity Level: Major ⚠️
- ❌ Dashboard selector cannot change existing async overrides.
- ❌ Dashboard selector cannot restore deployment-default behavior.
- ⚠️ Users can still edit `async_mode` manually in Advanced JSON.

Use CodeAnt Skill

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

**Path:** superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
**Line:** 360:368
**Comment:**
	*Incorrect Condition Logic: The Advanced JSON editor is initialized from metadata that still contains an existing `async_mode`, so `jsonMetadataObj.async_mode` is already defined whenever a dashboard has a prior override. The conditional therefore skips the selector value entirely, preventing users from changing or clearing an existing `force_on` or `force_off` setting. Exclude this field from the editor copy or otherwise distinguish an intentional editor edit from the existing value before applying the selector.

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
👍 | 👎

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. In superset-frontend/src/dashboard/components/PropertiesModal/index.tsx, the logic if (jsonMetadataObj.async_mode === undefined) prevents the modal from updating jsonMetadataObj.async_mode if it already exists in the metadata. This effectively locks the value to its initial state, preventing users from changing or clearing an existing override via the UI.

To resolve this, you should remove the if (jsonMetadataObj.async_mode === undefined) check and allow the modal state to overwrite the metadata value, ensuring that 'default' correctly removes the key.

Would you like me to implement this fix and check for other comments on this PR?

superset-frontend/src/dashboard/components/PropertiesModal/index.tsx

// Persist the per-dashboard async override (unless set directly in the
    // Advanced JSON editor). 'default' clears it so the deployment default applies.
    if (asyncMode === 'default') {
      delete jsonMetadataObj.async_mode;
    } else {
      jsonMetadataObj.async_mode = asyncMode;
    }

Comment on lines +454 to +460
const asyncMode =
resultFormat === 'json' &&
resultType === 'full' &&
resolveAsyncMode(requestParams.async_mode_override);
const body = JSON.stringify(
asyncMode ? { ...payload, async_mode: true } : payload,
);

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: Applying resolveAsyncMode inside the shared v1ChartDataRequest opts every default JSON/full caller into asynchronous execution when the feature flag and deployment default are enabled. Callers such as AlertReportModal call getChartDataRequest directly and then immediately read response.json.result without handling HTTP 202 task responses, so they receive {task_ids} and fail instead of receiving chart data. Restrict this opt-in to callers that implement the async polling path, or update every affected caller to await the async response. [api mismatch]

Severity Level: Major ⚠️
- ❌ Alert dashboard-filter values fail when async execution is enabled.
- ❌ Direct chart-data consumers receive task IDs instead of query results.
- ⚠️ Only deployments with async queries and uncached requests are affected.

Use CodeAnt Skill

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

**Path:** superset-frontend/src/components/Chart/chartAction.ts
**Line:** 454:460
**Comment:**
	*Api Mismatch: Applying `resolveAsyncMode` inside the shared `v1ChartDataRequest` opts every default JSON/full caller into asynchronous execution when the feature flag and deployment default are enabled. Callers such as `AlertReportModal` call `getChartDataRequest` directly and then immediately read `response.json.result` without handling HTTP 202 task responses, so they receive `{task_ids}` and fail instead of receiving chart data. Restrict this opt-in to callers that implement the async polling path, or update every affected caller to await the async response.

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
👍 | 👎

@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.82%. Comparing base (fb20230) to head (8a15ec2).

Files with missing lines Patch % Lines
...ents/PropertiesModal/sections/AsyncModeSection.tsx 33.33% 4 Missing ⚠️
...erset-frontend/src/components/Chart/chartAction.ts 87.50% 1 Missing ⚠️
...src/dashboard/components/PropertiesModal/index.tsx 80.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff               @@
##           gaq-to-gtf   #43429      +/-   ##
==============================================
- Coverage       78.83%   78.82%   -0.01%     
==============================================
  Files            2879     2881       +2     
  Lines          164332   164477     +145     
  Branches        37984    38024      +40     
==============================================
+ Hits           129551   129656     +105     
- Misses          32341    32381      +40     
  Partials         2440     2440              
Flag Coverage Δ
hive 38.16% <41.66%> (-0.01%) ⬇️
mysql 57.74% <75.00%> (-0.01%) ⬇️
postgres 57.77% <75.00%> (-0.01%) ⬇️
presto 40.10% <66.66%> (-0.01%) ⬇️
python 83.50% <100.00%> (-0.01%) ⬇️
sqlite 57.46% <75.00%> (-0.01%) ⬇️
unit 73.57% <83.33%> (-0.02%) ⬇️

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.

…sistence

Address review feedback on apache#43429:
- async opt-in was applied inside the shared v1ChartDataRequest, so direct
  callers that read response.json.result (AlertReportModal, ViewQueryModal,
  fetchTopNValues, useResultsPane) would receive a 202 {task_ids} and break.
  Gate injection on an explicit getChartDataRequest({enableAsyncMode}) that only
  the 202-handling callers set (exploreJSON, FilterValue, FiltersConfigForm,
  DrillByModal, ChartVersionPreview).
- the dashboard properties async override was never applied when a dashboard
  already had json_metadata.async_mode: the editor copy carried it so the
  '=== undefined' guard skipped the dropdown. Omit async_mode from the Advanced
  JSON editor copy (dropdown is the sole source of truth, mirroring
  show_chart_timestamps) and apply the dropdown value unconditionally on save.
force: fromCache ? false : shouldRefresh,
ownState: filterOwnState,
// 202 is handled below via waitForAsyncData.
enableAsyncMode: 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.

A dashboard set to force async off still enables this request whenever the deployment default is on, because this path never receives the dashboard override that exploreJSON passes to resolveAsyncMode. That makes filter values use a 202/polling flow while the dashboard's charts stay synchronous. Could this request receive the same override (and be covered for both override directions)?

resultType === 'full' &&
resolveAsyncMode(requestParams.async_mode_override);
const body = JSON.stringify(
asyncMode ? { ...payload, async_mode: true } : payload,

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 makes async opt-in only for the v1ChartDataRequest producer. StatefulChart still posts full JSON chart-data requests directly, even though it already handles 202 responses, so its Matrixify cells now always run synchronously when this endpoint requires async_mode. Could the resolved policy be shared with that producer as well?

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 api Related to the REST API change:backend Requires changing the backend change:frontend Requires changing the frontend dashboard:properties Related to the properties of the Dashboard global:async-query Related to Async Queries feature size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants