mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Time to Visualize] Fix Dashboard OnAppLeave (#86193)
Added isTransferInProgress to embeddable_state_transfer for apps to determine whether or not to show onAppLeave confirm
This commit is contained in:
parent
c33835e87c
commit
8ce9b474d6
15 changed files with 102 additions and 21 deletions
|
@ -9,7 +9,7 @@ Constructs a new instance of the `EmbeddableStateTransfer` class
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: ReadonlyMap<string, PublicAppInfo> | undefined, customStorage?: Storage);
|
||||
constructor(navigateToApp: ApplicationStart['navigateToApp'], currentAppId$: ApplicationStart['currentAppId$'], appList?: ReadonlyMap<string, PublicAppInfo> | undefined, customStorage?: Storage);
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
@ -17,6 +17,7 @@ constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: Readonly
|
|||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| navigateToApp | <code>ApplicationStart['navigateToApp']</code> | |
|
||||
| currentAppId$ | <code>ApplicationStart['currentAppId$']</code> | |
|
||||
| appList | <code>ReadonlyMap<string, PublicAppInfo> | undefined</code> | |
|
||||
| customStorage | <code>Storage</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md) > [isTransferInProgress](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md)
|
||||
|
||||
## EmbeddableStateTransfer.isTransferInProgress property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
isTransferInProgress: boolean;
|
||||
```
|
|
@ -16,13 +16,14 @@ export declare class EmbeddableStateTransfer
|
|||
|
||||
| Constructor | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [(constructor)(navigateToApp, appList, customStorage)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the <code>EmbeddableStateTransfer</code> class |
|
||||
| [(constructor)(navigateToApp, currentAppId$, appList, customStorage)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the <code>EmbeddableStateTransfer</code> class |
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [getAppNameFromId](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getappnamefromid.md) | | <code>(appId: string) => string | undefined</code> | Fetches an internationalized app title when given an appId. |
|
||||
| [isTransferInProgress](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md) | | <code>boolean</code> | |
|
||||
|
||||
## Methods
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import { removeQueryParam } from '../services/kibana_utils';
|
|||
import { IndexPattern } from '../services/data';
|
||||
import { EmbeddableRenderer } from '../services/embeddable';
|
||||
import { DashboardContainerInput } from '.';
|
||||
import { leaveConfirmStrings } from '../dashboard_strings';
|
||||
|
||||
export interface DashboardAppProps {
|
||||
history: History;
|
||||
|
@ -64,8 +65,9 @@ export function DashboardApp({
|
|||
core,
|
||||
onAppLeave,
|
||||
uiSettings,
|
||||
indexPatterns: indexPatternService,
|
||||
embeddable,
|
||||
dashboardCapabilities,
|
||||
indexPatterns: indexPatternService,
|
||||
} = useKibana<DashboardAppServices>().services;
|
||||
|
||||
const [lastReloadTime, setLastReloadTime] = useState(0);
|
||||
|
@ -196,9 +198,14 @@ export function DashboardApp({
|
|||
return;
|
||||
}
|
||||
onAppLeave((actions) => {
|
||||
if (dashboardStateManager?.getIsDirty()) {
|
||||
// TODO: Finish App leave handler with overrides when redirecting to an editor.
|
||||
// return actions.confirm(leaveConfirmStrings.leaveSubtitle, leaveConfirmStrings.leaveTitle);
|
||||
if (
|
||||
dashboardStateManager?.getIsDirty() &&
|
||||
!embeddable.getStateTransfer().isTransferInProgress
|
||||
) {
|
||||
return actions.confirm(
|
||||
leaveConfirmStrings.getLeaveSubtitle(),
|
||||
leaveConfirmStrings.getLeaveTitle()
|
||||
);
|
||||
}
|
||||
return actions.default();
|
||||
});
|
||||
|
@ -206,7 +213,7 @@ export function DashboardApp({
|
|||
// reset on app leave handler so leaving from the listing page doesn't trigger a confirmation
|
||||
onAppLeave((actions) => actions.default());
|
||||
};
|
||||
}, [dashboardStateManager, dashboardContainer, onAppLeave]);
|
||||
}, [dashboardStateManager, dashboardContainer, onAppLeave, embeddable]);
|
||||
|
||||
// Refresh the dashboard container when lastReloadTime changes
|
||||
useEffect(() => {
|
||||
|
|
|
@ -23,6 +23,7 @@ import { EmbeddableStateTransfer } from '.';
|
|||
import { ApplicationStart, PublicAppInfo } from '../../../../../core/public';
|
||||
import { EMBEDDABLE_EDITOR_STATE_KEY, EMBEDDABLE_PACKAGE_STATE_KEY } from './types';
|
||||
import { EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY } from './embeddable_state_transfer';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
const createStorage = (): Storage => {
|
||||
const createMockStore = () => {
|
||||
|
@ -46,16 +47,24 @@ const createStorage = (): Storage => {
|
|||
describe('embeddable state transfer', () => {
|
||||
let application: jest.Mocked<ApplicationStart>;
|
||||
let stateTransfer: EmbeddableStateTransfer;
|
||||
let currentAppId$: Subject<string | undefined>;
|
||||
let store: Storage;
|
||||
|
||||
const destinationApp = 'superUltraVisualize';
|
||||
const originatingApp = 'superUltraTestDashboard';
|
||||
|
||||
beforeEach(() => {
|
||||
currentAppId$ = new Subject();
|
||||
currentAppId$.next(originatingApp);
|
||||
const core = coreMock.createStart();
|
||||
application = core.application;
|
||||
store = createStorage();
|
||||
stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, store);
|
||||
stateTransfer = new EmbeddableStateTransfer(
|
||||
application.navigateToApp,
|
||||
currentAppId$,
|
||||
undefined,
|
||||
store
|
||||
);
|
||||
});
|
||||
|
||||
it('cannot fetch app name when given no app list', async () => {
|
||||
|
@ -67,7 +76,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, appsList);
|
||||
stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, currentAppId$, appsList);
|
||||
expect(stateTransfer.getAppNameFromId('kibanana')).toBeUndefined();
|
||||
});
|
||||
|
||||
|
@ -76,7 +85,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, appsList);
|
||||
stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, currentAppId$, appsList);
|
||||
expect(stateTransfer.getAppNameFromId('testId')).toBe('State Transfer Test App Hello');
|
||||
expect(stateTransfer.getAppNameFromId('testId2')).toBe('State Transfer Test App Goodbye');
|
||||
});
|
||||
|
@ -107,6 +116,13 @@ describe('embeddable state transfer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('sets isTransferInProgress to true when sending an outgoing editor state', async () => {
|
||||
await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp } });
|
||||
expect(stateTransfer.isTransferInProgress).toEqual(true);
|
||||
currentAppId$.next(destinationApp);
|
||||
expect(stateTransfer.isTransferInProgress).toEqual(false);
|
||||
});
|
||||
|
||||
it('can send an outgoing embeddable package state', async () => {
|
||||
await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, {
|
||||
state: { type: 'coolestType', input: { savedObjectId: '150' } },
|
||||
|
@ -135,6 +151,15 @@ describe('embeddable state transfer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('sets isTransferInProgress to true when sending an outgoing embeddable package state', async () => {
|
||||
await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, {
|
||||
state: { type: 'coolestType', input: { savedObjectId: '150' } },
|
||||
});
|
||||
expect(stateTransfer.isTransferInProgress).toEqual(true);
|
||||
currentAppId$.next(destinationApp);
|
||||
expect(stateTransfer.isTransferInProgress).toEqual(false);
|
||||
});
|
||||
|
||||
it('can fetch an incoming editor state', async () => {
|
||||
store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, {
|
||||
[EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' },
|
||||
|
|
|
@ -38,14 +38,20 @@ export const EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY = 'EMBEDDABLE_STATE_TRANSFER'
|
|||
* @public
|
||||
*/
|
||||
export class EmbeddableStateTransfer {
|
||||
public isTransferInProgress: boolean;
|
||||
private storage: Storage;
|
||||
|
||||
constructor(
|
||||
private navigateToApp: ApplicationStart['navigateToApp'],
|
||||
currentAppId$: ApplicationStart['currentAppId$'],
|
||||
private appList?: ReadonlyMap<string, PublicAppInfo> | undefined,
|
||||
customStorage?: Storage
|
||||
) {
|
||||
this.storage = customStorage ? customStorage : new Storage(sessionStorage);
|
||||
this.isTransferInProgress = false;
|
||||
currentAppId$.subscribe(() => {
|
||||
this.isTransferInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,6 +111,7 @@ export class EmbeddableStateTransfer {
|
|||
state: EmbeddableEditorState;
|
||||
}
|
||||
): Promise<void> {
|
||||
this.isTransferInProgress = true;
|
||||
await this.navigateToWithState<EmbeddableEditorState>(appId, EMBEDDABLE_EDITOR_STATE_KEY, {
|
||||
...options,
|
||||
appendToExistingState: true,
|
||||
|
@ -119,6 +126,7 @@ export class EmbeddableStateTransfer {
|
|||
appId: string,
|
||||
options?: { path?: string; state: EmbeddablePackageState }
|
||||
): Promise<void> {
|
||||
this.isTransferInProgress = true;
|
||||
await this.navigateToWithState<EmbeddablePackageState>(appId, EMBEDDABLE_PACKAGE_STATE_KEY, {
|
||||
...options,
|
||||
appendToExistingState: true,
|
||||
|
|
|
@ -161,6 +161,7 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
|
||||
this.stateTransferService = new EmbeddableStateTransfer(
|
||||
core.application.navigateToApp,
|
||||
core.application.currentAppId$,
|
||||
this.appList
|
||||
);
|
||||
this.isRegistryReady = true;
|
||||
|
@ -206,7 +207,12 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
),
|
||||
getStateTransfer: (storage?: Storage) =>
|
||||
storage
|
||||
? new EmbeddableStateTransfer(core.application.navigateToApp, this.appList, storage)
|
||||
? new EmbeddableStateTransfer(
|
||||
core.application.navigateToApp,
|
||||
core.application.currentAppId$,
|
||||
this.appList,
|
||||
storage
|
||||
)
|
||||
: this.stateTransferService,
|
||||
EmbeddablePanel: getEmbeddablePanelHoc(),
|
||||
telemetry: getTelemetryFunction(commonContract),
|
||||
|
|
|
@ -628,12 +628,14 @@ export interface EmbeddableStartDependencies {
|
|||
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'], appList?: ReadonlyMap<string, PublicAppInfo> | undefined, customStorage?: Storage);
|
||||
constructor(navigateToApp: ApplicationStart['navigateToApp'], currentAppId$: ApplicationStart['currentAppId$'], appList?: ReadonlyMap<string, PublicAppInfo> | undefined, customStorage?: Storage);
|
||||
// (undocumented)
|
||||
clearEditorState(): void;
|
||||
getAppNameFromId: (appId: string) => string | undefined;
|
||||
getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined;
|
||||
getIncomingEmbeddablePackage(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"
|
||||
navigateToEditor(appId: string, options?: {
|
||||
path?: string;
|
||||
|
|
|
@ -87,11 +87,11 @@ export async function createTestUserService(
|
|||
});
|
||||
|
||||
if (browser && testSubjects && shouldRefreshBrowser) {
|
||||
// accept alert if it pops up
|
||||
const alert = await browser.getAlert();
|
||||
await alert?.accept();
|
||||
if (await testSubjects.exists('kibanaChrome', { allowHidden: true })) {
|
||||
await browser.refresh();
|
||||
// accept alert if it pops up
|
||||
const alert = await browser.getAlert();
|
||||
await alert?.accept();
|
||||
await testSubjects.find('kibanaChrome', config.get('timeouts.find') * 10);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('saves the saved visualization url to the app link', async () => {
|
||||
await PageObjects.header.clickVisualize();
|
||||
await PageObjects.header.clickVisualize(true);
|
||||
const currentUrl = await browser.getCurrentUrl();
|
||||
expect(currentUrl).to.contain(VisualizeConstants.EDIT_PATH);
|
||||
});
|
||||
|
|
|
@ -40,6 +40,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
after(async () => {
|
||||
await kibanaServer.uiSettings.replace({});
|
||||
await browser.refresh();
|
||||
const alert = await browser.getAlert();
|
||||
await alert?.accept();
|
||||
});
|
||||
|
||||
it('Visualization updated when time picker changes', async () => {
|
||||
|
@ -88,6 +90,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
`)`;
|
||||
log.debug('go to url' + `${kibanaBaseUrl}#${urlQuery}`);
|
||||
await browser.get(`${kibanaBaseUrl}#${urlQuery}`, true);
|
||||
const alert = await browser.getAlert();
|
||||
await alert?.accept();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const time = await PageObjects.timePicker.getTimeConfig();
|
||||
const refresh = await PageObjects.timePicker.getRefreshConfig();
|
||||
|
@ -99,6 +103,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('Timepicker respects dateFormat from UI settings', async () => {
|
||||
await kibanaServer.uiSettings.replace({ dateFormat: 'YYYY-MM-DD HH:mm:ss.SSS' });
|
||||
await browser.refresh();
|
||||
const alert = await browser.getAlert();
|
||||
await alert?.accept();
|
||||
await PageObjects.dashboard.gotoDashboardLandingPage();
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
await PageObjects.dashboard.addVisualizations([PIE_CHART_VIS_NAME]);
|
||||
|
|
|
@ -110,7 +110,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const searchName = 'my search';
|
||||
|
||||
before(async () => {
|
||||
await PageObjects.header.clickDiscover();
|
||||
await PageObjects.header.clickDiscover(true);
|
||||
await PageObjects.discover.clickNewSearchButton();
|
||||
await dashboardVisualizations.createSavedSearch({ name: searchName, fields: ['bytes'] });
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
|
|
@ -70,6 +70,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('replaced panel persisted correctly when dashboard is hard refreshed', async () => {
|
||||
const currentUrl = await browser.getCurrentUrl();
|
||||
await browser.get(currentUrl, true);
|
||||
const alert = await browser.getAlert();
|
||||
await alert?.accept();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
const panelTitles = await PageObjects.dashboard.getPanelTitles();
|
||||
|
|
|
@ -31,14 +31,16 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo
|
|||
const defaultFindTimeout = config.get('timeouts.find');
|
||||
|
||||
class HeaderPage {
|
||||
public async clickDiscover() {
|
||||
public async clickDiscover(ignoreAppLeaveWarning = false) {
|
||||
await appsMenu.clickLink('Discover', { category: 'kibana' });
|
||||
await this.onAppLeaveWarning(ignoreAppLeaveWarning);
|
||||
await PageObjects.common.waitForTopNavToBeVisible();
|
||||
await this.awaitGlobalLoadingIndicatorHidden();
|
||||
}
|
||||
|
||||
public async clickVisualize() {
|
||||
public async clickVisualize(ignoreAppLeaveWarning = false) {
|
||||
await appsMenu.clickLink('Visualize', { category: 'kibana' });
|
||||
await this.onAppLeaveWarning(ignoreAppLeaveWarning);
|
||||
await this.awaitGlobalLoadingIndicatorHidden();
|
||||
await retry.waitFor('first breadcrumb to be "Visualize"', async () => {
|
||||
const firstBreadcrumb = await globalNav.getFirstBreadcrumb();
|
||||
|
@ -95,6 +97,17 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo
|
|||
log.debug('awaitKibanaChrome');
|
||||
await testSubjects.find('kibanaChrome', defaultFindTimeout * 10);
|
||||
}
|
||||
|
||||
public async onAppLeaveWarning(ignoreWarning = false) {
|
||||
await retry.try(async () => {
|
||||
const warning = await testSubjects.exists('confirmModalTitleText');
|
||||
if (warning) {
|
||||
await testSubjects.click(
|
||||
ignoreWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new HeaderPage();
|
||||
|
|
|
@ -58,8 +58,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F
|
|||
fields?: string[];
|
||||
}) {
|
||||
log.debug(`createSavedSearch(${name})`);
|
||||
await PageObjects.header.clickDiscover();
|
||||
|
||||
await PageObjects.header.clickDiscover(true);
|
||||
await PageObjects.timePicker.setHistoricalDataRange();
|
||||
|
||||
if (query) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue