Skip to content

fix(sqllab): disable Save dataset until the query runs successfully - #43330

Open
msyavuz wants to merge 3 commits into
apache:masterfrom
msyavuz:msyavuz/fix/sqllab-save-dataset-failed-query
Open

fix(sqllab): disable Save dataset until the query runs successfully#43330
msyavuz wants to merge 3 commits into
apache:masterfrom
msyavuz:msyavuz/fix/sqllab-save-dataset-failed-query

Conversation

@msyavuz

@msyavuz msyavuz commented Aug 19, 2026

Copy link
Copy Markdown
Member

SUMMARY

In SQL Lab the Save dataset toolbar button was gated only on database.allows_virtual_table_explore, never on the query result. After a failed run it stayed enabled, so you could create a virtual dataset off SQL that produced no result set (columns come from latestQuery.results.columns, which is stale from an earlier success or empty).

The button is now gated on latestQuery?.state === 'success' and rendered disabled with the tooltip "You must run the query successfully first" — the same pattern the Schedule query button already uses. Save query is unaffected; only dataset creation requires a successful run.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Before:
image

After:
image

TESTING INSTRUCTIONS

  1. SQL Lab → examples / main → run SELECT bad FROM definitely_not_a_table_xyz.
  2. Once the query fails, Save dataset is disabled and hovering it explains why.
  3. Run a valid query — the button becomes enabled again.

Unit tests: npm run test -- src/SqlLab/components/SaveDatasetActionButton src/SqlLab/components/SqlEditor

ADDITIONAL INFORMATION

  • Has associated issue: No
  • Required feature flags: None
  • Changes UI — Save dataset button is disabled after a failed/unrun query
  • Includes DB Migration: No
  • Introduces new feature or API: No
  • Removes existing feature or API: No

The Save dataset toolbar button was gated only on whether the database
allows virtual table explore, so it stayed clickable after a failed run
and would build a virtual dataset from stale or empty result columns.
Gate it on the latest query state, mirroring the Schedule query button.
@dosubot dosubot Bot added change:frontend Requires changing the frontend sqllab Namespace | Anything related to the SQL Lab labels Aug 19, 2026
@bito-code-review

bito-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #8683b6

Actionable Suggestions - 0
Review Details
  • Files reviewed - 5 · Commit Range: 4e441f7..4e441f7
    • superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx
    • superset-frontend/src/SqlLab/components/SaveDatasetActionButton/index.tsx
    • superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
    • superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx
    • superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
  • Files skipped - 0
  • Tools
    • 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

}
saveQueryWarning={saveQueryWarning}
database={database}
canSaveDataset={latestQuery?.state === 'success'}

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 success state belongs to the previous latestQuery, and editing SQL does not clear or replace latestQueryId, so this remains enabled after a user changes the SQL following a successful run. The dataset modal then receives the newly edited SQL together with columns from the older result. Track the SQL associated with the successful query or invalidate dataset saving whenever the editor SQL changes. [stale reference]

Severity Level: Major ⚠️
- ❌ Dataset creation can save unexecuted edited SQL.
- ⚠️ Dataset columns can come from an earlier query result.
- ⚠️ SQL Lab presents stale success state after edits.

Use CodeAnt Skill

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

**Path:** superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
**Line:** 868:868
**Comment:**
	*Stale Reference: The success state belongs to the previous `latestQuery`, and editing SQL does not clear or replace `latestQueryId`, so this remains enabled after a user changes the SQL following a successful run. The dataset modal then receives the newly edited SQL together with columns from the older result. Track the SQL associated with the successful query or invalidate dataset saving whenever the editor SQL changes.

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 Author

Choose a reason for hiding this comment

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

This is pre-existing and orthogonal to this PR

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct: the current implementation only checks if the latestQuery state is 'success', but it does not invalidate this state when the user modifies the SQL in the editor. This allows saving a dataset based on stale results from a previous query.

