Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Time To Visualize] Make State Transfer App Specific #89804

Merged
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