From 305a7200e86458f449dc0f9a30f00acb250b0dba Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Fri, 26 Jun 2020 11:45:49 -0400 Subject: [PATCH] prep state transfer for passing embeddables by value to editor and back (#69991) (#70073) Co-authored-by: Elastic Machine --- .../application/dashboard_app_controller.tsx | 14 ++++++-- src/plugins/embeddable/public/index.ts | 2 +- .../lib/actions/edit_panel_action.test.tsx | 2 +- .../public/lib/actions/edit_panel_action.ts | 6 ++-- .../embeddable_state_transfer.test.ts | 8 ++--- .../embeddable_state_transfer.ts | 20 ++++++------ .../public/lib/state_transfer/index.ts | 2 +- .../public/lib/state_transfer/types.ts | 32 ++++++++++++++----- src/plugins/embeddable/public/mocks.tsx | 4 +-- .../public/wizard/new_vis_modal.test.tsx | 2 +- .../public/wizard/new_vis_modal.tsx | 2 +- .../public/application/editor/editor.js | 2 +- .../lens/public/app_plugin/mounter.tsx | 2 +- 13 files changed, 61 insertions(+), 37 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 3c559a6cde211..b52bf5bf02b7b 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -60,6 +60,7 @@ import { ViewMode, SavedObjectEmbeddableInput, ContainerOutput, + EmbeddableInput, } from '../../../embeddable/public'; import { NavAction, SavedDashboardPanel } from '../types'; @@ -430,9 +431,16 @@ export class DashboardAppController { .getStateTransfer(scopedHistory()) .getIncomingEmbeddablePackage(); if (incomingState) { - container.addNewEmbeddable(incomingState.type, { - savedObjectId: incomingState.id, - }); + if ('id' in incomingState) { + container.addNewEmbeddable(incomingState.type, { + savedObjectId: incomingState.id, + }); + } else if ('input' in incomingState) { + container.addNewEmbeddable( + incomingState.type, + incomingState.input + ); + } } } diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 9f0ccbe2b00ef..f19974942c43d 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -70,7 +70,7 @@ export { isRangeSelectTriggerContext, isValueClickTriggerContext, EmbeddableStateTransfer, - EmbeddableOriginatingAppState, + EmbeddableEditorState, EmbeddablePackageState, EmbeddableRenderer, EmbeddableRendererProps, diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index 4b602efb02717..594a7ad73c396 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -59,7 +59,7 @@ test('redirects to app using state transfer', async () => { const embeddable = new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true); embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' })); await action.execute({ embeddable }); - expect(stateTransferMock.navigateToWithOriginatingApp).toHaveBeenCalledWith('ultraVisualize', { + expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', { path: '/123', state: { originatingApp: 'superCoolCurrentApp' }, }); diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index d983dc9f41853..9177a77d547b0 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -24,7 +24,7 @@ import { take } from 'rxjs/operators'; import { ViewMode } from '../types'; import { EmbeddableFactoryNotFoundError } from '../errors'; import { EmbeddableStart } from '../../plugin'; -import { IEmbeddable, EmbeddableOriginatingAppState, EmbeddableStateTransfer } from '../..'; +import { IEmbeddable, EmbeddableEditorState, EmbeddableStateTransfer } from '../..'; export const ACTION_EDIT_PANEL = 'editPanel'; @@ -35,7 +35,7 @@ interface ActionContext { interface NavigationContext { app: string; path: string; - state?: EmbeddableOriginatingAppState; + state?: EmbeddableEditorState; } export class EditPanelAction implements Action { @@ -88,7 +88,7 @@ export class EditPanelAction implements Action { const appTarget = this.getAppTarget(context); if (appTarget) { if (this.stateTransfer && appTarget.state) { - await this.stateTransfer.navigateToWithOriginatingApp(appTarget.app, { + await this.stateTransfer.navigateToEditor(appTarget.app, { path: appTarget.path, state: appTarget.state, }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts index 0d5ae6be68185..b7dd95ccba32c 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts @@ -38,7 +38,7 @@ describe('embeddable state transfer', () => { }); it('can send an outgoing originating app state', async () => { - await stateTransfer.navigateToWithOriginatingApp(destinationApp, { state: { originatingApp } }); + await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp } }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { state: { originatingApp: 'superUltraTestDashboard' }, }); @@ -50,7 +50,7 @@ describe('embeddable state transfer', () => { application.navigateToApp, (historyMock as unknown) as ScopedHistory ); - await stateTransfer.navigateToWithOriginatingApp(destinationApp, { + await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp }, appendToExistingState: true, }); @@ -94,7 +94,7 @@ describe('embeddable state transfer', () => { application.navigateToApp, (historyMock as unknown) as ScopedHistory ); - const fetchedState = stateTransfer.getIncomingOriginatingApp(); + const fetchedState = stateTransfer.getIncomingEditorState(); expect(fetchedState).toEqual({ originatingApp: 'extremeSportsKibana' }); }); @@ -104,7 +104,7 @@ describe('embeddable state transfer', () => { application.navigateToApp, (historyMock as unknown) as ScopedHistory ); - const fetchedState = stateTransfer.getIncomingOriginatingApp(); + const fetchedState = stateTransfer.getIncomingEditorState(); expect(fetchedState).toBeUndefined(); }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts index 57b425d2df45c..8f70e5a66c478 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -20,8 +20,8 @@ import { cloneDeep } from 'lodash'; import { ScopedHistory, ApplicationStart } from '../../../../../core/public'; import { - EmbeddableOriginatingAppState, - isEmbeddableOriginatingAppState, + EmbeddableEditorState, + isEmbeddableEditorState, EmbeddablePackageState, isEmbeddablePackageState, } from './types'; @@ -39,16 +39,16 @@ export class EmbeddableStateTransfer { ) {} /** - * Fetches an {@link EmbeddableOriginatingAppState | originating app} argument from the scoped + * Fetches an {@link EmbeddableEditorState | originating app} argument from the scoped * history's location state. * * @param history - the scoped history to fetch from * @param options.keysToRemoveAfterFetch - an array of keys to be removed from the state after they are retrieved */ - public getIncomingOriginatingApp(options?: { + public getIncomingEditorState(options?: { keysToRemoveAfterFetch?: string[]; - }): EmbeddableOriginatingAppState | undefined { - return this.getIncomingState(isEmbeddableOriginatingAppState, { + }): EmbeddableEditorState | undefined { + return this.getIncomingState(isEmbeddableEditorState, { keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch, }); } @@ -70,17 +70,17 @@ export class EmbeddableStateTransfer { /** * A wrapper around the {@link ApplicationStart.navigateToApp} method which navigates to the specified appId - * with {@link EmbeddableOriginatingAppState | originating app state} + * with {@link EmbeddableEditorState | embeddable editor state} */ - public async navigateToWithOriginatingApp( + public async navigateToEditor( appId: string, options?: { path?: string; - state: EmbeddableOriginatingAppState; + state: EmbeddableEditorState; appendToExistingState?: boolean; } ): Promise { - await this.navigateToWithState(appId, options); + await this.navigateToWithState(appId, options); } /** diff --git a/src/plugins/embeddable/public/lib/state_transfer/index.ts b/src/plugins/embeddable/public/lib/state_transfer/index.ts index e51efc5dcca26..7daa7a0ea81d6 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/index.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/index.ts @@ -18,4 +18,4 @@ */ export { EmbeddableStateTransfer } from './embeddable_state_transfer'; -export { EmbeddableOriginatingAppState, EmbeddablePackageState } from './types'; +export { EmbeddableEditorState, EmbeddablePackageState } from './types'; diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index 8eae441d1be23..a6721784302ac 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -17,33 +17,49 @@ * under the License. */ +import { EmbeddableInput } from '..'; + /** * Represents a state package that contains the last active app id. * @public */ -export interface EmbeddableOriginatingAppState { +export interface EmbeddableEditorState { originatingApp: string; + byValueMode?: boolean; + valueInput?: EmbeddableInput; } -export function isEmbeddableOriginatingAppState( - state: unknown -): state is EmbeddableOriginatingAppState { +export function isEmbeddableEditorState(state: unknown): state is EmbeddableEditorState { return ensureFieldOfTypeExists('originatingApp', state, 'string'); } /** - * Represents a state package that contains all fields necessary to create an embeddable in a container. + * Represents a state package that contains all fields necessary to create an embeddable by reference in a container. * @public */ -export interface EmbeddablePackageState { +export interface EmbeddablePackageByReferenceState { type: string; id: string; } +/** + * Represents a state package that contains all fields necessary to create an embeddable by value in a container. + * @public + */ +export interface EmbeddablePackageByValueState { + type: string; + input: EmbeddableInput; +} + +export type EmbeddablePackageState = + | EmbeddablePackageByReferenceState + | EmbeddablePackageByValueState; + export function isEmbeddablePackageState(state: unknown): state is EmbeddablePackageState { return ( - ensureFieldOfTypeExists('type', state, 'string') && - ensureFieldOfTypeExists('id', state, 'string') + (ensureFieldOfTypeExists('type', state, 'string') && + ensureFieldOfTypeExists('id', state, 'string')) || + ensureFieldOfTypeExists('input', state, 'object') ); } diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index c98416cb3e8c7..efd0ccdc4553d 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -79,9 +79,9 @@ export const createEmbeddablePanelMock = ({ export const createEmbeddableStateTransferMock = (): Partial => { return { - getIncomingOriginatingApp: jest.fn(), + getIncomingEditorState: jest.fn(), getIncomingEmbeddablePackage: jest.fn(), - navigateToWithOriginatingApp: jest.fn(), + navigateToEditor: jest.fn(), navigateToWithEmbeddablePackage: jest.fn(), }; }; diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index dd89e98fb8fe5..f48febfef5b43 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -165,7 +165,7 @@ describe('NewVisModal', () => { ); const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); visButton.simulate('click'); - expect(stateTransfer.navigateToWithOriginatingApp).toBeCalledWith('otherApp', { + expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', { path: '#/aliasUrl', state: { originatingApp: 'coolJestTestApp' }, }); diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 84a5bca0ed0ed..1d01900ceffc2 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -172,7 +172,7 @@ class NewVisModal extends React.Component originatingApp; const visStateToEditorState = () => { diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 7a33241792a58..1ee618a31a698 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -38,7 +38,7 @@ export async function mountApp( const stateTransfer = embeddable?.getStateTransfer(params.history); const { originatingApp } = - stateTransfer?.getIncomingOriginatingApp({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; + stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; const instance = await createEditorFrame();