From fd36836dcddc6d884ed8e978944e3fe4280f610f Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 26 Nov 2024 17:05:34 -0700 Subject: [PATCH] [dashboard] fix Reset dashboard breaks panel state when ran after editor edits (#201687) Closes https://github.com/elastic/kibana/issues/201627 ### The problem `ReactEmbeddableRender` uses `lastSavedRuntimeState` as last saved state baseline. Resetting reverts to this state. ``` // Code sample from src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx const serializedState = parentApi.getSerializedStateForChild(uuid); const lastSavedRuntimeState = serializedState ? await factory.deserializeState(serializedState) : ({} as RuntimeState); ... const unsavedChanges = initializeUnsavedChanges( lastSavedRuntimeState, parentApi, comparators ); ``` `DashboardContainer` getSerializedStateForChild implemenation ``` // Code sample from src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx public getSerializedStateForChild = (childId: string) => { const rawState = this.getInput().panels[childId].explicitInput; const { id, ...serializedState } = rawState; if (!rawState || Object.keys(serializedState).length === 0) return; const references = getReferencesForPanelId(childId, this.savedObjectReferences); return { rawState, // references from old installations may not be prefixed with panel id // fall back to passing all references in these cases to preserve backwards compatability references: references.length > 0 ? references : this.savedObjectReferences, }; }; ``` The problem is that `create_dashboard` clears `this.getInput().panels[childId].explicitInput` for a panel with embeddable transfer state. This causes `lastSavedRuntimeState` to be an empty object, which causes reset to use `undefined` for the previous value when reseting an embeddables keys. ``` // Code sample from src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts const incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.(); if ( incomingEmbeddable.embeddableId && Boolean(initialDashboardInput.panels[incomingEmbeddable.embeddableId]) ) { // this embeddable already exists, we will update the explicit input. const panelToUpdate = initialDashboardInput.panels[incomingEmbeddable.embeddableId]; const sameType = panelToUpdate.type === incomingEmbeddable.type; panelToUpdate.type = incomingEmbeddable.type; const nextRuntimeState = { // if the incoming panel is the same type as what was there before we can safely spread the old panel's explicit input ...(sameType ? panelToUpdate.explicitInput : {}), ...incomingEmbeddable.input, id: incomingEmbeddable.embeddableId, // maintain hide panel titles setting. hidePanelTitles: panelToUpdate.explicitInput.hidePanelTitles, }; if (embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) { panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id }; runtimePanelsToRestore[incomingEmbeddable.embeddableId] = nextRuntimeState; } else { panelToUpdate.explicitInput = nextRuntimeState; } } ``` ### The fix The solution is to retain `panelToUpdate.explicitInput` original value so that `lastSavedRuntimeState` is set to the original runtime state and reset will revert to this value instead of `{}`. ### test instructions 1) install web logs sample data 2) create new dashboard 3) click "Add panel" and select "Legacy => Agg based" 4) create pie chart with terms split on "machine.os", click "Save and return" 5) save dashboard 6) edit pie chart, Under "options", unselect "donut". Click "Save and return" 7) click reset in dashboard. Verify that visualization returns to original state. Note, the same will not happen for map embeddables. There is a bug in map embeddables where resetting "attributes" state does not update the current map. Co-authored-by: Elastic Machine (cherry picked from commit 289bb1684abb65b53db650a67c5dac8acc72df39) --- .../dashboard_container/embeddable/create/create_dashboard.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 2510f2e015dfb..d5314c274531f 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -362,7 +362,6 @@ export const initializeDashboard = async ({ hidePanelTitles: panelToUpdate.explicitInput.hidePanelTitles, }; if (embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type)) { - panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id }; runtimePanelsToRestore[incomingEmbeddable.embeddableId] = nextRuntimeState; } else { panelToUpdate.explicitInput = nextRuntimeState;