To resolve this, you should track whether the SQL in the editor matches the SQL of the successful query. A concise way to implement this is to compare the current editor SQL with the SQL of the latestQuery whenever the editor content changes, and only enable the save button if they match and the state is 'success'.

Would you like me to fetch all other comments on this PR to validate and implement fixes for them as well?

superset-frontend/src/SqlLab/components/SqlEditor/index.tsx

const canSaveDataset = latestQuery?.state === 'success' && latestQuery?.sql === queryEditor.sql;

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.67%. Comparing base (097c99b) to head (4559ffd).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #43330   +/-   ##
=======================================
  Coverage   66.67%   66.67%           
=======================================
  Files        2876     2876           
  Lines      164007   164011    +4     
  Branches    37834    37838    +4     
=======================================
+ Hits       109347   109351    +4     
  Misses      52514    52514           
  Partials     2146     2146           
Flag Coverage Δ
javascript 73.91% <100.00%> (+<0.01%) ⬆️

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.

}
saveQueryWarning={saveQueryWarning}
database={database}
canSaveDataset={latestQuery?.state === 'success'}

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.

Nit: This uses a raw string 'success' but the test file imports QueryState.Success from @superset-ui/core. Could we use the enum here too for consistency and type safety?

canSaveDataset={latestQuery?.state === QueryState.Success}

color="default"
variant="text"
onClick={() => onSaveAsExplore?.()}
disabled={!canSaveDataset}

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.

