Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dashboard] fix Reset dashboard breaks panel state when ran after editor edits #201687

Merged
merged 2 commits into from
Nov 27, 2024

Conversation

nreese
Copy link
Contributor

@nreese nreese commented Nov 25, 2024

Closes #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<RuntimeState>(
  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.

@nreese
Copy link
Contributor Author

nreese commented Nov 25, 2024

/ci

@elasticmachine
Copy link
Contributor

💔 Build Failed

Failed CI Steps

Metrics [docs]

‼️ ERROR: no builds found for mergeBase sha [d3ee297]

@nreese
Copy link
Contributor Author

nreese commented Nov 25, 2024

@elasticmachine merge upstream

@nreese
Copy link
Contributor Author

nreese commented Nov 25, 2024

/ci

@nreese nreese marked this pull request as ready for review November 26, 2024 02:29
@nreese nreese requested a review from a team as a code owner November 26, 2024 02:29
@nreese nreese added release_note:fix Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas project:embeddableRebuild labels Nov 26, 2024
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-presentation (Team:Presentation)

Copy link
Contributor

@Heenawter Heenawter left a comment

Choose a reason for hiding this comment

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

Code review + local test. Changes LGTM - thanks for the thorough explanation, that helped a lot :)

@nreese nreese merged commit 289bb16 into elastic:main Nov 27, 2024
28 checks passed
@kibanamachine
Copy link
Contributor

Starting backport for target branches: 8.15, 8.16, 8.17

https://github.com/elastic/kibana/actions/runs/12041134726

kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Nov 27, 2024
…tor edits (elastic#201687)

Closes elastic#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<RuntimeState>(
  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 <[email protected]>
(cherry picked from commit 289bb16)
kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Nov 27, 2024
…tor edits (elastic#201687)

Closes elastic#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<RuntimeState>(
  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 <[email protected]>
(cherry picked from commit 289bb16)
@kibanamachine
Copy link
Contributor

💔 Some backports could not be created

Status Branch Result
8.15 Backport failed because of merge conflicts
8.16
8.17

Note: Successful backport PRs will be merged automatically after passing CI.

Manual backport

To create the backport manually run:

node scripts/backport --pr 201687

Questions ?

Please refer to the Backport tool documentation

kibanamachine added a commit that referenced this pull request Nov 27, 2024
…ter editor edits (#201687) (#201896)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)](#201687)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Nathan
Reese","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-27T00:05:34Z","message":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor edits
(#201687)\n\nCloses
https://github.com/elastic/kibana/issues/201627\r\n\r\n### The
problem\r\n`ReactEmbeddableRender` uses `lastSavedRuntimeState` as last
saved state\r\nbaseline. Resetting reverts to this state.\r\n```\r\n//
Code sample from
src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx\r\nconst
serializedState = parentApi.getSerializedStateForChild(uuid);\r\nconst
lastSavedRuntimeState = serializedState\r\n ? await
factory.deserializeState(serializedState)\r\n : ({} as
RuntimeState);\r\n\r\n...\r\n\r\nconst unsavedChanges =
initializeUnsavedChanges<RuntimeState>(\r\n lastSavedRuntimeState,\r\n
parentApi,\r\n comparators\r\n);\r\n```\r\n\r\n`DashboardContainer`
getSerializedStateForChild implemenation\r\n```\r\n// Code sample from
src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx\r\npublic
getSerializedStateForChild = (childId: string) => {\r\n const rawState =
this.getInput().panels[childId].explicitInput;\r\n const { id,
...serializedState } = rawState;\r\n if (!rawState ||
Object.keys(serializedState).length === 0) return;\r\n const references
= getReferencesForPanelId(childId, this.savedObjectReferences);\r\n
return {\r\n rawState,\r\n // references from old installations may not
be prefixed with panel id\r\n // fall back to passing all references in
these cases to preserve backwards compatability\r\n references:
references.length > 0 ? references : this.savedObjectReferences,\r\n
};\r\n };\r\n```\r\n\r\nThe problem is that `create_dashboard`
clears\r\n`this.getInput().panels[childId].explicitInput` for a panel
with\r\nembeddable transfer state. This causes `lastSavedRuntimeState`
to be an\r\nempty object, which causes reset to use `undefined` for the
previous\r\nvalue when reseting an embeddables keys.\r\n```\r\n// Code
sample from
src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts\r\nconst
incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.();\r\nif
(\r\n incomingEmbeddable.embeddableId &&\r\n
Boolean(initialDashboardInput.panels[incomingEmbeddable.embeddableId])\r\n)
{\r\n // this embeddable already exists, we will update the explicit
input.\r\n const panelToUpdate =
initialDashboardInput.panels[incomingEmbeddable.embeddableId];\r\n const
sameType = panelToUpdate.type === incomingEmbeddable.type;\r\n\r\n
panelToUpdate.type = incomingEmbeddable.type;\r\n const nextRuntimeState
= {\r\n // if the incoming panel is the same type as what was there
before we can safely spread the old panel's explicit input\r\n
...(sameType ? panelToUpdate.explicitInput : {}),\r\n\r\n
...incomingEmbeddable.input,\r\n id:
incomingEmbeddable.embeddableId,\r\n\r\n // maintain hide panel titles
setting.\r\n hidePanelTitles:
panelToUpdate.explicitInput.hidePanelTitles,\r\n };\r\n if
(embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type))
{\r\n panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id
};\r\n runtimePanelsToRestore[incomingEmbeddable.embeddableId] =
nextRuntimeState;\r\n } else {\r\n panelToUpdate.explicitInput =
nextRuntimeState;\r\n }\r\n}\r\n```\r\n\r\n### The fix\r\nThe solution
is to retain `panelToUpdate.explicitInput` original value\r\nso that
`lastSavedRuntimeState` is set to the original runtime state
and\r\nreset will revert to this value instead of `{}`.\r\n\r\n### test
instructions\r\n1) install web logs sample data\r\n2) create new
dashboard\r\n3) click \"Add panel\" and select \"Legacy => Agg
based\"\r\n4) create pie chart with terms split on \"machine.os\", click
\"Save and\r\nreturn\"\r\n5) save dashboard\r\n6) edit pie chart, Under
\"options\", unselect \"donut\". Click \"Save and\r\nreturn\"\r\n7)
click reset in dashboard. Verify that visualization returns
to\r\noriginal state. Note, the same will not happen for map
embeddables.\r\nThere is a bug in map embeddables where resetting
\"attributes\" state\r\ndoes not update the current
map.\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"289bb1684abb65b53db650a67c5dac8acc72df39","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Presentation","v9.0.0","project:embeddableRebuild","backport:version","v8.17.0","v8.16.2","v8.15.6"],"title":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor
edits","number":201687,"url":"https://github.com/elastic/kibana/pull/201687","mergeCommit":{"message":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor edits
(#201687)\n\nCloses
https://github.com/elastic/kibana/issues/201627\r\n\r\n### The
problem\r\n`ReactEmbeddableRender` uses `lastSavedRuntimeState` as last
saved state\r\nbaseline. Resetting reverts to this state.\r\n```\r\n//
Code sample from
src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx\r\nconst
serializedState = parentApi.getSerializedStateForChild(uuid);\r\nconst
lastSavedRuntimeState = serializedState\r\n ? await
factory.deserializeState(serializedState)\r\n : ({} as
RuntimeState);\r\n\r\n...\r\n\r\nconst unsavedChanges =
initializeUnsavedChanges<RuntimeState>(\r\n lastSavedRuntimeState,\r\n
parentApi,\r\n comparators\r\n);\r\n```\r\n\r\n`DashboardContainer`
getSerializedStateForChild implemenation\r\n```\r\n// Code sample from
src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx\r\npublic
getSerializedStateForChild = (childId: string) => {\r\n const rawState =
this.getInput().panels[childId].explicitInput;\r\n const { id,
...serializedState } = rawState;\r\n if (!rawState ||
Object.keys(serializedState).length === 0) return;\r\n const references
= getReferencesForPanelId(childId, this.savedObjectReferences);\r\n
return {\r\n rawState,\r\n // references from old installations may not
be prefixed with panel id\r\n // fall back to passing all references in
these cases to preserve backwards compatability\r\n references:
references.length > 0 ? references : this.savedObjectReferences,\r\n
};\r\n };\r\n```\r\n\r\nThe problem is that `create_dashboard`
clears\r\n`this.getInput().panels[childId].explicitInput` for a panel
with\r\nembeddable transfer state. This causes `lastSavedRuntimeState`
to be an\r\nempty object, which causes reset to use `undefined` for the
previous\r\nvalue when reseting an embeddables keys.\r\n```\r\n// Code
sample from
src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts\r\nconst
incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.();\r\nif
(\r\n incomingEmbeddable.embeddableId &&\r\n
Boolean(initialDashboardInput.panels[incomingEmbeddable.embeddableId])\r\n)
{\r\n // this embeddable already exists, we will update the explicit
input.\r\n const panelToUpdate =
initialDashboardInput.panels[incomingEmbeddable.embeddableId];\r\n const
sameType = panelToUpdate.type === incomingEmbeddable.type;\r\n\r\n
panelToUpdate.type = incomingEmbeddable.type;\r\n const nextRuntimeState
= {\r\n // if the incoming panel is the same type as what was there
before we can safely spread the old panel's explicit input\r\n
...(sameType ? panelToUpdate.explicitInput : {}),\r\n\r\n
...incomingEmbeddable.input,\r\n id:
incomingEmbeddable.embeddableId,\r\n\r\n // maintain hide panel titles
setting.\r\n hidePanelTitles:
panelToUpdate.explicitInput.hidePanelTitles,\r\n };\r\n if
(embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type))
{\r\n panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id
};\r\n runtimePanelsToRestore[incomingEmbeddable.embeddableId] =
nextRuntimeState;\r\n } else {\r\n panelToUpdate.explicitInput =
nextRuntimeState;\r\n }\r\n}\r\n```\r\n\r\n### The fix\r\nThe solution
is to retain `panelToUpdate.explicitInput` original value\r\nso that
`lastSavedRuntimeState` is set to the original runtime state
and\r\nreset will revert to this value instead of `{}`.\r\n\r\n### test
instructions\r\n1) install web logs sample data\r\n2) create new
dashboard\r\n3) click \"Add panel\" and select \"Legacy => Agg
based\"\r\n4) create pie chart with terms split on \"machine.os\", click
\"Save and\r\nreturn\"\r\n5) save dashboard\r\n6) edit pie chart, Under
\"options\", unselect \"donut\". Click \"Save and\r\nreturn\"\r\n7)
click reset in dashboard. Verify that visualization returns
to\r\noriginal state. Note, the same will not happen for map
embeddables.\r\nThere is a bug in map embeddables where resetting
\"attributes\" state\r\ndoes not update the current
map.\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"289bb1684abb65b53db650a67c5dac8acc72df39"}},"sourceBranch":"main","suggestedTargetBranches":["8.17","8.16","8.15"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201687","number":201687,"mergeCommit":{"message":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor edits
(#201687)\n\nCloses
https://github.com/elastic/kibana/issues/201627\r\n\r\n### The
problem\r\n`ReactEmbeddableRender` uses `lastSavedRuntimeState` as last
saved state\r\nbaseline. Resetting reverts to this state.\r\n```\r\n//
Code sample from
src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx\r\nconst
serializedState = parentApi.getSerializedStateForChild(uuid);\r\nconst
lastSavedRuntimeState = serializedState\r\n ? await
factory.deserializeState(serializedState)\r\n : ({} as
RuntimeState);\r\n\r\n...\r\n\r\nconst unsavedChanges =
initializeUnsavedChanges<RuntimeState>(\r\n lastSavedRuntimeState,\r\n
parentApi,\r\n comparators\r\n);\r\n```\r\n\r\n`DashboardContainer`
getSerializedStateForChild implemenation\r\n```\r\n// Code sample from
src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx\r\npublic
getSerializedStateForChild = (childId: string) => {\r\n const rawState =
this.getInput().panels[childId].explicitInput;\r\n const { id,
...serializedState } = rawState;\r\n if (!rawState ||
Object.keys(serializedState).length === 0) return;\r\n const references
= getReferencesForPanelId(childId, this.savedObjectReferences);\r\n
return {\r\n rawState,\r\n // references from old installations may not
be prefixed with panel id\r\n // fall back to passing all references in
these cases to preserve backwards compatability\r\n references:
references.length > 0 ? references : this.savedObjectReferences,\r\n
};\r\n };\r\n```\r\n\r\nThe problem is that `create_dashboard`
clears\r\n`this.getInput().panels[childId].explicitInput` for a panel
with\r\nembeddable transfer state. This causes `lastSavedRuntimeState`
to be an\r\nempty object, which causes reset to use `undefined` for the
previous\r\nvalue when reseting an embeddables keys.\r\n```\r\n// Code
sample from
src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts\r\nconst
incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.();\r\nif
(\r\n incomingEmbeddable.embeddableId &&\r\n
Boolean(initialDashboardInput.panels[incomingEmbeddable.embeddableId])\r\n)
{\r\n // this embeddable already exists, we will update the explicit
input.\r\n const panelToUpdate =
initialDashboardInput.panels[incomingEmbeddable.embeddableId];\r\n const
sameType = panelToUpdate.type === incomingEmbeddable.type;\r\n\r\n
panelToUpdate.type = incomingEmbeddable.type;\r\n const nextRuntimeState
= {\r\n // if the incoming panel is the same type as what was there
before we can safely spread the old panel's explicit input\r\n
...(sameType ? panelToUpdate.explicitInput : {}),\r\n\r\n
...incomingEmbeddable.input,\r\n id:
incomingEmbeddable.embeddableId,\r\n\r\n // maintain hide panel titles
setting.\r\n hidePanelTitles:
panelToUpdate.explicitInput.hidePanelTitles,\r\n };\r\n if
(embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type))
{\r\n panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id
};\r\n runtimePanelsToRestore[incomingEmbeddable.embeddableId] =
nextRuntimeState;\r\n } else {\r\n panelToUpdate.explicitInput =
nextRuntimeState;\r\n }\r\n}\r\n```\r\n\r\n### The fix\r\nThe solution
is to retain `panelToUpdate.explicitInput` original value\r\nso that
`lastSavedRuntimeState` is set to the original runtime state
and\r\nreset will revert to this value instead of `{}`.\r\n\r\n### test
instructions\r\n1) install web logs sample data\r\n2) create new
dashboard\r\n3) click \"Add panel\" and select \"Legacy => Agg
based\"\r\n4) create pie chart with terms split on \"machine.os\", click
\"Save and\r\nreturn\"\r\n5) save dashboard\r\n6) edit pie chart, Under
\"options\", unselect \"donut\". Click \"Save and\r\nreturn\"\r\n7)
click reset in dashboard. Verify that visualization returns
to\r\noriginal state. Note, the same will not happen for map
embeddables.\r\nThere is a bug in map embeddables where resetting
\"attributes\" state\r\ndoes not update the current
map.\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"289bb1684abb65b53db650a67c5dac8acc72df39"}},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.15","label":"v8.15.6","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Nathan Reese <[email protected]>
kibanamachine added a commit that referenced this pull request Nov 27, 2024
…ter editor edits (#201687) (#201897)

# Backport

This will backport the following commits from `main` to `8.17`:
- [[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)](#201687)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Nathan
Reese","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-27T00:05:34Z","message":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor edits
(#201687)\n\nCloses
https://github.com/elastic/kibana/issues/201627\r\n\r\n### The
problem\r\n`ReactEmbeddableRender` uses `lastSavedRuntimeState` as last
saved state\r\nbaseline. Resetting reverts to this state.\r\n```\r\n//
Code sample from
src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx\r\nconst
serializedState = parentApi.getSerializedStateForChild(uuid);\r\nconst
lastSavedRuntimeState = serializedState\r\n ? await
factory.deserializeState(serializedState)\r\n : ({} as
RuntimeState);\r\n\r\n...\r\n\r\nconst unsavedChanges =
initializeUnsavedChanges<RuntimeState>(\r\n lastSavedRuntimeState,\r\n
parentApi,\r\n comparators\r\n);\r\n```\r\n\r\n`DashboardContainer`
getSerializedStateForChild implemenation\r\n```\r\n// Code sample from
src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx\r\npublic
getSerializedStateForChild = (childId: string) => {\r\n const rawState =
this.getInput().panels[childId].explicitInput;\r\n const { id,
...serializedState } = rawState;\r\n if (!rawState ||
Object.keys(serializedState).length === 0) return;\r\n const references
= getReferencesForPanelId(childId, this.savedObjectReferences);\r\n
return {\r\n rawState,\r\n // references from old installations may not
be prefixed with panel id\r\n // fall back to passing all references in
these cases to preserve backwards compatability\r\n references:
references.length > 0 ? references : this.savedObjectReferences,\r\n
};\r\n };\r\n```\r\n\r\nThe problem is that `create_dashboard`
clears\r\n`this.getInput().panels[childId].explicitInput` for a panel
with\r\nembeddable transfer state. This causes `lastSavedRuntimeState`
to be an\r\nempty object, which causes reset to use `undefined` for the
previous\r\nvalue when reseting an embeddables keys.\r\n```\r\n// Code
sample from
src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts\r\nconst
incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.();\r\nif
(\r\n incomingEmbeddable.embeddableId &&\r\n
Boolean(initialDashboardInput.panels[incomingEmbeddable.embeddableId])\r\n)
{\r\n // this embeddable already exists, we will update the explicit
input.\r\n const panelToUpdate =
initialDashboardInput.panels[incomingEmbeddable.embeddableId];\r\n const
sameType = panelToUpdate.type === incomingEmbeddable.type;\r\n\r\n
panelToUpdate.type = incomingEmbeddable.type;\r\n const nextRuntimeState
= {\r\n // if the incoming panel is the same type as what was there
before we can safely spread the old panel's explicit input\r\n
...(sameType ? panelToUpdate.explicitInput : {}),\r\n\r\n
...incomingEmbeddable.input,\r\n id:
incomingEmbeddable.embeddableId,\r\n\r\n // maintain hide panel titles
setting.\r\n hidePanelTitles:
panelToUpdate.explicitInput.hidePanelTitles,\r\n };\r\n if
(embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type))
{\r\n panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id
};\r\n runtimePanelsToRestore[incomingEmbeddable.embeddableId] =
nextRuntimeState;\r\n } else {\r\n panelToUpdate.explicitInput =
nextRuntimeState;\r\n }\r\n}\r\n```\r\n\r\n### The fix\r\nThe solution
is to retain `panelToUpdate.explicitInput` original value\r\nso that
`lastSavedRuntimeState` is set to the original runtime state
and\r\nreset will revert to this value instead of `{}`.\r\n\r\n### test
instructions\r\n1) install web logs sample data\r\n2) create new
dashboard\r\n3) click \"Add panel\" and select \"Legacy => Agg
based\"\r\n4) create pie chart with terms split on \"machine.os\", click
\"Save and\r\nreturn\"\r\n5) save dashboard\r\n6) edit pie chart, Under
\"options\", unselect \"donut\". Click \"Save and\r\nreturn\"\r\n7)
click reset in dashboard. Verify that visualization returns
to\r\noriginal state. Note, the same will not happen for map
embeddables.\r\nThere is a bug in map embeddables where resetting
\"attributes\" state\r\ndoes not update the current
map.\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"289bb1684abb65b53db650a67c5dac8acc72df39","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Presentation","v9.0.0","project:embeddableRebuild","backport:version","v8.17.0","v8.16.2","v8.15.6"],"title":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor
edits","number":201687,"url":"https://github.com/elastic/kibana/pull/201687","mergeCommit":{"message":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor edits
(#201687)\n\nCloses
https://github.com/elastic/kibana/issues/201627\r\n\r\n### The
problem\r\n`ReactEmbeddableRender` uses `lastSavedRuntimeState` as last
saved state\r\nbaseline. Resetting reverts to this state.\r\n```\r\n//
Code sample from
src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx\r\nconst
serializedState = parentApi.getSerializedStateForChild(uuid);\r\nconst
lastSavedRuntimeState = serializedState\r\n ? await
factory.deserializeState(serializedState)\r\n : ({} as
RuntimeState);\r\n\r\n...\r\n\r\nconst unsavedChanges =
initializeUnsavedChanges<RuntimeState>(\r\n lastSavedRuntimeState,\r\n
parentApi,\r\n comparators\r\n);\r\n```\r\n\r\n`DashboardContainer`
getSerializedStateForChild implemenation\r\n```\r\n// Code sample from
src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx\r\npublic
getSerializedStateForChild = (childId: string) => {\r\n const rawState =
this.getInput().panels[childId].explicitInput;\r\n const { id,
...serializedState } = rawState;\r\n if (!rawState ||
Object.keys(serializedState).length === 0) return;\r\n const references
= getReferencesForPanelId(childId, this.savedObjectReferences);\r\n
return {\r\n rawState,\r\n // references from old installations may not
be prefixed with panel id\r\n // fall back to passing all references in
these cases to preserve backwards compatability\r\n references:
references.length > 0 ? references : this.savedObjectReferences,\r\n
};\r\n };\r\n```\r\n\r\nThe problem is that `create_dashboard`
clears\r\n`this.getInput().panels[childId].explicitInput` for a panel
with\r\nembeddable transfer state. This causes `lastSavedRuntimeState`
to be an\r\nempty object, which causes reset to use `undefined` for the
previous\r\nvalue when reseting an embeddables keys.\r\n```\r\n// Code
sample from
src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts\r\nconst
incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.();\r\nif
(\r\n incomingEmbeddable.embeddableId &&\r\n
Boolean(initialDashboardInput.panels[incomingEmbeddable.embeddableId])\r\n)
{\r\n // this embeddable already exists, we will update the explicit
input.\r\n const panelToUpdate =
initialDashboardInput.panels[incomingEmbeddable.embeddableId];\r\n const
sameType = panelToUpdate.type === incomingEmbeddable.type;\r\n\r\n
panelToUpdate.type = incomingEmbeddable.type;\r\n const nextRuntimeState
= {\r\n // if the incoming panel is the same type as what was there
before we can safely spread the old panel's explicit input\r\n
...(sameType ? panelToUpdate.explicitInput : {}),\r\n\r\n
...incomingEmbeddable.input,\r\n id:
incomingEmbeddable.embeddableId,\r\n\r\n // maintain hide panel titles
setting.\r\n hidePanelTitles:
panelToUpdate.explicitInput.hidePanelTitles,\r\n };\r\n if
(embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type))
{\r\n panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id
};\r\n runtimePanelsToRestore[incomingEmbeddable.embeddableId] =
nextRuntimeState;\r\n } else {\r\n panelToUpdate.explicitInput =
nextRuntimeState;\r\n }\r\n}\r\n```\r\n\r\n### The fix\r\nThe solution
is to retain `panelToUpdate.explicitInput` original value\r\nso that
`lastSavedRuntimeState` is set to the original runtime state
and\r\nreset will revert to this value instead of `{}`.\r\n\r\n### test
instructions\r\n1) install web logs sample data\r\n2) create new
dashboard\r\n3) click \"Add panel\" and select \"Legacy => Agg
based\"\r\n4) create pie chart with terms split on \"machine.os\", click
\"Save and\r\nreturn\"\r\n5) save dashboard\r\n6) edit pie chart, Under
\"options\", unselect \"donut\". Click \"Save and\r\nreturn\"\r\n7)
click reset in dashboard. Verify that visualization returns
to\r\noriginal state. Note, the same will not happen for map
embeddables.\r\nThere is a bug in map embeddables where resetting
\"attributes\" state\r\ndoes not update the current
map.\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"289bb1684abb65b53db650a67c5dac8acc72df39"}},"sourceBranch":"main","suggestedTargetBranches":["8.17","8.16","8.15"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201687","number":201687,"mergeCommit":{"message":"[dashboard]
fix Reset dashboard breaks panel state when ran after editor edits
(#201687)\n\nCloses
https://github.com/elastic/kibana/issues/201627\r\n\r\n### The
problem\r\n`ReactEmbeddableRender` uses `lastSavedRuntimeState` as last
saved state\r\nbaseline. Resetting reverts to this state.\r\n```\r\n//
Code sample from
src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx\r\nconst
serializedState = parentApi.getSerializedStateForChild(uuid);\r\nconst
lastSavedRuntimeState = serializedState\r\n ? await
factory.deserializeState(serializedState)\r\n : ({} as
RuntimeState);\r\n\r\n...\r\n\r\nconst unsavedChanges =
initializeUnsavedChanges<RuntimeState>(\r\n lastSavedRuntimeState,\r\n
parentApi,\r\n comparators\r\n);\r\n```\r\n\r\n`DashboardContainer`
getSerializedStateForChild implemenation\r\n```\r\n// Code sample from
src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx\r\npublic
getSerializedStateForChild = (childId: string) => {\r\n const rawState =
this.getInput().panels[childId].explicitInput;\r\n const { id,
...serializedState } = rawState;\r\n if (!rawState ||
Object.keys(serializedState).length === 0) return;\r\n const references
= getReferencesForPanelId(childId, this.savedObjectReferences);\r\n
return {\r\n rawState,\r\n // references from old installations may not
be prefixed with panel id\r\n // fall back to passing all references in
these cases to preserve backwards compatability\r\n references:
references.length > 0 ? references : this.savedObjectReferences,\r\n
};\r\n };\r\n```\r\n\r\nThe problem is that `create_dashboard`
clears\r\n`this.getInput().panels[childId].explicitInput` for a panel
with\r\nembeddable transfer state. This causes `lastSavedRuntimeState`
to be an\r\nempty object, which causes reset to use `undefined` for the
previous\r\nvalue when reseting an embeddables keys.\r\n```\r\n// Code
sample from
src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts\r\nconst
incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.();\r\nif
(\r\n incomingEmbeddable.embeddableId &&\r\n
Boolean(initialDashboardInput.panels[incomingEmbeddable.embeddableId])\r\n)
{\r\n // this embeddable already exists, we will update the explicit
input.\r\n const panelToUpdate =
initialDashboardInput.panels[incomingEmbeddable.embeddableId];\r\n const
sameType = panelToUpdate.type === incomingEmbeddable.type;\r\n\r\n
panelToUpdate.type = incomingEmbeddable.type;\r\n const nextRuntimeState
= {\r\n // if the incoming panel is the same type as what was there
before we can safely spread the old panel's explicit input\r\n
...(sameType ? panelToUpdate.explicitInput : {}),\r\n\r\n
...incomingEmbeddable.input,\r\n id:
incomingEmbeddable.embeddableId,\r\n\r\n // maintain hide panel titles
setting.\r\n hidePanelTitles:
panelToUpdate.explicitInput.hidePanelTitles,\r\n };\r\n if
(embeddableService.reactEmbeddableRegistryHasKey(incomingEmbeddable.type))
{\r\n panelToUpdate.explicitInput = { id: panelToUpdate.explicitInput.id
};\r\n runtimePanelsToRestore[incomingEmbeddable.embeddableId] =
nextRuntimeState;\r\n } else {\r\n panelToUpdate.explicitInput =
nextRuntimeState;\r\n }\r\n}\r\n```\r\n\r\n### The fix\r\nThe solution
is to retain `panelToUpdate.explicitInput` original value\r\nso that
`lastSavedRuntimeState` is set to the original runtime state
and\r\nreset will revert to this value instead of `{}`.\r\n\r\n### test
instructions\r\n1) install web logs sample data\r\n2) create new
dashboard\r\n3) click \"Add panel\" and select \"Legacy => Agg
based\"\r\n4) create pie chart with terms split on \"machine.os\", click
\"Save and\r\nreturn\"\r\n5) save dashboard\r\n6) edit pie chart, Under
\"options\", unselect \"donut\". Click \"Save and\r\nreturn\"\r\n7)
click reset in dashboard. Verify that visualization returns
to\r\noriginal state. Note, the same will not happen for map
embeddables.\r\nThere is a bug in map embeddables where resetting
\"attributes\" state\r\ndoes not update the current
map.\r\n\r\nCo-authored-by: Elastic Machine
<[email protected]>","sha":"289bb1684abb65b53db650a67c5dac8acc72df39"}},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.15","label":"v8.15.6","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Nathan Reese <[email protected]>
jbudz added a commit to jbudz/kibana that referenced this pull request Nov 27, 2024
@jbudz
Copy link
Member

