[Maps] show dialog to save map when leaving app (#40215)

This commit is contained in:
Thomas Neirynck 2019-07-11 08:28:49 -04:00 committed by GitHub
parent 3789010d47
commit 6ae5b56df6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 107 additions and 15 deletions

View file

@ -37,6 +37,33 @@ export function CommonPageProvider({ getService, getPageObjects }) {
const defaultFindTimeout = config.get('timeouts.find');
class CommonPage {
static async navigateToUrlAndHandleAlert(url, shouldAcceptAlert) {
log.debug('Navigate to: ' + url);
try {
await browser.get(url);
} catch(navigationError) {
log.debug('Error navigating to url');
const alert = await browser.getAlert();
if (alert && alert.accept) {
if (shouldAcceptAlert) {
log.debug('Should accept alert');
try {
await alert.accept();
} catch(alertException) {
log.debug('Error accepting alert');
throw alertException;
}
} else {
log.debug('Will not accept alert');
throw navigationError;
}
} else {
throw navigationError;
}
}
}
getHostPort() {
return getUrl.baseUrl(config.get('servers.kibana'));
}
@ -52,7 +79,8 @@ export function CommonPageProvider({ getService, getPageObjects }) {
async navigateToUrl(appName, subUrl, {
basePath = '',
ensureCurrentUrl = true,
shouldLoginIfPrompted = true
shouldLoginIfPrompted = true,
shouldAcceptAlert = true
} = {}) {
// we onlt use the pathname from the appConfig and use the subUrl as the hash
const appConfig = {
@ -62,9 +90,7 @@ export function CommonPageProvider({ getService, getPageObjects }) {
const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig);
await retry.try(async () => {
log.debug(`navigateToUrl ${appUrl}`);
await browser.get(appUrl);
await CommonPage.navigateToUrlAndHandleAlert(appUrl, shouldAcceptAlert);
const currentUrl = shouldLoginIfPrompted ? await this.loginIfPrompted(appUrl) : await browser.getCurrentUrl();
if (ensureCurrentUrl && !currentUrl.includes(appUrl)) {
@ -123,7 +149,7 @@ export function CommonPageProvider({ getService, getPageObjects }) {
return currentUrl;
}
navigateToApp(appName, { basePath = '', shouldLoginIfPrompted = true, hash = '' } = {}) {
navigateToApp(appName, { basePath = '', shouldLoginIfPrompted = true, shouldAcceptAlert = true, hash = '' } = {}) {
const self = this;
const appConfig = config.get(['apps', appName]);
const appUrl = getUrl.noAuth(config.get('servers.kibana'), {
@ -136,9 +162,8 @@ export function CommonPageProvider({ getService, getPageObjects }) {
return retry.try(function () {
// since we're using hash URLs, always reload first to force re-render
return kibanaServer.uiSettings.getDefaultIndex()
.then(function () {
log.debug('navigate to: ' + url);
return browser.get(url);
.then(async function () {
return await CommonPage.navigateToUrlAndHandleAlert(url, shouldAcceptAlert);
})
.then(function () {
return self.sleep(700);

View file

@ -82,6 +82,18 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
: (driver as any).actions({ bridge: true });
}
/**
* Get handle for an alert, confirm, or prompt dialog. (if any).
* @return {Promise<void>}
*/
public async getAlert() {
try {
return await driver.switchTo().alert();
} catch (e) {
return null;
}
}
/**
* Retrieves the a rect describing the current top-level window's size and position.
* https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_Window.html

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export const GIS_API_PATH = 'api/maps';
export const EMS_DATA_FILE_PATH = 'ems/file';
export const EMS_DATA_TMS_PATH = 'ems/tms';
export const EMS_META_PATH = 'ems/meta';
@ -15,7 +14,10 @@ export const MAP_SAVED_OBJECT_TYPE = 'map';
export const APP_ID = 'maps';
export const APP_ICON = 'gisApp';
export const MAP_BASE_URL = `/app/maps#/map`;
export const MAP_APP_PATH = `app/${APP_ID}`;
export const GIS_API_PATH = `api/${APP_ID}`;
export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`;
export function createMapPath(id) {
return `${MAP_BASE_URL}/${id}`;

View file

@ -35,7 +35,8 @@ import {
setOpenTOCDetails,
} from '../actions/ui_actions';
import { getIsFullScreen } from '../selectors/ui_selectors';
import { getQueryableUniqueIndexPatternIds, hasDirtyState } from '../selectors/map_selectors';
import { copyPersistentState } from '../reducers/util';
import { getQueryableUniqueIndexPatternIds, hasDirtyState, getLayerListRaw } from '../selectors/map_selectors';
import { getInspectorAdapters } from '../reducers/non_serializable_instances';
import { Inspector } from 'ui/inspector';
import { docTitle } from 'ui/doc_title';
@ -47,17 +48,21 @@ import { getInitialLayers } from './get_initial_layers';
import { getInitialQuery } from './get_initial_query';
import { getInitialTimeFilters } from './get_initial_time_filters';
import { getInitialRefreshConfig } from './get_initial_refresh_config';
import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants';
import {
MAP_SAVED_OBJECT_TYPE,
MAP_APP_PATH
} from '../../common/constants';
const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-maps-root';
const app = uiModules.get('app/maps', []);
const app = uiModules.get(MAP_APP_PATH, []);
app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage, AppState, globalState) => {
const savedMap = $route.current.locals.map;
let unsubscribe;
let initialLayerListConfig;
const store = createMapStore();
@ -123,6 +128,36 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
store.dispatch(setRefreshConfig($scope.refreshConfig));
};
function hasUnsavedChanges() {
const state = store.getState();
const layerList = getLayerListRaw(state);
const layerListConfigOnly = copyPersistentState(layerList);
const savedLayerList = savedMap.getLayerList();
const oldConfig = savedLayerList ? savedLayerList : initialLayerListConfig;
return !_.isEqual(layerListConfigOnly, oldConfig);
}
function isOnMapNow() {
return window.location.hash.startsWith(`#/${MAP_SAVED_OBJECT_TYPE}`);
}
function beforeUnload(event) {
if (!isOnMapNow()) {
return;
}
const hasChanged = hasUnsavedChanges();
if (hasChanged) {
event.preventDefault();
event.returnValue = 'foobar';//this is required for Chrome
}
}
window.addEventListener('beforeunload', beforeUnload);
function renderMap() {
// clear old UI state
store.dispatch(setSelectedLayer(null));
@ -152,8 +187,8 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
const isDarkMode = config.get('theme:darkMode', false);
const layerList = getInitialLayers(savedMap.layerListJSON, isDarkMode);
initialLayerListConfig = copyPersistentState(layerList);
store.dispatch(replaceLayerList(layerList));
store.dispatch(setRefreshConfig($scope.refreshConfig));
store.dispatch(setQuery({ query: $scope.query, timeFilters: $scope.time }));
@ -215,6 +250,9 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
}
$scope.$on('$destroy', () => {
window.removeEventListener('beforeunload', beforeUnload);
if (unsubscribe) {
unsubscribe();
}
@ -230,7 +268,18 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
defaultMessage: 'Maps'
}),
href: '#'
onClick: () => {
if (isOnMapNow() && hasUnsavedChanges()) {
const navigateAway = window.confirm(i18n.translate('xpack.maps.mapController.unsavedChangesWarning', {
defaultMessage: `Your unsaved changes might not be saved`,
}));
if (navigateAway) {
window.location.hash = '#';
}
} else {
window.location.hash = '#';
}
}
},
{ text: savedMap.title }
]);

View file

@ -87,6 +87,10 @@ module.factory('SavedGisMap', function (Private) {
return `/app/maps#map/${this.id}`;
};
SavedGisMap.prototype.getLayerList = function () {
return this.layerListJSON ? JSON.parse(this.layerListJSON) : null;
};
SavedGisMap.prototype.syncWithStore = function (state) {
const layerList = getLayerListRaw(state);
const layerListConfigOnly = copyPersistentState(layerList);