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

   # Round 3 — verification of the seven-commit batch, re-pinned to `136d0fc826`
   
   Reviewed `91706dff80..9197772542` with the same two reviewers, then 
re-verified every finding at `136d0fc826` after you pushed the four-lens commit 
mid-pass. Everything below is re-checked at that head.
   
   **Disclosure:** `136d0fc826` itself (the 
popup-block/in-flight-lock/URL-param/missing-uuid batch) landed after my 
reviewers' pass and is **not** covered by this round. Both findings below 
survive it unchanged.
   
   **All three round-2 findings are properly fixed, the unflagged surface is 
clean, and I could not find a third regression in most of the batch — but there 
is one, in the restore-hydration guard, plus a new defect in the theme 
adapter.**
   
   ---
   
   ## Confirmed fixed
   
   **Round-2 finding 1 — the over-cancellation.** The guard is now split: a 
cleanup effect keyed on `[sliceId, uuid]` bumps `restoreHydrationIdRef`, and a 
per-fetch token is captured at the fetch. `refreshActivity` remains in the main 
effect's deps but no longer cancels anything, which is exactly the distinction 
that broke the previous attempt. Revert-proved: restored to the round-2 shape 
the new test **fails** (`Number of calls: 0`), restored again 6/6 pass.
   
   **Round-2 finding 2 — the Edit Dataset dirty gate.** Recording 
`UPDATE_FORM_DATA_BY_DATASOURCE` rather than special-casing the derived write 
is the better fix. Both routes now go dirty; on the swap route 
`ChangeDatasourceModal` invokes `onDatasourceSave` before `onChange`, so the 
two entries are consecutive with the same `controlName` and the reducer 
genuinely collapses them. A full sweep found no automated producer of that 
action type.
   
   **Round-2 finding 3 — `extra_editors`.** Fixed, and splitting the unflagged 
widening into #42708 is the right call. I diffed the two: functionally 
identical on the three shared files, differing only in comment wording. One 
note — the branch commit carries a **fourth** file (`canOverwriteSlice.ts`) 
that #42708 doesn't, and the comment wording differs on the shared files, so 
`2a0dae0e8e` will **conflict** rather than rebase away cleanly when the split 
PR merges.
   
   **The unflagged surface is clean, and I checked it rather than trusting the 
"refactor" label.** `canOverwriteSlice`'s new form agrees with the old for 
every input: the slice/`is_managed_externally` guards fire first, 
`canOverwrite` and admin short-circuit in the same order, both collections 
treat null/undefined as empty, and the `number | {id}` normaliser is unchanged. 
`isUserDashboardEditor`'s widening is strict — all prior prerequisites 
retained, membership only widened. Neither can narrow for any supported input.
   
   **Both newly-recorded action types are safe** — the round-2 trap didn't 
recur with the polarity flipped. `SET_EXPLORE_CONTROLS` (whose value is 
confusingly `'UPDATE_EXPLORE_CONTROLS'`, now pinned by test) has one 
dispatcher, gated on `action === 'POP'` plus two further conditions. Neither 
fires on hydration, save-as, or a programmatic rebuild.
   
   Also verified: the theme-lookup race fix (`266f6764a9`) — no await now 
remains between the cache commit and `hydrateWith`, and I walked every 
statement in that stretch; `AnnotationLayerControl:114-121` marked; the three 
snapshot-nullability guards each address a real path. Suites 407 green across 
three commands, nothing previously green regressed.
   
   ---
   
   ## 1. The new restore token no longer invalidates on a same-chart save — 
Medium
   
   This one my two reviewers disagreed on, so I settled it against the round-2 
source directly.
   
   `20627e44e1` correctly stops search/include churn from cancelling the 
post-restore GET, but it narrows invalidation too far. The token is bumped 
**only** by the cleanup on `[sliceId, uuid]` and when another restore begins. 
The same-chart save branch calls `refreshActivity()` and never bumps it.
   
   The round-2 implementation *did* invalidate on save — not by design, but 
because `saveSignal` was in the effect's dependency array and the cleanup ran 
on every re-run. So the fix removed a valid boundary along with the invalid 
one. I confirmed this by reading the round-2 file at `91706dff80`: `saveSignal` 
is in the deps, `return () => { cancelled = true; }` is the cleanup.
   
   **Ordering:**
   1. Restore succeeds and starts `fetchExploreRehydrationData(sliceId)`.
   2. `confirmRestore` clears the preview, so Save is no longer held disabled.
   3. The user edits and overwrites the same chart; the save rehydrates newer 
