mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Maps] Migrate maps client router to react (#65079)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
b41ade3588
commit
55a73616ab
61 changed files with 1459 additions and 1128 deletions
|
@ -104,7 +104,7 @@ export function getMapsAppUrl(
|
|||
|
||||
return {
|
||||
app: 'maps',
|
||||
path: `#/map?${mapAppParams.toString()}`,
|
||||
path: `/map#?${mapAppParams.toString()}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -162,6 +162,27 @@ export async function FindProvider({ getService }: FtrProviderContext) {
|
|||
return wrapAll(elements);
|
||||
}
|
||||
|
||||
public async allByButtonText(
|
||||
buttonText: string,
|
||||
element: WebDriver | WebElement | WebElementWrapper = driver,
|
||||
timeout: number = defaultFindTimeout
|
||||
): Promise<string[]> {
|
||||
log.debug(`Find.byButtonText('${buttonText}') with timeout=${timeout}`);
|
||||
return await retry.tryForTime(timeout, async () => {
|
||||
// tslint:disable-next-line:variable-name
|
||||
const _element = element instanceof WebElementWrapper ? element._webElement : element;
|
||||
await this._withTimeout(0);
|
||||
const allButtons = wrapAll(await _element.findElements(By.tagName('button')));
|
||||
await this._withTimeout(defaultFindTimeout);
|
||||
const buttonTexts = await Promise.all(
|
||||
allButtons.map(async (el) => {
|
||||
return el.getVisibleText();
|
||||
})
|
||||
);
|
||||
return buttonTexts.filter((text) => text.trim() === buttonText.trim());
|
||||
});
|
||||
}
|
||||
|
||||
public async allByCssSelector(
|
||||
selector: string,
|
||||
timeout: number = defaultFindTimeout
|
||||
|
|
|
@ -9,7 +9,6 @@ import { monitoring } from './legacy/plugins/monitoring';
|
|||
import { security } from './legacy/plugins/security';
|
||||
import { dashboardMode } from './legacy/plugins/dashboard_mode';
|
||||
import { beats } from './legacy/plugins/beats_management';
|
||||
import { maps } from './legacy/plugins/maps';
|
||||
import { spaces } from './legacy/plugins/spaces';
|
||||
import { ingestManager } from './legacy/plugins/ingest_manager';
|
||||
|
||||
|
@ -21,7 +20,6 @@ module.exports = function (kibana) {
|
|||
security(kibana),
|
||||
dashboardMode(kibana),
|
||||
beats(kibana),
|
||||
maps(kibana),
|
||||
ingestManager(kibana),
|
||||
];
|
||||
};
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { resolve } from 'path';
|
||||
import { getAppTitle } from '../../../plugins/maps/common/i18n_getters';
|
||||
import { APP_ID, APP_ICON } from '../../../plugins/maps/common/constants';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
|
||||
|
||||
export function maps(kibana) {
|
||||
return new kibana.Plugin({
|
||||
require: ['kibana', 'elasticsearch'],
|
||||
id: APP_ID,
|
||||
configPrefix: 'xpack.maps',
|
||||
publicDir: resolve(__dirname, 'public'),
|
||||
config(Joi) {
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
})
|
||||
.unknown()
|
||||
.default();
|
||||
},
|
||||
uiExports: {
|
||||
app: {
|
||||
title: getAppTitle(),
|
||||
description: i18n.translate('xpack.maps.appDescription', {
|
||||
defaultMessage: 'Map application',
|
||||
}),
|
||||
main: 'plugins/maps/legacy',
|
||||
icon: 'plugins/maps/icon.svg',
|
||||
euiIconType: APP_ICON,
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
order: 4000,
|
||||
},
|
||||
styleSheetPaths: `${__dirname}/public/index.scss`,
|
||||
},
|
||||
});
|
||||
}
|
|
@ -1,686 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import rison from 'rison-node';
|
||||
import 'ui/directives/listen';
|
||||
import 'ui/directives/storage';
|
||||
import React from 'react';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import {
|
||||
getTimeFilter,
|
||||
getIndexPatternService,
|
||||
getInspector,
|
||||
getNavigation,
|
||||
getData,
|
||||
getCoreI18n,
|
||||
getCoreChrome,
|
||||
getMapsCapabilities,
|
||||
getToasts,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../plugins/maps/public/kibana_services';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { createMapStore } from '../../../../../plugins/maps/public/reducers/store';
|
||||
import { Provider } from 'react-redux';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { GisMap } from '../../../../../plugins/maps/public/connected_components/gis_map';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { addHelpMenuToAppChrome } from '../../../../../plugins/maps/public/help_menu_util';
|
||||
import {
|
||||
setSelectedLayer,
|
||||
setRefreshConfig,
|
||||
setGotoWithCenter,
|
||||
replaceLayerList,
|
||||
setQuery,
|
||||
setMapSettings,
|
||||
enableFullScreen,
|
||||
updateFlyout,
|
||||
setReadOnly,
|
||||
setIsLayerTOCOpen,
|
||||
setOpenTOCDetails,
|
||||
openMapSettings,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../plugins/maps/public/actions';
|
||||
import {
|
||||
DEFAULT_IS_LAYER_TOC_OPEN,
|
||||
FLYOUT_STATE,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../plugins/maps/public/reducers/ui';
|
||||
import {
|
||||
getIsFullScreen,
|
||||
getFlyoutDisplay,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../plugins/maps/public/selectors/ui_selectors';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util';
|
||||
import {
|
||||
getQueryableUniqueIndexPatternIds,
|
||||
hasDirtyState,
|
||||
getLayerListRaw,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../plugins/maps/public/selectors/map_selectors';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getInitialLayers } from '../../../../../plugins/maps/public/angular/get_initial_layers';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getInitialQuery } from '../../../../../plugins/maps/public/angular/get_initial_query';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getInitialTimeFilters } from '../../../../../plugins/maps/public/angular/get_initial_time_filters';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getInitialRefreshConfig } from '../../../../../plugins/maps/public/angular/get_initial_refresh_config';
|
||||
import { MAP_SAVED_OBJECT_TYPE, MAP_APP_PATH } from '../../../../../plugins/maps/common/constants';
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import { esFilters } from '../../../../../../src/plugins/data/public';
|
||||
import {
|
||||
SavedObjectSaveModal,
|
||||
showSaveModal,
|
||||
} from '../../../../../../src/plugins/saved_objects/public';
|
||||
import { loadKbnTopNavDirectives } from '../../../../../../src/plugins/kibana_legacy/public';
|
||||
import {
|
||||
bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins,
|
||||
bindStartCoreAndPlugins as bindNpStartCoreAndPlugins,
|
||||
} from '../../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths
|
||||
|
||||
const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-maps-root';
|
||||
|
||||
const app = uiModules.get(MAP_APP_PATH, []);
|
||||
|
||||
// Init required services. Necessary while in legacy
|
||||
const config = _.get(npSetup, 'plugins.maps.config', {});
|
||||
const kibanaVersion = npSetup.core.injectedMetadata.getKibanaVersion();
|
||||
bindNpSetupCoreAndPlugins(npSetup.core, npSetup.plugins, config, kibanaVersion);
|
||||
bindNpStartCoreAndPlugins(npStart.core, npStart.plugins);
|
||||
|
||||
loadKbnTopNavDirectives(getNavigation().ui);
|
||||
|
||||
function getInitialLayersFromUrlParam() {
|
||||
const locationSplit = window.location.href.split('?');
|
||||
if (locationSplit.length <= 1) {
|
||||
return [];
|
||||
}
|
||||
const mapAppParams = new URLSearchParams(locationSplit[1]);
|
||||
if (!mapAppParams.has('initialLayers')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return rison.decode_array(mapAppParams.get('initialLayers'));
|
||||
} catch (e) {
|
||||
getToasts().addWarning({
|
||||
title: i18n.translate('xpack.maps.initialLayers.unableToParseTitle', {
|
||||
defaultMessage: `Inital layers not added to map`,
|
||||
}),
|
||||
text: i18n.translate('xpack.maps.initialLayers.unableToParseMessage', {
|
||||
defaultMessage: `Unable to parse contents of 'initialLayers' parameter. Error: {errorMsg}`,
|
||||
values: { errorMsg: e.message },
|
||||
}),
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
app.controller(
|
||||
'GisMapController',
|
||||
($scope, $route, kbnUrl, localStorage, AppState, globalState) => {
|
||||
const savedQueryService = getData().query.savedQueries;
|
||||
const { filterManager } = getData().query;
|
||||
const savedMap = $route.current.locals.map;
|
||||
$scope.screenTitle = savedMap.title;
|
||||
let unsubscribe;
|
||||
let initialLayerListConfig;
|
||||
const $state = new AppState();
|
||||
const store = createMapStore();
|
||||
|
||||
function getAppStateFilters() {
|
||||
return _.get($state, 'filters', []);
|
||||
}
|
||||
|
||||
const visibleSubscription = getCoreChrome()
|
||||
.getIsVisible$()
|
||||
.subscribe((isVisible) => {
|
||||
$scope.$evalAsync(() => {
|
||||
$scope.isVisible = isVisible;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$listen(globalState, 'fetch_with_changes', (diff) => {
|
||||
if (diff.includes('time') || diff.includes('filters')) {
|
||||
onQueryChange({
|
||||
filters: [...globalState.filters, ...getAppStateFilters()],
|
||||
time: globalState.time,
|
||||
});
|
||||
}
|
||||
if (diff.includes('refreshInterval')) {
|
||||
$scope.onRefreshChange({ isPaused: globalState.pause, refreshInterval: globalState.value });
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$listen($state, 'fetch_with_changes', function (diff) {
|
||||
if ((diff.includes('query') || diff.includes('filters')) && $state.query) {
|
||||
onQueryChange({
|
||||
filters: [...globalState.filters, ...getAppStateFilters()],
|
||||
query: $state.query,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function syncAppAndGlobalState() {
|
||||
$scope.$evalAsync(() => {
|
||||
// appState
|
||||
$state.query = $scope.query;
|
||||
$state.filters = filterManager.getAppFilters();
|
||||
$state.save();
|
||||
|
||||
// globalState
|
||||
globalState.time = $scope.time;
|
||||
globalState.refreshInterval = {
|
||||
pause: $scope.refreshConfig.isPaused,
|
||||
value: $scope.refreshConfig.interval,
|
||||
};
|
||||
globalState.filters = filterManager.getGlobalFilters();
|
||||
globalState.save();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.query = getInitialQuery({
|
||||
mapStateJSON: savedMap.mapStateJSON,
|
||||
appState: $state,
|
||||
userQueryLanguage: localStorage.get('kibana.userQueryLanguage'),
|
||||
});
|
||||
$scope.time = getInitialTimeFilters({
|
||||
mapStateJSON: savedMap.mapStateJSON,
|
||||
globalState: globalState,
|
||||
});
|
||||
$scope.refreshConfig = getInitialRefreshConfig({
|
||||
mapStateJSON: savedMap.mapStateJSON,
|
||||
globalState: globalState,
|
||||
});
|
||||
|
||||
/* Saved Queries */
|
||||
$scope.showSaveQuery = getMapsCapabilities().saveQuery;
|
||||
|
||||
$scope.$watch(
|
||||
() => getMapsCapabilities().saveQuery,
|
||||
(newCapability) => {
|
||||
$scope.showSaveQuery = newCapability;
|
||||
}
|
||||
);
|
||||
|
||||
$scope.onQuerySaved = (savedQuery) => {
|
||||
$scope.savedQuery = savedQuery;
|
||||
};
|
||||
|
||||
$scope.onSavedQueryUpdated = (savedQuery) => {
|
||||
$scope.savedQuery = { ...savedQuery };
|
||||
};
|
||||
|
||||
$scope.onClearSavedQuery = () => {
|
||||
delete $scope.savedQuery;
|
||||
delete $state.savedQuery;
|
||||
onQueryChange({
|
||||
filters: filterManager.getGlobalFilters(),
|
||||
query: {
|
||||
query: '',
|
||||
language: localStorage.get('kibana.userQueryLanguage'),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
function updateStateFromSavedQuery(savedQuery) {
|
||||
const savedQueryFilters = savedQuery.attributes.filters || [];
|
||||
const globalFilters = filterManager.getGlobalFilters();
|
||||
const allFilters = [...savedQueryFilters, ...globalFilters];
|
||||
|
||||
if (savedQuery.attributes.timefilter) {
|
||||
if (savedQuery.attributes.timefilter.refreshInterval) {
|
||||
$scope.onRefreshChange({
|
||||
isPaused: savedQuery.attributes.timefilter.refreshInterval.pause,
|
||||
refreshInterval: savedQuery.attributes.timefilter.refreshInterval.value,
|
||||
});
|
||||
}
|
||||
onQueryChange({
|
||||
filters: allFilters,
|
||||
query: savedQuery.attributes.query,
|
||||
time: savedQuery.attributes.timefilter,
|
||||
});
|
||||
} else {
|
||||
onQueryChange({
|
||||
filters: allFilters,
|
||||
query: savedQuery.attributes.query,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('savedQuery', (newSavedQuery) => {
|
||||
if (!newSavedQuery) return;
|
||||
|
||||
$state.savedQuery = newSavedQuery.id;
|
||||
updateStateFromSavedQuery(newSavedQuery);
|
||||
});
|
||||
|
||||
$scope.$watch(
|
||||
() => $state.savedQuery,
|
||||
(newSavedQueryId) => {
|
||||
if (!newSavedQueryId) {
|
||||
$scope.savedQuery = undefined;
|
||||
return;
|
||||
}
|
||||
if ($scope.savedQuery && newSavedQueryId !== $scope.savedQuery.id) {
|
||||
savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => {
|
||||
$scope.$evalAsync(() => {
|
||||
$scope.savedQuery = savedQuery;
|
||||
updateStateFromSavedQuery(savedQuery);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
/* End of Saved Queries */
|
||||
async function onQueryChange({ filters, query, time, refresh }) {
|
||||
if (filters) {
|
||||
filterManager.setFilters(filters); // Maps and merges filters
|
||||
$scope.filters = filterManager.getFilters();
|
||||
}
|
||||
if (query) {
|
||||
$scope.query = query;
|
||||
}
|
||||
if (time) {
|
||||
$scope.time = time;
|
||||
}
|
||||
syncAppAndGlobalState();
|
||||
dispatchSetQuery(refresh);
|
||||
}
|
||||
|
||||
function dispatchSetQuery(refresh) {
|
||||
store.dispatch(
|
||||
setQuery({
|
||||
filters: $scope.filters,
|
||||
query: $scope.query,
|
||||
timeFilters: $scope.time,
|
||||
refresh,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
$scope.indexPatterns = [];
|
||||
$scope.onQuerySubmit = function ({ dateRange, query }) {
|
||||
onQueryChange({
|
||||
query,
|
||||
time: dateRange,
|
||||
refresh: true,
|
||||
});
|
||||
};
|
||||
$scope.updateFiltersAndDispatch = function (filters) {
|
||||
onQueryChange({
|
||||
filters,
|
||||
});
|
||||
};
|
||||
$scope.onRefreshChange = function ({ isPaused, refreshInterval }) {
|
||||
$scope.refreshConfig = {
|
||||
isPaused,
|
||||
interval: refreshInterval ? refreshInterval : $scope.refreshConfig.interval,
|
||||
};
|
||||
syncAppAndGlobalState();
|
||||
|
||||
store.dispatch(setRefreshConfig($scope.refreshConfig));
|
||||
};
|
||||
|
||||
function addFilters(newFilters) {
|
||||
newFilters.forEach((filter) => {
|
||||
filter.$state = { store: esFilters.FilterStateStore.APP_STATE };
|
||||
});
|
||||
$scope.updateFiltersAndDispatch([...$scope.filters, ...newFilters]);
|
||||
}
|
||||
|
||||
function hasUnsavedChanges() {
|
||||
const state = store.getState();
|
||||
const layerList = getLayerListRaw(state);
|
||||
const layerListConfigOnly = copyPersistentState(layerList);
|
||||
|
||||
const savedLayerList = savedMap.getLayerList();
|
||||
|
||||
return !savedLayerList
|
||||
? !_.isEqual(layerListConfigOnly, initialLayerListConfig)
|
||||
: // savedMap stores layerList as a JSON string using JSON.stringify.
|
||||
// JSON.stringify removes undefined properties from objects.
|
||||
// savedMap.getLayerList converts the JSON string back into Javascript array of objects.
|
||||
// Need to perform the same process for layerListConfigOnly to compare apples to apples
|
||||
// and avoid undefined properties in layerListConfigOnly triggering unsaved changes.
|
||||
!_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
async function renderMap() {
|
||||
// clear old UI state
|
||||
store.dispatch(setSelectedLayer(null));
|
||||
store.dispatch(updateFlyout(FLYOUT_STATE.NONE));
|
||||
store.dispatch(setReadOnly(!getMapsCapabilities().save));
|
||||
|
||||
handleStoreChanges(store);
|
||||
unsubscribe = store.subscribe(() => {
|
||||
handleStoreChanges(store);
|
||||
});
|
||||
|
||||
// sync store with savedMap mapState
|
||||
let savedObjectFilters = [];
|
||||
if (savedMap.mapStateJSON) {
|
||||
const mapState = JSON.parse(savedMap.mapStateJSON);
|
||||
store.dispatch(
|
||||
setGotoWithCenter({
|
||||
lat: mapState.center.lat,
|
||||
lon: mapState.center.lon,
|
||||
zoom: mapState.zoom,
|
||||
})
|
||||
);
|
||||
if (mapState.filters) {
|
||||
savedObjectFilters = mapState.filters;
|
||||
}
|
||||
if (mapState.settings) {
|
||||
store.dispatch(setMapSettings(mapState.settings));
|
||||
}
|
||||
}
|
||||
|
||||
if (savedMap.uiStateJSON) {
|
||||
const uiState = JSON.parse(savedMap.uiStateJSON);
|
||||
store.dispatch(
|
||||
setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN))
|
||||
);
|
||||
store.dispatch(setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])));
|
||||
}
|
||||
|
||||
const layerList = getInitialLayers(savedMap.layerListJSON, getInitialLayersFromUrlParam());
|
||||
initialLayerListConfig = copyPersistentState(layerList);
|
||||
store.dispatch(replaceLayerList(layerList));
|
||||
store.dispatch(setRefreshConfig($scope.refreshConfig));
|
||||
|
||||
const initialFilters = [
|
||||
..._.get(globalState, 'filters', []),
|
||||
...getAppStateFilters(),
|
||||
...savedObjectFilters,
|
||||
];
|
||||
await onQueryChange({ filters: initialFilters });
|
||||
|
||||
const root = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<I18nProvider>
|
||||
<GisMap addFilters={addFilters} />
|
||||
</I18nProvider>
|
||||
</Provider>,
|
||||
root
|
||||
);
|
||||
}
|
||||
renderMap();
|
||||
|
||||
let prevIndexPatternIds;
|
||||
async function updateIndexPatterns(nextIndexPatternIds) {
|
||||
const indexPatterns = [];
|
||||
const getIndexPatternPromises = nextIndexPatternIds.map(async (indexPatternId) => {
|
||||
try {
|
||||
const indexPattern = await getIndexPatternService().get(indexPatternId);
|
||||
indexPatterns.push(indexPattern);
|
||||
} catch (err) {
|
||||
// unable to fetch index pattern
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(getIndexPatternPromises);
|
||||
// ignore outdated results
|
||||
if (prevIndexPatternIds !== nextIndexPatternIds) {
|
||||
return;
|
||||
}
|
||||
$scope.$evalAsync(() => {
|
||||
$scope.indexPatterns = indexPatterns;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.isFullScreen = false;
|
||||
$scope.isSaveDisabled = false;
|
||||
$scope.isOpenSettingsDisabled = false;
|
||||
function handleStoreChanges(store) {
|
||||
const nextIsFullScreen = getIsFullScreen(store.getState());
|
||||
if (nextIsFullScreen !== $scope.isFullScreen) {
|
||||
// Must trigger digest cycle for angular top nav to redraw itself when isFullScreen changes
|
||||
$scope.$evalAsync(() => {
|
||||
$scope.isFullScreen = nextIsFullScreen;
|
||||
});
|
||||
}
|
||||
|
||||
const nextIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState());
|
||||
if (nextIndexPatternIds !== prevIndexPatternIds) {
|
||||
prevIndexPatternIds = nextIndexPatternIds;
|
||||
updateIndexPatterns(nextIndexPatternIds);
|
||||
}
|
||||
|
||||
const nextIsSaveDisabled = hasDirtyState(store.getState());
|
||||
if (nextIsSaveDisabled !== $scope.isSaveDisabled) {
|
||||
$scope.$evalAsync(() => {
|
||||
$scope.isSaveDisabled = nextIsSaveDisabled;
|
||||
});
|
||||
}
|
||||
|
||||
const flyoutDisplay = getFlyoutDisplay(store.getState());
|
||||
const nextIsOpenSettingsDisabled = flyoutDisplay !== FLYOUT_STATE.NONE;
|
||||
if (nextIsOpenSettingsDisabled !== $scope.isOpenSettingsDisabled) {
|
||||
$scope.$evalAsync(() => {
|
||||
$scope.isOpenSettingsDisabled = nextIsOpenSettingsDisabled;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
window.removeEventListener('beforeunload', beforeUnload);
|
||||
visibleSubscription.unsubscribe();
|
||||
getCoreChrome().setIsVisible(true);
|
||||
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
}
|
||||
const node = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
|
||||
if (node) {
|
||||
unmountComponentAtNode(node);
|
||||
}
|
||||
});
|
||||
|
||||
const updateBreadcrumbs = () => {
|
||||
getCoreChrome().setBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
|
||||
defaultMessage: 'Maps',
|
||||
}),
|
||||
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 },
|
||||
]);
|
||||
};
|
||||
updateBreadcrumbs();
|
||||
|
||||
addHelpMenuToAppChrome();
|
||||
|
||||
async function doSave(saveOptions) {
|
||||
savedMap.syncWithStore(store.getState());
|
||||
let id;
|
||||
|
||||
try {
|
||||
id = await savedMap.save(saveOptions);
|
||||
getCoreChrome().docTitle.change(savedMap.title);
|
||||
} catch (err) {
|
||||
getToasts().addDanger({
|
||||
title: i18n.translate('xpack.maps.mapController.saveErrorMessage', {
|
||||
defaultMessage: `Error on saving '{title}'`,
|
||||
values: { title: savedMap.title },
|
||||
}),
|
||||
text: err.message,
|
||||
'data-test-subj': 'saveMapError',
|
||||
});
|
||||
return { error: err };
|
||||
}
|
||||
|
||||
if (id) {
|
||||
getToasts().addSuccess({
|
||||
title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', {
|
||||
defaultMessage: `Saved '{title}'`,
|
||||
values: { title: savedMap.title },
|
||||
}),
|
||||
'data-test-subj': 'saveMapSuccess',
|
||||
});
|
||||
|
||||
updateBreadcrumbs();
|
||||
|
||||
if (savedMap.id !== $route.current.params.id) {
|
||||
$scope.$evalAsync(() => {
|
||||
kbnUrl.change(`map/{{id}}`, { id: savedMap.id });
|
||||
});
|
||||
}
|
||||
}
|
||||
return { id };
|
||||
}
|
||||
|
||||
// Hide angular timepicer/refresh UI from top nav
|
||||
getTimeFilter().disableTimeRangeSelector();
|
||||
getTimeFilter().disableAutoRefreshSelector();
|
||||
$scope.showDatePicker = true; // used by query-bar directive to enable timepikcer in query bar
|
||||
$scope.topNavMenu = [
|
||||
{
|
||||
id: 'full-screen',
|
||||
label: i18n.translate('xpack.maps.mapController.fullScreenButtonLabel', {
|
||||
defaultMessage: `full screen`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.fullScreenDescription', {
|
||||
defaultMessage: `full screen`,
|
||||
}),
|
||||
testId: 'mapsFullScreenMode',
|
||||
run() {
|
||||
getCoreChrome().setIsVisible(false);
|
||||
store.dispatch(enableFullScreen());
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'inspect',
|
||||
label: i18n.translate('xpack.maps.mapController.openInspectorButtonLabel', {
|
||||
defaultMessage: `inspect`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.openInspectorDescription', {
|
||||
defaultMessage: `Open Inspector`,
|
||||
}),
|
||||
testId: 'openInspectorButton',
|
||||
run() {
|
||||
const inspectorAdapters = getInspectorAdapters(store.getState());
|
||||
getInspector().open(inspectorAdapters, {});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'mapSettings',
|
||||
label: i18n.translate('xpack.maps.mapController.openSettingsButtonLabel', {
|
||||
defaultMessage: `Map settings`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.openSettingsDescription', {
|
||||
defaultMessage: `Open map settings`,
|
||||
}),
|
||||
testId: 'openSettingsButton',
|
||||
disableButton() {
|
||||
return $scope.isOpenSettingsDisabled;
|
||||
},
|
||||
run() {
|
||||
store.dispatch(openMapSettings());
|
||||
},
|
||||
},
|
||||
...(getMapsCapabilities().save
|
||||
? [
|
||||
{
|
||||
id: 'save',
|
||||
label: i18n.translate('xpack.maps.mapController.saveMapButtonLabel', {
|
||||
defaultMessage: `save`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.saveMapDescription', {
|
||||
defaultMessage: `Save map`,
|
||||
}),
|
||||
testId: 'mapSaveButton',
|
||||
disableButton() {
|
||||
return $scope.isSaveDisabled;
|
||||
},
|
||||
tooltip() {
|
||||
if ($scope.isSaveDisabled) {
|
||||
return i18n.translate('xpack.maps.mapController.saveMapDisabledButtonTooltip', {
|
||||
defaultMessage: 'Save or Cancel your layer changes before saving',
|
||||
});
|
||||
}
|
||||
},
|
||||
run: async () => {
|
||||
const onSave = ({
|
||||
newTitle,
|
||||
newCopyOnSave,
|
||||
isTitleDuplicateConfirmed,
|
||||
onTitleDuplicate,
|
||||
}) => {
|
||||
const currentTitle = savedMap.title;
|
||||
savedMap.title = newTitle;
|
||||
savedMap.copyOnSave = newCopyOnSave;
|
||||
const saveOptions = {
|
||||
confirmOverwrite: false,
|
||||
isTitleDuplicateConfirmed,
|
||||
onTitleDuplicate,
|
||||
};
|
||||
return doSave(saveOptions).then((response) => {
|
||||
// If the save wasn't successful, put the original values back.
|
||||
if (!response.id || response.error) {
|
||||
savedMap.title = currentTitle;
|
||||
}
|
||||
return response;
|
||||
});
|
||||
};
|
||||
|
||||
const saveModal = (
|
||||
<SavedObjectSaveModal
|
||||
onSave={onSave}
|
||||
onClose={() => {}}
|
||||
title={savedMap.title}
|
||||
showCopyOnSave={savedMap.id ? true : false}
|
||||
objectType={MAP_SAVED_OBJECT_TYPE}
|
||||
showDescription={false}
|
||||
/>
|
||||
);
|
||||
showSaveModal(saveModal, getCoreI18n().Context);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}
|
||||
);
|
|
@ -1,3 +0,0 @@
|
|||
/* GIS plugin styles */
|
||||
|
||||
@import '../../../../plugins/maps/public/index';
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import '../../../../plugins/maps/public/kibana_services';
|
||||
|
||||
// import the uiExports that we want to "use"
|
||||
import 'uiExports/inspectorViews';
|
||||
import 'uiExports/search';
|
||||
import 'uiExports/embeddableFactories';
|
||||
import 'uiExports/embeddableActions';
|
||||
|
||||
import 'ui/autoload/all';
|
||||
import 'react-vis/dist/style.css';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import '../../../../plugins/maps/public/angular/services/gis_map_saved_object_loader';
|
||||
import './angular/map_controller';
|
||||
import './routes';
|
||||
// @ts-ignore
|
||||
import { MapsPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => {
|
||||
return new MapsPlugin();
|
||||
};
|
||||
|
||||
export {
|
||||
RenderTooltipContentParams,
|
||||
ITooltipProperty,
|
||||
} from '../../../../plugins/maps/public/classes/tooltips/tooltip_property'; // eslint-disable-line @kbn/eslint/no-restricted-paths
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
export { MapEmbeddable, MapEmbeddableInput } from '../../../../plugins/maps/public/embeddable';
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
// @ts-ignore Untyped Module
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { plugin } from '.';
|
||||
|
||||
const pluginInstance = plugin();
|
||||
|
||||
const setupPlugins = {
|
||||
__LEGACY: {
|
||||
uiModules,
|
||||
},
|
||||
np: npSetup.plugins,
|
||||
};
|
||||
|
||||
export const setup = pluginInstance.setup(npSetup.core, setupPlugins);
|
||||
export const start = pluginInstance.start(npStart.core, npStart.plugins);
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { Plugin, CoreStart, CoreSetup } from 'src/core/public';
|
||||
// @ts-ignore
|
||||
import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
|
||||
// @ts-ignore
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
// @ts-ignore
|
||||
import { MapListing } from '../../../../plugins/maps/public/components/map_listing'; // eslint-disable-line @kbn/eslint/no-restricted-paths
|
||||
// @ts-ignore
|
||||
import {
|
||||
bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins,
|
||||
bindStartCoreAndPlugins as bindNpStartCoreAndPlugins,
|
||||
} from '../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths
|
||||
import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public';
|
||||
import { LicensingPluginSetup } from '../../../../plugins/licensing/public';
|
||||
import {
|
||||
DataPublicPluginSetup,
|
||||
DataPublicPluginStart,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
|
||||
/**
|
||||
* These are the interfaces with your public contracts. You should export these
|
||||
* for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces.
|
||||
* @public
|
||||
*/
|
||||
export type MapsPluginSetup = ReturnType<MapsPlugin['setup']>;
|
||||
export type MapsPluginStart = ReturnType<MapsPlugin['start']>;
|
||||
|
||||
interface MapsPluginSetupDependencies {
|
||||
__LEGACY: any;
|
||||
np: {
|
||||
licensing?: LicensingPluginSetup;
|
||||
home: HomePublicPluginSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
};
|
||||
}
|
||||
|
||||
interface MapsPluginStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
inspector: InspectorStartContract;
|
||||
// file_upload TODO: Export type from file upload and use here
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class MapsPlugin implements Plugin<MapsPluginSetup, MapsPluginStart> {
|
||||
public setup(core: CoreSetup, { __LEGACY: { uiModules }, np }: MapsPluginSetupDependencies) {
|
||||
uiModules
|
||||
.get('app/maps', ['ngRoute', 'react'])
|
||||
.directive('mapListing', function (reactDirective: any) {
|
||||
return reactDirective(wrapInI18nContext(MapListing));
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const config = _.get(np, 'maps.config', {});
|
||||
// @ts-ignore
|
||||
const kibanaVersion = core.injectedMetadata.getKibanaVersion();
|
||||
// @ts-ignore
|
||||
bindNpSetupCoreAndPlugins(core, np, config, kibanaVersion);
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: MapsPluginStartDependencies) {
|
||||
bindNpStartCoreAndPlugins(core, plugins);
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import routes from 'ui/routes';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import listingTemplate from '../../../../plugins/maps/public/angular/listing_ng_wrapper.html';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import mapTemplate from '../../../../plugins/maps/public/angular/map.html';
|
||||
import {
|
||||
getSavedObjectsClient,
|
||||
getCoreChrome,
|
||||
getMapsCapabilities,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../plugins/maps/public/kibana_services';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getMapsSavedObjectLoader } from '../../../../plugins/maps/public/angular/services/gis_map_saved_object_loader';
|
||||
import { LISTING_LIMIT_SETTING } from '../../../../../src/plugins/saved_objects/common';
|
||||
|
||||
routes.enable();
|
||||
|
||||
routes
|
||||
.defaults(/.*/, {
|
||||
badge: () => {
|
||||
if (getMapsCapabilities().save) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
text: i18n.translate('xpack.maps.badge.readOnly.text', {
|
||||
defaultMessage: 'Read only',
|
||||
}),
|
||||
tooltip: i18n.translate('xpack.maps.badge.readOnly.tooltip', {
|
||||
defaultMessage: 'Unable to save maps',
|
||||
}),
|
||||
iconType: 'glasses',
|
||||
};
|
||||
},
|
||||
})
|
||||
.when('/', {
|
||||
template: listingTemplate,
|
||||
controller($scope, config) {
|
||||
const gisMapSavedObjectLoader = getMapsSavedObjectLoader();
|
||||
$scope.listingLimit = config.get(LISTING_LIMIT_SETTING);
|
||||
$scope.find = (search) => {
|
||||
return gisMapSavedObjectLoader.find(search, $scope.listingLimit);
|
||||
};
|
||||
$scope.delete = (ids) => {
|
||||
return gisMapSavedObjectLoader.delete(ids);
|
||||
};
|
||||
$scope.readOnly = !getMapsCapabilities().save;
|
||||
},
|
||||
resolve: {
|
||||
hasMaps: function (kbnUrl) {
|
||||
getSavedObjectsClient()
|
||||
.find({ type: 'map', perPage: 1 })
|
||||
.then((resp) => {
|
||||
// Do not show empty listing page, just redirect to a new map
|
||||
if (resp.savedObjects.length === 0) {
|
||||
kbnUrl.redirect('/map');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
.when('/map', {
|
||||
template: mapTemplate,
|
||||
controller: 'GisMapController',
|
||||
resolve: {
|
||||
map: function (redirectWhenMissing) {
|
||||
const gisMapSavedObjectLoader = getMapsSavedObjectLoader();
|
||||
return gisMapSavedObjectLoader.get().catch(
|
||||
redirectWhenMissing({
|
||||
map: '/',
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
})
|
||||
.when('/map/:id', {
|
||||
template: mapTemplate,
|
||||
controller: 'GisMapController',
|
||||
resolve: {
|
||||
map: function (redirectWhenMissing, $route) {
|
||||
const gisMapSavedObjectLoader = getMapsSavedObjectLoader();
|
||||
const id = $route.current.params.id;
|
||||
return gisMapSavedObjectLoader
|
||||
.get(id)
|
||||
.then((savedMap) => {
|
||||
getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, id);
|
||||
getCoreChrome().docTitle.change(savedMap.title);
|
||||
return savedMap;
|
||||
})
|
||||
.catch(
|
||||
redirectWhenMissing({
|
||||
map: '/',
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
})
|
||||
.otherwise({
|
||||
redirectTo: '/',
|
||||
});
|
|
@ -13,7 +13,8 @@ import {
|
|||
EmbeddableExpression,
|
||||
} from '../../expression_types';
|
||||
import { getFunctionHelp } from '../../../i18n';
|
||||
import { MapEmbeddableInput } from '../../../../../legacy/plugins/maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { MapEmbeddableInput } from '../../../../../plugins/maps/public/embeddable';
|
||||
|
||||
interface Arguments {
|
||||
id: string;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { toExpression } from './map';
|
||||
import { MapEmbeddableInput } from '../../../../../../legacy/plugins/maps/public';
|
||||
import { MapEmbeddableInput } from '../../../../../../plugins/maps/public/embeddable';
|
||||
import { fromExpression, Ast } from '@kbn/interpreter/common';
|
||||
|
||||
const baseSavedMapInput = {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { MapEmbeddableInput } from '../../../../../../legacy/plugins/maps/public';
|
||||
import { MapEmbeddableInput } from '../../../../../../plugins/maps/public/embeddable';
|
||||
|
||||
export function toExpression(input: MapEmbeddableInput): string {
|
||||
const expressionParts = [] as string[];
|
||||
|
|
|
@ -32,7 +32,7 @@ export const GIS_API_PATH = `api/${APP_ID}`;
|
|||
export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`;
|
||||
export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`;
|
||||
|
||||
export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`;
|
||||
export const MAP_BASE_URL = `/${MAP_APP_PATH}/${MAP_SAVED_OBJECT_TYPE}`;
|
||||
|
||||
export function createMapPath(id: string) {
|
||||
return `${MAP_BASE_URL}/${id}`;
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<map-listing
|
||||
find="find"
|
||||
delete="delete"
|
||||
listing-limit="listingLimit"
|
||||
read-only="readOnly"
|
||||
></map-listing>
|
|
@ -1,35 +0,0 @@
|
|||
<div id="maps-plugin" ng-class="{mapFullScreen: isFullScreen}">
|
||||
|
||||
<div id="maps-top-nav">
|
||||
<div>
|
||||
<kbn-top-nav
|
||||
ng-show="isVisible"
|
||||
app-name="'maps'"
|
||||
config="topNavMenu"
|
||||
show-search-bar="isVisible"
|
||||
show-filter-bar="isVisible"
|
||||
show-date-picker="showDatePicker"
|
||||
show-save-query="showSaveQuery"
|
||||
query="query"
|
||||
saved-query="savedQuery"
|
||||
on-query-submit="onQuerySubmit"
|
||||
filters="filters"
|
||||
on-filters-updated="updateFiltersAndDispatch"
|
||||
index-patterns="indexPatterns"
|
||||
date-range-from="time.from"
|
||||
date-range-to="time.to"
|
||||
is-refresh-paused="refreshConfig.isPaused"
|
||||
refresh-interval="refreshConfig.interval"
|
||||
on-refresh-change="onRefreshChange"
|
||||
on-saved="onQuerySaved"
|
||||
on-saved-query-updated="onSavedQueryUpdated"
|
||||
on-clear-saved-query="onClearSavedQuery"
|
||||
>
|
||||
</kbn-top-nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="euiScreenReaderOnly">{{screenTitle}}</h1>
|
||||
<div id="react-maps-root"></div>
|
||||
|
||||
</div>
|
|
@ -29,14 +29,13 @@ async function fetchIndexSettings(indexPatternTitle) {
|
|||
const http = getHttp();
|
||||
const toasts = getToasts();
|
||||
try {
|
||||
const indexSettings = await http.fetch(`../${INDEX_SETTINGS_API_PATH}`, {
|
||||
return await http.fetch(`/${INDEX_SETTINGS_API_PATH}`, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
query: {
|
||||
indexPatternTitle,
|
||||
},
|
||||
});
|
||||
return indexSettings;
|
||||
} catch (err) {
|
||||
const warningMsg = i18n.translate('xpack.maps.indexSettings.fetchErrorMsg', {
|
||||
defaultMessage: `Unable to fetch index settings for index pattern '{indexPatternTitle}'.
|
||||
|
|
|
@ -14,7 +14,6 @@ import { LayerPanel } from '../layer_panel';
|
|||
import { AddLayerPanel } from '../add_layer_panel';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui';
|
||||
import { ExitFullScreenButton } from '../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import { getIndexPatternsFromIds } from '../../index_pattern_util';
|
||||
import { ES_GEO_FIELD_TYPE } from '../../../common/constants';
|
||||
import { indexPatterns as indexPatternsUtils } from '../../../../../../src/plugins/data/public';
|
||||
|
@ -23,6 +22,7 @@ import uuid from 'uuid/v4';
|
|||
import { FLYOUT_STATE } from '../../reducers/ui';
|
||||
import { MapSettingsPanel } from '../map_settings_panel';
|
||||
import { registerLayerWizards } from '../../classes/layers/load_layer_wizards';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
|
||||
const RENDER_COMPLETE_EVENT = 'renderComplete';
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import _ from 'lodash';
|
|||
import React, { Suspense, lazy } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Unsubscribe } from 'redux';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
|
|
@ -17,5 +17,3 @@ export const plugin: PluginInitializer<MapsPluginSetup, MapsPluginStart> = (
|
|||
};
|
||||
|
||||
export { MAP_SAVED_OBJECT_TYPE } from '../common/constants';
|
||||
export { ITooltipProperty } from './classes/tooltips/tooltip_property';
|
||||
export { MapsPluginStart } from './plugin';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { AnyAction } from 'redux';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { IndexPatternsService } from 'src/plugins/data/public/index_patterns';
|
||||
import { ReactElement } from 'react';
|
||||
import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public';
|
||||
import { LayerDescriptor } from '../../common/descriptor_types';
|
||||
import { MapStore, MapStoreState } from '../reducers/store';
|
||||
|
@ -36,6 +37,7 @@ interface LazyLoadedMapModules {
|
|||
initialLayers?: LayerDescriptor[]
|
||||
) => LayerDescriptor[];
|
||||
mergeInputWithSavedMap: any;
|
||||
renderApp: (context: unknown, params: unknown) => ReactElement<any>;
|
||||
createSecurityLayerDescriptors: (
|
||||
indexPatternId: string,
|
||||
indexPatternTitle: string
|
||||
|
@ -49,7 +51,7 @@ export async function lazyLoadMapModules(): Promise<LazyLoadedMapModules> {
|
|||
|
||||
loadModulesPromise = new Promise(async (resolve) => {
|
||||
const {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
getMapsSavedObjectLoader,
|
||||
getQueryableUniqueIndexPatternIds,
|
||||
MapEmbeddable,
|
||||
|
@ -60,6 +62,8 @@ export async function lazyLoadMapModules(): Promise<LazyLoadedMapModules> {
|
|||
addLayerWithoutDataSync,
|
||||
getInitialLayers,
|
||||
mergeInputWithSavedMap,
|
||||
// @ts-expect-error
|
||||
renderApp,
|
||||
createSecurityLayerDescriptors,
|
||||
} = await import('./lazy');
|
||||
|
||||
|
@ -74,6 +78,7 @@ export async function lazyLoadMapModules(): Promise<LazyLoadedMapModules> {
|
|||
addLayerWithoutDataSync,
|
||||
getInitialLayers,
|
||||
mergeInputWithSavedMap,
|
||||
renderApp,
|
||||
createSecurityLayerDescriptors,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
// These are map-dependencies of the embeddable.
|
||||
// By lazy-loading these, the Maps-app can register the embeddable when the plugin mounts, without actually pulling all the code.
|
||||
|
||||
// @ts-ignore
|
||||
export * from '../../angular/services/gis_map_saved_object_loader';
|
||||
// @ts-expect-error
|
||||
export * from '../../routing/bootstrap/services/gis_map_saved_object_loader';
|
||||
export * from '../../embeddable/map_embeddable';
|
||||
export * from '../../kibana_services';
|
||||
export * from '../../reducers/store';
|
||||
export * from '../../actions';
|
||||
export * from '../../selectors/map_selectors';
|
||||
export * from '../../angular/get_initial_layers';
|
||||
export * from '../../routing/bootstrap/get_initial_layers';
|
||||
export * from '../../embeddable/merge_input_with_saved_map';
|
||||
// @ts-expect-error
|
||||
export * from '../../routing/maps_router';
|
||||
export * from '../../classes/layers/solution_layers/security';
|
||||
|
|
|
@ -28,7 +28,7 @@ The Maps app offers more functionality and is easier to use.`,
|
|||
|
||||
return {
|
||||
aliasApp: APP_ID,
|
||||
aliasPath: `#/${MAP_SAVED_OBJECT_TYPE}`,
|
||||
aliasPath: `/${MAP_SAVED_OBJECT_TYPE}`,
|
||||
name: APP_ID,
|
||||
title: i18n.translate('xpack.maps.visTypeAlias.title', {
|
||||
defaultMessage: 'Maps',
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public';
|
||||
import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public';
|
||||
import {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
} from '../../../../src/core/public';
|
||||
// @ts-ignore
|
||||
import { MapView } from './inspector/views/map_view';
|
||||
import {
|
||||
|
@ -41,11 +47,13 @@ import { featureCatalogueEntry } from './feature_catalogue_entry';
|
|||
import { getMapsVisTypeAlias } from './maps_vis_type_alias';
|
||||
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||
import { VisualizationsSetup } from '../../../../src/plugins/visualizations/public';
|
||||
import { APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
|
||||
import { APP_ICON, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
|
||||
import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory';
|
||||
import { EmbeddableSetup } from '../../../../src/plugins/embeddable/public';
|
||||
import { MapsConfigType, MapsXPackConfig } from '../config';
|
||||
import { MapsXPackConfig, MapsConfigType } from '../config';
|
||||
import { getAppTitle } from '../common/i18n_getters';
|
||||
import { ILicense } from '../../licensing/common/types';
|
||||
import { lazyLoadMapModules } from './lazy_load_bundle';
|
||||
import { MapsStartApi } from './api';
|
||||
import { createSecurityLayerDescriptors } from './api/create_security_layer_descriptors';
|
||||
|
||||
|
@ -140,9 +148,22 @@ export class MapsPlugin
|
|||
home.featureCatalogue.register(featureCatalogueEntry);
|
||||
visualizations.registerAlias(getMapsVisTypeAlias());
|
||||
embeddable.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory());
|
||||
return {
|
||||
config,
|
||||
};
|
||||
|
||||
core.application.register({
|
||||
id: APP_ID,
|
||||
title: getAppTitle(),
|
||||
order: 4000,
|
||||
icon: `plugins/${APP_ID}/icon.svg`,
|
||||
euiIconType: APP_ICON,
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
// @ts-expect-error
|
||||
async mount(context, params) {
|
||||
const [coreStart, startPlugins] = await core.getStartServices();
|
||||
bindStartCoreAndPlugins(coreStart, startPlugins);
|
||||
const { renderApp } = await lazyLoadMapModules();
|
||||
return renderApp(context, params);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: any): MapsStartApi {
|
||||
|
|
|
@ -9,6 +9,7 @@ import thunk from 'redux-thunk';
|
|||
import { ui, DEFAULT_MAP_UI_STATE } from './ui';
|
||||
import { map, DEFAULT_MAP_STATE } from './map'; // eslint-disable-line import/named
|
||||
import { nonSerializableInstances } from './non_serializable_instances';
|
||||
import { MAP_DESTROYED } from '../actions';
|
||||
|
||||
export const DEFAULT_MAP_STORE_STATE = {
|
||||
ui: { ...DEFAULT_MAP_UI_STATE },
|
||||
|
@ -17,11 +18,21 @@ export const DEFAULT_MAP_STORE_STATE = {
|
|||
|
||||
export function createMapStore() {
|
||||
const enhancers = [applyMiddleware(thunk)];
|
||||
const rootReducer = combineReducers({
|
||||
const combinedReducers = combineReducers({
|
||||
map,
|
||||
ui,
|
||||
nonSerializableInstances,
|
||||
});
|
||||
|
||||
const rootReducer = (state, action) => {
|
||||
// Reset store on map destroyed
|
||||
if (action.type === MAP_DESTROYED) {
|
||||
state = undefined;
|
||||
}
|
||||
|
||||
return combinedReducers(state, action);
|
||||
};
|
||||
|
||||
const storeConfig = {};
|
||||
return createStore(rootReducer, storeConfig, compose(...enhancers));
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { LayerDescriptor } from '../../common/descriptor_types';
|
||||
import { LayerDescriptor } from '../../../common/descriptor_types';
|
||||
|
||||
export function getInitialLayers(
|
||||
layerListJSON?: string,
|
|
@ -5,25 +5,24 @@
|
|||
*/
|
||||
import _ from 'lodash';
|
||||
// Import each layer type, even those not used, to init in registry
|
||||
import '../classes/sources/wms_source';
|
||||
import '../classes/sources/ems_file_source';
|
||||
import '../classes/sources/es_search_source';
|
||||
import '../classes/sources/es_pew_pew_source';
|
||||
import '../classes/sources/kibana_regionmap_source';
|
||||
import '../classes/sources/es_geo_grid_source';
|
||||
import '../classes/sources/xyz_tms_source';
|
||||
import { KibanaTilemapSource } from '../classes/sources/kibana_tilemap_source';
|
||||
import { TileLayer } from '../classes/layers/tile_layer/tile_layer';
|
||||
import { EMSTMSSource } from '../classes/sources/ems_tms_source';
|
||||
import { VectorTileLayer } from '../classes/layers/vector_tile_layer/vector_tile_layer';
|
||||
import { getIsEmsEnabled } from '../kibana_services';
|
||||
import { getKibanaTileMap } from '../meta';
|
||||
import '../../classes/sources/wms_source';
|
||||
import '../../classes/sources/ems_file_source';
|
||||
import '../../classes/sources/es_search_source';
|
||||
import '../../classes/sources/es_pew_pew_source';
|
||||
import '../../classes/sources/kibana_regionmap_source';
|
||||
import '../../classes/sources/es_geo_grid_source';
|
||||
import '../../classes/sources/xyz_tms_source';
|
||||
import { KibanaTilemapSource } from '../../classes/sources/kibana_tilemap_source';
|
||||
import { TileLayer } from '../../classes/layers/tile_layer/tile_layer';
|
||||
import { EMSTMSSource } from '../../classes/sources/ems_tms_source';
|
||||
import { VectorTileLayer } from '../../classes/layers/vector_tile_layer/vector_tile_layer';
|
||||
import { getIsEmsEnabled } from '../../kibana_services';
|
||||
import { getKibanaTileMap } from '../../meta';
|
||||
|
||||
export function getInitialLayers(layerListJSON, initialLayers = []) {
|
||||
if (layerListJSON) {
|
||||
return JSON.parse(layerListJSON);
|
||||
}
|
||||
|
||||
const tilemapSourceFromKibana = getKibanaTileMap();
|
||||
if (_.get(tilemapSourceFromKibana, 'url')) {
|
||||
const layerDescriptor = TileLayer.createDescriptor({
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('../meta', () => {
|
||||
jest.mock('../../meta', () => {
|
||||
return {};
|
||||
});
|
||||
jest.mock('../kibana_services');
|
||||
jest.mock('../../kibana_services');
|
||||
|
||||
import { getInitialLayers } from './get_initial_layers';
|
||||
|
||||
|
@ -15,7 +15,7 @@ const layerListNotProvided = undefined;
|
|||
|
||||
describe('Saved object has layer list', () => {
|
||||
beforeEach(() => {
|
||||
require('../kibana_services').getIsEmsEnabled = () => true;
|
||||
require('../../kibana_services').getIsEmsEnabled = () => true;
|
||||
});
|
||||
|
||||
it('Should get initial layers from saved object', () => {
|
||||
|
@ -32,7 +32,7 @@ describe('Saved object has layer list', () => {
|
|||
|
||||
describe('kibana.yml configured with map.tilemap.url', () => {
|
||||
beforeAll(() => {
|
||||
require('../meta').getKibanaTileMap = () => {
|
||||
require('../../meta').getKibanaTileMap = () => {
|
||||
return {
|
||||
url: 'myTileUrl',
|
||||
};
|
||||
|
@ -62,11 +62,11 @@ describe('kibana.yml configured with map.tilemap.url', () => {
|
|||
|
||||
describe('EMS is enabled', () => {
|
||||
beforeAll(() => {
|
||||
require('../meta').getKibanaTileMap = () => {
|
||||
require('../../meta').getKibanaTileMap = () => {
|
||||
return null;
|
||||
};
|
||||
require('../kibana_services').getIsEmsEnabled = () => true;
|
||||
require('../kibana_services').getEmsTileLayerId = () => ({
|
||||
require('../../kibana_services').getIsEmsEnabled = () => true;
|
||||
require('../../kibana_services').getEmsTileLayerId = () => ({
|
||||
bright: 'road_map',
|
||||
desaturated: 'road_map_desaturated',
|
||||
dark: 'dark_map',
|
||||
|
@ -98,10 +98,10 @@ describe('EMS is enabled', () => {
|
|||
|
||||
describe('EMS is not enabled', () => {
|
||||
beforeAll(() => {
|
||||
require('../meta').getKibanaTileMap = () => {
|
||||
require('../../meta').getKibanaTileMap = () => {
|
||||
return null;
|
||||
};
|
||||
require('../kibana_services').getIsEmsEnabled = () => false;
|
||||
require('../../kibana_services').getIsEmsEnabled = () => false;
|
||||
});
|
||||
|
||||
it('Should return empty layer list since there are no configured tile layers', () => {
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getUiSettings } from '../kibana_services';
|
||||
import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
|
||||
import { getUiSettings } from '../../kibana_services';
|
||||
import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
|
||||
|
||||
export function getInitialQuery({ mapStateJSON, appState = {}, userQueryLanguage }) {
|
||||
const settings = getUiSettings();
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getUiSettings } from '../kibana_services';
|
||||
import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
|
||||
import { getUiSettings } from '../../kibana_services';
|
||||
import { UI_SETTINGS } from '../../../../../../src/plugins/data/public';
|
||||
|
||||
export function getInitialRefreshConfig({ mapStateJSON, globalState = {} }) {
|
||||
const uiSettings = getUiSettings();
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getUiSettings } from '../kibana_services';
|
||||
import { getUiSettings } from '../../kibana_services';
|
||||
|
||||
export function getInitialTimeFilters({ mapStateJSON, globalState = {} }) {
|
||||
export function getInitialTimeFilters({ mapStateJSON, globalState }) {
|
||||
if (mapStateJSON) {
|
||||
const mapState = JSON.parse(mapStateJSON);
|
||||
if (mapState.timeFilters) {
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { createSavedGisMapClass } from './saved_gis_map';
|
||||
import { SavedObjectLoader } from '../../../../../../src/plugins/saved_objects/public';
|
||||
import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public';
|
||||
import {
|
||||
getCoreChrome,
|
||||
getSavedObjectsClient,
|
||||
getIndexPatternService,
|
||||
getCoreOverlays,
|
||||
getData,
|
||||
} from '../../kibana_services';
|
||||
} from '../../../kibana_services';
|
||||
|
||||
export const getMapsSavedObjectLoader = _.once(function () {
|
||||
const services = {
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { createSavedObjectClass } from '../../../../../../src/plugins/saved_objects/public';
|
||||
import { createSavedObjectClass } from '../../../../../../../src/plugins/saved_objects/public';
|
||||
import {
|
||||
getTimeFilters,
|
||||
getMapZoom,
|
||||
|
@ -15,11 +15,12 @@ import {
|
|||
getQuery,
|
||||
getFilters,
|
||||
getMapSettings,
|
||||
} from '../../selectors/map_selectors';
|
||||
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors';
|
||||
import { copyPersistentState } from '../../reducers/util';
|
||||
import { extractReferences, injectReferences } from '../../../common/migrations/references';
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants';
|
||||
} from '../../../selectors/map_selectors';
|
||||
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors';
|
||||
import { copyPersistentState } from '../../../reducers/util';
|
||||
import { extractReferences, injectReferences } from '../../../../common/migrations/references';
|
||||
import { MAP_BASE_URL, MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
|
||||
import { getStore } from '../../store_operations';
|
||||
|
||||
export function createSavedGisMapClass(services) {
|
||||
const SavedObjectClass = createSavedObjectClass(services);
|
||||
|
@ -73,14 +74,17 @@ export function createSavedGisMapClass(services) {
|
|||
});
|
||||
this.showInRecentlyAccessed = true;
|
||||
}
|
||||
|
||||
getFullPath() {
|
||||
return `/app/maps#map/${this.id}`;
|
||||
return `${MAP_BASE_URL}/${this.id}`;
|
||||
}
|
||||
|
||||
getLayerList() {
|
||||
return this.layerListJSON ? JSON.parse(this.layerListJSON) : null;
|
||||
}
|
||||
|
||||
syncWithStore(state) {
|
||||
syncWithStore() {
|
||||
const state = getStore().getState();
|
||||
const layerList = getLayerListRaw(state);
|
||||
const layerListConfigOnly = copyPersistentState(layerList);
|
||||
this.layerListJSON = JSON.stringify(layerListConfigOnly);
|
62
x-pack/plugins/maps/public/routing/maps_router.js
Normal file
62
x-pack/plugins/maps/public/routing/maps_router.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { Router, Switch, Route, Redirect } from 'react-router-dom';
|
||||
import { getCoreI18n } from '../kibana_services';
|
||||
import { createKbnUrlStateStorage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { getStore } from './store_operations';
|
||||
import { Provider } from 'react-redux';
|
||||
import { LoadListAndRender } from './routes/list/load_list_and_render';
|
||||
import { LoadMapAndRender } from './routes/maps_app/load_map_and_render';
|
||||
|
||||
export let goToSpecifiedPath;
|
||||
export let kbnUrlStateStorage;
|
||||
|
||||
export async function renderApp(context, { appBasePath, element, history }) {
|
||||
goToSpecifiedPath = (path) => history.push(path);
|
||||
kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history });
|
||||
|
||||
render(<App history={history} appBasePath={appBasePath} />, element);
|
||||
|
||||
return () => {
|
||||
unmountComponentAtNode(element);
|
||||
};
|
||||
}
|
||||
|
||||
const App = ({ history, appBasePath }) => {
|
||||
const store = getStore();
|
||||
const I18nContext = getCoreI18n().Context;
|
||||
|
||||
return (
|
||||
<I18nContext>
|
||||
<Provider store={store}>
|
||||
<Router basename={appBasePath} history={history}>
|
||||
<Switch>
|
||||
<Route path={`/map/:savedMapId`} component={LoadMapAndRender} />
|
||||
<Route exact path={`/map`} component={LoadMapAndRender} />
|
||||
// Redirect other routes to list, or if hash-containing, their non-hash equivalents
|
||||
<Route
|
||||
path={``}
|
||||
render={({ location: { pathname, hash } }) => {
|
||||
if (hash) {
|
||||
// Remove leading hash
|
||||
const newPath = hash.substr(1);
|
||||
return <Redirect to={newPath} />;
|
||||
} else if (pathname === '/' || pathname === '') {
|
||||
return <LoadListAndRender />;
|
||||
} else {
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</Router>
|
||||
</Provider>
|
||||
</I18nContext>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getCoreChrome } from '../../kibana_services';
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants';
|
||||
import _ from 'lodash';
|
||||
import { getLayerListRaw } from '../../selectors/map_selectors';
|
||||
import { copyPersistentState } from '../../reducers/util';
|
||||
import { getStore } from '../store_operations';
|
||||
import { goToSpecifiedPath } from '../maps_router';
|
||||
|
||||
function hasUnsavedChanges(savedMap, initialLayerListConfig) {
|
||||
const state = getStore().getState();
|
||||
const layerList = getLayerListRaw(state);
|
||||
const layerListConfigOnly = copyPersistentState(layerList);
|
||||
|
||||
const savedLayerList = savedMap.getLayerList();
|
||||
|
||||
return !savedLayerList
|
||||
? !_.isEqual(layerListConfigOnly, initialLayerListConfig)
|
||||
: // savedMap stores layerList as a JSON string using JSON.stringify.
|
||||
// JSON.stringify removes undefined properties from objects.
|
||||
// savedMap.getLayerList converts the JSON string back into Javascript array of objects.
|
||||
// Need to perform the same process for layerListConfigOnly to compare apples to apples
|
||||
// and avoid undefined properties in layerListConfigOnly triggering unsaved changes.
|
||||
!_.isEqual(JSON.parse(JSON.stringify(layerListConfigOnly)), savedLayerList);
|
||||
}
|
||||
|
||||
export const updateBreadcrumbs = (savedMap, initialLayerListConfig, currentPath = '') => {
|
||||
const isOnMapNow = currentPath.startsWith(`/${MAP_SAVED_OBJECT_TYPE}`);
|
||||
const breadCrumbs = isOnMapNow
|
||||
? [
|
||||
{
|
||||
text: i18n.translate('xpack.maps.mapController.mapsBreadcrumbLabel', {
|
||||
defaultMessage: 'Maps',
|
||||
}),
|
||||
onClick: () => {
|
||||
if (hasUnsavedChanges(savedMap, initialLayerListConfig)) {
|
||||
const navigateAway = window.confirm(
|
||||
i18n.translate('xpack.maps.breadCrumbs.unsavedChangesWarning', {
|
||||
defaultMessage: `Your unsaved changes might not be saved`,
|
||||
})
|
||||
);
|
||||
if (navigateAway) {
|
||||
goToSpecifiedPath('/');
|
||||
}
|
||||
} else {
|
||||
goToSpecifiedPath('/');
|
||||
}
|
||||
},
|
||||
},
|
||||
{ text: savedMap.title },
|
||||
]
|
||||
: [];
|
||||
getCoreChrome().setBreadcrumbs(breadCrumbs);
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { MapsTopNavMenu } from './top_nav_menu';
|
||||
import {
|
||||
enableFullScreen,
|
||||
openMapSettings,
|
||||
removePreviewLayers,
|
||||
setRefreshConfig,
|
||||
setSelectedLayer,
|
||||
updateFlyout,
|
||||
} from '../../../actions';
|
||||
import { FLYOUT_STATE } from '../../../reducers/ui';
|
||||
import { getInspectorAdapters } from '../../../reducers/non_serializable_instances';
|
||||
import { getFlyoutDisplay } from '../../../selectors/ui_selectors';
|
||||
import { hasDirtyState } from '../../../selectors/map_selectors';
|
||||
|
||||
function mapStateToProps(state = {}) {
|
||||
return {
|
||||
isOpenSettingsDisabled: getFlyoutDisplay(state) !== FLYOUT_STATE.NONE,
|
||||
inspectorAdapters: getInspectorAdapters(state),
|
||||
isSaveDisabled: hasDirtyState(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
closeFlyout: () => {
|
||||
dispatch(setSelectedLayer(null));
|
||||
dispatch(updateFlyout(FLYOUT_STATE.NONE));
|
||||
dispatch(removePreviewLayers());
|
||||
},
|
||||
setRefreshStoreConfig: (refreshConfig) => dispatch(setRefreshConfig(refreshConfig)),
|
||||
enableFullScreen: () => dispatch(enableFullScreen()),
|
||||
openMapSettings: () => dispatch(openMapSettings()),
|
||||
};
|
||||
}
|
||||
|
||||
const connectedMapsTopNavMenu = connect(mapStateToProps, mapDispatchToProps)(MapsTopNavMenu);
|
||||
export { connectedMapsTopNavMenu as MapsTopNavMenu };
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
getNavigation,
|
||||
getCoreChrome,
|
||||
getMapsCapabilities,
|
||||
getInspector,
|
||||
getToasts,
|
||||
getCoreI18n,
|
||||
getData,
|
||||
getUiSettings,
|
||||
} from '../../../kibana_services';
|
||||
import {
|
||||
SavedObjectSaveModal,
|
||||
showSaveModal,
|
||||
} from '../../../../../../../src/plugins/saved_objects/public';
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
|
||||
import { updateBreadcrumbs } from '../breadcrumbs';
|
||||
import { goToSpecifiedPath } from '../../maps_router';
|
||||
|
||||
export function MapsTopNavMenu({
|
||||
savedMap,
|
||||
query,
|
||||
onQueryChange,
|
||||
onQuerySaved,
|
||||
onSavedQueryUpdated,
|
||||
savedQuery,
|
||||
time,
|
||||
refreshConfig,
|
||||
setRefreshConfig,
|
||||
setRefreshStoreConfig,
|
||||
initialLayerListConfig,
|
||||
indexPatterns,
|
||||
updateFiltersAndDispatch,
|
||||
isSaveDisabled,
|
||||
closeFlyout,
|
||||
enableFullScreen,
|
||||
openMapSettings,
|
||||
inspectorAdapters,
|
||||
syncAppAndGlobalState,
|
||||
currentPath,
|
||||
isOpenSettingsDisabled,
|
||||
}) {
|
||||
const { TopNavMenu } = getNavigation().ui;
|
||||
const { filterManager } = getData().query;
|
||||
const showSaveQuery = getMapsCapabilities().saveQuery;
|
||||
const onClearSavedQuery = () => {
|
||||
onQuerySaved(undefined);
|
||||
onQueryChange({
|
||||
filters: filterManager.getGlobalFilters(),
|
||||
query: {
|
||||
query: '',
|
||||
language: getUiSettings().get('search:queryLanguage'),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Nav settings
|
||||
const config = getTopNavConfig(
|
||||
savedMap,
|
||||
initialLayerListConfig,
|
||||
isOpenSettingsDisabled,
|
||||
isSaveDisabled,
|
||||
closeFlyout,
|
||||
enableFullScreen,
|
||||
openMapSettings,
|
||||
inspectorAdapters,
|
||||
currentPath
|
||||
);
|
||||
|
||||
const submitQuery = function ({ dateRange, query }) {
|
||||
onQueryChange({
|
||||
query,
|
||||
time: dateRange,
|
||||
refresh: true,
|
||||
});
|
||||
};
|
||||
|
||||
const onRefreshChange = function ({ isPaused, refreshInterval }) {
|
||||
const newRefreshConfig = {
|
||||
isPaused,
|
||||
interval: isNaN(refreshInterval) ? refreshConfig.interval : refreshInterval,
|
||||
};
|
||||
setRefreshConfig(newRefreshConfig, () => {
|
||||
setRefreshStoreConfig(newRefreshConfig);
|
||||
syncAppAndGlobalState();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<TopNavMenu
|
||||
appName="maps"
|
||||
config={config}
|
||||
indexPatterns={indexPatterns || []}
|
||||
filters={filterManager.getFilters()}
|
||||
query={query}
|
||||
onQuerySubmit={submitQuery}
|
||||
onFiltersUpdated={updateFiltersAndDispatch}
|
||||
dateRangeFrom={time.from}
|
||||
dateRangeTo={time.to}
|
||||
isRefreshPaused={refreshConfig.isPaused}
|
||||
refreshInterval={refreshConfig.interval}
|
||||
onRefreshChange={onRefreshChange}
|
||||
showSearchBar={true}
|
||||
showFilterBar={true}
|
||||
showDatePicker={true}
|
||||
showSaveQuery={showSaveQuery}
|
||||
savedQuery={savedQuery}
|
||||
onSaved={onQuerySaved}
|
||||
onSavedQueryUpdated={onSavedQueryUpdated}
|
||||
onClearSavedQuery={onClearSavedQuery}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getTopNavConfig(
|
||||
savedMap,
|
||||
initialLayerListConfig,
|
||||
isOpenSettingsDisabled,
|
||||
isSaveDisabled,
|
||||
closeFlyout,
|
||||
enableFullScreen,
|
||||
openMapSettings,
|
||||
inspectorAdapters,
|
||||
currentPath
|
||||
) {
|
||||
return [
|
||||
{
|
||||
id: 'full-screen',
|
||||
label: i18n.translate('xpack.maps.mapController.fullScreenButtonLabel', {
|
||||
defaultMessage: `full screen`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.fullScreenDescription', {
|
||||
defaultMessage: `full screen`,
|
||||
}),
|
||||
testId: 'mapsFullScreenMode',
|
||||
run() {
|
||||
getCoreChrome().setIsVisible(false);
|
||||
enableFullScreen();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'inspect',
|
||||
label: i18n.translate('xpack.maps.mapController.openInspectorButtonLabel', {
|
||||
defaultMessage: `inspect`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.openInspectorDescription', {
|
||||
defaultMessage: `Open Inspector`,
|
||||
}),
|
||||
testId: 'openInspectorButton',
|
||||
run() {
|
||||
getInspector().open(inspectorAdapters, {});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'mapSettings',
|
||||
label: i18n.translate('xpack.maps.mapController.openSettingsButtonLabel', {
|
||||
defaultMessage: `Map settings`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.openSettingsDescription', {
|
||||
defaultMessage: `Open map settings`,
|
||||
}),
|
||||
testId: 'openSettingsButton',
|
||||
disableButton() {
|
||||
return isOpenSettingsDisabled;
|
||||
},
|
||||
run() {
|
||||
openMapSettings();
|
||||
},
|
||||
},
|
||||
...(getMapsCapabilities().save
|
||||
? [
|
||||
{
|
||||
id: 'save',
|
||||
label: i18n.translate('xpack.maps.mapController.saveMapButtonLabel', {
|
||||
defaultMessage: `save`,
|
||||
}),
|
||||
description: i18n.translate('xpack.maps.mapController.saveMapDescription', {
|
||||
defaultMessage: `Save map`,
|
||||
}),
|
||||
testId: 'mapSaveButton',
|
||||
disableButton() {
|
||||
return isSaveDisabled;
|
||||
},
|
||||
tooltip() {
|
||||
if (isSaveDisabled) {
|
||||
return i18n.translate('xpack.maps.mapController.saveMapDisabledButtonTooltip', {
|
||||
defaultMessage: 'Save or Cancel your layer changes before saving',
|
||||
});
|
||||
}
|
||||
},
|
||||
run: async () => {
|
||||
const onSave = ({
|
||||
newTitle,
|
||||
newCopyOnSave,
|
||||
isTitleDuplicateConfirmed,
|
||||
onTitleDuplicate,
|
||||
}) => {
|
||||
const currentTitle = savedMap.title;
|
||||
savedMap.title = newTitle;
|
||||
savedMap.copyOnSave = newCopyOnSave;
|
||||
const saveOptions = {
|
||||
confirmOverwrite: false,
|
||||
isTitleDuplicateConfirmed,
|
||||
onTitleDuplicate,
|
||||
};
|
||||
return doSave(
|
||||
savedMap,
|
||||
saveOptions,
|
||||
initialLayerListConfig,
|
||||
closeFlyout,
|
||||
currentPath
|
||||
).then((response) => {
|
||||
// If the save wasn't successful, put the original values back.
|
||||
if (!response.id || response.error) {
|
||||
savedMap.title = currentTitle;
|
||||
}
|
||||
return response;
|
||||
});
|
||||
};
|
||||
|
||||
const saveModal = (
|
||||
<SavedObjectSaveModal
|
||||
onSave={onSave}
|
||||
onClose={() => {}}
|
||||
title={savedMap.title}
|
||||
showCopyOnSave={!!savedMap.id}
|
||||
objectType={MAP_SAVED_OBJECT_TYPE}
|
||||
showDescription={false}
|
||||
/>
|
||||
);
|
||||
showSaveModal(saveModal, getCoreI18n().Context);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}
|
||||
|
||||
async function doSave(savedMap, saveOptions, initialLayerListConfig, closeFlyout, currentPath) {
|
||||
closeFlyout();
|
||||
savedMap.syncWithStore();
|
||||
let id;
|
||||
|
||||
try {
|
||||
id = await savedMap.save(saveOptions);
|
||||
getCoreChrome().docTitle.change(savedMap.title);
|
||||
} catch (err) {
|
||||
getToasts().addDanger({
|
||||
title: i18n.translate('xpack.maps.mapController.saveErrorMessage', {
|
||||
defaultMessage: `Error on saving '{title}'`,
|
||||
values: { title: savedMap.title },
|
||||
}),
|
||||
text: err.message,
|
||||
'data-test-subj': 'saveMapError',
|
||||
});
|
||||
return { error: err };
|
||||
}
|
||||
|
||||
if (id) {
|
||||
goToSpecifiedPath(`/map/${id}${window.location.hash}`);
|
||||
updateBreadcrumbs(savedMap, initialLayerListConfig, currentPath);
|
||||
|
||||
getToasts().addSuccess({
|
||||
title: i18n.translate('xpack.maps.mapController.saveSuccessMessage', {
|
||||
defaultMessage: `Saved '{title}'`,
|
||||
values: { title: savedMap.title },
|
||||
}),
|
||||
'data-test-subj': 'saveMapSuccess',
|
||||
});
|
||||
}
|
||||
return { id };
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader';
|
||||
import { getToasts } from '../../../kibana_services';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { MapsListView } from './maps_list_view';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
export class LoadListAndRender extends React.Component {
|
||||
state = {
|
||||
mapsLoaded: false,
|
||||
hasSavedMaps: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadMapsList();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
async _loadMapsList() {
|
||||
try {
|
||||
const { hits = [] } = await getMapsSavedObjectLoader().find('', 1);
|
||||
if (this._isMounted) {
|
||||
this.setState({ mapsLoaded: true, hasSavedMaps: !!hits.length });
|
||||
}
|
||||
} catch (err) {
|
||||
if (this._isMounted) {
|
||||
this.setState({ mapsLoaded: true, hasSavedMaps: false });
|
||||
getToasts().addDanger({
|
||||
title: i18n.translate('xpack.maps.mapListing.errorAttemptingToLoadSavedMaps', {
|
||||
defaultMessage: `Unable to load maps`,
|
||||
}),
|
||||
text: `${err}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { mapsLoaded, hasSavedMaps } = this.state;
|
||||
|
||||
if (mapsLoaded) {
|
||||
return hasSavedMaps ? <MapsListView /> : <Redirect to="/map" />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,14 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getToasts } from '../kibana_services';
|
||||
import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader';
|
||||
import {
|
||||
getMapsCapabilities,
|
||||
getUiSettings,
|
||||
getToasts,
|
||||
getCoreChrome,
|
||||
} from '../../../kibana_services';
|
||||
import {
|
||||
EuiTitle,
|
||||
EuiFieldSearch,
|
||||
|
@ -27,11 +31,14 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { addHelpMenuToAppChrome } from '../help_menu_util';
|
||||
import { addHelpMenuToAppChrome } from '../../../help_menu_util';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { updateBreadcrumbs } from '../../page_elements/breadcrumbs';
|
||||
import { goToSpecifiedPath } from '../../maps_router';
|
||||
|
||||
export const EMPTY_FILTER = '';
|
||||
|
||||
export class MapListing extends React.Component {
|
||||
export class MapsListView extends React.Component {
|
||||
state = {
|
||||
hasInitialFetchReturned: false,
|
||||
isFetchingItems: false,
|
||||
|
@ -42,10 +49,13 @@ export class MapListing extends React.Component {
|
|||
selectedIds: [],
|
||||
page: 0,
|
||||
perPage: 20,
|
||||
readOnly: !getMapsCapabilities().save,
|
||||
listingLimit: getUiSettings().get('savedObjects:listingLimit'),
|
||||
};
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this._isMounted = true;
|
||||
updateBreadcrumbs();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -54,12 +64,21 @@ export class MapListing extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchItems();
|
||||
addHelpMenuToAppChrome();
|
||||
this.initMapList();
|
||||
}
|
||||
|
||||
async initMapList() {
|
||||
this.fetchItems();
|
||||
addHelpMenuToAppChrome();
|
||||
getCoreChrome().docTitle.change('Maps');
|
||||
}
|
||||
|
||||
_find = (search) => getMapsSavedObjectLoader().find(search, this.state.listingLimit);
|
||||
|
||||
_delete = (ids) => getMapsSavedObjectLoader().delete(ids);
|
||||
|
||||
debouncedFetch = _.debounce(async (filter) => {
|
||||
const response = await this.props.find(filter);
|
||||
const response = await this._find(filter);
|
||||
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
|
@ -73,7 +92,7 @@ export class MapListing extends React.Component {
|
|||
isFetchingItems: false,
|
||||
items: response.hits,
|
||||
totalItems: response.total,
|
||||
showLimitError: response.total > this.props.listingLimit,
|
||||
showLimitError: response.total > this.state.listingLimit,
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
|
@ -89,7 +108,7 @@ export class MapListing extends React.Component {
|
|||
|
||||
deleteSelectedItems = async () => {
|
||||
try {
|
||||
await this.props.delete(this.state.selectedIds);
|
||||
await this._delete(this.state.selectedIds);
|
||||
} catch (error) {
|
||||
getToasts().addDanger({
|
||||
title: i18n.translate('xpack.maps.mapListing.unableToDeleteToastTitle', {
|
||||
|
@ -211,11 +230,11 @@ export class MapListing extends React.Component {
|
|||
<FormattedMessage
|
||||
id="xpack.maps.mapListing.limitHelpDescription"
|
||||
defaultMessage="You have {totalItems} items,
|
||||
but your <strong>listingLimit</strong> setting prevents the table below from displaying more than {listingLimit}.
|
||||
You can change this setting under "
|
||||
but your <strong>listingLimit</strong> setting prevents the table below from displaying more than {listingLimit}.
|
||||
You can change this setting under "
|
||||
values={{
|
||||
totalItems: this.state.totalItems,
|
||||
listingLimit: this.props.listingLimit,
|
||||
listingLimit: this.state.listingLimit,
|
||||
}}
|
||||
/>
|
||||
<EuiLink href="#/management/kibana/settings">
|
||||
|
@ -307,7 +326,10 @@ export class MapListing extends React.Component {
|
|||
sortable: true,
|
||||
render: (field, record) => (
|
||||
<EuiLink
|
||||
href={`#/map/${record.id}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
goToSpecifiedPath(`/map/${record.id}`);
|
||||
}}
|
||||
data-test-subj={`mapListingTitleLink-${record.title.split(' ').join('-')}`}
|
||||
>
|
||||
{field}
|
||||
|
@ -331,7 +353,7 @@ export class MapListing extends React.Component {
|
|||
};
|
||||
|
||||
let selection = false;
|
||||
if (!this.props.readOnly) {
|
||||
if (!this.state.readOnly) {
|
||||
selection = {
|
||||
onSelectionChange: (selection) => {
|
||||
this.setState({
|
||||
|
@ -369,14 +391,16 @@ export class MapListing extends React.Component {
|
|||
|
||||
renderListing() {
|
||||
let createButton;
|
||||
if (!this.props.readOnly) {
|
||||
if (!this.state.readOnly) {
|
||||
createButton = (
|
||||
<EuiButton href={`#/map`} data-test-subj="newMapLink" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.maps.mapListing.createMapButtonLabel"
|
||||
defaultMessage="Create map"
|
||||
/>
|
||||
</EuiButton>
|
||||
<Link to={'/map'}>
|
||||
<EuiButton data-test-subj="newMapLink" fill>
|
||||
<FormattedMessage
|
||||
id="xpack.maps.mapListing.createMapButtonLabel"
|
||||
defaultMessage="Create map"
|
||||
/>
|
||||
</EuiButton>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
@ -427,10 +451,3 @@ export class MapListing extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
MapListing.propTypes = {
|
||||
readOnly: PropTypes.bool.isRequired,
|
||||
find: PropTypes.func.isRequired,
|
||||
delete: PropTypes.func.isRequired,
|
||||
listingLimit: PropTypes.number.isRequired,
|
||||
};
|
67
x-pack/plugins/maps/public/routing/routes/maps_app/index.js
Normal file
67
x-pack/plugins/maps/public/routing/routes/maps_app/index.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { MapsAppView } from './maps_app_view';
|
||||
import { getFlyoutDisplay, getIsFullScreen } from '../../../selectors/ui_selectors';
|
||||
import {
|
||||
getFilters,
|
||||
getQueryableUniqueIndexPatternIds,
|
||||
getRefreshConfig,
|
||||
} from '../../../selectors/map_selectors';
|
||||
import {
|
||||
replaceLayerList,
|
||||
setGotoWithCenter,
|
||||
setIsLayerTOCOpen,
|
||||
setMapSettings,
|
||||
setOpenTOCDetails,
|
||||
setQuery,
|
||||
setReadOnly,
|
||||
setRefreshConfig,
|
||||
setSelectedLayer,
|
||||
updateFlyout,
|
||||
} from '../../../actions';
|
||||
import { FLYOUT_STATE } from '../../../reducers/ui';
|
||||
import { getMapsCapabilities } from '../../../kibana_services';
|
||||
|
||||
function mapStateToProps(state = {}) {
|
||||
return {
|
||||
isFullScreen: getIsFullScreen(state),
|
||||
nextIndexPatternIds: getQueryableUniqueIndexPatternIds(state),
|
||||
flyoutDisplay: getFlyoutDisplay(state),
|
||||
refreshConfig: getRefreshConfig(state),
|
||||
filters: getFilters(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
dispatchSetQuery: (refresh, filters, query, time) => {
|
||||
dispatch(
|
||||
setQuery({
|
||||
filters,
|
||||
query,
|
||||
timeFilters: time,
|
||||
refresh,
|
||||
})
|
||||
);
|
||||
},
|
||||
setRefreshConfig: (refreshConfig) => dispatch(setRefreshConfig(refreshConfig)),
|
||||
replaceLayerList: (layerList) => dispatch(replaceLayerList(layerList)),
|
||||
setGotoWithCenter: (latLonZoom) => dispatch(setGotoWithCenter(latLonZoom)),
|
||||
setMapSettings: (mapSettings) => dispatch(setMapSettings(mapSettings)),
|
||||
setIsLayerTOCOpen: (isLayerTOCOpen) => dispatch(setIsLayerTOCOpen(isLayerTOCOpen)),
|
||||
setOpenTOCDetails: (openTOCDetails) => dispatch(setOpenTOCDetails(openTOCDetails)),
|
||||
clearUi: () => {
|
||||
dispatch(setSelectedLayer(null));
|
||||
dispatch(updateFlyout(FLYOUT_STATE.NONE));
|
||||
dispatch(setReadOnly(!getMapsCapabilities().save));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const connectedMapsAppView = connect(mapStateToProps, mapDispatchToProps)(MapsAppView);
|
||||
export { connectedMapsAppView as MapsAppView };
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MapsAppView } from '.';
|
||||
import { getMapsSavedObjectLoader } from '../../bootstrap/services/gis_map_saved_object_loader';
|
||||
import { getToasts } from '../../../kibana_services';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
export const LoadMapAndRender = class extends React.Component {
|
||||
state = {
|
||||
savedMap: null,
|
||||
failedToLoad: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadSavedMap();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
async _loadSavedMap() {
|
||||
const { savedMapId } = this.props.match.params;
|
||||
try {
|
||||
const savedMap = await getMapsSavedObjectLoader().get(savedMapId);
|
||||
if (this._isMounted) {
|
||||
this.setState({ savedMap });
|
||||
}
|
||||
} catch (err) {
|
||||
if (this._isMounted) {
|
||||
this.setState({ failedToLoad: true });
|
||||
getToasts().addWarning({
|
||||
title: i18n.translate('xpack.maps.loadMap.errorAttemptingToLoadSavedMap', {
|
||||
defaultMessage: `Unable to load map`,
|
||||
}),
|
||||
text: `${err.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { savedMap, failedToLoad } = this.state;
|
||||
if (failedToLoad) {
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
|
||||
const currentPath = this.props.match.url;
|
||||
return savedMap ? <MapsAppView savedMap={savedMap} currentPath={currentPath} /> : null;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import _ from 'lodash';
|
||||
import { DEFAULT_IS_LAYER_TOC_OPEN } from '../../../reducers/ui';
|
||||
import {
|
||||
getIndexPatternService,
|
||||
getToasts,
|
||||
getData,
|
||||
getUiSettings,
|
||||
getCoreChrome,
|
||||
} from '../../../kibana_services';
|
||||
import { copyPersistentState } from '../../../reducers/util';
|
||||
import { getInitialLayers } from '../../bootstrap/get_initial_layers';
|
||||
import rison from 'rison-node';
|
||||
import { getInitialTimeFilters } from '../../bootstrap/get_initial_time_filters';
|
||||
import { getInitialRefreshConfig } from '../../bootstrap/get_initial_refresh_config';
|
||||
import { getInitialQuery } from '../../bootstrap/get_initial_query';
|
||||
import { MapsTopNavMenu } from '../../page_elements/top_nav_menu';
|
||||
import {
|
||||
getGlobalState,
|
||||
updateGlobalState,
|
||||
useGlobalStateSyncing,
|
||||
} from '../../state_syncing/global_sync';
|
||||
import { AppStateManager } from '../../state_syncing/app_state_manager';
|
||||
import { useAppStateSyncing } from '../../state_syncing/app_sync';
|
||||
import { updateBreadcrumbs } from '../../page_elements/breadcrumbs';
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/public';
|
||||
import { GisMap } from '../../../connected_components/gis_map';
|
||||
|
||||
export class MapsAppView extends React.Component {
|
||||
_visibleSubscription = null;
|
||||
_globalSyncUnsubscribe = null;
|
||||
_globalSyncChangeMonitorSubscription = null;
|
||||
_appSyncUnsubscribe = null;
|
||||
_appStateManager = new AppStateManager();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
indexPatterns: [],
|
||||
prevIndexPatternIds: [],
|
||||
initialized: false,
|
||||
isVisible: true,
|
||||
savedQuery: '',
|
||||
currentPath: '',
|
||||
initialLayerListConfig: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { savedMap, currentPath } = this.props;
|
||||
this.setState({ currentPath });
|
||||
|
||||
getCoreChrome().docTitle.change(savedMap.title);
|
||||
getCoreChrome().recentlyAccessed.add(savedMap.getFullPath(), savedMap.title, savedMap.id);
|
||||
|
||||
// Init sync utils
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
this._globalSyncUnsubscribe = useGlobalStateSyncing();
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
this._appSyncUnsubscribe = useAppStateSyncing(this._appStateManager);
|
||||
this._globalSyncChangeMonitorSubscription = getData().query.state$.subscribe(
|
||||
this._updateFromGlobalState
|
||||
);
|
||||
|
||||
// Check app state in case of refresh
|
||||
const initAppState = this._appStateManager.getAppState();
|
||||
this._onQueryChange(initAppState);
|
||||
if (initAppState.savedQuery) {
|
||||
this._updateStateFromSavedQuery(initAppState.savedQuery);
|
||||
}
|
||||
|
||||
// Monitor visibility
|
||||
this._visibleSubscription = getCoreChrome()
|
||||
.getIsVisible$()
|
||||
.subscribe((isVisible) => this.setState({ isVisible }));
|
||||
this._initMap();
|
||||
}
|
||||
|
||||
_initBreadcrumbUpdater = () => {
|
||||
const { initialLayerListConfig, currentPath } = this.state;
|
||||
updateBreadcrumbs(this.props.savedMap, initialLayerListConfig, currentPath);
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { currentPath: prevCurrentPath } = prevState;
|
||||
const { currentPath, initialLayerListConfig } = this.state;
|
||||
const { savedMap } = this.props;
|
||||
if (savedMap && initialLayerListConfig && currentPath !== prevCurrentPath) {
|
||||
updateBreadcrumbs(savedMap, initialLayerListConfig, currentPath);
|
||||
}
|
||||
// TODO: Handle null when converting to TS
|
||||
this._handleStoreChanges();
|
||||
}
|
||||
|
||||
_updateFromGlobalState = ({ changes, state: globalState }) => {
|
||||
if (!changes || !globalState) {
|
||||
return;
|
||||
}
|
||||
const newState = {};
|
||||
Object.keys(changes).forEach((key) => {
|
||||
if (changes[key]) {
|
||||
newState[key] = globalState[key];
|
||||
}
|
||||
});
|
||||
|
||||
this.setState(newState, () => {
|
||||
this._appStateManager.setQueryAndFilters({
|
||||
filters: getData().query.filterManager.getAppFilters(),
|
||||
});
|
||||
const { time, filters, refreshInterval } = globalState;
|
||||
this.props.dispatchSetQuery(refreshInterval, filters, this.state.query, time);
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._globalSyncUnsubscribe) {
|
||||
this._globalSyncUnsubscribe();
|
||||
}
|
||||
if (this._appSyncUnsubscribe) {
|
||||
this._appSyncUnsubscribe();
|
||||
}
|
||||
if (this._visibleSubscription) {
|
||||
this._visibleSubscription.unsubscribe();
|
||||
}
|
||||
if (this._globalSyncChangeMonitorSubscription) {
|
||||
this._globalSyncChangeMonitorSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
// Clean up app state filters
|
||||
const { filterManager } = getData().query;
|
||||
filterManager.filters.forEach((filter) => {
|
||||
if (filter.$state.store === esFilters.FilterStateStore.APP_STATE) {
|
||||
filterManager.removeFilter(filter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_getInitialLayersFromUrlParam() {
|
||||
const locationSplit = window.location.href.split('?');
|
||||
if (locationSplit.length <= 1) {
|
||||
return [];
|
||||
}
|
||||
const mapAppParams = new URLSearchParams(locationSplit[1]);
|
||||
if (!mapAppParams.has('initialLayers')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
let mapInitLayers = mapAppParams.get('initialLayers');
|
||||
if (mapInitLayers[mapInitLayers.length - 1] === '#') {
|
||||
mapInitLayers = mapInitLayers.substr(0, mapInitLayers.length - 1);
|
||||
}
|
||||
return rison.decode_array(mapInitLayers);
|
||||
} catch (e) {
|
||||
getToasts().addWarning({
|
||||
title: i18n.translate('xpack.maps.initialLayers.unableToParseTitle', {
|
||||
defaultMessage: `Initial layers not added to map`,
|
||||
}),
|
||||
text: i18n.translate('xpack.maps.initialLayers.unableToParseMessage', {
|
||||
defaultMessage: `Unable to parse contents of 'initialLayers' parameter. Error: {errorMsg}`,
|
||||
values: { errorMsg: e.message },
|
||||
}),
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async _updateIndexPatterns(nextIndexPatternIds) {
|
||||
const indexPatterns = [];
|
||||
const getIndexPatternPromises = nextIndexPatternIds.map(async (indexPatternId) => {
|
||||
try {
|
||||
const indexPattern = await getIndexPatternService().get(indexPatternId);
|
||||
indexPatterns.push(indexPattern);
|
||||
} catch (err) {
|
||||
// unable to fetch index pattern
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(getIndexPatternPromises);
|
||||
this.setState({
|
||||
indexPatterns,
|
||||
});
|
||||
}
|
||||
|
||||
_handleStoreChanges = () => {
|
||||
const { prevIndexPatternIds } = this.state;
|
||||
const { nextIndexPatternIds } = this.props;
|
||||
|
||||
if (nextIndexPatternIds !== prevIndexPatternIds) {
|
||||
this.setState({ prevIndexPatternIds: nextIndexPatternIds });
|
||||
this._updateIndexPatterns(nextIndexPatternIds);
|
||||
}
|
||||
};
|
||||
|
||||
_getAppStateFilters = () => {
|
||||
return this._appStateManager.getFilters() || [];
|
||||
};
|
||||
|
||||
_syncAppAndGlobalState = () => {
|
||||
const { query, time, initialized } = this.state;
|
||||
const { refreshConfig } = this.props;
|
||||
const { filterManager } = getData().query;
|
||||
|
||||
// appState
|
||||
this._appStateManager.setQueryAndFilters({
|
||||
query: query,
|
||||
filters: filterManager.getAppFilters(),
|
||||
});
|
||||
|
||||
// globalState
|
||||
const refreshInterval = {
|
||||
pause: refreshConfig.isPaused,
|
||||
value: refreshConfig.interval,
|
||||
};
|
||||
updateGlobalState(
|
||||
{
|
||||
time: time,
|
||||
refreshInterval,
|
||||
filters: filterManager.getGlobalFilters(),
|
||||
},
|
||||
!initialized
|
||||
);
|
||||
this.setState({ refreshInterval });
|
||||
};
|
||||
|
||||
_onQueryChange = async ({ filters, query, time, refresh }) => {
|
||||
const { filterManager } = getData().query;
|
||||
const { dispatchSetQuery } = this.props;
|
||||
const newState = {};
|
||||
let newFilters;
|
||||
if (filters) {
|
||||
filterManager.setFilters(filters); // Maps and merges filters
|
||||
newFilters = filterManager.getFilters();
|
||||
}
|
||||
if (query) {
|
||||
newState.query = query;
|
||||
}
|
||||
if (time) {
|
||||
newState.time = time;
|
||||
}
|
||||
this.setState(newState, () => {
|
||||
this._syncAppAndGlobalState();
|
||||
dispatchSetQuery(
|
||||
refresh,
|
||||
newFilters || this.props.filters,
|
||||
query || this.state.query,
|
||||
time || this.state.time
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
_initQueryTimeRefresh() {
|
||||
const { setRefreshConfig, savedMap } = this.props;
|
||||
// TODO: Handle null when converting to TS
|
||||
const globalState = getGlobalState();
|
||||
const mapStateJSON = savedMap ? savedMap.mapStateJSON : undefined;
|
||||
const newState = {
|
||||
query: getInitialQuery({
|
||||
mapStateJSON,
|
||||
appState: this._appStateManager.getAppState(),
|
||||
userQueryLanguage: getUiSettings().get('search:queryLanguage'),
|
||||
}),
|
||||
time: getInitialTimeFilters({
|
||||
mapStateJSON,
|
||||
globalState,
|
||||
}),
|
||||
refreshConfig: getInitialRefreshConfig({
|
||||
mapStateJSON,
|
||||
globalState,
|
||||
}),
|
||||
};
|
||||
this.setState({ query: newState.query, time: newState.time });
|
||||
updateGlobalState(
|
||||
{
|
||||
time: newState.time,
|
||||
refreshInterval: {
|
||||
value: newState.refreshConfig.interval,
|
||||
pause: newState.refreshConfig.isPaused,
|
||||
},
|
||||
},
|
||||
!this.state.initialized
|
||||
);
|
||||
setRefreshConfig(newState.refreshConfig);
|
||||
}
|
||||
|
||||
_initMapAndLayerSettings() {
|
||||
const { savedMap } = this.props;
|
||||
// Get saved map & layer settings
|
||||
this._initQueryTimeRefresh();
|
||||
|
||||
const layerList = getInitialLayers(
|
||||
savedMap.layerListJSON,
|
||||
this._getInitialLayersFromUrlParam()
|
||||
);
|
||||
this.props.replaceLayerList(layerList);
|
||||
this.setState(
|
||||
{
|
||||
initialLayerListConfig: copyPersistentState(layerList),
|
||||
savedMap,
|
||||
},
|
||||
this._initBreadcrumbUpdater
|
||||
);
|
||||
}
|
||||
|
||||
_updateFiltersAndDispatch = (filters) => {
|
||||
this._onQueryChange({
|
||||
filters,
|
||||
});
|
||||
};
|
||||
|
||||
_onRefreshChange = ({ isPaused, refreshInterval }) => {
|
||||
const { refreshConfig } = this.props;
|
||||
const newRefreshConfig = {
|
||||
isPaused,
|
||||
interval: isNaN(refreshInterval) ? refreshConfig.interval : refreshInterval,
|
||||
};
|
||||
this.setState({ refreshConfig: newRefreshConfig }, this._syncAppAndGlobalState);
|
||||
this.props.setRefreshConfig(newRefreshConfig);
|
||||
};
|
||||
|
||||
_updateStateFromSavedQuery(savedQuery) {
|
||||
if (!savedQuery) {
|
||||
this.setState({ savedQuery: '' });
|
||||
return;
|
||||
}
|
||||
const { filterManager } = getData().query;
|
||||
const savedQueryFilters = savedQuery.attributes.filters || [];
|
||||
const globalFilters = filterManager.getGlobalFilters();
|
||||
const allFilters = [...savedQueryFilters, ...globalFilters];
|
||||
|
||||
if (savedQuery.attributes.timefilter) {
|
||||
if (savedQuery.attributes.timefilter.refreshInterval) {
|
||||
this._onRefreshChange({
|
||||
isPaused: savedQuery.attributes.timefilter.refreshInterval.pause,
|
||||
refreshInterval: savedQuery.attributes.timefilter.refreshInterval.value,
|
||||
});
|
||||
}
|
||||
this._onQueryChange({
|
||||
filters: allFilters,
|
||||
query: savedQuery.attributes.query,
|
||||
time: savedQuery.attributes.timefilter,
|
||||
});
|
||||
} else {
|
||||
this._onQueryChange({
|
||||
filters: allFilters,
|
||||
query: savedQuery.attributes.query,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_syncStoreAndGetFilters() {
|
||||
const {
|
||||
savedMap,
|
||||
setGotoWithCenter,
|
||||
setMapSettings,
|
||||
setIsLayerTOCOpen,
|
||||
setOpenTOCDetails,
|
||||
} = this.props;
|
||||
let savedObjectFilters = [];
|
||||
if (savedMap.mapStateJSON) {
|
||||
const mapState = JSON.parse(savedMap.mapStateJSON);
|
||||
setGotoWithCenter({
|
||||
lat: mapState.center.lat,
|
||||
lon: mapState.center.lon,
|
||||
zoom: mapState.zoom,
|
||||
});
|
||||
if (mapState.filters) {
|
||||
savedObjectFilters = mapState.filters;
|
||||
}
|
||||
if (mapState.settings) {
|
||||
setMapSettings(mapState.settings);
|
||||
}
|
||||
}
|
||||
|
||||
if (savedMap.uiStateJSON) {
|
||||
const uiState = JSON.parse(savedMap.uiStateJSON);
|
||||
setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN));
|
||||
setOpenTOCDetails(_.get(uiState, 'openTOCDetails', []));
|
||||
}
|
||||
return savedObjectFilters;
|
||||
}
|
||||
|
||||
async _initMap() {
|
||||
const { clearUi, savedMap } = this.props;
|
||||
// TODO: Handle null when converting to TS
|
||||
const globalState = getGlobalState();
|
||||
this._initMapAndLayerSettings();
|
||||
clearUi();
|
||||
|
||||
const savedObjectFilters = this._syncStoreAndGetFilters(savedMap);
|
||||
await this._onQueryChange({
|
||||
filters: [
|
||||
..._.get(globalState, 'filters', []),
|
||||
...this._getAppStateFilters(),
|
||||
...savedObjectFilters,
|
||||
],
|
||||
});
|
||||
this.setState({ initialized: true });
|
||||
}
|
||||
|
||||
_renderTopNav() {
|
||||
const {
|
||||
query,
|
||||
time,
|
||||
savedQuery,
|
||||
initialLayerListConfig,
|
||||
isVisible,
|
||||
indexPatterns,
|
||||
currentPath,
|
||||
} = this.state;
|
||||
const { savedMap, refreshConfig } = this.props;
|
||||
|
||||
return isVisible ? (
|
||||
<MapsTopNavMenu
|
||||
savedMap={savedMap}
|
||||
query={query}
|
||||
savedQuery={savedQuery}
|
||||
onQueryChange={this._onQueryChange}
|
||||
time={time}
|
||||
refreshConfig={refreshConfig}
|
||||
setRefreshConfig={(newConfig, callback) => {
|
||||
this.setState(
|
||||
{
|
||||
refreshConfig: newConfig,
|
||||
},
|
||||
callback
|
||||
);
|
||||
}}
|
||||
initialLayerListConfig={initialLayerListConfig}
|
||||
indexPatterns={indexPatterns}
|
||||
updateFiltersAndDispatch={this._updateFiltersAndDispatch}
|
||||
onQuerySaved={(query) => {
|
||||
this.setState({ savedQuery: query });
|
||||
this._appStateManager.setQueryAndFilters({ savedQuery: query });
|
||||
this._updateStateFromSavedQuery(query);
|
||||
}}
|
||||
onSavedQueryUpdated={(query) => {
|
||||
this.setState({ savedQuery: { ...query } });
|
||||
this._appStateManager.setQueryAndFilters({ savedQuery: query });
|
||||
this._updateStateFromSavedQuery(query);
|
||||
}}
|
||||
syncAppAndGlobalState={this._syncAppAndGlobalState}
|
||||
currentPath={currentPath}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { filters, isFullScreen } = this.props;
|
||||
|
||||
return this.state.initialized ? (
|
||||
<div id="maps-plugin" className={isFullScreen ? 'mapFullScreen' : ''}>
|
||||
{this._renderTopNav()}
|
||||
<h1 className="euiScreenReaderOnly">{`screenTitle placeholder`}</h1>
|
||||
<div id="react-maps-root">
|
||||
<GisMap
|
||||
addFilters={(newFilters) => {
|
||||
newFilters.forEach((filter) => {
|
||||
filter.$state = { store: esFilters.FilterStateStore.APP_STATE };
|
||||
});
|
||||
this._updateFiltersAndDispatch([...filters, ...newFilters]);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export class AppStateManager {
|
||||
_query = '';
|
||||
_savedQuery = '';
|
||||
_filters = [];
|
||||
|
||||
_updated$ = new Subject();
|
||||
|
||||
setQueryAndFilters({ query, savedQuery, filters }) {
|
||||
if (this._query !== query) {
|
||||
this._query = query;
|
||||
}
|
||||
if (this._savedQuery !== savedQuery) {
|
||||
this._savedQuery = savedQuery;
|
||||
}
|
||||
if (this._filters !== filters) {
|
||||
this._filters = filters;
|
||||
}
|
||||
this._updated$.next();
|
||||
}
|
||||
|
||||
getQuery() {
|
||||
return this._query;
|
||||
}
|
||||
|
||||
getFilters() {
|
||||
return this._filters;
|
||||
}
|
||||
|
||||
getAppState() {
|
||||
return {
|
||||
query: this._query,
|
||||
savedQuery: this._savedQuery,
|
||||
filters: this._filters,
|
||||
};
|
||||
}
|
||||
}
|
61
x-pack/plugins/maps/public/routing/state_syncing/app_sync.js
Normal file
61
x-pack/plugins/maps/public/routing/state_syncing/app_sync.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { connectToQueryState, esFilters } from '../../../../../../src/plugins/data/public';
|
||||
import { syncState } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { getData } from '../../kibana_services';
|
||||
import { kbnUrlStateStorage } from '../maps_router';
|
||||
|
||||
export function useAppStateSyncing(appStateManager) {
|
||||
// get appStateContainer
|
||||
// sync app filters with app state container from data.query to state container
|
||||
const { query } = getData();
|
||||
|
||||
const stateContainer = {
|
||||
get: () => ({
|
||||
query: appStateManager.getQuery(),
|
||||
filters: appStateManager.getFilters(),
|
||||
}),
|
||||
set: (state) =>
|
||||
state && appStateManager.setQueryAndFilters({ query: state.query, filters: state.filters }),
|
||||
state$: appStateManager._updated$.pipe(
|
||||
map(() => ({
|
||||
query: appStateManager.getQuery(),
|
||||
filters: appStateManager.getFilters(),
|
||||
}))
|
||||
),
|
||||
};
|
||||
const stopSyncingQueryAppStateWithStateContainer = connectToQueryState(query, stateContainer, {
|
||||
filters: esFilters.FilterStateStore.APP_STATE,
|
||||
});
|
||||
|
||||
// sets up syncing app state container with url
|
||||
const { start: startSyncingAppStateWithUrl, stop: stopSyncingAppStateWithUrl } = syncState({
|
||||
storageKey: '_a',
|
||||
stateStorage: kbnUrlStateStorage,
|
||||
stateContainer,
|
||||
});
|
||||
|
||||
// merge initial state from app state container and current state in url
|
||||
const initialAppState = {
|
||||
...stateContainer.get(),
|
||||
...kbnUrlStateStorage.get('_a'),
|
||||
};
|
||||
// trigger state update. actually needed in case some data was in url
|
||||
stateContainer.set(initialAppState);
|
||||
|
||||
// set current url to whatever is in app state container
|
||||
kbnUrlStateStorage.set('_a', initialAppState);
|
||||
|
||||
// finally start syncing state containers with url
|
||||
startSyncingAppStateWithUrl();
|
||||
|
||||
return () => {
|
||||
stopSyncingQueryAppStateWithStateContainer();
|
||||
stopSyncingAppStateWithUrl();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { syncQueryStateWithUrl } from '../../../../../../src/plugins/data/public';
|
||||
import { getData } from '../../kibana_services';
|
||||
// @ts-ignore
|
||||
import { kbnUrlStateStorage } from '../maps_router';
|
||||
|
||||
export function useGlobalStateSyncing() {
|
||||
const { stop } = syncQueryStateWithUrl(getData().query, kbnUrlStateStorage);
|
||||
return stop;
|
||||
}
|
||||
|
||||
export function getGlobalState() {
|
||||
return kbnUrlStateStorage.get('_g');
|
||||
}
|
||||
|
||||
export function updateGlobalState(newState: unknown, flushUrlState = false) {
|
||||
const globalState = getGlobalState();
|
||||
kbnUrlStateStorage.set('_g', {
|
||||
// @ts-ignore
|
||||
...globalState,
|
||||
// @ts-ignore
|
||||
...newState,
|
||||
});
|
||||
if (flushUrlState) {
|
||||
kbnUrlStateStorage.flush({ replace: true });
|
||||
}
|
||||
}
|
11
x-pack/plugins/maps/public/routing/store_operations.js
Normal file
11
x-pack/plugins/maps/public/routing/store_operations.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { createMapStore } from '../reducers/store';
|
||||
|
||||
const store = createMapStore();
|
||||
|
||||
export const getStore = () => store;
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TutorialsCategory } from '../../../../../../src/plugins/home/server';
|
||||
import { MAP_BASE_URL } from '../../../common/constants';
|
||||
|
||||
export function emsBoundariesSpecProvider({
|
||||
emsLandingPageUrl,
|
||||
|
@ -63,7 +64,7 @@ Indexing EMS administrative boundaries in Elasticsearch allows for search on bou
|
|||
2. Click `Add layer`, then select `Upload GeoJSON`.\n\
|
||||
3. Upload the GeoJSON file and click `Import file`.',
|
||||
values: {
|
||||
newMapUrl: prependBasePath('/app/maps#/map'),
|
||||
newMapUrl: prependBasePath(MAP_BASE_URL),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -22,7 +22,8 @@ import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
|
|||
import { MapToolTip } from './map_tool_tip/map_tool_tip';
|
||||
import * as i18n from './translations';
|
||||
import { SetQuery } from './types';
|
||||
import { MapEmbeddable } from '../../../../../../legacy/plugins/maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { MapEmbeddable } from '../../../../../../plugins/maps/public/embeddable';
|
||||
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
|
||||
import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
|
||||
|
||||
|
|
|
@ -13,9 +13,13 @@ import { getLayerList } from './map_config';
|
|||
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../maps/public';
|
||||
import {
|
||||
MapEmbeddable,
|
||||
RenderTooltipContentParams,
|
||||
MapEmbeddableInput,
|
||||
} from '../../../../../../legacy/plugins/maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../plugins/maps/public/embeddable';
|
||||
import {
|
||||
RenderTooltipContentParams,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../plugins/maps/public/classes/tooltips/tooltip_property';
|
||||
import * as i18n from './translations';
|
||||
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
|
||||
import {
|
||||
|
|
|
@ -13,8 +13,10 @@ import {
|
|||
SUM_OF_SERVER_BYTES,
|
||||
SUM_OF_SOURCE_BYTES,
|
||||
} from '../map_config';
|
||||
import { ITooltipProperty } from '../../../../../../maps/public';
|
||||
import { TooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
import {
|
||||
ITooltipProperty,
|
||||
TooltipProperty,
|
||||
} from '../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
describe('LineToolTipContent', () => {
|
||||
const mockFeatureProps: ITooltipProperty[] = [
|
||||
|
|
|
@ -16,7 +16,8 @@ import {
|
|||
} from '../map_config';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
import { ITooltipProperty } from '../../../../../../maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
const FlowBadge = (styled(EuiBadge)`
|
||||
height: 45px;
|
||||
|
|
|
@ -17,7 +17,8 @@ import { LineToolTipContent } from './line_tool_tip_content';
|
|||
import { PointToolTipContent } from './point_tool_tip_content';
|
||||
import { Loader } from '../../../../common/components/loader';
|
||||
import * as i18n from '../translations';
|
||||
import { ITooltipProperty } from '../../../../../../maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
export const MapToolTipComponent = ({
|
||||
closeTooltip,
|
||||
|
|
|
@ -11,8 +11,10 @@ import { TestProviders } from '../../../../common/mock';
|
|||
import { getEmptyStringTag } from '../../../../common/components/empty_value';
|
||||
import { HostDetailsLink, IPDetailsLink } from '../../../../common/components/links';
|
||||
import { FlowTarget } from '../../../../graphql/types';
|
||||
import { ITooltipProperty } from '../../../../../../maps/public';
|
||||
import { TooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
import {
|
||||
TooltipProperty,
|
||||
ITooltipProperty,
|
||||
} from '../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
describe('PointToolTipContent', () => {
|
||||
const mockFeatureProps: ITooltipProperty[] = [
|
||||
|
|
|
@ -14,7 +14,8 @@ import { DescriptionListStyled } from '../../../../common/components/page';
|
|||
import { HostDetailsLink, IPDetailsLink } from '../../../../common/components/links';
|
||||
import { DefaultFieldRenderer } from '../../../../timelines/components/field_renderers/field_renderers';
|
||||
import { FlowTarget } from '../../../../graphql/types';
|
||||
import { ITooltipProperty } from '../../../../../../maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
interface PointToolTipContentProps {
|
||||
contextId: string;
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RenderTooltipContentParams } from '../../../../../../legacy/plugins/maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { RenderTooltipContentParams } from '../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
import { inputsModel } from '../../../common/store/inputs';
|
||||
|
||||
export interface IndexPatternMapping {
|
||||
|
|
|
@ -8971,10 +8971,7 @@
|
|||
"xpack.maps.addLayerPanel.footer.cancelButtonLabel": "キャンセル",
|
||||
"xpack.maps.addLayerPanel.importFile": "ファイルのインポート",
|
||||
"xpack.maps.aggs.defaultCountLabel": "カウント",
|
||||
"xpack.maps.appDescription": "マップアプリケーション",
|
||||
"xpack.maps.appTitle": "マップ",
|
||||
"xpack.maps.badge.readOnly.text": "読み込み専用",
|
||||
"xpack.maps.badge.readOnly.tooltip": "マップを保存できませんで",
|
||||
"xpack.maps.blendedVectorLayer.clusteredLayerName": "クラスター化 {displayName}",
|
||||
"xpack.maps.common.esSpatialRelation.containsLabel": "contains",
|
||||
"xpack.maps.common.esSpatialRelation.disjointLabel": "disjoint",
|
||||
|
@ -9105,7 +9102,6 @@
|
|||
"xpack.maps.mapController.saveMapDescription": "マップを保存",
|
||||
"xpack.maps.mapController.saveMapDisabledButtonTooltip": "保存する前に、レイヤーの変更を保存するか、キャンセルしてください",
|
||||
"xpack.maps.mapController.saveSuccessMessage": "「{title}」が保存されました",
|
||||
"xpack.maps.mapController.unsavedChangesWarning": "保存されていない変更は保存されない可能性があります",
|
||||
"xpack.maps.mapEmbeddableFactory.invalidLayerList": "不正な形式のレイヤーリストによりマップを読み込めません",
|
||||
"xpack.maps.mapEmbeddableFactory.invalidSavedObject": "不正な形式の保存済みオブジェクトによりマップを読み込めません",
|
||||
"xpack.maps.mapListing.advancedSettingsLinkText": "高度な設定",
|
||||
|
|
|
@ -8975,10 +8975,7 @@
|
|||
"xpack.maps.addLayerPanel.footer.cancelButtonLabel": "鍙栨秷",
|
||||
"xpack.maps.addLayerPanel.importFile": "导入文件",
|
||||
"xpack.maps.aggs.defaultCountLabel": "计数",
|
||||
"xpack.maps.appDescription": "地图应用程序",
|
||||
"xpack.maps.appTitle": "Maps",
|
||||
"xpack.maps.badge.readOnly.text": "只读",
|
||||
"xpack.maps.badge.readOnly.tooltip": "无法保存地图",
|
||||
"xpack.maps.blendedVectorLayer.clusteredLayerName": "集群 {displayName}",
|
||||
"xpack.maps.common.esSpatialRelation.containsLabel": "contains",
|
||||
"xpack.maps.common.esSpatialRelation.disjointLabel": "disjoint",
|
||||
|
@ -9109,7 +9106,6 @@
|
|||
"xpack.maps.mapController.saveMapDescription": "保存地图",
|
||||
"xpack.maps.mapController.saveMapDisabledButtonTooltip": "保存或在保存之前取消您的图层更改",
|
||||
"xpack.maps.mapController.saveSuccessMessage": "已保存“{title}”",
|
||||
"xpack.maps.mapController.unsavedChangesWarning": "可能不会保存您未保存的更改",
|
||||
"xpack.maps.mapEmbeddableFactory.invalidLayerList": "无法加载地图,图层列表格式不正确",
|
||||
"xpack.maps.mapEmbeddableFactory.invalidSavedObject": "无法加载地图,已保存对象格式错误",
|
||||
"xpack.maps.mapListing.advancedSettingsLinkText": "高级设置",
|
||||
|
|
|
@ -11,7 +11,8 @@ import { createPortalNode, InPortal, OutPortal } from 'react-reverse-portal';
|
|||
import {
|
||||
MapEmbeddable,
|
||||
MapEmbeddableInput,
|
||||
} from '../../../../../../../../legacy/plugins/maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../../maps/public/embeddable';
|
||||
import * as i18n from './translations';
|
||||
import { GeoPoint } from '../../../../../../common/runtime_types';
|
||||
import { getLayerList } from './map_config';
|
||||
|
@ -23,7 +24,8 @@ import {
|
|||
} from '../../../../../../../../../src/plugins/embeddable/public';
|
||||
import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../../maps/public';
|
||||
import { MapToolTipComponent } from './map_tool_tip';
|
||||
import { RenderTooltipContentParams } from '../../../../../../../../legacy/plugins/maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { RenderTooltipContentParams } from '../../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
|
||||
export interface EmbeddedMapProps {
|
||||
upPoints: LocationPoint[];
|
||||
|
|
|
@ -21,7 +21,8 @@ import { AppState } from '../../../../../state';
|
|||
import { monitorLocationsSelector } from '../../../../../state/selectors';
|
||||
import { useMonitorId } from '../../../../../hooks';
|
||||
import { MonitorLocation } from '../../../../../../common/runtime_types/monitor';
|
||||
import { RenderTooltipContentParams } from '../../../../../../../../legacy/plugins/maps/public';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { RenderTooltipContentParams } from '../../../../../../../maps/public/classes/tooltips/tooltip_property';
|
||||
import { formatAvailabilityValue } from '../../availability_reporting/availability_reporting';
|
||||
import { LastCheckLabel } from '../../translations';
|
||||
|
||||
|
|
|
@ -77,9 +77,9 @@ export default function ({ getPageObjects, getService }) {
|
|||
|
||||
it('should override query stored with map when query is provided in app state', async () => {
|
||||
const currentUrl = await browser.getCurrentUrl();
|
||||
const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('#'));
|
||||
const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('/maps/'));
|
||||
const appState = `_a=(query:(language:kuery,query:'machine.os.raw%20:%20"win%208"'))`;
|
||||
const urlWithQueryInAppState = `${kibanaBaseUrl}#/map/8eabdab0-144f-11e9-809f-ad25bb78262c?${appState}`;
|
||||
const urlWithQueryInAppState = `${kibanaBaseUrl}/maps/map/8eabdab0-144f-11e9-809f-ad25bb78262c#?${appState}`;
|
||||
|
||||
await browser.get(urlWithQueryInAppState, true);
|
||||
await PageObjects.maps.waitForLayersToLoad();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { APP_ID } from '../../../plugins/maps/common/constants';
|
||||
|
||||
export function GisPageProvider({ getService, getPageObjects }) {
|
||||
const PageObjects = getPageObjects(['common', 'header', 'timePicker']);
|
||||
|
@ -159,7 +160,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
|
|||
|
||||
async onMapListingPage() {
|
||||
log.debug(`onMapListingPage`);
|
||||
const exists = await testSubjects.exists('mapsListingPage');
|
||||
const exists = await testSubjects.exists('mapsListingPage', { timeout: 3500 });
|
||||
return exists;
|
||||
}
|
||||
|
||||
|
@ -197,7 +198,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
|
|||
const onPage = await this.onMapListingPage();
|
||||
if (!onPage) {
|
||||
await retry.try(async () => {
|
||||
await PageObjects.common.navigateToUrl('maps', '/', { basePath: this.basePath });
|
||||
await PageObjects.common.navigateToUrlWithBrowserHistory(APP_ID, '/');
|
||||
const onMapListingPage = await this.onMapListingPage();
|
||||
if (!onMapListingPage) throw new Error('Not on map listing page.');
|
||||
});
|
||||
|
@ -209,8 +210,8 @@ export function GisPageProvider({ getService, getPageObjects }) {
|
|||
|
||||
log.debug(`getMapCountWithName: ${name}`);
|
||||
await this.searchForMapWithName(name);
|
||||
const links = await find.allByLinkText(name);
|
||||
return links.length;
|
||||
const buttons = await find.allByButtonText(name);
|
||||
return buttons.length;
|
||||
}
|
||||
|
||||
async isSetViewPopoverOpen() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue