Skip to content

Commit

Permalink
[Embeddables rebuild] Support for by reference embeddables (#182523)
Browse files Browse the repository at this point in the history
Adds first-class by reference support to the new Embeddable framework and adds an example of how a new-styled by reference embeddable could work.
  • Loading branch information
ThomThomson authored May 21, 2024
1 parent 95ad2f7 commit 53435ea
Show file tree
Hide file tree
Showing 53 changed files with 1,451 additions and 508 deletions.
21 changes: 7 additions & 14 deletions examples/embeddable_examples/public/app/render_examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,14 @@ import { SEARCH_EMBEDDABLE_ID } from '../react_embeddables/search/constants';
import type { SearchApi, SearchSerializedState } from '../react_embeddables/search/types';

export const RenderExamples = () => {
const initialState = useMemo(() => {
return {
rawState: {
timeRange: undefined,
},
references: [],
};
// only run onMount
}, []);

const parentApi = useMemo(() => {
return {
reload$: new Subject<void>(),
getSerializedStateForChild: () => ({
rawState: {
timeRange: undefined,
},
}),
timeRange$: new BehaviorSubject<TimeRange>({
from: 'now-24h',
to: 'now',
Expand Down Expand Up @@ -85,8 +80,7 @@ export const RenderExamples = () => {
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{`<ReactEmbeddableRenderer<State, Api>
type={SEARCH_EMBEDDABLE_ID}
state={initialState}
parentApi={parentApi}
getParentApi={() => parentApi}
onApiAvailable={(newApi) => {
setApi(newApi);
}}
Expand All @@ -107,8 +101,7 @@ export const RenderExamples = () => {
<ReactEmbeddableRenderer<SearchSerializedState, SearchApi>
key={hidePanelChrome ? 'hideChrome' : 'showChrome'}
type={SEARCH_EMBEDDABLE_ID}
state={initialState}
parentApi={parentApi}
getParentApi={() => parentApi}
onApiAvailable={(newApi) => {
setApi(newApi);
}}
Expand Down
14 changes: 13 additions & 1 deletion examples/embeddable_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ 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 { registerCreateFieldListAction } from './react_embeddables/field_list/create_field_list_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';

Expand Down Expand Up @@ -73,6 +75,14 @@ export class EmbeddableExamplesPlugin implements Plugin<void, void, SetupDeps, S
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)))
Expand All @@ -88,6 +98,8 @@ export class EmbeddableExamplesPlugin implements Plugin<void, void, SetupDeps, S
registerAddSearchPanelAction(deps.uiActions);

registerCreateDataTableAction(deps.uiActions);

registerCreateSavedBookAction(deps.uiActions, core);
}

public stop() {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import { BehaviorSubject } from 'rxjs';
import { BookAttributes, BookAttributesManager } from './types';

export const defaultBookAttributes: BookAttributes = {
bookTitle: 'Pillars of the earth',
authorName: 'Ken follett',
numberOfPages: 973,
bookSynopsis:
'A spellbinding epic set in 12th-century England, The Pillars of the Earth tells the story of the struggle to build the greatest Gothic cathedral the world has known.',
};

export const stateManagerFromAttributes = (attributes: BookAttributes): BookAttributesManager => {
const bookTitle = new BehaviorSubject<string>(attributes.bookTitle);
const authorName = new BehaviorSubject<string>(attributes.authorName);
const numberOfPages = new BehaviorSubject<number>(attributes.numberOfPages);
const bookSynopsis = new BehaviorSubject<string | undefined>(attributes.bookSynopsis);

return {
bookTitle,
authorName,
numberOfPages,
bookSynopsis,
comparators: {
bookTitle: [bookTitle, (val) => bookTitle.next(val)],
authorName: [authorName, (val) => authorName.next(val)],
numberOfPages: [numberOfPages, (val) => numberOfPages.next(val)],
bookSynopsis: [bookSynopsis, (val) => bookSynopsis.next(val)],
},
};
};

export const serializeBookAttributes = (stateManager: BookAttributesManager): BookAttributes => ({
bookTitle: stateManager.bookTitle.value,
authorName: stateManager.authorName.value,
numberOfPages: stateManager.numberOfPages.value,
bookSynopsis: stateManager.bookSynopsis.value,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

export const SAVED_BOOK_ID = 'book';
export const ADD_SAVED_BOOK_ACTION_ID = 'create_saved_book';
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import {
defaultBookAttributes,
serializeBookAttributes,
stateManagerFromAttributes,
} from './book_state';
import { ADD_SAVED_BOOK_ACTION_ID, SAVED_BOOK_ID } from './constants';
import { openSavedBookEditor } from './saved_book_editor';
import { saveBookAttributes } from './saved_book_library';
import {
BookByReferenceSerializedState,
BookByValueSerializedState,
BookSerializedState,
} from './types';

export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, core: CoreStart) => {
uiActions.registerAction<EmbeddableApiContext>({
id: ADD_SAVED_BOOK_ACTION_ID,
getIconType: () => 'folderClosed',
grouping: [embeddableExamplesGrouping],
isCompatible: async ({ embeddable }) => {
return apiIsPresentationContainer(embeddable);
},
execute: async ({ embeddable }) => {
if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError();
const newPanelStateManager = stateManagerFromAttributes(defaultBookAttributes);

const { addToLibrary } = await openSavedBookEditor(newPanelStateManager, true, core, {
parentApi: embeddable,
});

const initialState: BookSerializedState = await (async () => {
// if we're adding this to the library, we only need to return the by reference state.
if (addToLibrary) {
const savedBookId = await saveBookAttributes(
undefined,
serializeBookAttributes(newPanelStateManager)
);
return { savedBookId } as BookByReferenceSerializedState;
}
return {
attributes: serializeBookAttributes(newPanelStateManager),
} as BookByValueSerializedState;
})();

embeddable.addNewPanel<BookSerializedState>({
panelType: SAVED_BOOK_ID,
initialState,
});
},
getDisplayName: () =>
i18n.translate('embeddableExamples.savedbook.addBookAction.displayName', {
defaultMessage: 'Book',
}),
});
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_SAVED_BOOK_ACTION_ID);
};
Loading

0 comments on commit 53435ea

Please sign in to comment.