kibana/examples/embeddable_examples/public/plugin.ts
Devon Thomson 3e882d8cd9
[Embeddables] Serialized State Only (#215947)
Closes https://github.com/elastic/kibana/issues/205531
Closes #219877.
Closes https://github.com/elastic/kibana/issues/213153
Closes https://github.com/elastic/kibana/issues/150920
Closes https://github.com/elastic/kibana/issues/203130
 
### Overview
The embeddable framework has two types of state: `SerializedState` and
`RuntimeState`.

`SerializedState` is the form of the state when saved into a Dashboard
saved object. I.e. the References are extracted, and state saved
externally (by reference) is removed. In contrast `RuntimeState` is an
exact snapshot of the state used by the embeddable to render.

<b>Exposing SerializedState and RuntimeState was a mistake</b> that
caused numerous regressions and architectural complexities.

This PR simplifies the embeddable framework by only exposing
`SerializedState`. `RuntimeState` stays localized to the embeddable
implementation and is never leaked to the embeddable framework.

### Whats changed
* `ReactEmbeddableFactory<SerializedState, RuntimeState, Api>` =>
`EmbeddableFactory<SerializedState, Api>`
* `deserializeState` removed from embeddable factory. Instead,
`SerializedState` is passed directly into `buildEmbeddable`.
* `buildEmbeddable` parameter `buildApi` replaced with `finalizeApi`.
`buildApi({ api, comparators })` => `finalizeApi(api)`.
* The embeddable framework previously used its knowledge of
`RuntimeState` to setup and monitor unsaved changes. Now, unsaved
changes setup is pushed down to the embeddable implementation since the
embeddable framework no longer has knowledge of embeddable RuntimeState.

### Reviewer instructions
<b>Please prioritize reviews.</b> This is a large effort from our team
and is blocking many other initiatives. Getting this merged is a top
priority.

This is a large change that would best be reviewed by manually testing
the changes
* adding/editing your embeddable types
* Ensuring dashboard shows unsaved changes as expected
* Ensuring dashboard resets unsaved changes as expected
* Ensuring dashboard does not show unsaved changes after save and reset
* Returning to a dashboard with unsaved changes renders embeddables with
those unsaved changes

---------

Co-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>
Co-authored-by: Nathan Reese <reese.nathan@elastic.co>
Co-authored-by: Nick Peihl <nick.peihl@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Catherine Liu <catherine.liu@elastic.co>
Co-authored-by: Ola Pawlus <98127445+olapawlus@users.noreply.github.com>
2025-05-06 15:08:34 -06:00

107 lines
4.7 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { DashboardStart } from '@kbn/dashboard-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { setupApp } from './app/setup_app';
import { DATA_TABLE_ID } from './react_embeddables/data_table/constants';
import { registerCreateDataTableAction } from './react_embeddables/data_table/create_data_table_action';
import { EUI_MARKDOWN_ID } from './react_embeddables/eui_markdown/constants';
import { registerCreateEuiMarkdownAction } from './react_embeddables/eui_markdown/create_eui_markdown_action';
import { FIELD_LIST_ID } from './react_embeddables/field_list/constants';
import { registerCreateFieldListAction } from './react_embeddables/field_list/create_field_list_action';
import { registerFieldListPanelPlacementSetting } from './react_embeddables/field_list/register_field_list_embeddable';
import { SAVED_BOOK_ID } from './react_embeddables/saved_book/constants';
import { registerCreateSavedBookAction } from './react_embeddables/saved_book/create_saved_book_action';
import { registerAddSearchPanelAction } from './react_embeddables/search/register_add_search_panel_action';
import { registerSearchEmbeddable } from './react_embeddables/search/register_search_embeddable';
export interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
embeddable: EmbeddableSetup;
uiActions: UiActionsStart;
}
export interface StartDeps {
dataViews: DataViewsPublicPluginStart;
dataViewFieldEditor: DataViewFieldEditorStart;
embeddable: EmbeddableStart;
uiActions: UiActionsStart;
data: DataPublicPluginStart;
charts: ChartsPluginStart;
fieldFormats: FieldFormatsStart;
dashboard: DashboardStart;
}
export class EmbeddableExamplesPlugin implements Plugin<void, void, SetupDeps, StartDeps> {
public setup(core: CoreSetup<StartDeps>, { embeddable, developerExamples }: SetupDeps) {
setupApp(core, developerExamples);
const startServicesPromise = core.getStartServices();
embeddable.registerReactEmbeddableFactory(FIELD_LIST_ID, async () => {
const { getFieldListFactory } = await import(
'./react_embeddables/field_list/field_list_embeddable'
);
const [coreStart, deps] = await startServicesPromise;
return getFieldListFactory(coreStart, deps);
});
embeddable.registerReactEmbeddableFactory(EUI_MARKDOWN_ID, async () => {
const { markdownEmbeddableFactory } = await import(
'./react_embeddables/eui_markdown/eui_markdown_react_embeddable'
);
return markdownEmbeddableFactory;
});
embeddable.registerReactEmbeddableFactory(DATA_TABLE_ID, async () => {
const { getDataTableFactory } = await import(
'./react_embeddables/data_table/data_table_react_embeddable'
);
const [coreStart, deps] = await startServicesPromise;
return getDataTableFactory(coreStart, deps);
});
embeddable.registerReactEmbeddableFactory(SAVED_BOOK_ID, async () => {
const { getSavedBookEmbeddableFactory } = await import(
'./react_embeddables/saved_book/saved_book_react_embeddable'
);
const [coreStart] = await startServicesPromise;
return getSavedBookEmbeddableFactory(coreStart);
});
registerSearchEmbeddable(
embeddable,
new Promise((resolve) => startServicesPromise.then(([_, startDeps]) => resolve(startDeps)))
);
}
public start(core: CoreStart, deps: StartDeps) {
registerCreateFieldListAction(deps.uiActions);
registerFieldListPanelPlacementSetting(deps.dashboard);
registerCreateEuiMarkdownAction(deps.uiActions);
registerAddSearchPanelAction(deps.uiActions);
registerCreateDataTableAction(deps.uiActions);
registerCreateSavedBookAction(deps.uiActions, core);
}
public stop() {}
}