Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
67e7452
[dashboards as code] embeddable transform registry
nreese Jul 7, 2025
5b26155
transformPanelProperties
nreese Jul 7, 2025
5d74e0f
transformPanelsIn
nreese Jul 7, 2025
7a6a3aa
saved object and server-side content management
nreese Jul 8, 2025
592fada
Merge branch 'main' into embeddable_transforms
elasticmachine Jul 9, 2025
1d6d8a0
fix tslint
nreese Jul 9, 2025
ded197a
client cm
nreese Jul 9, 2025
c485d2a
fix cm on client
nreese Jul 9, 2025
5f4cb53
fix create
nreese Jul 9, 2025
e8e7749
implement transforms
nreese Jul 9, 2025
92300cc
transform in
nreese Jul 9, 2025
6bdf84e
register server transforms
nreese Jul 10, 2025
8f7618e
fix transformIn
nreese Jul 10, 2025
31f1c29
register transforms client-side
nreese Jul 10, 2025
9abc658
transform override panels
nreese Jul 10, 2025
bda58a3
clean up
nreese Jul 10, 2025
ba2df22
more cleanup
nreese Jul 10, 2025
a1d820f
harden transformIn
nreese Jul 10, 2025
7d1cd68
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Jul 10, 2025
2b51d40
[CI] Auto-commit changed files from 'node scripts/eslint_all_files --…
kibanamachine Jul 10, 2025
cdd8567
harden transformOut
nreese Jul 10, 2025
9106892
fix jest tests
nreese Jul 10, 2025
d26f4ac
Merge branch 'main' into embeddable_transforms
elasticmachine Jul 11, 2025
455429a
Merge branch 'main' into embeddable_transforms
elasticmachine Jul 14, 2025
d054015
merge with main
nreese Jul 16, 2025
c20199f
log warning on transform throw
nreese Jul 16, 2025
86bff4f
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Jul 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/embeddable_examples/common/book/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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".
*/

export const BOOK_EMBEDDABLE_TYPE = 'book';
export const BOOK_SAVED_OBJECT_TYPE = 'book';
export const BOOK_CONTENT_ID = 'book';
export const BOOK_LATEST_VERSION = 1;
17 changes: 17 additions & 0 deletions examples/embeddable_examples/common/book/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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".
*/

export type { BookEmbeddableState, BookByReferenceState } from './types';

export {
BOOK_CONTENT_ID,
BOOK_EMBEDDABLE_TYPE,
BOOK_LATEST_VERSION,
BOOK_SAVED_OBJECT_TYPE,
} from './constants';
16 changes: 16 additions & 0 deletions examples/embeddable_examples/common/book/transforms/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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 { transformIn } from './transform_in';
import { transformOut } from './transform_out';

