feat(versioning): Version History UI for Explore and Dashboard#40334
Draft
kgabryje wants to merge 17 commits into
Draft
feat(versioning): Version History UI for Explore and Dashboard#40334kgabryje wants to merge 17 commits into
kgabryje wants to merge 17 commits into
Conversation
Adds the cross-entity primitives (types, hooks, context, components) used to build chart and dashboard version history UI on top of the backend versioning endpoints introduced in apache#39603. Gated behind a new VERSION_HISTORY feature flag (default off). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wraps the Explore subtree in VersionHistoryProvider when the VERSION_HISTORY feature flag is on and the chart has a uuid, mounting the side panel + preview banner. Adds a "View version history" item to the additional-actions menu; the item is disabled with a tooltip when the chart has unsaved form-control changes. While previewing, the Save button is disabled and the chart panel shadow-renders snapshot form_data via a dedicated context — no Redux mutation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mounts VersionHistoryProvider + side panel on the dashboard Header when the VERSION_HISTORY feature flag is on and the dashboard has a uuid. Adds a "View version history" item to the bottom of the dashboard options menu; disabled with a tooltip while in edit mode. The panel lists versions and the restore flow goes through the backend endpoint. Inline preview rendering of historical layouts on the dashboard grid is deferred — that needs a Redux capture-and-replay of sliceEntities + dashboardLayout, and is tracked as a follow-up. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers summarizeChange across the layout/field/fallback branches that Mike's demo handles, groupVersionsByDate edge cases, the snapshot → form_data adapter, and the useVersionList hook's API-shape handling (oldest-first → newest-first reversal, error path). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-define oxlint flagged the wrapping components for using their inner helpers before declaration. Moves inner helpers above their outer wrappers; no behavior change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Includes one type-check fix (Change[] cast went through unknown rather than directly) and a prettier autofix on the snapshotToFormData spread. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The username-sniffing heuristic guessed too aggressively. AI attribution will land properly once the backend exposes an authored_by-flavored field; until then, all versions render with their plain user name and the search input drives the only filter. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds ENTER_VERSION_PREVIEW / EXIT_VERSION_PREVIEW actions wired through three reducers (dashboardState, sliceEntities, dashboardLayout). The enter thunk captures the live sliceEntities + dashboardLayout.present into dashboardState.versionPreview, then dispatches the snapshot's slices + parsed position_json. The exit thunk reads back the captured originals and dispatches them as the new values; no backend re-fetch, no editMode toggle. DashboardPreviewBridge subscribes to VersionHistoryContext's previewVersionUuid, fetches the snapshot, and drives the swap. Cleanup on unmount calls exitVersionPreview. DashboardPreviewBanner mounts above DashboardGrid and offers Close preview + Restore (with the existing RestoreConfirmModal). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds forkChartFromSnapshot + forkDashboardFromSnapshot that POST the
snapshot's fields to the resource's create endpoint with a copy-name
("{original} (copy from {YYYY-MM-DD})") and return the new id. The
Explore and dashboard mount components fetch the snapshot for the
selected version, fork it, toast on success, and navigate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Exercises ENTER/EXIT_VERSION_PREVIEW across all three reducers (state, slices, layout), the captured-original roundtrip, and the chart + dashboard fork POST bodies built from a snapshot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Correctness: - Preserve the original live-state capture when switching between two version previews (A → B without exit no longer corrupts the exit target). - Clear redux-undo history at the boundaries of preview enter/exit so Ctrl+Z in a later edit-mode session can't walk back into a historical layout. - Reload after a successful restore so the in-memory entity reflects the new backend state (Redux slices were silently stale otherwise). - Validate the ?version_uuid= URL param against a canonical UUID regex before it reaches SupersetClient endpoints. - Cancel stale useVersionList responses via a request-id token so a late reply can't clobber a newer one. - Clean up the ?version_uuid= URL param on DashboardPreviewBridge unmount so reloading the page doesn't silently re-enter preview. Type safety: - Extend Slice.uuid, DashboardInfo.uuid, DashboardState.versionPreview and VersionSnapshot.changes so the previously-cast call sites type-check without `as unknown as` ladders. Resilience / UX: - useRestoreVersion now logs and surfaces the server error detail instead of swallowing the rejection. - Fork-action helpers accept an optional ownerId so the new resource starts with the current user as an owner. - Defensive fallback for formatVersionUser when name + username are both empty. - PreviewBanner buttons are both disabled while a restore is in flight. A11y / API: - Replace the row-level <button> in VersionItem with a div role=button + keyboard handler so the dropdown trigger isn't nested inside another native button. - Add useRequiredVersionHistory variant that throws when no provider is mounted (useVersionHistory continues to return a no-op stub for the legitimate "feature flag off" case). Tests: - Add a thunk-level A → B preview switch test that pins the new preserve-original behavior. - Verify exitVersionPreview is a no-op when no preview is active. - Add an owners-included case to the fork-action tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The chart-side menu item was rendering but inert because state.explore.slice.uuid was never populated — Slice.data didn't serialize it, so the bootstrap payload arrived without uuid, the ExploreVersionHistoryRoot never wrapped the tree, and the menu fell back to the no-op stub from useVersionHistory. Backend: - Include uuid in Slice.data so the Explore bootstrap payload carries it through hydrateExplore into state.explore.slice. SliceSchema gets the matching field for OpenAPI parity. Frontend: - Switch ExploreChartHeader to useOptionalVersionHistory so the menu item gate (slice && onOpenVersionHistory && featureEnabled) is only truthy when a provider has actually mounted. The previous stub fallback rendered the item but did nothing on click. - Tag both Explore and Dashboard provider mounts with a data-test="version-history-provider-mount" marker for Playwright smoke tests to detect mount. Tests: - Backend: Slice.data now returns uuid (covered with + without uuid). - Frontend: hydrateExplore preserves slice.uuid through to state.explore.slice. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dashboard preview crash: The snapshot endpoint for dashboards returns only scalar columns plus a ``_version`` metadata blob — there is no ``slices`` field today. enterVersionPreview was replacing sliceEntities.slices with the normalized (and therefore empty) snapshot value, wiping every chart entry. DragDroppable then looked up each CHART- component's chartId in an empty map, got undefined, and crashed on ``.type``. Fix: merge the snapshot's slices on top of the live ones rather than replacing. The live map wins for ids the snapshot doesn't carry; the snapshot wins for ids it does (matching the eventual contract). The ``slices`` field is still optional in the payload — once the backend emits per-version slice form_data, callers get the right behavior without any further frontend change. Guard: enterVersionPreview now bails (returns false) when the parsed position_json lacks ROOT_ID / GRID_ID structure. The Dashboard mount shows ``Snapshot is missing layout structure, cannot preview`` and backs out of preview mode instead of dispatching a malformed swap. Banner Invalid Date: ExplorePreviewBanner read issued_at off the snapshot root, but the single-version endpoint nests version metadata under ``_version`` and omits root-level ``issued_at``. The banner now prefers the matching list-row's issued_at (already loaded via useVersionList), falls back to ``snapshot._version.issued_at``, and renders an empty string only if both are missing — no more "Invalid Date" rendering. Tests: - enterVersionPreview merges live slices when the snapshot omits them. - enterVersionPreview bails on missing / empty position_json. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SupersetClient.get returns ``{ json: { result: ... } }`` but the
snapshot hook was setting the envelope itself as the snapshot value.
Every downstream consumer then saw ``snapshot.position_json`` /
``snapshot.issued_at`` / ``snapshot.params`` as undefined.
The dashboard guard added in the previous commit caught the
position_json absence and bailed into the danger toast, which masked
the real bug — healthy snapshots were being rejected. The chart
preview's "Invalid Date" was the same root cause for issued_at, masked
by the list-row fallback.
useVersionList already unwraps the envelope; useVersionSnapshot now
matches that contract.
Test: new __tests__/useVersionSnapshot.test.ts pins the unwrap with a
realistic dashboard payload and asserts position_json + issued_at land
at the root of the returned snapshot.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #40334 +/- ##
===========================================
- Coverage 64.16% 41.61% -22.55%
===========================================
Files 2591 769 -1822
Lines 138293 65065 -73228
Branches 32084 7929 -24155
===========================================
- Hits 88737 27080 -61657
+ Misses 48026 37269 -10757
+ Partials 1530 716 -814
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
#1 Fork handlers unwrap json.result. ExploreVersionHistoryMount and DashboardVersionHistoryMount were passing the SupersetClient envelope straight to the fork helpers, so every fork POSTed an empty body and the new resource ended up without datasource / params / layout. #2 useRestoreVersion returns { ok, error } directly. The previous lastError-via-state approach hit a stale-closure bug: the danger toast read the pre-restore state value instead of the current error detail, silently dropping the formatted message. #3 Dashboard menu gates on useOptionalVersionHistory. The strict useVersionHistory stub fallback returns a truthy openPanel, so the menu item rendered as an inert click target when the provider wasn't mounted (uuid not yet loaded). Symmetric with the chart-side fix in 40fb5ac. #4 DashboardPreviewBanner surfaces the restore error detail. Mirrors the side panel's behavior now that #2 exposes the error directly. #5 Drop _version.issued_at / _version.changes fallback ladders. These were workarounds for the envelope unwrap bug fixed in 98e2217; with the unwrap in place the root-level issued_at is populated and the fallback chain only created noise. #13 Consistent fork id reading. forkDashboardFromSnapshot stops double-checking json.result.id; both fork endpoints return the id at the root. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ss 3 Selecting a historical version of a dashboard now updates the visible title, description, slug, CSS, json_metadata, and published flag — not only the slice grid. The Header reads its H1 from ``layout[DASHBOARD_HEADER_ID].meta.text`` and the rest is read from ``state.dashboardInfo``; neither lives in ``dashboardLayout.present`` alone, so the previous swap left them showing live values during preview. The thunk now captures these scalars into ``dashboardState.versionPreview.capturedDashboardInfo`` on enter, applies the snapshot's values via new ``ENTER_VERSION_PREVIEW`` / ``EXIT_VERSION_PREVIEW`` handlers in ``dashboardInfo.ts``, and injects the snapshot's ``dashboard_title`` into ``DASHBOARD_HEADER_ID`` as a defense (some snapshots' ``position_json`` lacks the header block). A → B switches preserve the original captured live state so exit always restores the user's pre-preview values. For charts, a new ``ChartPreviewSliceOverrides`` shape on ``ChartPreviewContext`` carries snapshot ``slice_name``, ``description``, ``certified_by`` and ``certification_details``; ``ExploreChartHeader`` prefers those during preview without mutating ``state.explore.slice``, preserving the "chart preview = no Redux mutation" invariant. Title editing and Save are also disabled while previewing. Also rolls in the second-pass review follow-ups: - useVersionList: cancellation race test (issued vs. winning request). - dashboardPreview: snapshot-wins merge direction + dashboardInfo capture/restore coverage. - DashboardPreviewBridge: adopt strict useRequiredVersionHistory; bridge is the sole owner of exitVersionPreview cleanup (banner stops double-dispatching). - VersionItem: actions dropdown moved out of the clickable row so role=button no longer nests inside role=button. - formatVersionDate: empty-string guard returns '' instead of "Invalid Date". - useVersionSnapshot: chart-shape unwrap test pinned alongside the existing dashboard-shape one. - VersionSnapshot: index signature replaced with a tight field set (scalars, slices, position_json, _version, etc.); migrated the few ``as unknown as`` ladders this enabled. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SUMMARY
Adds the production frontend for entity version history on top of the backend in #39603. Implements the "View version history" experience on Explore (chart edit page) and Dashboard view. Gated behind a new
VERSION_HISTORYfeature flag (defaultFalse).🚧 Draft — stacks on #39603. PR base is currently
sc-103156-versioning(a mirror of #39603's head branch on apache/superset, pushed to make the diff reviewable in isolation). Once #39603 merges to master, I'll re-base this branch onto master and re-target the PR base. The current diff is only the frontend changes (+4326 / -30 lines, ~54 files insuperset-frontend/plus the feature-flag entry insuperset/config.pyand a smallSlice.dataserialization patch).What this PR adds:
superset-frontend/src/features/versionHistory/:VersionHistoryPanel— right-side Drawer with version list, search, date-grouped sectionsPreviewBanner— top banner shown while previewing a historical version, with Close / Open as new / Restore actionsRestoreConfirmModal— destructive-action confirmationuseVersionList,useVersionSnapshot,useRestoreVersionVersionHistoryContext(panel/preview state, URL roundtrip via?version_uuid=) andChartPreviewContext(chart shadow-render ofform_data+ slice scalars)summarizeChange,formatChangeTitle,groupVersionsByDate,snapshotToFormData,forkActions— change-formatting logic ported from the debug dropdown in feat(versioning): capture and expose version history for charts, dashboards, and datasets #39603hasUnsavedChangesform_dataviaChartPreviewContextduring preview — no Redux mutation. Slice-level scalar fields (description, name display) also shadow-rendered.form_dataand POSTs a new chart with name pattern{original} (copy from {date})Slice.databackend serialization now exposesuuidso the chart's frontend can address the versioning endpointsENTER_VERSION_PREVIEW/EXIT_VERSION_PREVIEWactions, four reducers extended (dashboardState,sliceEntities,dashboardLayout,dashboardInfo),undoableDashboardLayout.TRACKED_ACTIONSupdated. Enter captures livesliceEntities+dashboardLayout+ scalardashboardInfofields and swaps in the snapshot's values; exit restores the captured originals. No refetch.DashboardPreviewBannermounts aboveDashboardGridinDashboardContainer.tsx/api/v1/dashboard/with the snapshot's fieldsVERSION_HISTORYadded tosuperset/config.pyDEFAULT_FEATURE_FLAGS(defaultFalse), the frontendFeatureFlagenum, anddocs/static/feature-flags.json.What is NOT in this PR (deferred to follow-up work):
changed_byis rendered as the user name; a chatbot-author tag is deferred until a stable identity convention lands.position_jsonis versioned as an opaque blob (restored wholesale on dashboard restore); finer-grained layout versioning is a follow-up.BEFORE/AFTER SCREENSHOTS
To attach after stack-base settles.
TESTING INSTRUCTIONS
Enable the flag and seed history against #39603's endpoints.
superset_config.py:Chart preview + restore:
View version history. Right-side panel opens listing versions newest-first, with author + timestamp.form_data(including title and description in the header), Save button is disabled.Restore this version→ confirm modal → success toast → panel refreshes with the restore as the new latest version.Chart fork (Open as new chart):
Open as new chart.{original} (copy from {YYYY-MM-DD}). Success toast. Navigates to the new chart's explore URL.Dashboard preview + restore:
View version history. Panel opens. (In edit mode the menu item is disabled with a tooltip.)Close preview→ grid + scalars restore to live state.Restore this version→ confirm modal → success → page reflects the restored state.Dashboard fork (Open as new dashboard):
Open as new dashboard. New dashboard created with snapshot's layout. Navigate.Feature flag off: disable
VERSION_HISTORY→ menu items vanish, panels do not mount, no console errors.Tests:
ADDITIONAL INFORMATION
VERSION_HISTORY(added in this PR)Depends on: #39603 (backend versioning endpoints). Base will be re-pointed to
masterafter #39603 merges.