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] Clear All Editor State when Visualize Listing Page Loads #91005

Merged
merged 3 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Clears the [editor state](./kibana-plugin-plugins-embeddable-public.embeddableed
<b>Signature:</b>

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

## Parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ describe('embeddable state transfer', () => {

const testAppId = 'testApp';

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

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

it('can fetch an incoming editor state', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[buildKey(testAppId, EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'superUltraTestDashboard',
[EMBEDDABLE_EDITOR_STATE_KEY]: {
[testAppId]: {
originatingApp: 'superUltraTestDashboard',
},
},
});
const fetchedState = stateTransfer.getIncomingEditorState(testAppId);
Expand All @@ -175,14 +183,16 @@ describe('embeddable state transfer', () => {

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',
[EMBEDDABLE_EDITOR_STATE_KEY]: {
otherApp1: {
originatingApp: 'whoops not me',
},
otherApp2: {
originatingApp: 'otherTestDashboard',
},
[testAppId]: {
originatingApp: 'superUltraTestDashboard',
},
},
});
const fetchedState = stateTransfer.getIncomingEditorState(testAppId);
Expand All @@ -194,8 +204,10 @@ describe('embeddable state transfer', () => {

it('incoming editor state returns undefined when state is not in the right shape', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[buildKey(testAppId, EMBEDDABLE_EDITOR_STATE_KEY)]: {
helloSportsKibana: 'superUltraTestDashboard',
[EMBEDDABLE_EDITOR_STATE_KEY]: {
[testAppId]: {
helloSportsKibana: 'superUltraTestDashboard',
},
},
});
const fetchedState = stateTransfer.getIncomingEditorState(testAppId);
Expand All @@ -204,9 +216,11 @@ describe('embeddable state transfer', () => {

it('can fetch an incoming embeddable package state', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[buildKey(testAppId, EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'skisEmbeddable',
input: { savedObjectId: '123' },
[EMBEDDABLE_PACKAGE_STATE_KEY]: {
[testAppId]: {
type: 'skisEmbeddable',
input: { savedObjectId: '123' },
},
},
});
const fetchedState = stateTransfer.getIncomingEmbeddablePackage(testAppId);
Expand All @@ -215,13 +229,15 @@ describe('embeddable state transfer', () => {

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' },
[EMBEDDABLE_PACKAGE_STATE_KEY]: {
[testAppId]: {
type: 'skisEmbeddable',
input: { savedObjectId: '123' },
},
testApp2: {
type: 'crossCountryEmbeddable',
input: { savedObjectId: '456' },
},
},
});
const fetchedState = stateTransfer.getIncomingEmbeddablePackage(testAppId);
Expand All @@ -236,17 +252,23 @@ describe('embeddable state transfer', () => {

it('embeddable package state returns undefined when state is not in the right shape', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[buildKey(testAppId, EMBEDDABLE_PACKAGE_STATE_KEY)]: { kibanaIsFor: 'sports' },
[EMBEDDABLE_PACKAGE_STATE_KEY]: {
[testAppId]: {
kibanaIsFor: 'sports',
},
},
});
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, {
[buildKey(testAppId, EMBEDDABLE_PACKAGE_STATE_KEY)]: {
type: 'coolestType',
input: { savedObjectId: '150' },
[EMBEDDABLE_PACKAGE_STATE_KEY]: {
[testAppId]: {
type: 'coolestType',
input: { savedObjectId: '150' },
},
},
iSHouldStillbeHere: 'doing the sports thing',
});
Expand All @@ -258,8 +280,10 @@ describe('embeddable state transfer', () => {

it('removes editor state key when removeAfterFetch is true', async () => {
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
[buildKey(testAppId, EMBEDDABLE_EDITOR_STATE_KEY)]: {
originatingApp: 'superCoolFootballDashboard',
[EMBEDDABLE_EDITOR_STATE_KEY]: {
[testAppId]: {
originatingApp: 'superCoolFootballDashboard',
},
},
iSHouldStillbeHere: 'doing the sports thing',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,14 @@ export class EmbeddableStateTransfer {
* @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) {
public clearEditorState(appId?: string) {
const currentState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY);
if (currentState) {
delete currentState[this.buildKey(appId, EMBEDDABLE_EDITOR_STATE_KEY)];
if (appId) {
delete currentState[EMBEDDABLE_EDITOR_STATE_KEY]?.[appId];
} else {
delete currentState[EMBEDDABLE_EDITOR_STATE_KEY];
}
this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, currentState);
}
}
Expand Down Expand Up @@ -117,7 +121,6 @@ export class EmbeddableStateTransfer {
this.isTransferInProgress = true;
await this.navigateToWithState<EmbeddableEditorState>(appId, EMBEDDABLE_EDITOR_STATE_KEY, {
...options,
appendToExistingState: true,
});
}

Expand All @@ -132,14 +135,9 @@ export class EmbeddableStateTransfer {
this.isTransferInProgress = true;
await this.navigateToWithState<EmbeddablePackageState>(appId, EMBEDDABLE_PACKAGE_STATE_KEY, {
...options,
appendToExistingState: true,
});
}

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

private getIncomingState<IncomingStateType>(
guard: (state: unknown) => state is IncomingStateType,
appId: string,
Expand All @@ -148,15 +146,13 @@ export class EmbeddableStateTransfer {
keysToRemoveAfterFetch?: string[];
}
): IncomingStateType | undefined {
const incomingState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)?.[
this.buildKey(appId, key)
];
const incomingState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)?.[key]?.[appId];
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[this.buildKey(appId, keyToRemove)];
delete stateReplace[keyToRemove];
});
this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateReplace);
}
Expand All @@ -166,14 +162,16 @@ export class EmbeddableStateTransfer {
private async navigateToWithState<OutgoingStateType = unknown>(
appId: string,
key: string,
options?: { path?: string; state?: OutgoingStateType; appendToExistingState?: boolean }
options?: { path?: string; state?: OutgoingStateType }
): Promise<void> {
const stateObject = options?.appendToExistingState
? {
...this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY),
[this.buildKey(appId, key)]: options.state,
}
: { [this.buildKey(appId, key)]: options?.state };
const existingAppState = this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY)?.[key] || {};
const stateObject = {
...this.storage.get(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY),
[key]: {
...existingAppState,
[appId]: options?.state,
},
};
this.storage.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, stateObject);
await this.navigateToApp(appId, { path: options?.path });
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/embeddable/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ 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);
clearEditorState(appId: string): void;
clearEditorState(appId?: string): void;
getAppNameFromId: (appId: string) => string | undefined;
getIncomingEditorState(appId: string, removeAfterFetch?: boolean): EmbeddableEditorState | undefined;
getIncomingEmbeddablePackage(appId: string, removeAfterFetch?: boolean): EmbeddablePackageState | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export const VisualizeListing = () => {
}, [history, pathname, visualizations]);

useMount(() => {
// Reset editor state if the visualize listing page is loaded.
stateTransferService.clearEditorState(VisualizeConstants.APP_ID);
// Reset editor state for all apps if the visualize listing page is loaded.
stateTransferService.clearEditorState();
chrome.setBreadcrumbs([
{
text: i18n.translate('visualize.visualizeListingBreadcrumbsTitle', {
Expand Down
27 changes: 26 additions & 1 deletion x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';

export default function ({ getPageObjects, getService }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens']);
const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens', 'header']);

const find = getService('find');
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const dashboardPanelActions = getService('dashboardPanelActions');
const dashboardVisualizations = getService('dashboardVisualizations');

Expand Down Expand Up @@ -69,5 +70,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const titles = await PageObjects.dashboard.getPanelTitles();
expect(titles.indexOf(newTitle)).to.not.be(-1);
});

it('is no longer linked to a dashboard after visiting the visuali1ze listing page', async () => {
await PageObjects.visualize.gotoVisualizationLandingPage();
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickLensWidget();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.lens.configureDimension({
dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension',
operation: 'date_histogram',
field: '@timestamp',
});
await PageObjects.lens.configureDimension({
dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
operation: 'avg',
field: 'bytes',
});
await PageObjects.lens.notLinkedToOriginatingApp();
await PageObjects.header.waitUntilLoadingHasFinished();

// return to origin should not be present in save modal
await testSubjects.click('lnsApp_saveButton');
const redirectToOriginCheckboxExists = await testSubjects.exists('returnToOriginModeSwitch');
expect(redirectToOriginCheckboxExists).to.be(false);
});
});
}