export const bookTransforms = {
transformOut,
transformIn,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { BOOK_SAVED_OBJECT_TYPE } from '../constants';
import { BookByReferenceState, BookEmbeddableState } from '../types';

export function transformIn(state: BookEmbeddableState) {
// extract saved object reference for by-reference state
if ((state as BookByReferenceState).savedObjectId) {
const { savedObjectId, ...rest } = state as BookByReferenceState;
return {
state: rest,
references: [
{
name: 'savedObjectRef',
type: BOOK_SAVED_OBJECT_TYPE,
id: savedObjectId,
},
],
};
}

// no reference extraction needed for by-value state
return { state };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 type { Reference } from '@kbn/content-management-utils';
import type { BookState } from '../../../server';
import { BookEmbeddableState, BookEmbeddableState910 } from '../types';
import { BOOK_SAVED_OBJECT_TYPE } from '../constants';

export function transformOut(
storedState: BookEmbeddableState | BookEmbeddableState910,
references?: Reference[]
): BookEmbeddableState {
// storedState may contain legacy state stored from dashboards or URL

// 9.1.0 by-value state stored book state under attributes
if ('attributes' in storedState) {
const { attributes, ...rest } = storedState as { attributes: BookState };
return {
...attributes,
...rest,
};
}

// 9.1.0 by-reference state stored by-reference id as savedBookId
if ('savedBookId' in storedState) {
const { savedBookId, ...rest } = storedState as { savedBookId: string };
return {
...rest,
savedObjectId: savedBookId,
};
}

// inject saved object reference when by-reference
const savedObjectRef = (references ?? []).find(
({ name, type }) => name === 'savedObjectRef' && type === BOOK_SAVED_OBJECT_TYPE
);
if (savedObjectRef) {
return {
...(storedState as BookEmbeddableState),
savedObjectId: savedObjectRef.id,
};
}

// storedState is current by-value state
return storedState as BookEmbeddableState;
}
21 changes: 21 additions & 0 deletions examples/embeddable_examples/common/book/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 type { SerializedTitles } from '@kbn/presentation-publishing';
import type { BookState } from '../../server';

export interface BookByReferenceState {
savedObjectId: string;
}

export type BookEmbeddableState = SerializedTitles & (BookState | BookByReferenceState);

// Shape of stored state <=9.1
export type BookEmbeddableState910 = SerializedTitles &
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Where is this type used? Can we have all BWC related code and types isolated in a BWC file or folder?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

BookEmbeddableState910 is used to type transformOut storedState parameter as storedState: BookEmbeddableState | BookEmbeddableState910.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can we have all BWC related code and types isolated in a BWC file or folder?

I am not sure that is possible since transformOut receives current state and legacy state. Handling of both types occurs in the same method.

({ attributes: BookState } | { savedBookId: string });
17 changes: 17 additions & 0 deletions examples/embeddable_examples/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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".
*/

export type { BookEmbeddableState, BookByReferenceState } from './book';

export {
BOOK_CONTENT_ID,
BOOK_EMBEDDABLE_TYPE,
BOOK_LATEST_VERSION,
BOOK_SAVED_OBJECT_TYPE,
} from './book';
3 changes: 2 additions & 1 deletion examples/embeddable_examples/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"description": "Example app that shows how to register custom embeddables",
"plugin": {
"id": "embeddableExamples",
"server": false,
"server": true,
"browser": true,
"requiredPlugins": [
"contentManagement",
"dataViews",
"embeddable",
"uiActions",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import type { SerializedPanelState } from '@kbn/presentation-publishing';
import { BookSerializedState } from '../../react_embeddables/saved_book/types';
import { BookEmbeddableState } from '../../../common';

const SAVED_STATE_SESSION_STORAGE_KEY =
'kibana.examples.embeddables.stateManagementExample.savedState';
Expand All @@ -18,7 +18,7 @@ export const WEB_LOGS_DATA_VIEW_ID = '90943e30-9a47-11e8-b64d-95841ca0b247';

export const savedStateManager = {
clear: () => sessionStorage.removeItem(SAVED_STATE_SESSION_STORAGE_KEY),
set: (serializedState: SerializedPanelState<BookSerializedState>) =>
set: (serializedState: SerializedPanelState<BookEmbeddableState>) =>
sessionStorage.setItem(SAVED_STATE_SESSION_STORAGE_KEY, JSON.stringify(serializedState)),
get: () => {
const serializedStateJSON = sessionStorage.getItem(SAVED_STATE_SESSION_STORAGE_KEY);
Expand All @@ -28,7 +28,7 @@ export const savedStateManager = {

export const unsavedStateManager = {
clear: () => sessionStorage.removeItem(UNSAVED_STATE_SESSION_STORAGE_KEY),
set: (serializedState: SerializedPanelState<BookSerializedState>) =>
set: (serializedState: SerializedPanelState<BookEmbeddableState>) =>
sessionStorage.setItem(UNSAVED_STATE_SESSION_STORAGE_KEY, JSON.stringify(serializedState)),
get: () => {
const serializedStateJSON = sessionStorage.getItem(UNSAVED_STATE_SESSION_STORAGE_KEY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { SerializedPanelState, ViewMode } from '@kbn/presentation-publishing';
import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
import { BehaviorSubject, of } from 'rxjs';
import { SAVED_BOOK_ID } from '../../react_embeddables/saved_book/constants';
import { BookApi, BookSerializedState } from '../../react_embeddables/saved_book/types';
import { BookApi } from '../../react_embeddables/saved_book/types';
import { savedStateManager, unsavedStateManager } from './session_storage';
import { BOOK_EMBEDDABLE_TYPE, type BookEmbeddableState } from '../../../common';

const BOOK_EMBEDDABLE_ID = 'BOOK_EMBEDDABLE_ID';

Expand Down Expand Up @@ -59,7 +59,7 @@ export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStar
references: [],
};
},
setLastSavedBookState: (savedState: SerializedPanelState<BookSerializedState>) => {
setLastSavedBookState: (savedState: SerializedPanelState<BookEmbeddableState>) => {
lastSavedBookState$.next(savedState);
},
};
Expand Down Expand Up @@ -171,8 +171,8 @@ export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStar

<EuiSpacer size="m" />

<EmbeddableRenderer<BookSerializedState, BookApi>
type={SAVED_BOOK_ID}
<EmbeddableRenderer<BookEmbeddableState, BookApi>
type={BOOK_EMBEDDABLE_TYPE}
maybeId={BOOK_EMBEDDABLE_ID}
getParentApi={() => parentApi}
onApiAvailable={(api) => {
Expand Down
18 changes: 18 additions & 0 deletions examples/embeddable_examples/public/kibana_services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import { CoreStart } from '@kbn/core/public';
import type { StartDeps } from './plugin';

export let contentManagement: ContentManagementPublicStart;

export const setKibanaServices = (kibanaCore: CoreStart, deps: StartDeps) => {
contentManagement = deps.contentManagement;
};
28 changes: 17 additions & 11 deletions examples/embeddable_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ 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 { ADD_PANEL_TRIGGER, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { ADD_PANEL_TRIGGER, UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import {
ContentManagementPublicSetup,
ContentManagementPublicStart,
} from '@kbn/content-management-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';
Expand All @@ -27,18 +31,21 @@ import {
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';
import { setKibanaServices } from './kibana_services';
import { setupBookEmbeddable } from './react_embeddables/saved_book/setup_book_embeddable';

export interface SetupDeps {
contentManagement: ContentManagementPublicSetup;
developerExamples: DeveloperExamplesSetup;
embeddable: EmbeddableSetup;
uiActions: UiActionsStart;
uiActions: UiActionsSetup;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It doesn't look like we even use the UIActionsSetup dependency. I wonder if we can get rid of it?

}

export interface StartDeps {
contentManagement: ContentManagementPublicStart;
dataViews: DataViewsPublicPluginStart;
dataViewFieldEditor: DataViewFieldEditorStart;
embeddable: EmbeddableStart;
Expand All @@ -50,7 +57,10 @@ export interface StartDeps {
}

export class EmbeddableExamplesPlugin implements Plugin<void, void, SetupDeps, StartDeps> {
public setup(core: CoreSetup<StartDeps>, { embeddable, developerExamples }: SetupDeps) {
public setup(
core: CoreSetup<StartDeps>,
{ contentManagement, embeddable, developerExamples }: SetupDeps
) {
setupApp(core, developerExamples);

const startServicesPromise = core.getStartServices();
Expand Down Expand Up @@ -78,13 +88,7 @@ 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);
});
setupBookEmbeddable(core, embeddable, contentManagement);

registerSearchEmbeddable(
embeddable,
Expand All @@ -93,6 +97,8 @@ export class EmbeddableExamplesPlugin implements Plugin<void, void, SetupDeps, S
}

public start(core: CoreStart, deps: StartDeps) {
setKibanaServices(core, deps);

registerCreateFieldListAction(deps.uiActions);
registerFieldListPanelPlacementSetting(deps.dashboard);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
* License v3.0 only", 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
Expand Up @@ -14,10 +14,11 @@ import { EmbeddableApiContext, initializeStateManager } from '@kbn/presentation-
import { ADD_PANEL_TRIGGER, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import { openLazyFlyout } from '@kbn/presentation-util';
import type { BookState } from '../../../server';
import { BOOK_EMBEDDABLE_TYPE, type BookEmbeddableState } from '../../../common';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import { defaultBookAttributes } from './book_state';
import { ADD_SAVED_BOOK_ACTION_ID, SAVED_BOOK_ID } from './constants';
import { BookAttributes, BookSerializedState } from './types';
import { defaultBookState } from './default_book_state';
import { ADD_SAVED_BOOK_ACTION_ID } from './constants';

export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, core: CoreStart) => {
uiActions.registerAction<EmbeddableApiContext>({
Expand All @@ -29,9 +30,9 @@ export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, c
},
execute: async ({ embeddable }) => {
if (!apiCanAddNewPanel(embeddable)) throw new IncompatibleActionError();
const newPanelStateManager = initializeStateManager<BookAttributes>(
defaultBookAttributes,
defaultBookAttributes
const newBookStateManager = initializeStateManager<BookState>(
defaultBookState,
defaultBookState
);
openLazyFlyout({
core,
Expand All @@ -40,17 +41,16 @@ export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, c
const { getSavedBookEditor } = await import('./saved_book_editor');
return getSavedBookEditor({
closeFlyout,
attributesManager: newPanelStateManager,
stateManager: newBookStateManager,
isCreate: true,
onSubmit: async ({ savedBookId }) => {
const bookAttributes = newPanelStateManager.getLatestState();
const initialState: BookSerializedState = savedBookId
? { savedBookId }
: { attributes: bookAttributes };

embeddable.addNewPanel<BookSerializedState>({
panelType: SAVED_BOOK_ID,
serializedState: { rawState: initialState },
onSubmit: async ({ savedObjectId }) => {
embeddable.addNewPanel<BookEmbeddableState>({
panelType: BOOK_EMBEDDABLE_TYPE,
serializedState: {
rawState: savedObjectId
? { savedObjectId }
: newBookStateManager.getLatestState(),
},
});
},
});
Expand Down
Loading