jbudz commented Nov 27, 2024

Partially reverted on main only via #201902. See https://buildkite.com/elastic/kibana-on-merge/builds/55970 for failures . Maybe due to a conflict with #186642.

@nreese
Copy link
Contributor Author

nreese commented Nov 27, 2024

Partially reverted on main only via #201902. See https://buildkite.com/elastic/kibana-on-merge/builds/55970 for failures . Maybe due to a conflict with #186642.

Should this be reverted on the other branches as well?

@jbudz
Copy link
Member

jbudz commented Nov 27, 2024

Remaining reverts / backports in progress, tracked in #201902

kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Nov 27, 2024
…fter editor edits (elastic#201687)" (elastic#201902)

This reverts commit 289bb16.

(cherry picked from commit a0f88d9)
kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Nov 27, 2024
…fter editor edits (elastic#201687)" (elastic#201902)

This reverts commit 289bb16.

(cherry picked from commit a0f88d9)
kibanamachine added a commit that referenced this pull request Nov 27, 2024
…e when ran after editor edits (#201687)&quot; (#201902) (#202019)

# Backport

This will backport the following commits from `main` to `8.17`:
- [Revert &quot;[dashboard] fix Reset dashboard breaks panel state when
ran after editor edits (#201687)&quot;
(#201902)](#201902)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Jon","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-27T04:53:22Z","message":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)\" (#201902)\n\nThis reverts commit
289bb16.","sha":"a0f88d90134f8c49b32cffe23f438a91f77c7055","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["v9.0.0","backport:version","v8.17.0","v8.16.2"],"title":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits
(#201687)\"","number":201902,"url":"https://github.com/elastic/kibana/pull/201902","mergeCommit":{"message":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)\" (#201902)\n\nThis reverts commit
289bb16.","sha":"a0f88d90134f8c49b32cffe23f438a91f77c7055"}},"sourceBranch":"main","suggestedTargetBranches":["8.17","8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201902","number":201902,"mergeCommit":{"message":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)\" (#201902)\n\nThis reverts commit
289bb16.","sha":"a0f88d90134f8c49b32cffe23f438a91f77c7055"}},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Jon <[email protected]>
kibanamachine added a commit that referenced this pull request Nov 27, 2024
…e when ran after editor edits (#201687)&quot; (#201902) (#202018)

# Backport

This will backport the following commits from `main` to `8.16`:
- [Revert &quot;[dashboard] fix Reset dashboard breaks panel state when
ran after editor edits (#201687)&quot;
(#201902)](#201902)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Jon","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-27T04:53:22Z","message":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)\" (#201902)\n\nThis reverts commit
289bb16.","sha":"a0f88d90134f8c49b32cffe23f438a91f77c7055","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["v9.0.0","backport:version","v8.17.0","v8.16.2"],"title":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits
(#201687)\"","number":201902,"url":"https://github.com/elastic/kibana/pull/201902","mergeCommit":{"message":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)\" (#201902)\n\nThis reverts commit
289bb16.","sha":"a0f88d90134f8c49b32cffe23f438a91f77c7055"}},"sourceBranch":"main","suggestedTargetBranches":["8.17","8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201902","number":201902,"mergeCommit":{"message":"Revert
\"[dashboard] fix Reset dashboard breaks panel state when ran after
editor edits (#201687)\" (#201902)\n\nThis reverts commit
289bb16.","sha":"a0f88d90134f8c49b32cffe23f438a91f77c7055"}},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Jon <[email protected]>
dej611 added a commit that referenced this pull request Nov 29, 2024
## Summary
Fixes: #190737, #190802, #190990, #179307

This test started failing after @nreese Reset PR was merged here:
#201687 and the Lens embeddable
refactor PR introducing a regression which affected the test.
Later on the PR was reverted with
#201902 .


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
CAWilson94 pushed a commit to CAWilson94/kibana that referenced this pull request Dec 9, 2024
…2275)

## Summary
Fixes: elastic#190737, elastic#190802, elastic#190990, elastic#179307

This test started failing after @nreese Reset PR was merged here:
elastic#201687 and the Lens embeddable
refactor PR introducing a regression which affected the test.
Later on the PR was reverted with
elastic#201902 .


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
CAWilson94 pushed a commit to CAWilson94/kibana that referenced this pull request Dec 12, 2024
…tor edits (elastic#201687)

Closes elastic#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<RuntimeState>(
  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 <[email protected]>
CAWilson94 pushed a commit to CAWilson94/kibana that referenced this pull request Dec 12, 2024
CAWilson94 pushed a commit to CAWilson94/kibana that referenced this pull request Dec 12, 2024
…2275)

## Summary
Fixes: elastic#190737, elastic#190802, elastic#190990, elastic#179307

This test started failing after @nreese Reset PR was merged here:
elastic#201687 and the Lens embeddable
refactor PR introducing a regression which affected the test.
Later on the PR was reverted with
elastic#201902 .


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:version Backport to applied version labels project:embeddableRebuild release_note:fix reverted Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas v8.15.6 v8.16.2 v8.17.0 v9.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Embeddable] Reset dashboard breaks panel state when ran after editor edits
5 participants