server state and moves `saveSignal`.
   4. The older GET — whose server read predates the overwrite — resolves. Its 
token is still current, so `hydrateExplore` replaces the just-saved UI with the 
stale restored data.
   
   The server then holds the new save while the page shows the older restore 
until reload, and a further save can persist edits made from that stale base. 
The window needs the rehydration GET still in flight when the user saves, so 
it's tight on a fast network and realistic on a slow one — I'd rate it Medium 
on impact, lower on likelihood.
   
   Capturing a save generation at fetch start and rejecting on mismatch, or 
bumping `restoreHydrationIdRef` on a non-initial save signal, preserves the 
boundary you had while still excluding search/include identity churn. There's 
no save-mid-fetch test; the new test covers search and the older one covers 
unmount.
   
   ## 2. `fetchDashboardTheme` overwrites the theme id it just parsed — Medium
   
   ```ts
   const { id, result } = json as { id: number; result: Omit<DashboardTheme, 
'id'> };
   return { ...result, id };
   ```
   
   `result.id` is definitely present and correct — `show_columns` includes 
`"id"` (`superset/themes/api.py:95-109`) and the integration test asserts 
`data["result"]["id"] == theme.id`. The adapter spreads that correct value and 
then overwrites it with a top-level `id`.
   
   I could **not** confirm from source whether FAB's `show` envelope carries a 
top-level `id` — the package isn't installed in my checkout, and the only 
superset test reading `data["id"]` is on a **POST**, where FAB certainly does 
return one. So I'll state the two cases rather than assert one:
   
   - if `show` has no top-level `id`, the theme is hydrated with `id: 
undefined`;
   - if it has one (FAB's documented show envelope suggests it does, typically 
stringified), the numeric `result.id` is replaced by a string.
   
   Either way the spread is wrong and the fix is the same: return `json.result` 
as the complete `DashboardTheme`. `Header/index.tsx:290-298` reads that id in 
production. Worth confirming against your pinned flask-appbuilder rather than 
taking my reading — I flagged the same version caveat on #41550.
   
   The tests can't catch this: `useDashboardVersionPreview.test.tsx` mocks 
`fetchDashboardTheme` itself and returns an already-correct object, and 
`api.test.ts` doesn't cover the adapter. An adapter-level fetch mock returning 
`{ result: { id, theme_name, json_data } }` would.
   
   ---
   
   ## Two scope claims are still overstated
   
   - **"Every snapshot scalar."** `is_managed_externally` and `external_url` 
are in the dashboard shadow table and projected by the snapshot endpoint, but 
are absent from both the snapshot interface and the hydrate overlay — while 
restore, going through Continuum's `revert`, *does* restore them. Also 
`published: null` previews as `false`, and a failed theme lookup shows the live 
theme. Preview and restore therefore still don't agree on every scalar. 
Excluding historical external-management state from permission gates may well 
be deliberate — if so the claim, tests and docs should say so rather than 
assert completeness.
   - **"The whole wire shape."** Chart snapshots still lack typed `id`, 
`datasource_name`, `certified_by`, `certification_details`, 
`is_managed_externally`, `external_url`; dashboard snapshots lack `id`, 
`is_managed_externally`, `external_url`. And `query_context` is still typed 
required although `Slice.__versioned__` excludes it and `slices_version` has no 
such column — so the server omits the key rather than sending null. The index 
signature hides all of these. Related: the docs now say Open as new copies 
chart query context, but `createChartFromSnapshot` can only conditionally 
include a property the endpoint never supplies.
   
   ---
   
   Worth saying plainly: three of the four items you self-found this round were 
real, and finding the race your own scalars commit introduced — before I did — 
is the thing that most improves my confidence in this branch. Naming the 
pattern and proposing baseline diffing and `listenerMiddleware` as flag-on 
gates is the right conclusion to draw from three batches of writer-enumeration 
and ref-coordination bugs; both are what I'd have proposed. Finding 1 above is 
arguably the fourth instance of exactly that class, which I'd treat as further 
evidence for the second of those two rather than as an argument for another 
point patch.
   


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