From d6d6a167d00c6cddfe740d7d964ed2765db85b84 Mon Sep 17 00:00:00 2001 From: Devon A Thomson Date: Thu, 10 Dec 2020 17:54:16 -0500 Subject: [PATCH 1/6] Transitioned embeddable state transfer service to use sessionStorage --- ...mbeddable-public.embeddable.getupdated_.md | 2 +- ...in-plugins-embeddable-public.embeddable.md | 2 +- ...blic.embeddablestart.getembeddablepanel.md | 11 -- ...public.embeddablestart.getstatetransfer.md | 2 +- ...ugins-embeddable-public.embeddablestart.md | 3 +- ...c.embeddablestatetransfer._constructor_.md | 4 +- ...mbeddablestatetransfer.cleareditorstate.md | 15 ++ ...blestatetransfer.getincomingeditorstate.md | 8 +- ...tetransfer.getincomingembeddablepackage.md | 8 +- ...beddable-public.embeddablestatetransfer.md | 9 +- ...mbeddablestatetransfer.navigatetoeditor.md | 3 +- ...ransfer.navigatetowithembeddablepackage.md | 3 +- ...kibana-plugin-plugins-embeddable-public.md | 2 +- .../application/dashboard_app_controller.tsx | 4 +- .../embeddable/dashboard_container.tsx | 9 +- .../dashboard_container_factory.tsx | 9 +- .../embeddable/grid/dashboard_grid.tsx | 5 +- .../viewport/dashboard_viewport.tsx | 7 +- src/plugins/dashboard/public/plugin.tsx | 5 +- .../embeddable_state_transfer.test.ts | 154 ++++++++++-------- .../embeddable_state_transfer.ts | 113 +++++++------ .../public/lib/state_transfer/types.ts | 4 + src/plugins/embeddable/public/mocks.tsx | 1 + src/plugins/embeddable/public/plugin.tsx | 24 ++- src/plugins/embeddable/public/public.api.md | 24 +-- .../components/visualize_byvalue_editor.tsx | 5 +- .../components/visualize_editor.tsx | 4 +- .../application/utils/get_top_nav_config.tsx | 3 + .../lens/public/app_plugin/app.test.tsx | 7 +- x-pack/plugins/lens/public/app_plugin/app.tsx | 4 + .../lens/public/app_plugin/mounter.tsx | 26 ++- .../plugins/lens/public/app_plugin/types.ts | 6 +- x-pack/plugins/maps/public/render_app.tsx | 6 +- 33 files changed, 261 insertions(+), 231 deletions(-) delete mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablepanel.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.cleareditorstate.md diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md index 474962e614aa7..5201444e69867 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md @@ -4,7 +4,7 @@ ## Embeddable.getUpdated$() method -Merges input$ and output$ streams and denounces emit till next macro-task Could be useful to batch reactions to input$ and output$ updates that happen separately but synchronously In case corresponding state change triggered `reload` this stream is guarantied to emit later which allows to skip any state handling in case `reload` already handled it +Merges input$ and output$ streams and debounces emit till next macro-task. Could be useful to batch reactions to input$ and output$ updates that happen separately but synchronously. In case corresponding state change triggered `reload` this stream is guarantied to emit later, which allows to skip any state handling in case `reload` already handled it. Signature: diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md index 4541afec29fa5..fe64bcf7c1177 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddable.md @@ -44,7 +44,7 @@ export declare abstract class Embeddablereload this stream is guarantied to emit later which allows to skip any state handling in case reload already handled it | +| [getUpdated$()](./kibana-plugin-plugins-embeddable-public.embeddable.getupdated_.md) | | Merges input$ and output$ streams and debounces emit till next macro-task. Could be useful to batch reactions to input$ and output$ updates that happen separately but synchronously. In case corresponding state change triggered reload this stream is guarantied to emit later, which allows to skip any state handling in case reload already handled it. | | [onFatalError(e)](./kibana-plugin-plugins-embeddable-public.embeddable.onfatalerror.md) | | | | [reload()](./kibana-plugin-plugins-embeddable-public.embeddable.reload.md) | | Reload will be called when there is a request to refresh the data or view, even if the input data did not change.In case if input data did change and reload is requested input$ and output$ would still emit before reload is calledThe order would be as follows: input$ output$ reload() \-\-\-- updated$ | | [render(el)](./kibana-plugin-plugins-embeddable-public.embeddable.render.md) | | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablepanel.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablepanel.md deleted file mode 100644 index 7ba24a62a3893..0000000000000 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablepanel.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStart](./kibana-plugin-plugins-embeddable-public.embeddablestart.md) > [getEmbeddablePanel](./kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablepanel.md) - -## EmbeddableStart.getEmbeddablePanel property - -Signature: - -```typescript -getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC; -``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getstatetransfer.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getstatetransfer.md index dafc66b1a6e15..a07021ee456e0 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getstatetransfer.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getstatetransfer.md @@ -7,5 +7,5 @@ Signature: ```typescript -getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer; +getStateTransfer: (storage?: Storage) => EmbeddableStateTransfer; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md index f500196d850a2..2b04d4502e8a8 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md @@ -18,6 +18,5 @@ export interface EmbeddableStart extends PersistableStateService<A extends {
title: string;
}, V extends EmbeddableInput & {
[ATTRIBUTE_SERVICE_KEY]: A;
} = EmbeddableInput & {
[ATTRIBUTE_SERVICE_KEY]: A;
}, R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput>(type: string, options: AttributeServiceOptions<A>) => AttributeService<A, V, R> | | | [getEmbeddableFactories](./kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablefactories.md) | () => IterableIterator<EmbeddableFactory> | | | [getEmbeddableFactory](./kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablefactory.md) | <I extends EmbeddableInput = EmbeddableInput, O extends EmbeddableOutput = EmbeddableOutput, E extends IEmbeddable<I, O> = IEmbeddable<I, O>>(embeddableFactoryId: string) => EmbeddableFactory<I, O, E> | undefined | | -| [getEmbeddablePanel](./kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablepanel.md) | (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC | | -| [getStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestart.getstatetransfer.md) | (history?: ScopedHistory) => EmbeddableStateTransfer | | +| [getStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestart.getstatetransfer.md) | (storage?: Storage) => EmbeddableStateTransfer | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md index 323ed5e38bde1..276499b435e1f 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `EmbeddableStateTransfer` class Signature: ```typescript -constructor(navigateToApp: ApplicationStart['navigateToApp'], scopedHistory?: ScopedHistory | undefined, appList?: ReadonlyMap | undefined); +constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: ReadonlyMap | undefined, customStorage?: Storage); ``` ## Parameters @@ -17,6 +17,6 @@ constructor(navigateToApp: ApplicationStart['navigateToApp'], scopedHistory?: Sc | Parameter | Type | Description | | --- | --- | --- | | navigateToApp | ApplicationStart['navigateToApp'] | | -| scopedHistory | ScopedHistory<unknown> | undefined | | | appList | ReadonlyMap<string, PublicAppInfo> | undefined | | +| customStorage | Storage | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.cleareditorstate.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.cleareditorstate.md new file mode 100644 index 0000000000000..5c1a6a0393c2e --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.cleareditorstate.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md) > [clearEditorState](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.cleareditorstate.md) + +## EmbeddableStateTransfer.clearEditorState() method + +Signature: + +```typescript +clearEditorState(): void; +``` +Returns: + +`void` + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingeditorstate.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingeditorstate.md index 2a0823a9bf835..1434de2c9870e 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingeditorstate.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingeditorstate.md @@ -4,21 +4,19 @@ ## EmbeddableStateTransfer.getIncomingEditorState() method -Fetches an [originating app](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) argument from the scoped history's location state. +Fetches an [originating app](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) argument from the sessionStorage Signature: ```typescript -getIncomingEditorState(options?: { - keysToRemoveAfterFetch?: string[]; - }): EmbeddableEditorState | undefined; +getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| options | {
keysToRemoveAfterFetch?: string[];
} | | +| removeAfterFetch | boolean | Whether to remove the package state after fetch to prevent duplicates. | Returns: diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingembeddablepackage.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingembeddablepackage.md index 2069f0ce084f9..9ead71f0bb22c 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingembeddablepackage.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingembeddablepackage.md @@ -4,21 +4,19 @@ ## EmbeddableStateTransfer.getIncomingEmbeddablePackage() method -Fetches an [embeddable package](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) argument from the scoped history's location state. +Fetches an [embeddable package](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) argument from the sessionStorage Signature: ```typescript -getIncomingEmbeddablePackage(options?: { - keysToRemoveAfterFetch?: string[]; - }): EmbeddablePackageState | undefined; +getIncomingEmbeddablePackage(removeAfterFetch?: boolean): EmbeddablePackageState | undefined; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| options | {
keysToRemoveAfterFetch?: string[];
} | | +| removeAfterFetch | boolean | Whether to remove the package state after fetch to prevent duplicates. | Returns: diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md index 2b44693e14846..3676b744b8cc9 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md @@ -4,7 +4,7 @@ ## EmbeddableStateTransfer class -A wrapper around the state object in which provides strongly typed helper methods for common incoming and outgoing states used by the embeddable infrastructure. +A wrapper around the session storage which provides strongly typed helper methods for common incoming and outgoing states used by the embeddable infrastructure. Signature: @@ -16,7 +16,7 @@ export declare class EmbeddableStateTransfer | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(navigateToApp, scopedHistory, appList)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the EmbeddableStateTransfer class | +| [(constructor)(navigateToApp, appList, customStorage)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the EmbeddableStateTransfer class | ## Properties @@ -28,8 +28,9 @@ export declare class EmbeddableStateTransfer | Method | Modifiers | Description | | --- | --- | --- | -| [getIncomingEditorState(options)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingeditorstate.md) | | Fetches an [originating app](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) argument from the scoped history's location state. | -| [getIncomingEmbeddablePackage(options)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingembeddablepackage.md) | | Fetches an [embeddable package](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) argument from the scoped history's location state. | +| [clearEditorState()](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.cleareditorstate.md) | | | +| [getIncomingEditorState(removeAfterFetch)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingeditorstate.md) | | Fetches an [originating app](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) argument from the sessionStorage | +| [getIncomingEmbeddablePackage(removeAfterFetch)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingembeddablepackage.md) | | Fetches an [embeddable package](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) argument from the sessionStorage | | [navigateToEditor(appId, options)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetoeditor.md) | | A wrapper around the method which navigates to the specified appId with [embeddable editor state](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) | | [navigateToWithEmbeddablePackage(appId, options)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetowithembeddablepackage.md) | | A wrapper around the method which navigates to the specified appId with [embeddable package state](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetoeditor.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetoeditor.md index fa24784d9aac5..4bd5f44084a33 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetoeditor.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetoeditor.md @@ -12,7 +12,6 @@ A wrapper around the method which navigates to the specified appId with [embedd navigateToEditor(appId: string, options?: { path?: string; state: EmbeddableEditorState; - appendToExistingState?: boolean; }): Promise; ``` @@ -21,7 +20,7 @@ navigateToEditor(appId: string, options?: { | Parameter | Type | Description | | --- | --- | --- | | appId | string | | -| options | {
path?: string;
state: EmbeddableEditorState;
appendToExistingState?: boolean;
} | | +| options | {
path?: string;
state: EmbeddableEditorState;
} | | Returns: diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetowithembeddablepackage.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetowithembeddablepackage.md index 7173bc8b127cd..0fd82167805ab 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetowithembeddablepackage.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.navigatetowithembeddablepackage.md @@ -12,7 +12,6 @@ A wrapper around the method which navigates to the specified appId with [embedd navigateToWithEmbeddablePackage(appId: string, options?: { path?: string; state: EmbeddablePackageState; - appendToExistingState?: boolean; }): Promise; ``` @@ -21,7 +20,7 @@ navigateToWithEmbeddablePackage(appId: string, options?: { | Parameter | Type | Description | | --- | --- | --- | | appId | string | | -| options | {
path?: string;
state: EmbeddablePackageState;
appendToExistingState?: boolean;
} | | +| options | {
path?: string;
state: EmbeddablePackageState;
} | | Returns: diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index 06f792837e4fe..9c4e493b848cb 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -17,7 +17,7 @@ | [EmbeddableFactoryNotFoundError](./kibana-plugin-plugins-embeddable-public.embeddablefactorynotfounderror.md) | | | [EmbeddablePanel](./kibana-plugin-plugins-embeddable-public.embeddablepanel.md) | | | [EmbeddableRoot](./kibana-plugin-plugins-embeddable-public.embeddableroot.md) | | -| [EmbeddableStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md) | A wrapper around the state object in which provides strongly typed helper methods for common incoming and outgoing states used by the embeddable infrastructure. | +| [EmbeddableStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md) | A wrapper around the session storage which provides strongly typed helper methods for common incoming and outgoing states used by the embeddable infrastructure. | | [ErrorEmbeddable](./kibana-plugin-plugins-embeddable-public.errorembeddable.md) | | | [PanelNotFoundError](./kibana-plugin-plugins-embeddable-public.panelnotfounderror.md) | | diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 4d5a3fb9a8cc9..eb911b6085c5b 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -190,9 +190,7 @@ export class DashboardAppController { chrome.docTitle.change(dash.title); } - let incomingEmbeddable = embeddable - .getStateTransfer(scopedHistory()) - .getIncomingEmbeddablePackage(); + let incomingEmbeddable = embeddable.getStateTransfer().getIncomingEmbeddablePackage(true); // TS is picky with type guards, we can't just inline `() => false` function defaultTaggingGuard(obj: SavedObject): obj is TagDecoratedSavedObject { diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index e80d387fa3066..ab6c8256966a4 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -100,8 +100,6 @@ export class DashboardContainer extends Container React.ReactNode); - private embeddablePanel: EmbeddableStart['EmbeddablePanel']; - constructor( initialInput: DashboardContainerInput, private readonly options: DashboardContainerOptions, @@ -116,7 +114,6 @@ export class DashboardContainer extends Container - + , dom diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index 4107a00ba80ce..c51316e7fa169 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; -import { CoreStart, ScopedHistory } from 'src/core/public'; +import { CoreStart } from 'src/core/public'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { EmbeddableFactory, EmbeddableStart } from '../../../../embeddable/public'; import { @@ -54,10 +54,7 @@ export class DashboardContainerFactoryDefinition public readonly isContainerType = true; public readonly type = DASHBOARD_CONTAINER_TYPE; - constructor( - private readonly getStartServices: () => Promise, - private getHistory: () => ScopedHistory - ) {} + constructor(private readonly getStartServices: () => Promise) {} public isEditable = async () => { const { capabilities } = await this.getStartServices(); @@ -84,7 +81,7 @@ export class DashboardContainerFactoryDefinition parent?: Container ): Promise => { const services = await this.getStartServices(); - const stateTransfer = services.embeddable.getStateTransfer(this.getHistory()); + const stateTransfer = services.embeddable.getStateTransfer(); return new DashboardContainer(initialInput, services, stateTransfer, parent); }; } diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index 03c92d91a80cc..fdc02ee834574 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -30,7 +30,7 @@ import React from 'react'; import { Subscription } from 'rxjs'; import ReactGridLayout, { Layout } from 'react-grid-layout'; import { GridData } from '../../../../common'; -import { ViewMode, EmbeddableChildPanel, EmbeddableStart } from '../../../embeddable_plugin'; +import { ViewMode, EmbeddableChildPanel } from '../../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; import { DashboardPanelState } from '../types'; import { withKibana } from '../../../../../kibana_react/public'; @@ -115,7 +115,6 @@ const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid); export interface DashboardGridProps extends ReactIntl.InjectedIntlProps { kibana: DashboardReactContextValue; - PanelComponent: EmbeddableStart['EmbeddablePanel']; container: DashboardContainer; } @@ -277,7 +276,7 @@ class DashboardGridUi extends React.Component { key={panel.type} embeddableId={panel.explicitInput.id} container={this.props.container} - PanelComponent={this.props.PanelComponent} + PanelComponent={this.props.kibana.services.embeddable.EmbeddablePanel} /> ); diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index 15a486f99a37f..971e096641f17 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -19,14 +19,13 @@ import React from 'react'; import { Subscription } from 'rxjs'; -import { PanelState, EmbeddableStart } from '../../../embeddable_plugin'; +import { PanelState } from '../../../embeddable_plugin'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; import { DashboardGrid } from '../grid'; import { context } from '../../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; - PanelComponent: EmbeddableStart['EmbeddablePanel']; renderEmpty?: () => React.ReactNode; } @@ -121,7 +120,7 @@ export class DashboardViewport extends React.Component )} - + ); } diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 76b1ccc037e89..8c578ee0c1762 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -242,10 +242,7 @@ export class DashboardPlugin }; }; - const factory = new DashboardContainerFactoryDefinition( - getStartServices, - () => this.currentHistory! - ); + const factory = new DashboardContainerFactoryDefinition(getStartServices); embeddable.registerEmbeddableFactory(factory.type, factory); const placeholderFactory = new PlaceholderEmbeddableFactory(); 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 4155cb4d3b60c..cbaeddf472d52 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 @@ -17,24 +17,45 @@ * under the License. */ -import { coreMock, scopedHistoryMock } from '../../../../../core/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; +import { Storage } from '../../../../kibana_utils/public'; import { EmbeddableStateTransfer } from '.'; import { ApplicationStart, PublicAppInfo } from '../../../../../core/public'; - -function mockHistoryState(state: unknown) { - return scopedHistoryMock.create({ state }); -} +import { EMBEDDABLE_EDITOR_STATE_KEY, EMBEDDABLE_PACKAGE_STATE_KEY } from './types'; +import { EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY } from './embeddable_state_transfer'; + +const createStorage = (): Storage => { + const createMockStore = () => { + let innerStore: Record = {}; + return { + getItem: jest.fn().mockImplementation((key) => innerStore[key]), + setItem: jest.fn().mockImplementation((key, value) => (innerStore[key] = value)), + removeItem: jest.fn().mockImplementation((key: string) => delete innerStore[key]), + clear: jest.fn().mockImplementation(() => (innerStore = {})), + }; + }; + const store = createMockStore(); + const storage = new Storage(store); + storage.get = jest.fn().mockImplementation((key) => store.getItem(key)); + storage.set = jest.fn().mockImplementation((key, value) => store.setItem(key, value)); + storage.remove = jest.fn().mockImplementation((key: string) => store.removeItem(key)); + storage.clear = jest.fn().mockImplementation(() => store.clear()); + return storage; +}; describe('embeddable state transfer', () => { let application: jest.Mocked; let stateTransfer: EmbeddableStateTransfer; + let store: Storage; + const destinationApp = 'superUltraVisualize'; const originatingApp = 'superUltraTestDashboard'; beforeEach(() => { const core = coreMock.createStart(); application = core.application; - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp); + store = createStorage(); + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, store); }); it('cannot fetch app name when given no app list', async () => { @@ -46,7 +67,7 @@ describe('embeddable state transfer', () => { ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo], ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo], ]); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, appsList); + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, appsList); expect(stateTransfer.getAppNameFromId('kibanana')).toBeUndefined(); }); @@ -55,31 +76,34 @@ describe('embeddable state transfer', () => { ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo], ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo], ]); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, appsList); + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, appsList); expect(stateTransfer.getAppNameFromId('testId')).toBe('State Transfer Test App Hello'); expect(stateTransfer.getAppNameFromId('testId2')).toBe('State Transfer Test App Goodbye'); }); - it('can send an outgoing originating app state', async () => { + it('can send an outgoing editor state', async () => { await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp } }); + expect(store.set).toHaveBeenCalledWith(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' }, + }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { - state: { originatingApp: 'superUltraTestDashboard' }, + path: undefined, }); }); - it('can send an outgoing originating app state in append mode', async () => { - const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); + it('can send an outgoing editor state and retain other embeddable state keys', async () => { + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + kibanaIsNowForSports: 'extremeSportsKibana', + }); await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp }, - appendToExistingState: true, + }); + expect(store.set).toHaveBeenCalledWith(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + kibanaIsNowForSports: 'extremeSportsKibana', + [EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' }, }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { path: undefined, - state: { - kibanaIsNowForSports: 'extremeSportsKibana', - originatingApp: 'superUltraTestDashboard', - }, }); }); @@ -87,87 +111,81 @@ describe('embeddable state transfer', () => { await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { state: { type: 'coolestType', input: { savedObjectId: '150' } }, }); + expect(store.set).toHaveBeenCalledWith(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_PACKAGE_STATE_KEY]: { type: 'coolestType', input: { savedObjectId: '150' } }, + }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { - state: { type: 'coolestType', input: { savedObjectId: '150' } }, + path: undefined, }); }); - it('can send an outgoing embeddable package state in append mode', async () => { - const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); + it('can send an outgoing embeddable and retain other embeddable state keys', async () => { + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + kibanaIsNowForSports: 'extremeSportsKibana', + }); await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { state: { type: 'coolestType', input: { savedObjectId: '150' } }, - appendToExistingState: true, + }); + expect(store.set).toHaveBeenCalledWith(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + kibanaIsNowForSports: 'extremeSportsKibana', + [EMBEDDABLE_PACKAGE_STATE_KEY]: { type: 'coolestType', input: { savedObjectId: '150' } }, }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { path: undefined, - state: { - kibanaIsNowForSports: 'extremeSportsKibana', - type: 'coolestType', - input: { savedObjectId: '150' }, - }, }); }); - it('can fetch an incoming originating app state', async () => { - const historyMock = mockHistoryState({ originatingApp: 'extremeSportsKibana' }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); + it('can fetch an incoming editor state', async () => { + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' }, + }); const fetchedState = stateTransfer.getIncomingEditorState(); - expect(fetchedState).toEqual({ originatingApp: 'extremeSportsKibana' }); + expect(fetchedState).toEqual({ originatingApp: 'superUltraTestDashboard' }); }); - it('returns undefined with originating app state is not in the right shape', async () => { - const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); + it('incoming editor state returns undefined when state is not in the right shape', async () => { + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_EDITOR_STATE_KEY]: { helloSportsKibana: 'superUltraTestDashboard' }, + }); const fetchedState = stateTransfer.getIncomingEditorState(); expect(fetchedState).toBeUndefined(); }); it('can fetch an incoming embeddable package state', async () => { - const historyMock = mockHistoryState({ - type: 'skisEmbeddable', - input: { savedObjectId: '123' }, + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_PACKAGE_STATE_KEY]: { type: 'skisEmbeddable', input: { savedObjectId: '123' } }, }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); const fetchedState = stateTransfer.getIncomingEmbeddablePackage(); expect(fetchedState).toEqual({ type: 'skisEmbeddable', input: { savedObjectId: '123' } }); }); - it('returns undefined when embeddable package is not in the right shape', async () => { - const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); + it('embeddable package state returns undefined when state is not in the right shape', async () => { + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_PACKAGE_STATE_KEY]: { kibanaIsFor: 'sports' }, + }); const fetchedState = stateTransfer.getIncomingEmbeddablePackage(); expect(fetchedState).toBeUndefined(); }); - it('removes all keys in the keysToRemoveAfterFetch array', async () => { - const historyMock = mockHistoryState({ - type: 'skisEmbeddable', - input: { savedObjectId: '123' }, - test1: 'test1', - test2: 'test2', - }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); - stateTransfer.getIncomingEmbeddablePackage({ keysToRemoveAfterFetch: ['type', 'input'] }); - expect(historyMock.replace).toHaveBeenCalledWith( - expect.objectContaining({ state: { test1: 'test1', test2: 'test2' } }) - ); + it('removes embeddable package key when removeAfterFetch is true', async () => { + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_PACKAGE_STATE_KEY]: { type: 'coolestType', input: { savedObjectId: '150' } }, + iSHouldStillbeHere: 'doing the sports thing', + }); + stateTransfer.getIncomingEmbeddablePackage(true); + expect(store.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)).toEqual({ + iSHouldStillbeHere: 'doing the sports thing', + }); }); - it('leaves state as is when no keysToRemove are supplied', async () => { - const historyMock = mockHistoryState({ - type: 'skisEmbeddable', - input: { savedObjectId: '123' }, - test1: 'test1', - test2: 'test2', - }); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); - stateTransfer.getIncomingEmbeddablePackage(); - expect(historyMock.location.state).toEqual({ - type: 'skisEmbeddable', - input: { savedObjectId: '123' }, - test1: 'test1', - test2: 'test2', + it('removes editor state key when removeAfterFetch is true', async () => { + store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { + [EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superCoolFootballDashboard' }, + iSHouldStillbeHere: 'doing the sports thing', + }); + stateTransfer.getIncomingEditorState(true); + expect(store.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)).toEqual({ + iSHouldStillbeHere: 'doing the sports thing', }); }); }); 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 184178ba80e84..29e61044e9cc1 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 @@ -18,26 +18,35 @@ */ import { cloneDeep } from 'lodash'; -import { ScopedHistory, ApplicationStart, PublicAppInfo } from '../../../../../core/public'; +import { Storage } from '../../../../kibana_utils/public'; +import { ApplicationStart, PublicAppInfo } from '../../../../../core/public'; import { EmbeddableEditorState, isEmbeddableEditorState, EmbeddablePackageState, isEmbeddablePackageState, + EMBEDDABLE_PACKAGE_STATE_KEY, + EMBEDDABLE_EDITOR_STATE_KEY, } from './types'; +export const EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY = 'EMBEDDABLE_STATE_TRANSFER'; + /** - * A wrapper around the state object in {@link ScopedHistory | core scoped history} which provides - * strongly typed helper methods for common incoming and outgoing states used by the embeddable infrastructure. + * A wrapper around the session storage which provides strongly typed helper methods + * for common incoming and outgoing states used by the embeddable infrastructure. * * @public */ export class EmbeddableStateTransfer { + private storage: Storage; + constructor( private navigateToApp: ApplicationStart['navigateToApp'], - private scopedHistory?: ScopedHistory, - private appList?: ReadonlyMap | undefined - ) {} + private appList?: ReadonlyMap | undefined, + customStorage?: Storage + ) { + this.storage = customStorage ? customStorage : new Storage(sessionStorage); + } /** * Fetches an internationalized app title when given an appId. @@ -46,33 +55,41 @@ export class EmbeddableStateTransfer { public getAppNameFromId = (appId: string): string | undefined => this.appList?.get(appId)?.title; /** - * Fetches an {@link EmbeddableEditorState | originating app} argument from the scoped - * history's location state. + * Fetches an {@link EmbeddableEditorState | originating app} argument from the sessionStorage * - * @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 + * @param removeAfterFetch - Whether to remove the package state after fetch to prevent duplicates. */ - public getIncomingEditorState(options?: { - keysToRemoveAfterFetch?: string[]; - }): EmbeddableEditorState | undefined { - return this.getIncomingState(isEmbeddableEditorState, { - keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch, - }); + public getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined { + return this.getIncomingState( + isEmbeddableEditorState, + EMBEDDABLE_EDITOR_STATE_KEY, + { + keysToRemoveAfterFetch: removeAfterFetch ? [EMBEDDABLE_EDITOR_STATE_KEY] : undefined, + } + ); + } + + public clearEditorState() { + const currentState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY); + delete currentState[EMBEDDABLE_EDITOR_STATE_KEY]; + this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, currentState); } /** - * Fetches an {@link EmbeddablePackageState | embeddable package} argument from the scoped - * history's location state. + * Fetches an {@link EmbeddablePackageState | embeddable package} argument from the sessionStorage * - * @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 + * @param removeAfterFetch - Whether to remove the package state after fetch to prevent duplicates. */ - public getIncomingEmbeddablePackage(options?: { - keysToRemoveAfterFetch?: string[]; - }): EmbeddablePackageState | undefined { - return this.getIncomingState(isEmbeddablePackageState, { - keysToRemoveAfterFetch: options?.keysToRemoveAfterFetch, - }); + public getIncomingEmbeddablePackage( + removeAfterFetch?: boolean + ): EmbeddablePackageState | undefined { + return this.getIncomingState( + isEmbeddablePackageState, + EMBEDDABLE_PACKAGE_STATE_KEY, + { + keysToRemoveAfterFetch: removeAfterFetch ? [EMBEDDABLE_PACKAGE_STATE_KEY] : undefined, + } + ); } /** @@ -84,10 +101,12 @@ export class EmbeddableStateTransfer { options?: { path?: string; state: EmbeddableEditorState; - appendToExistingState?: boolean; } ): Promise { - await this.navigateToWithState(appId, options); + await this.navigateToWithState(appId, EMBEDDABLE_EDITOR_STATE_KEY, { + ...options, + appendToExistingState: true, + }); } /** @@ -96,44 +115,46 @@ export class EmbeddableStateTransfer { */ public async navigateToWithEmbeddablePackage( appId: string, - options?: { path?: string; state: EmbeddablePackageState; appendToExistingState?: boolean } + options?: { path?: string; state: EmbeddablePackageState } ): Promise { - await this.navigateToWithState(appId, options); + await this.navigateToWithState(appId, EMBEDDABLE_PACKAGE_STATE_KEY, { + ...options, + appendToExistingState: true, + }); } private getIncomingState( guard: (state: unknown) => state is IncomingStateType, + key: string, options?: { keysToRemoveAfterFetch?: string[]; } ): IncomingStateType | undefined { - if (!this.scopedHistory) { - throw new TypeError('ScopedHistory is required to fetch incoming state'); - } - const incomingState = this.scopedHistory.location?.state; + const incomingState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)?.[key]; const castState = !guard || guard(incomingState) ? (cloneDeep(incomingState) as IncomingStateType) : undefined; if (castState && options?.keysToRemoveAfterFetch) { - const stateReplace = { ...(this.scopedHistory.location.state as { [key: string]: unknown }) }; - options.keysToRemoveAfterFetch.forEach((key: string) => { - delete stateReplace[key]; + const stateReplace = { ...this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY) }; + options.keysToRemoveAfterFetch.forEach((keyToRemove: string) => { + delete stateReplace[keyToRemove]; }); - this.scopedHistory.replace({ ...this.scopedHistory.location, state: stateReplace }); + this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateReplace); } return castState; } private async navigateToWithState( appId: string, + key: string, options?: { path?: string; state?: OutgoingStateType; appendToExistingState?: boolean } ): Promise { - const stateObject = - options?.appendToExistingState && this.scopedHistory - ? { - ...(this.scopedHistory?.location.state as { [key: string]: unknown }), - ...options.state, - } - : options?.state; - await this.navigateToApp(appId, { path: options?.path, state: stateObject }); + const stateObject = options?.appendToExistingState + ? { + ...this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY), + [key]: options.state, + } + : { [key]: options?.state }; + this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateObject); + await this.navigateToApp(appId, { path: options?.path }); } } diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index d36954528dbf0..3ce62a6acb35b 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -20,6 +20,8 @@ import { Optional } from '@kbn/utility-types'; import { EmbeddableInput, SavedObjectEmbeddableInput } from '..'; +export const EMBEDDABLE_EDITOR_STATE_KEY = 'embeddable_editor_state'; + /** * A state package that contains information an editor will need to create or edit an embeddable then redirect back. * @public @@ -34,6 +36,8 @@ export function isEmbeddableEditorState(state: unknown): state is EmbeddableEdit return ensureFieldOfTypeExists('originatingApp', state, 'string'); } +export const EMBEDDABLE_PACKAGE_STATE_KEY = 'embeddable_package_state'; + /** * A state package that contains all fields necessary to create or update an embeddable by reference or by value in a container. * @public diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index c5a9860498117..18cf56a6dc8f1 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -81,6 +81,7 @@ export const createEmbeddablePanelMock = ({ export const createEmbeddableStateTransferMock = (): Partial => { return { + clearEditorState: jest.fn(), getIncomingEditorState: jest.fn(), getIncomingEmbeddablePackage: jest.fn(), navigateToEditor: jest.fn(), diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 4f3de0425579c..5118a1a8818c0 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -28,7 +28,6 @@ import { CoreSetup, CoreStart, Plugin, - ScopedHistory, PublicAppInfo, } from '../../../core/public'; import { @@ -50,6 +49,7 @@ import { } from './lib'; import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; import { EmbeddableStateTransfer } from './lib/state_transfer'; +import { Storage } from '../../kibana_utils/public'; import { PersistableStateService, SerializableState } from '../../kibana_utils/common'; import { ATTRIBUTE_SERVICE_KEY, AttributeService } from './lib/attribute_service'; import { AttributeServiceOptions } from './lib/attribute_service/attribute_service'; @@ -95,8 +95,7 @@ export interface EmbeddableStart extends PersistableStateService EmbeddableFactory | undefined; getEmbeddableFactories: () => IterableIterator; EmbeddablePanel: EmbeddablePanelHOC; - getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC; - getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer; + getStateTransfer: (storage?: Storage) => EmbeddableStateTransfer; getAttributeService: < A extends { title: string }, V extends EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: A } = EmbeddableInput & { @@ -119,7 +118,7 @@ export class EmbeddablePublicPlugin implements Plugin; private appListSubscription?: Subscription; @@ -160,14 +159,13 @@ export class EmbeddablePublicPlugin implements Plugin ({ + const getEmbeddablePanelHoc = () => ({ embeddable, hideHeader, }: { @@ -177,7 +175,7 @@ export class EmbeddablePublicPlugin implements Plugin { - return history - ? new EmbeddableStateTransfer(core.application.navigateToApp, history, this.appList) - : this.outgoingOnlyStateTransfer; - }, + getStateTransfer: (storage?: Storage) => + storage + ? new EmbeddableStateTransfer(core.application.navigateToApp, this.appList, storage) + : this.stateTransferService, EmbeddablePanel: getEmbeddablePanelHoc(), - getEmbeddablePanel: getEmbeddablePanelHoc, telemetry: getTelemetryFunction(commonContract), extract: getExtractFunction(commonContract), inject: getInjectFunction(commonContract), diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 8f7ea6f51c785..c54e537e93dbc 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -40,7 +40,7 @@ import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; import { ISearchOptions } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; -import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { IStorageWrapper as IStorageWrapper_2 } from 'src/plugins/kibana_utils/public'; import { IUiSettingsClient as IUiSettingsClient_2 } from 'src/core/public'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType } from 'src/core/server/kibana_config'; @@ -604,12 +604,10 @@ export interface EmbeddableStart extends PersistableStateService IterableIterator; // (undocumented) getEmbeddableFactory: = IEmbeddable>(embeddableFactoryId: string) => EmbeddableFactory | undefined; - // (undocumented) - getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC; - // Warning: (ae-forgotten-export) The symbol "ScopedHistory" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "Storage" needs to be exported by the entry point index.d.ts // // (undocumented) - getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer; + getStateTransfer: (storage?: Storage) => EmbeddableStateTransfer; } // Warning: (ae-missing-release-tag) "EmbeddableStartDependencies" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -630,31 +628,25 @@ export interface EmbeddableStartDependencies { uiActions: UiActionsStart; } -// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ScopedHistory" -// // @public export class EmbeddableStateTransfer { // Warning: (ae-forgotten-export) The symbol "ApplicationStart" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "PublicAppInfo" needs to be exported by the entry point index.d.ts - constructor(navigateToApp: ApplicationStart['navigateToApp'], scopedHistory?: ScopedHistory | undefined, appList?: ReadonlyMap | undefined); + constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: ReadonlyMap | undefined, customStorage?: Storage); + // (undocumented) + clearEditorState(): void; getAppNameFromId: (appId: string) => string | undefined; - getIncomingEditorState(options?: { - keysToRemoveAfterFetch?: string[]; - }): EmbeddableEditorState | undefined; - getIncomingEmbeddablePackage(options?: { - keysToRemoveAfterFetch?: string[]; - }): EmbeddablePackageState | undefined; + getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined; + getIncomingEmbeddablePackage(removeAfterFetch?: boolean): EmbeddablePackageState | undefined; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ApplicationStart" navigateToEditor(appId: string, options?: { path?: string; state: EmbeddableEditorState; - appendToExistingState?: boolean; }): Promise; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ApplicationStart" navigateToWithEmbeddablePackage(appId: string, options?: { path?: string; state: EmbeddablePackageState; - appendToExistingState?: boolean; }): Promise; } diff --git a/src/plugins/visualize/public/application/components/visualize_byvalue_editor.tsx b/src/plugins/visualize/public/application/components/visualize_byvalue_editor.tsx index 1c1eb9956a329..e4577cb76ab06 100644 --- a/src/plugins/visualize/public/application/components/visualize_byvalue_editor.tsx +++ b/src/plugins/visualize/public/application/components/visualize_byvalue_editor.tsx @@ -45,10 +45,7 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => { useEffect(() => { const { originatingApp: value, embeddableId: embeddableIdValue, valueInput: valueInputValue } = - services.embeddable - .getStateTransfer(services.scopedHistory) - .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'embeddableId', 'valueInput'] }) || - {}; + services.embeddable.getStateTransfer().getIncomingEditorState() || {}; setOriginatingApp(value); setValueInput(valueInputValue); setEmbeddableId(embeddableIdValue); diff --git a/src/plugins/visualize/public/application/components/visualize_editor.tsx b/src/plugins/visualize/public/application/components/visualize_editor.tsx index 7c0fa065c3a71..b13169d4b62ec 100644 --- a/src/plugins/visualize/public/application/components/visualize_editor.tsx +++ b/src/plugins/visualize/public/application/components/visualize_editor.tsx @@ -65,9 +65,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => { useEffect(() => { const { originatingApp: value } = - services.embeddable - .getStateTransfer(services.scopedHistory) - .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {}; + services.embeddable.getStateTransfer().getIncomingEditorState() || {}; setOriginatingApp(value); }, [services]); diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index dbdef182d419d..6ebe65fd960b4 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -77,6 +77,7 @@ export const getTopNavConfig = ( { application, chrome, + embeddable, history, share, setActiveUrl, @@ -137,6 +138,8 @@ export const getTopNavConfig = ( } else { if (setOriginatingApp && originatingApp && newlyCreated) { setOriginatingApp(undefined); + // remove editor state so the connection is still broken after reload + stateTransfer.clearEditorState(); } chrome.docTitle.change(savedVis.lastSavedTitle); chrome.setBreadcrumbs(getEditBreadcrumbs(savedVis.lastSavedTitle)); diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 7e7156793e18b..1496b0c335322 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -37,9 +37,13 @@ import { LensByReferenceInput, } from '../editor_frame_service/embeddable/embeddable'; import { SavedObjectReference } from '../../../../../src/core/types'; -import { mockAttributeService } from '../../../../../src/plugins/embeddable/public/mocks'; +import { + mockAttributeService, + createEmbeddableStateTransferMock, +} from '../../../../../src/plugins/embeddable/public/mocks'; import { LensAttributeService } from '../lens_attribute_service'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { EmbeddableStateTransfer } from '../../../../../src/plugins/embeddable/public'; jest.mock('../editor_frame_service/editor_frame/expression_helpers'); jest.mock('src/core/public'); @@ -181,6 +185,7 @@ describe('Lens App', () => { attributeService: makeAttributeService(), savedObjectsClient: core.savedObjects.client, dashboardFeatureFlag: { allowByValueEmbeddables: false }, + stateTransfer: createEmbeddableStateTransferMock() as EmbeddableStateTransfer, getOriginatingAppName: jest.fn(() => 'defaultOriginatingApp'), application: { ...core.application, diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 931e0df2cd50e..bb77c5998519d 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -59,6 +59,7 @@ export function App({ navigation, uiSettings, application, + stateTransfer, notifications, attributeService, savedObjectsClient, @@ -463,6 +464,9 @@ export function App({ isSaveModalVisible: false, isLinkedToOriginatingApp: false, })); + // remove editor state so the connection is still broken after reload + stateTransfer.clearEditorState(); + redirectTo(newInput.savedObjectId); return; } diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 3bc2a8956e61a..b09ecfdcd5553 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -46,7 +46,7 @@ export async function mountApp( const instance = await createEditorFrame(); const storage = new Storage(localStorage); - const stateTransfer = embeddable?.getStateTransfer(params.history); + const stateTransfer = embeddable?.getStateTransfer(); const historyLocationState = params.history.location.state as HistoryLocationState; const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(); @@ -54,6 +54,7 @@ export async function mountApp( data, storage, navigation, + stateTransfer, savedObjectsTagging, attributeService: await attributeService(), http: coreStart.http, @@ -86,14 +87,15 @@ export async function mountApp( ); const getInitialInput = ( - routeProps: RouteComponentProps<{ id?: string }> + routeProps: RouteComponentProps<{ id?: string }>, + editByValue?: boolean ): LensEmbeddableInput | undefined => { + if (editByValue) { + return embeddableEditorIncomingState?.valueInput as LensByValueInput; + } if (routeProps.match.params.id) { return { savedObjectId: routeProps.match.params.id } as LensByReferenceInput; } - if (embeddableEditorIncomingState?.valueInput) { - return embeddableEditorIncomingState?.valueInput as LensByValueInput; - } }; const redirectTo = (routeProps: RouteComponentProps<{ id?: string }>, savedObjectId?: string) => { @@ -142,14 +144,16 @@ export async function mountApp( } }; - // const featureFlagConfig = await getByValueFeatureFlag(); - const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => { + const renderEditor = ( + routeProps: RouteComponentProps<{ id?: string }>, + editByValue?: boolean + ) => { trackUiEvent('loaded'); return ( redirectTo(routeProps, savedObjectId)} redirectToOrigin={redirectToOrigin} redirectToDashboard={redirectToDashboard} @@ -182,7 +186,11 @@ export async function mountApp( - + renderEditor(routeProps, true)} + /> diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index e09d7389b9d46..869ccf52fb0bd 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -33,7 +33,10 @@ import { VisualizeFieldContext, ACTION_VISUALIZE_LENS_FIELD, } from '../../../../../src/plugins/ui_actions/public'; -import { EmbeddableEditorState } from '../../../../../src/plugins/embeddable/public'; +import { + EmbeddableEditorState, + EmbeddableStateTransfer, +} from '../../../../../src/plugins/embeddable/public'; import { TableInspectorAdapter } from '../editor_frame_service/types'; import { EditorFrameInstance } from '..'; @@ -100,6 +103,7 @@ export interface LensAppServices { uiSettings: IUiSettingsClient; application: ApplicationStart; notifications: NotificationsStart; + stateTransfer: EmbeddableStateTransfer; navigation: NavigationPublicPluginStart; attributeService: LensAttributeService; savedObjectsClient: SavedObjectsStart['client']; diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx index 68b74211ee273..a20649f8ebe1e 100644 --- a/x-pack/plugins/maps/public/render_app.tsx +++ b/x-pack/plugins/maps/public/render_app.tsx @@ -76,12 +76,10 @@ export async function renderApp({ setAppChrome(); function renderMapApp(routeProps: RouteComponentProps<{ savedMapId?: string }>) { - const stateTransfer = getEmbeddableService()?.getStateTransfer( - history as AppMountParameters['history'] - ); + const stateTransfer = getEmbeddableService()?.getStateTransfer(); const { embeddableId, originatingApp, valueInput } = - stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; + stateTransfer?.getIncomingEditorState() || {}; let mapEmbeddableInput; if (routeProps.match.params.savedMapId) { From c7995813c2dc1b8c5019fe7140d3fe62e525af31 Mon Sep 17 00:00:00 2001 From: Devon A Thomson Date: Thu, 10 Dec 2020 18:03:59 -0500 Subject: [PATCH 2/6] cleared editor state in maps --- .../maps/public/routes/map_page/saved_map/saved_map.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index 43f9e47aa8677..3930b57b32280 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -329,6 +329,10 @@ export class SavedMap { this._mapEmbeddableInput = updatedMapEmbeddableInput; // break connection to originating application this._originatingApp = undefined; + + // remove editor state so the connection is still broken after reload + this._getStateTransfer().clearEditorState(); + getToasts().addSuccess({ title: i18n.translate('xpack.maps.topNav.saveSuccessMessage', { defaultMessage: `Saved '{title}'`, From 24d7189afd4c4a2fbc21b260d768b82dfd00849d Mon Sep 17 00:00:00 2001 From: Devon A Thomson Date: Thu, 10 Dec 2020 19:46:06 -0500 Subject: [PATCH 3/6] Clear editor state on visualize listing page load --- .../public/application/components/visualize_listing.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/visualize/public/application/components/visualize_listing.tsx b/src/plugins/visualize/public/application/components/visualize_listing.tsx index b2ca784162623..5720eca57e7a5 100644 --- a/src/plugins/visualize/public/application/components/visualize_listing.tsx +++ b/src/plugins/visualize/public/application/components/visualize_listing.tsx @@ -45,6 +45,7 @@ export const VisualizeListing = () => { savedVisualizations, toastNotifications, visualizations, + embeddable, savedObjects, savedObjectsPublic, savedObjectsTagging, @@ -72,6 +73,8 @@ export const VisualizeListing = () => { }, [history, pathname, visualizations]); useMount(() => { + // Reset editor state if the visualize listing page is loaded. + embeddable.getStateTransfer().clearEditorState(); chrome.setBreadcrumbs([ { text: i18n.translate('visualize.visualizeListingBreadcrumbsTitle', { From 58888943d3c785fc37998ac0ee9dcdd90fce533c Mon Sep 17 00:00:00 2001 From: Devon A Thomson Date: Mon, 14 Dec 2020 11:07:46 -0500 Subject: [PATCH 4/6] fixed type, added null check to clearEditorState --- .../application/embeddable/grid/dashboard_grid.test.tsx | 1 - .../public/lib/state_transfer/embeddable_state_transfer.ts | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index fb29ef7b3c036..ef23c636ad482 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -83,7 +83,6 @@ function prepare(props?: Partial) { dashboardContainer = new DashboardContainer(initialInput, options); const defaultTestProps: DashboardGridProps = { container: dashboardContainer, - PanelComponent: () =>
, kibana: null as any, intl: null as any, }; 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 29e61044e9cc1..0b34bea810520 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 @@ -71,8 +71,10 @@ export class EmbeddableStateTransfer { public clearEditorState() { const currentState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY); - delete currentState[EMBEDDABLE_EDITOR_STATE_KEY]; - this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, currentState); + if (currentState) { + delete currentState[EMBEDDABLE_EDITOR_STATE_KEY]; + this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, currentState); + } } /** From 8f26e0603fcdad167521d1ed71b27a6ee9520a12 Mon Sep 17 00:00:00 2001 From: Devon A Thomson Date: Mon, 14 Dec 2020 11:45:26 -0500 Subject: [PATCH 5/6] Type fixes in tests --- .../application/embeddable/viewport/dashboard_viewport.test.tsx | 1 - src/plugins/embeddable/public/mocks.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index e5a1852fa61a5..925687a07bb42 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -92,7 +92,6 @@ function getProps( dashboardContainer = new DashboardContainer(input, options); const defaultTestProps: DashboardViewportProps = { container: dashboardContainer, - PanelComponent: () =>
, }; return { diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 18cf56a6dc8f1..df24d9c0393fe 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -126,7 +126,6 @@ const createStartContract = (): Start => { inject: jest.fn(), migrate: jest.fn(), EmbeddablePanel: jest.fn(), - getEmbeddablePanel: jest.fn(), getStateTransfer: jest.fn(() => createEmbeddableStateTransferMock() as EmbeddableStateTransfer), getAttributeService: jest.fn(), }; From ceae277d507bc083d11934f219b606d22a00030b Mon Sep 17 00:00:00 2001 From: Devon A Thomson Date: Mon, 14 Dec 2020 14:16:32 -0500 Subject: [PATCH 6/6] restored switchViewMode for DashboardViewport --- .../public/application/embeddable/dashboard_container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 0da87a6db3bd9..01b4e81fc484c 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -253,7 +253,7 @@ export class DashboardContainer extends Container - + , dom