Question: when onSaveAsExplore is undefined (database doesn't support virtual table explore), this button isn't rendered at all, so the user never sees the "run query first" disabled state. Is that intentional? Might be worth rendering the button as disabled in that case too, so the user understands why they can't save as dataset — otherwise it silently disappears.

@msyavuz msyavuz Aug 19, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, allows_virtual_table_explore is a permanent database capability rather than a transient state, so a always-disabled button would be noise on those databases. The hide-vs-disable behavior there is pre-existing and untouched by this PR; happy to revisit separately if we want capability gaps surfaced in the UI.

@varfigstar

Copy link
Copy Markdown
Contributor

What if the same query worked correctly before, but when we rerun it, the database happens to go down at that moment? Would we then have to wait for the DB to be fixed before saving the query?

I don't think it's a good idea for us to take responsibility for actions that are ultimately under the user's control.

@msyavuz

msyavuz commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

What if the same query worked correctly before, but when we rerun it, the database happens to go down at that moment? Would we then have to wait for the DB to be fixed before saving the query?

I don't think it's a good idea for us to take responsibility for actions that are ultimately under the user's control.

Save query is untouched:

Save query is unaffected; only dataset creation requires a successful run.

Or are you saying we shouldn't block not working datasets from being created. If that is the point i can reframe the condition as no columns available instead of last query failed

@bito-code-review

bito-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #1ecf4f

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 4e441f7..356b916
    • superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
  • Files skipped - 0
  • Tools
    • 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

@EnxDev

EnxDev commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

EnxDev's Review Agent — #43330 · HEAD 356b916

comment — Right fix, right pattern, real regression tests. But state === Success is a proxy for "we have columns for this SQL", and it misses the reload/cached path — the button is still enabled there with zero columns, which is the same hole the PR sets out to close.

CI is green (67 passing, 0 failing); codecov reports all modified lines covered. Title is Conventional-Commits valid.

🔴 Functional

  • superset-frontend/src/SqlLab/components/SqlEditor/index.tsx:869 · High — A query can be state === 'success' with results absent from the store: that exact combination is handled at ResultSet/index.tsx:828 (query.cached || (query.state === QueryState.Success && !query.results)), which renders Fetch data preview / Refetch results. It's reachable after a page reload — getInitialState.ts:147 hydrates queries from the bootstrap payload, and the queries API exposes results_key, not the result rows (superset/queries/api.py:104). In that state the new gate returns true, so Save dataset is enabled while columns={latestQuery?.results?.columns || []} (line 862) is [].

    Impact, for any user who reloads SQL Lab and clicks the button: the Overwrite path PUTs columns: [] with override_columns=true (SaveDatasetModal/index.tsx:178, :285, :293), wiping the target dataset's column metadata; the Create path makes a dataset whose Explore form_data carries all_columns: []. Overwrite is the destructive one — it silently drops columns on an existing dataset.

    Fix is the condition you already offered varfigstar — gate on columns, not on state:

    canSaveDataset={
      latestQuery?.state === QueryState.Success &&
      !!latestQuery?.results?.columns?.length
    }
    

    That subsumes the failed-query case and covers cached/reloaded results too. regression test: in SqlEditor.test.tsx, add a third case to setupWithQueryState with { ...latestQuery, state: QueryState.Success, results: undefined } and assert the button is disabled — it renders enabled at this HEAD, which is the gap.

🟡 Should-fix

  • SaveDatasetActionButton/index.tsx:33, SaveQuery/index.tsx:85canSaveDataset defaults to true, so the guard fails open if a call site omits it. There's exactly one call site (SqlEditor/index.tsx:869); make the prop required on both components so a future consumer can't silently re-open the hole.
  • SqlEditor/index.tsx:869 — On the stale-SQL thread: agreed the underlying bug predates this PR, so it's fine to leave. Worth noting the affordance changed though — the enabled state now asserts "the query ran successfully", and after editing SQL post-success it shows Save or Overwrite Dataset over columns that belong to different SQL. Gating on results.columns doesn't fix that either; a follow-up would need to compare latestQuery.sql against the editor's current SQL.

🙌 Praise

  • SqlEditor.test.tsx:358-366 — testing both directions (enabled on Success, disabled on Failed) with results left populated from queries[0] reproduces the actual bug: stale columns from an earlier success. That's a real regression guard, not a smoke test.
  • SaveDatasetActionButton.test.tsx:60 — hovering the wrapper is correct, not a workaround: the core Button wraps disabled buttons in a <span> precisely so the tooltip fires (packages/superset-ui-core/src/components/Button/index.tsx:246).

Note on the open thread

The DB-goes-down scenario raised in the thread doesn't cost anything today: START_QUERY sets latestQueryId to the new query (reducers/sqlLab.ts:386), so a failed re-run immediately makes latestQuery the failed one and columns resolves to []. Before this PR that path produced a column-less dataset, not a good one — so disabling the button there is the correct outcome, not a capability loss.

Reviewed by EnxDev's Review Agent — @EnxDev · HEAD 356b916.

A query can be in the success state with no results in the store — the
post-reload hydration path keeps only the results key — so gating on the
query state alone still allowed saving a dataset with no columns, which
the overwrite path would write over an existing dataset's metadata.
@varfigstar

Copy link
Copy Markdown
Contributor

Save query is untouched:
Yeah, I meant saving dataset

Or are you saying we shouldn't block not working datasets from being created. If that is the point i can reframe the condition as no columns available instead of last query failed

I think we shouldn't block saving dataset at all. Even if there is query error.

For example, I'm analyst and I made query in SQLab that fetched a lot of data. Superset retrieved data, but DB went down in next second after this. Analyst want to save dataset with this query but have no ability to do this even if the SQL expression is correct, 'cause of crashed DB.

@alexandrusoare alexandrusoare left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@msyavuz msyavuz added the hold! On hold label Aug 19, 2026
@bito-code-review

bito-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #3f7557

Actionable Suggestions - 0
Review Details
  • Files reviewed - 6 · Commit Range: 356b916..4559ffd
    • superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx
    • superset-frontend/src/SqlLab/components/SaveDatasetActionButton/index.tsx
    • superset-frontend/src/SqlLab/components/SaveQuery/SaveQuery.test.tsx
    • superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
    • superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx
    • superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
  • Files skipped - 0
  • Tools
    • 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

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

Labels

change:frontend Requires changing the frontend hold! On hold size/M sqllab Namespace | Anything related to the SQL Lab

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants