Skip to content

fix(dashboard): build a version copy's settings from that version - #45530

Merged
aminghadersohi merged 17 commits into
apache:masterfrom
mikebridge:sc-128646-fork-metadata
Oct 27, 2026
Merged

aminghadersohi merged 17 commits into
apache:masterfrom
mikebridge:sc-128646-fork-metadata

Conversation

@mikebridge

@mikebridge mikebridge commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

What users saw. Opening an older dashboard version as a new dashboard could bring along newer settings that were absent from that version. Settings could also still refer to charts omitted from the copied layout.

Stacked on #45528, please review that first; this PR's own change is the snapshot-metadata diff in 0099a023eb..46b2623bf2.

Why, in plain terms. The copy started with the live dashboard's settings and overlaid the selected version's values. Missing values therefore came from the live dashboard, and several chart-specific settings were never trimmed to the copied layout.

What this changes. Copies use the supplied version's metadata as their complete settings. Expanded charts, refresh exemptions, chart configuration and filter-scope chart references retain only charts in the copy; when charts are duplicated, those references use the new chart IDs. The source dashboard and ordinary dashboard-save behavior are unchanged.

The copy API already requires json_metadata; clients sending partial metadata must send the full settings they want copied. Omitted settings use defaults. UPDATING.md documents this compatibility change. The shared metadata-save method and existing legacy filter pruning remain unchanged.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Not applicable: backend copy correction without a UI change.

TESTING INSTRUCTIONS

  1. Save a dashboard version with chart A, then add chart B and change dashboard settings.
  2. Open the older version as a new dashboard.
  3. Confirm its settings come from the selected version and contain no chart-specific entries for B.
  4. When duplicating charts, verify retained chart settings reference the new chart IDs.

Local validation:

  • The new snapshot-copy API regression fails on master and passes with this fix.
  • 311 DAO/dashboard unit tests pass, including empty/subset layouts and clone-ID remapping.
  • Changed-file pre-commit checks pass, including MyPy.
  • All 12 copy API tests pass on isolated SQLite with fix(dashboard): don't clone charts a copied layout leaves out #45528 merged into this branch.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@github-actions github-actions Bot added the doc Namespace | Anything related to documentation label Oct 20, 2026
@netlify

netlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

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

@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.29630% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.33%. Comparing base (09207d9) to head (a2d8dec).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
superset/daos/dashboard.py 96.22% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #45530      +/-   ##
==========================================
+ Coverage   84.31%   84.33%   +0.01%     
==========================================
  Files        3059     3059              
  Lines      202225   202319      +94     
  Branches    47367    47390      +23     
==========================================
+ Hits       170516   170629     +113     
+ Misses      28451    28432      -19     
  Partials     3258     3258              
Flag Coverage Δ
hive 34.75% <9.25%> (-0.02%) ⬇️
javascript 80.49% <ø> (ø)
mysql 52.81% <66.66%> (-0.01%) ⬇️
postgres 52.82% <66.66%> (-0.01%) ⬇️
presto 36.48% <9.25%> (-0.02%) ⬇️
python 87.47% <96.29%> (+0.02%) ⬆️
sqlite 52.58% <66.66%> (-0.01%) ⬇️
unit 82.00% <96.29%> (+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.

@mikebridge
mikebridge marked this pull request as ready for review October 20, 2026 20:56
"""Drop omitted chart references and use clone IDs when applicable."""
new_id: int | None
return [
new_id for value in values if (new_id := key_map.get(str(value))) is not None

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: A valid whole-number float ID such as 12.0 becomes "12.0" and misses the "12" map key, silently dropping that chart setting during the copy.

Assessment: 🟠 Major · 🔁 Occurrence: Rarely · 🏷️ Type error

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

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

**Path:** superset/daos/dashboard.py
**Line:** 330:330
**Comment:**
	*Type Error: A valid whole-number float ID such as `12.0` becomes `"12.0"` and misses the `"12"` map key, silently dropping that chart setting during the copy.

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
Contributor Author

Choose a reason for hiding this comment

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

Thanks. In practice this can't happen. Chart IDs in json_metadata and position_json are written by the frontend as JSON integers (12, never 12.0), and json.loads turns those into int, so str(value) matches the clone map's keys. The other copy and import paths key their maps the same way.

Even if a malformed payload did carry a float, the result would be that the reference is dropped, not pointed at the wrong chart. That's the safe failure for a copy. Leaving this as is.

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:

Do not flag string-key lookup of chart IDs in dashboard copy logic for whole-number float IDs; dashboard metadata and position JSON encode these IDs as integers, and malformed references are safely dropped.

Applied to:

  • superset/daos/dashboard.py

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

@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt  CodeAnt PR Risk

🟢  Low Risk

  • The PR appears safe to merge; dashboard chart IDs are normally encoded as integers, and malformed float references are safely omitted rather than misdirected.

Assessed commit: a2d8dec87bf4

@fitzee fitzee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM with one docs nit. Reviewed the delta on top of #45528 (0099a023eb..46b2623bf2).

  • Where "that version's" metadata comes from. Dashboard.__versioned__ versions the whole json_metadata column, so the snapshot createDashboardFromSnapshot sends is the complete settings blob as of that version. A key missing from the snapshot was really absent at that point. Defaulting it (refresh_frequency 0, color_scheme "", label_colors {}, cross_filters_enabled true, no native filters) matches what that version actually rendered. Taking it from the live dashboard would be the leak this PR removes. shared_label_colors, map_label_colors and color_scheme_domain already reset from the payload, and the frontend recomputes them on load. No migration since the versioning tables (2026-05-28) rewrites dashboard json_metadata, so there's no schema drift between old snapshots and current validation yet.
  • Authz. No version ID reaches the server. The endpoint copies whatever metadata and layout the caller sends, gated on is_editor(source), as before. This PR adds no new trust surface. The layout-chart IDOR from #45472 still applies and is still open.
  • Save as. The DAO change also covers the regular "Save as" flow. Both UI callers (SaveModal → saveDashboardRequest, and version "Open as new") and the MCP duplicate_dashboard tool send the full metadata plus positions, so none of them lose settings.
  • Remap. It still works after the switch. For duplicate_slices, set_dash_metadata has already remapped chart_configuration, crossFilters, native-filter scopes and selectedLayers to clone IDs. _copy_chart_metadata then filters those by clone ID and maps expanded_slices and timed_refresh_immune_slices old→new. That also fixes a gap that predates this PR: Save-as-with-charts used to leave both lists on the original IDs. Archived slots become placeholders under #45528, so their entries are dropped. Soft-deleted members of a plain copy stay linked and keep their settings.
  • Tests. With only superset/daos/dashboard.py reverted to the #45528 version, test_copy_normalizes_legacy_chart_ids fails ({'1': True, '999999': True} != {'2': True}) and so does test_copy_metadata_uses_snapshot_and_layout (live_only inherited). With the fix, tests/unit_tests/dao/test_copy_dashboard_legacy_ids.py and dashboard_test.py pass (78). CI on 46b2623 is green, including test-postgres, test-mysql and test-sqlite.

UI check on a hosted environment: save a dashboard with charts A and B, set a refresh interval and a custom color scheme, and expand B. Open an earlier version that only had A via Version history → Open as new. The copy should have no refresh interval, the color scheme from that version, and no settings for B (Edit properties → Advanced).

only those fields, so properties such as description, slug, certification, and
theme come from the dashboard as it stands today, not from the version.

The new dashboard's settings come from the selected version's metadata. Settings

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: the paragraph just above (lines 304-308, unchanged) now contradicts this one. It still says a new dashboard "is a copy of the live dashboard with the version's title, CSS, metadata, and layout applied over it". Its last clause is also wrong, before and after this PR: "description, slug, certification, and theme come from the dashboard as it stands today". DashboardCopySchema accepts only dashboard_title, css, json_metadata and duplicate_slices, and copy_dashboard sets nothing else apart from editors/viewers. So the copy's description, slug, certification and theme are empty or default, not inherited. Suggest folding the two paragraphs into one: title, CSS, settings and layout come from the version. Description, slug, certification and theme are not copied.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, Matt. Fixed in b7db5b6: the docs now say title, CSS, settings and layout come from the version, while description, slug, certification and theme are not copied.

@fitzee fitzee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No blocking issues; LGTM. Dropping the live-metadata seed is correct, because Dashboard.__versioned__ stores the whole json_metadata column. The new chart-setting pruning is correct for both the shared-chart path and the duplicate-chart path. Nothing here weakens access control.

Reviewed the delta on top of #45528 (0099a023eb..b7db5b62c2) at head b7db5b62.

What I checked

  • Security-relevant fields. None of owners, roles, editors, viewers, published, slug, certified_by, theme_id or the embedded config live in json_metadata. The copy still sets editors and viewers to the creator, and leaves published, slug, certification and theme at the new row's defaults. The version copy cannot bring any of those across.
  • Native-filter dataset grants. native_filter_configuration targets do act as a datasource grant, but only for guest users and for promiscuous-mode viewers (security/manager.py, the NATIVE_FILTER branch). Copy callers already supplied that key verbatim before this PR, and an editor can PUT any value anyway. Removing the seed only removes inherited keys and grants nothing new. A filter that targets a since-deleted or unreadable dataset behaves the same as before, because query-time access checks still apply.
  • Chart access at copy time. This is unchanged. The server still links any Slice that the layout names. The only gate today is the frontend's fetchReachableChartIds swap, and #45538 adds the server-side check. This PR actually narrows the exposure: settings for charts that the client swapped to placeholders are now pruned too.
  • ID spaces in _copy_chart_metadata. expanded_slices and timed_refresh_immune_slices are not remapped by set_dash_metadata, so they correctly use original_key_map. chart_configuration, global_chart_configuration, native-filter scopes and customization scopes have already been remapped by _remap_filter_scopes, so they correctly use copied_key_map. Entries for charts that were not cloned keep their old IDs and drop out. An old ID cannot collide with a fresh clone ID on PG or MySQL.
  • Other callers still send full metadata. "Save as" (SaveModal sends ...dashboardInfo.metadata) and MCP duplicate_dashboard (source.json_metadata) both send the full metadata, so dropping the seed loses nothing for them.
  • Mutation tests. These all fail the unit suite: restoring dash.params = original_dash.params, removing the _copy_chart_metadata call, swapping the key map for expanded_slices or for timed_refresh_immune_slices, and skipping global_chart_configuration.

Non-blocking

  1. Conflict with #45538. superset/daos/dashboard.py:1104-1106: #45538 edits the same dash.params = original_dash.params / set_dash_metadata(...) lines to pass allowed_chart_ids. Whichever PR merges second has to drop the seed line and keep the kwarg. Otherwise one fix silently undoes the other. The two compose cleanly in meaning: #45538 raises inside set_dash_metadata before copied_ids is computed.
  2. config["id"] = int(chart_key) is untested. superset/daos/dashboard.py:403: no test fails if this line is deleted. It only matters when a stored id disagrees with its key, or isn't an int so _remap_chart_configuration skipped it. One assertion would cover it, for example a chart_configuration entry {"<id>": {"id": "<id>"}} in test_copy_metadata_uses_snapshot_and_layout.
  3. Old snapshots can fail validation (not introduced here). DashboardJSONMetadataSchema uses marshmallow's default unknown=RAISE. A version saved with a metadata key the schema has since dropped will 400 on copy, unless it's the one handled by remove_show_native_filters. The client sent the snapshot's metadata before this PR too, so nothing changes here. It may be worth a follow-up if version copy is expected to work across schema changes.

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

fitzee commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Re-checked at 38e286ce: LGTM still stands. The only new commit is a master merge, and it doesn't change this PR's logic.

  • What changed. b7db5b62..38e286ce is one merge commit (38e286ce, master at bc83e3e6fd). git range-diff against each side's merge-base shows the PR's 6 commits unchanged (=). The diff of the PR against master is identical apart from line offsets. The merge's only hand resolution is in UPDATING.md: the PR's entry and master's new MCP entries are both kept. fix(dashboard): don't clone charts a copied layout leaves out #45528 is also unchanged (head still 0099a023).
  • Master changes near this code. None of them affect the copy path:
  • fix(dashboard): validate dashboard layout chart references on the server #45538 is not merged. It's still open (head f9afb4ad), so this branch has nothing to resolve yet. A test merge of the two heads still conflicts:
    • superset/daos/dashboard.py:1111-1113: same as before, only shifted by the master merge. Whichever PR merges second must keep set_dash_metadata(dash, metadata, old_to_new_slice_ids, allowed_chart_ids=...) and must not bring back dash.params = original_dash.params.
    • tests/integration_tests/dashboards/api_tests.py: both PRs add tests at the same spot. Keep both.
  • Prior nits.
    1. fix(dashboard): validate dashboard layout chart references on the server #45538 conflict: still open, as above.
    2. config["id"] = int(chart_key) at superset/daos/dashboard.py:404 (was :403): still not covered by any test.
    3. DashboardJSONMetadataSchema unknown=RAISE on old snapshots: unchanged. It predates this PR.
  • Tests/CI. CI on 38e286ce is green (58 pass, 14 skipped), including test-postgres, test-mysql, test-sqlite and unit-tests. Locally, tests/unit_tests/dao/test_copy_dashboard_legacy_ids.py, dashboard_test.py, subjects/test_creator_group_call_sites.py and tests/unit_tests/mcp_service/dashboard pass at 38e286ce (1293 passed).

@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 22, 2026
Preserve both independent UPDATING.md notices while retaining the reviewed dashboard-copy behavior. Full unit suite has only the four accepted local baseline failures; focused copy tests and branch-file hooks pass.
@github-actions github-actions Bot added requires:rebase Requires rebasing on top of current master and removed requires:rebase Requires rebasing on top of current master labels Oct 22, 2026
@fitzee

fitzee commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Re-checked at d4b45d97: Still LGTM. The only new commit is a master merge, and the PR's own diff is unchanged.

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

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

Read through the delta on top of #45528 and the logic holds up. The remap is right in both copy modes, and Save as already sends the full synced metadata, so it won't lose settings.

Two small notes inline: whether the title wants a !, and a comment wording nit.

Comment thread UPDATING.md Outdated

## Next

- **Dashboard copies use the supplied metadata as their complete settings.**

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.

This changes the contract for any client that sends partial json_metadata to POST /copy/, not only the version fork.

Worth a ! in the title (fix(dashboard)!: ...) so it gets picked up as a breaking change? Your call, the entry here already covers the substance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, good call on the contract. Rather than change it for every copy caller, we made the replace-from-supplied-metadata behavior opt-in: replace_metadata on the copy request, which only the version fork sends. Save as, the MCP duplicate tool and other API clients keep seeding from the source as before. Chart-specific keys are still pruned and remapped to the copied layout in both modes. So this is no longer a breaking change, and I removed the UPDATING entry.

Comment thread superset/daos/dashboard.py Outdated
Comment on lines +1111 to +1112
# Copy callers supply the selected version's complete metadata. Seeding
# from the live dashboard would leak settings absent from that version.

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, take it or leave it. Save as and the MCP duplicate tool also land here, so pinning this on "the selected version" undersells the contract every caller now relies on.

Suggested change
# Copy callers supply the selected version's complete metadata. Seeding
# from the live dashboard would leak settings absent from that version.
# Every copy caller (Save as, version fork, MCP duplicate) sends the
# complete metadata. Seeding from the source would leak omitted settings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Took it, adjusted for the opt-in: the comment now says the version fork sends complete metadata with replace_metadata, and other callers keep seeding from the source.

@github-actions github-actions Bot added the requires:rebase Requires rebasing on top of current master label Oct 24, 2026
@github-actions github-actions Bot added api Related to the REST API and removed requires:rebase Requires rebasing on top of current master labels Oct 26, 2026

@aminghadersohi aminghadersohi 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. Reviewed the diff and the thread replies; the fixes and regression tests cover the reported cases.

@aminghadersohi
aminghadersohi merged commit 9bddbd6 into apache:master Oct 27, 2026
81 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Related to the REST API doc Namespace | Anything related to documentation size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants