kgabryje commented on PR #41551:
URL: https://github.com/apache/superset/pull/41551#issuecomment-5165080487

   # Round 2 — verification of the response batch, re-pinned to `91706dff80`
   
   Re-ran the same reviewers over `075241a2f7..3b16cc409e`, then re-verified 
everything at `91706dff80` after you pushed the staleness-edge commit mid-pass. 
Every claim below re-checked at that head myself. Thanks for the 
commit-by-commit disposition — it made this a fast pass.
   
   **Most of the batch is right, and the headline fixes are solid.** Two things 
need another look: the batch introduces **two regressions of its own**, both in 
code added to fix round-1 findings, and one deferral rests on a premise the 
source contradicts.
   
   ---
   
   ## Confirmed fixed
   
   **The re-selection wedge, and the test is genuinely load-bearing.** The 
reducer guard closes both routes. I walked the state machine and it holds on 
every path: exit → reselect the same version works (`CLEAR_VERSION_PREVIEW` 
nulls `preview`, so the `?.` makes the comparison miss), close-panel → reopen 
works, A → B → A works, and restore-while-previewing works because 
`confirmRestore` clears the preview before dispatching `versionRestored`. 
`appliedVersionRef` is nulled on both the restore and exit branches, and inside 
`apply()` the staleness check precedes the ref write, so a superseded apply 
can't set it.
   
   More usefully: **the adopted test was run against a reverted reducer and it 
fails.**
   
   ```
   $ npx jest src/features/versionHistory/previewReselect     # with the guard 
commented out
     ✕ re-selecting the already-previewed version does not re-enter applying
       Expected: false
       Received: true
         > 125 |   expect(applying()).toBe(false);
   
   $ git checkout -- .../reducer.ts && npx jest .../previewReselect
   Tests: 1 passed
   ```
   
   Not a tautology. Suites also stay green — the round-1 commands go 274 → 277 
with no regressions, and 186/186 including the newly-touched 
`DatasourceControl` and `Tab` suites.
   
   Also verified: `Control.tsx:87-91` marked; `UPDATE_CHART_TITLE` logged with 
the constant genuinely pinned to `exploreActions` by a test — and it has 
exactly one dispatcher (the editable title's `onSave`, which only fires when 
actually dirty), so it can't manufacture phantom entries; `Tab.tsx:159-166` 
gated; `hydrateWith` symmetric across entry/exit/restore with **no fourth call 
site**; `ActivityOperation` `'update'` reaching `describeRecord`'s generic tail 
with no dead branch; the docs caveat now honest about preview fidelity.
   
   A re-sweep for unmarked `setControlValue` inside effects across `src/`, 
`packages/` and `plugins/` now comes back **clean for core explore code**. The 
only residue is `plugin-chart-cartodiagram/.../OlChartMap.tsx:217`, which is 
structurally unmarkable through the plugin bridge — consistent with what you 
noted.
   
   **And `91706dff80` closed one I was about to file.** My draft had the 
probe's reset boundary as a Low: `probeIdRef` was bumped when the reset's *own* 
probe launched rather than at `fetchPage` entry, so on an A → B switch A's 
probe could resolve before B's page-0 response and repopulate `newestGroup`. 
Moving the bump to `:115` is exactly the fix. Dropped — codeant got there first.
   
   ---
   
   ## 1. `308f5cb859` over-cancels: a search keystroke silently drops the 
post-restore rehydration
   
   Still present at `91706dff80` (`ExploreVersionHistory.tsx:189`, cleanup 
`:226`, `refreshActivity` in deps at `:232`).
   
   The `cancelled` flag is scoped to the **effect run**, so its cleanup fires 
on every re-run, not only on unmount. The dependency array includes 
`refreshActivity`, whose identity is not stable:
   
   - `refreshActivity` = `activity.refresh` = `useCallback(…, [fetchPage])` 
(`useVersionActivity.ts:249-251`)
   - `fetchPage` = `useCallback(…, [entityType, uuid, include, q])`
   - `q` is the **debounced search term** (`ExploreVersionHistory.tsx:153-159`)
   
   So every time the debounce fires on a new search string — or the include 
filter changes — the effect re-runs and cancels the in-flight 
`fetchExploreRehydrationData`. And it never re-issues: 
`lastRestoreCountRef.current` was already synced, so the re-run takes the `else 
if` branch.
   
   **Repro:** restore a version, then type in the panel's search box within the 
rehydration round trip (~200–800 ms). The timeline shows the new "Restored 
version" entry and the success toast fired, but the chart still renders the 
**pre-restore** state — and the failure toast is suppressed too, so there's no 
signal. Only a reload recovers. That's worse than the bug the commit fixed, 
because the page silently contradicts itself.
   
   The goal is right — an unmount or a save-as slice swap really should 
invalidate the hydrate. The issue is binding the guard to *effect identity* 
rather than to the thing being guarded. **The correct pattern is the one you 
used twice in this same batch**: a dedicated counter bumped only by unmount or 
a newer restore, as in the newest-self probe and the dashboard preview's 
unmount cleanup. A `restoreHydrationIdRef`, or a resolve-time re-check that 
`sliceId`/`uuid` still match, closes it.
   
   ## 2. `d704f6ae88` makes an Edit Dataset reconciliation invisible to the 
restore dirty gate
   
   Still present at `91706dff80` (`DatasourceControl/index.tsx:276`).
   
   The mark is correct for a dataset **swap**: `ChangeDatasourceModal:187` 
calls `onChange(...)`, which routes through `Control.tsx` and emits the primary 
non-programmatic `SET_FIELD_VALUE` for `datasource`, so the log still goes 
dirty and the derived granularity write is rightly suppressed as a duplicate.
   
   But `handleDatasourceSave` also serves **Edit Dataset**, and that route 
passes no `onChange` — `DatasourceModal` receives only `onDatasourceSave` 
(`DatasourceControl/index.tsx:595-600`). It dispatches `changeDatasource`, 
which emits `SET_DATASOURCE` + `UPDATE_FORM_DATA_BY_DATASOURCE`, neither of 
which the middleware records.
   
   So when editing dataset metadata invalidates the chart's time column 
(`savedDatasource.type === 'table' && !isGranularitySqlaTemporal`), the chart's 
`granularity_sqla` is rewritten and **that write was the only session entry on 
this path** — now suppressed. The form has changed while `useVersionActions` 
sees an empty log, so a restore rehydrates Explore and discards the reconciled 
value with no warning. Before this commit that path blocked restore.
   
   The new tests do exercise Edit Dataset — `openAndSaveChanges` clicks 
`edit-dataset` — but assert only that the fourth argument is `{programmatic: 
true}`, never the resulting dirty behaviour.
   
   The derived write can stay programmatic; the dirty signal needs to account 
for the primary datasource reconciliation. Which is really an argument for the 
baseline-diffing fix you've already queued, rather than another point patch.
   
   ## 3. The dashboard `extra_editors` deferral — the payload does carry the 
field
   
   Still `editors`-only at `91706dff80` (`permissionUtils.ts:57-63`). I'd push 
back on the deferral, because the fix looks frontend-only rather than 
backend-first. The chain:
   
   - `superset/dashboards/api.py:612-614` sets `result["extra_editors"] = 
get_extra_editor_subject_ids(dash)` **after** `schema.dump(dash)`, so the 
`columns`/`DASHBOARD_GET_COLUMNS` projection never strips it
   - `useDashboard` spreads `...dashboard` wholesale 
(`hooks/apiResources/dashboards.ts:52-65`)
   - `hydrate.ts:358` spreads `...dashboard` into `dashboardInfo`
   
   The field is present exactly where `canUserEditDashboard` runs. It's gated 
on `EXTRA_EDITORS_RESOLVER` being configured — which is precisely the 
deployment where the bug bites, so the claim fails in the only case that 
matters.
   
   Your other point stands and is the better framing: `canUserEditDashboard` 
has the same omission platform-wide on the Edit button, so widening the shared 
predicate to intersect `editors ∪ extra_editors` (and typing the field on 
`Dashboard`) corrects Edit, the menu and restore together. That's a frontend 
change, and it doesn't need to wait on a backend one.
   
   ---
   
   ## On the deferrals
   
   Characterisations read as honest, with two scope notes:
   
   - **Session-log false positives.** "Over-blocking, not data loss" is 
accurate for the append-only/latching half. But finding 2 above shows the 
framing can't cover the dirty system as a whole — the same log is the restore 
safety signal, and programmatic suppression makes it fail *open*, not closed. 
Also still open in this family: `AnnotationLayerControl/index.tsx:108-116` 
dispatches an unmarked `setControlValue` from its validation-sync effect, so a 
failing annotation query can create a phantom edit on an untouched chart.
   - **Preview scalars.** The membership half genuinely needs the backend 
shape. The **scalar** half doesn't: the generic snapshot already projects every 
version-table scalar (`versioning/queries.py:491-501`), including 
description/theme/certification/published/slug — preview just doesn't apply 
them. Likewise "Open as new" is a copy-endpoint contract issue 
(`DashboardCopySchema` accepts only title/CSS/metadata + `duplicate_slices`), 
not a snapshot-shape dependency, and `version-history.mdx:92-96` still says it 
copies "the selected version" without qualification. Both are frontend/schema 
changes that don't have to wait.
   - **Wire types.** The two you named remain, and the set is slightly larger: 
the chart version table permits null for uuid, name, datasource id/type and viz 
type, all typed non-null on the client. Still type hygiene — worth modelling 
the whole shape once rather than the two examples.
   
   One small consequence of the rename fix worth knowing: renaming a chart and 
renaming it back now leaves an entry (collapsed to one, since both carry 
`controlName: 'slice_name'`) and blocks restore where round-1 code didn't. 
That's clearly the right trade — silently destroying a rename is far worse — 
but it slightly widens the latching problem until baseline diffing lands.
   
   Also noting, since it changed the reading: the `?edit=true` justification 
comment says previews can only be entered from view mode, but the gate tests 
`hasUnsavedChanges`, not `editMode` — a user can be in edit mode with nothing 
dirty and preview from there. Forcing `false` is still right; the stated reason 
just isn't the invariant.
   
   Nothing above touches `canOverwriteSlice`, the restore funnel, the scoped 
`VERSION_RESTORED` consumers or the flag-off path — all re-confirmed untouched 
by this batch.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to