Skip to content

Commit

Permalink
[Time To Visualize] Make State Transfer App Specific (#89804) (#90676)
Browse files Browse the repository at this point in the history
* made state transfer app specific
  • Loading branch information
ThomThomson authored Feb 8, 2021
1 parent deab7aa commit 5b861f7
Show file tree
Hide file tree
Showing 21 changed files with 162 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@

## EmbeddableStateTransfer.clearEditorState() method

Clears the [editor state](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) from the sessionStorage for the provided app id

<b>Signature:</b>

```typescript
clearEditorState(): void;
clearEditorState(appId: string): void;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| appId | <code>string</code> | The app to fetch incomingEditorState for |

<b>Returns:</b>

`void`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

## EmbeddableStateTransfer.getIncomingEditorState() method

Fetches an [originating app](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) argument from the sessionStorage
Fetches an [editor state](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) from the sessionStorage for the provided app id

<b>Signature:</b>

```typescript
getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined;
getIncomingEditorState(appId: string, removeAfterFetch?: boolean): EmbeddableEditorState | undefined;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| appId | <code>string</code> | The app to fetch incomingEditorState for |
| removeAfterFetch | <code>boolean</code> | Whether to remove the package state after fetch to prevent duplicates. |

<b>Returns:</b>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

## EmbeddableStateTransfer.getIncomingEmbeddablePackage() method

Fetches an [embeddable package](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) argument from the sessionStorage
Fetches an [embeddable package](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) from the sessionStorage for the given AppId

<b>Signature:</b>

```typescript
getIncomingEmbeddablePackage(removeAfterFetch?: boolean): EmbeddablePackageState | undefined;
getIncomingEmbeddablePackage(appId: string, removeAfterFetch?: boolean): EmbeddablePackageState | undefined;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| appId | <code>string</code> | The app to fetch EmbeddablePackageState for |
| removeAfterFetch | <code>boolean</code> | Whether to remove the package state after fetch to prevent duplicates. |

<b>Returns:</b>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export declare class EmbeddableStateTransfer

| Method | Modifiers | Description |
| --- | --- | --- |
| [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 |
| [clearEditorState(appId)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.cleareditorstate.md) | | Clears the [editor state](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) from the sessionStorage for the provided app id |
| [getIncomingEditorState(appId, removeAfterFetch)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingeditorstate.md) | | Fetches an [editor state](./kibana-plugin-plugins-embeddable-public.embeddableeditorstate.md) from the sessionStorage for the provided app id |
| [getIncomingEmbeddablePackage(appId, removeAfterFetch)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getincomingembeddablepackage.md) | | Fetches an [embeddable package](./kibana-plugin-plugins-embeddable-public.embeddablepackagestate.md) from the sessionStorage for the given AppId |
| [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) |

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {

import { DashboardStateManager } from '../dashboard_state_manager';
import { getDashboardContainerInput, getSearchSessionIdFromURL } from '../dashboard_app_functions';
import { DashboardContainer, DashboardContainerInput } from '../..';
import { DashboardConstants, DashboardContainer, DashboardContainerInput } from '../..';
import { DashboardAppServices } from '../types';
import { DASHBOARD_CONTAINER_TYPE } from '..';

Expand Down Expand Up @@ -68,7 +68,9 @@ export const useDashboardContainer = (
searchSession.restore(searchSessionIdFromURL);
}

const incomingEmbeddable = embeddable.getStateTransfer().getIncomingEmbeddablePackage(true);
const incomingEmbeddable = embeddable
.getStateTransfer()
.getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, true);

let canceled = false;
let pendingContainer: DashboardContainer | ErrorEmbeddable | null | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ describe('embeddable state transfer', () => {
const destinationApp = 'superUltraVisualize';
const originatingApp = 'superUltraTestDashboard';

const testAppId = 'testApp';

const buildKey = (appId: string, key: string) => `${appId}-${key}`;

beforeEach(() => {
currentAppId$ = new Subject();
currentAppId$.next(originatingApp);
Expand Down Expand Up @@ -82,7 +86,9 @@ describe('embeddable state transfer', () => {
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' },
[buildKey(destinationApp, EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'superUltraTestDashboard',
},
});
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
path: undefined,
Expand All @@ -98,7 +104,9 @@ describe('embeddable state transfer', () => {
});
expect(store.set).toHaveBeenCalledWith(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
kibanaIsNowForSports: 'extremeSportsKibana',
[EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' },
[buildKey(destinationApp, EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'superUltraTestDashboard',
},
});
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
path: undefined,
Expand All @@ -117,7 +125,10 @@ describe('embeddable state transfer', () => {
state: { type: 'coolestType', input: { savedObjectId: '150' } },
});
expect(store.set).toHaveBeenCalledWith(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[EMBEDDABLE_PACKAGE_STATE_KEY]: { type: 'coolestType', input: { savedObjectId: '150' } },
[buildKey(destinationApp, EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'coolestType',
input: { savedObjectId: '150' },
},
});
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
path: undefined,
Expand All @@ -133,7 +144,10 @@ describe('embeddable state transfer', () => {
});
expect(store.set).toHaveBeenCalledWith(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
kibanaIsNowForSports: 'extremeSportsKibana',
[EMBEDDABLE_PACKAGE_STATE_KEY]: { type: 'coolestType', input: { savedObjectId: '150' } },
[buildKey(destinationApp, EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'coolestType',
input: { savedObjectId: '150' },
},
});
expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', {
path: undefined,
Expand All @@ -151,53 +165,105 @@ describe('embeddable state transfer', () => {

it('can fetch an incoming editor state', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' },
[buildKey(testAppId, EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'superUltraTestDashboard',
},
});
const fetchedState = stateTransfer.getIncomingEditorState(testAppId);
expect(fetchedState).toEqual({ originatingApp: 'superUltraTestDashboard' });
});

it('can fetch an incoming editor state and ignore state for other apps', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[buildKey('otherApp1', EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'whoops not me',
},
[buildKey('otherApp2', EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'otherTestDashboard',
},
[buildKey(testAppId, EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'superUltraTestDashboard',
},
});
const fetchedState = stateTransfer.getIncomingEditorState();
const fetchedState = stateTransfer.getIncomingEditorState(testAppId);
expect(fetchedState).toEqual({ originatingApp: 'superUltraTestDashboard' });

const fetchedState2 = stateTransfer.getIncomingEditorState('otherApp2');
expect(fetchedState2).toEqual({ originatingApp: 'otherTestDashboard' });
});

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' },
[buildKey(testAppId, EMBEDDABLE_EDITOR_STATE_KEY)]: {
helloSportsKibana: 'superUltraTestDashboard',
},
});
const fetchedState = stateTransfer.getIncomingEditorState();
const fetchedState = stateTransfer.getIncomingEditorState(testAppId);
expect(fetchedState).toBeUndefined();
});

it('can fetch an incoming embeddable package state', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[EMBEDDABLE_PACKAGE_STATE_KEY]: { type: 'skisEmbeddable', input: { savedObjectId: '123' } },
[buildKey(testAppId, EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'skisEmbeddable',
input: { savedObjectId: '123' },
},
});
const fetchedState = stateTransfer.getIncomingEmbeddablePackage();
const fetchedState = stateTransfer.getIncomingEmbeddablePackage(testAppId);
expect(fetchedState).toEqual({ type: 'skisEmbeddable', input: { savedObjectId: '123' } });
});

it('can fetch an incoming embeddable package state and ignore state for other apps', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[buildKey(testAppId, EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'skisEmbeddable',
input: { savedObjectId: '123' },
},
[buildKey('testApp2', EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'crossCountryEmbeddable',
input: { savedObjectId: '456' },
},
});
const fetchedState = stateTransfer.getIncomingEmbeddablePackage(testAppId);
expect(fetchedState).toEqual({ type: 'skisEmbeddable', input: { savedObjectId: '123' } });

const fetchedState2 = stateTransfer.getIncomingEmbeddablePackage('testApp2');
expect(fetchedState2).toEqual({
type: 'crossCountryEmbeddable',
input: { savedObjectId: '456' },
});
});

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' },
[buildKey(testAppId, EMBEDDABLE_PACKAGE_STATE_KEY)]: { kibanaIsFor: 'sports' },
});
const fetchedState = stateTransfer.getIncomingEmbeddablePackage();
const fetchedState = stateTransfer.getIncomingEmbeddablePackage(testAppId);
expect(fetchedState).toBeUndefined();
});

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' } },
[buildKey(testAppId, EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'coolestType',
input: { savedObjectId: '150' },
},
iSHouldStillbeHere: 'doing the sports thing',
});
stateTransfer.getIncomingEmbeddablePackage(true);
stateTransfer.getIncomingEmbeddablePackage(testAppId, true);
expect(store.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)).toEqual({
iSHouldStillbeHere: 'doing the sports thing',
});
});

it('removes editor state key when removeAfterFetch is true', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superCoolFootballDashboard' },
[buildKey(testAppId, EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'superCoolFootballDashboard',
},
iSHouldStillbeHere: 'doing the sports thing',
});
stateTransfer.getIncomingEditorState(true);
stateTransfer.getIncomingEditorState(testAppId, true);
expect(store.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)).toEqual({
iSHouldStillbeHere: 'doing the sports thing',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,38 +50,52 @@ export class EmbeddableStateTransfer {
public getAppNameFromId = (appId: string): string | undefined => this.appList?.get(appId)?.title;

/**
* Fetches an {@link EmbeddableEditorState | originating app} argument from the sessionStorage
* Fetches an {@link EmbeddableEditorState | editor state} from the sessionStorage for the provided app id
*
* @param appId - The app to fetch incomingEditorState for
* @param removeAfterFetch - Whether to remove the package state after fetch to prevent duplicates.
*/
public getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined {
public getIncomingEditorState(
appId: string,
removeAfterFetch?: boolean
): EmbeddableEditorState | undefined {
return this.getIncomingState<EmbeddableEditorState>(
isEmbeddableEditorState,
appId,
EMBEDDABLE_EDITOR_STATE_KEY,
{
keysToRemoveAfterFetch: removeAfterFetch ? [EMBEDDABLE_EDITOR_STATE_KEY] : undefined,
}
);
}

public clearEditorState() {
/**
* Clears the {@link EmbeddableEditorState | editor state} from the sessionStorage for the provided app id
*
* @param appId - The app to fetch incomingEditorState for
* @param removeAfterFetch - Whether to remove the package state after fetch to prevent duplicates.
*/
public clearEditorState(appId: string) {
const currentState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY);
if (currentState) {
delete currentState[EMBEDDABLE_EDITOR_STATE_KEY];
delete currentState[this.buildKey(appId, EMBEDDABLE_EDITOR_STATE_KEY)];
this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, currentState);
}
}

/**
* Fetches an {@link EmbeddablePackageState | embeddable package} argument from the sessionStorage
* Fetches an {@link EmbeddablePackageState | embeddable package} from the sessionStorage for the given AppId
*
* @param appId - The app to fetch EmbeddablePackageState for
* @param removeAfterFetch - Whether to remove the package state after fetch to prevent duplicates.
*/
public getIncomingEmbeddablePackage(
appId: string,
removeAfterFetch?: boolean
): EmbeddablePackageState | undefined {
return this.getIncomingState<EmbeddablePackageState>(
isEmbeddablePackageState,
appId,
EMBEDDABLE_PACKAGE_STATE_KEY,
{
keysToRemoveAfterFetch: removeAfterFetch ? [EMBEDDABLE_PACKAGE_STATE_KEY] : undefined,
Expand Down Expand Up @@ -122,20 +136,27 @@ export class EmbeddableStateTransfer {
});
}

private buildKey(appId: string, key: string) {
return `${appId}-${key}`;
}

private getIncomingState<IncomingStateType>(
guard: (state: unknown) => state is IncomingStateType,
appId: string,
key: string,
options?: {
keysToRemoveAfterFetch?: string[];
}
): IncomingStateType | undefined {
const incomingState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)?.[key];
const incomingState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)?.[
this.buildKey(appId, key)
];
const castState =
!guard || guard(incomingState) ? (cloneDeep(incomingState) as IncomingStateType) : undefined;
if (castState && options?.keysToRemoveAfterFetch) {
const stateReplace = { ...this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY) };
options.keysToRemoveAfterFetch.forEach((keyToRemove: string) => {
delete stateReplace[keyToRemove];
delete stateReplace[this.buildKey(appId, keyToRemove)];
});
this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateReplace);
}
Expand All @@ -150,9 +171,9 @@ export class EmbeddableStateTransfer {
const stateObject = options?.appendToExistingState
? {
...this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY),
[key]: options.state,
[this.buildKey(appId, key)]: options.state,
}
: { [key]: options?.state };
: { [this.buildKey(appId, key)]: options?.state };
this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateObject);
await this.navigateToApp(appId, { path: options?.path });
}
Expand Down
7 changes: 3 additions & 4 deletions src/plugins/embeddable/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -590,11 +590,10 @@ 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'], currentAppId$: ApplicationStart['currentAppId$'], appList?: ReadonlyMap<string, PublicAppInfo> | undefined, customStorage?: Storage);
// (undocumented)
clearEditorState(): void;
clearEditorState(appId: string): void;
getAppNameFromId: (appId: string) => string | undefined;
getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined;
getIncomingEmbeddablePackage(removeAfterFetch?: boolean): EmbeddablePackageState | undefined;
getIncomingEditorState(appId: string, removeAfterFetch?: boolean): EmbeddableEditorState | undefined;
getIncomingEmbeddablePackage(appId: string, removeAfterFetch?: boolean): EmbeddablePackageState | undefined;
// (undocumented)
isTransferInProgress: boolean;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ApplicationStart"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {

useEffect(() => {
const { originatingApp: value, embeddableId: embeddableIdValue, valueInput: valueInputValue } =
services.stateTransferService.getIncomingEditorState() || {};
services.stateTransferService.getIncomingEditorState(VisualizeConstants.APP_ID) || {};
setOriginatingApp(value);
setValueInput(valueInputValue);
setEmbeddableId(embeddableIdValue);
Expand Down
Loading

0 comments on commit 5b861f7

Please sign in to comment.