[Maps] Provide ability to create MapEmbeddable directly from a map configuration (#43878)

* [Maps] Provide ability to create MapEmbeddable directly from a map configuration

* call updateInput instead of passing modified input to MapEmbeddable constructor

* cleanup i18n translations

* update map embeddable documenation to reflect createFromState examples
This commit is contained in:
Nathan Reese 2019-08-26 15:32:51 -06:00 committed by GitHub
parent 98e9708d7e
commit 48e563f554
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 65 deletions

View file

@ -9,10 +9,10 @@ import { EMSTMSSource } from '../layers/sources/ems_tms_source';
import chrome from 'ui/chrome';
import { getKibanaTileMap } from '../meta';
export function getInitialLayers(savedMapLayerListJSON) {
export function getInitialLayers(layerListJSON) {
if (savedMapLayerListJSON) {
return JSON.parse(savedMapLayerListJSON);
if (layerListJSON) {
return JSON.parse(layerListJSON);
}
const tilemapSourceFromKibana = getKibanaTileMap();

View file

@ -0,0 +1,38 @@
### Map specific `input` parameters
- **hideFilterActions:** (Boolean) Set to true to hide all filtering controls.
- **isLayerTOCOpen:** (Boolean) Set to false to render map with legend in collapsed state.
- **openTOCDetails:** (Array of Strings) Array of layer ids. Add layer id to show layer details on initial render.
- **mapCenter:** ({lat, lon, zoom }) Provide mapCenter to customize initial map location.
### Creating a Map embeddable from saved object
```
const factory = new MapEmbeddableFactory();
const input = {
hideFilterActions: true,
isLayerTOCOpen: false,
openTOCDetails: ['tfi3f', 'edh66'],
mapCenter: { lat: 0.0, lon: 0.0, zoom: 7 }
}
const mapEmbeddable = await factory.createFromSavedObject(
'de71f4f0-1902-11e9-919b-ffe5949a18d2',
input,
parent
);
```
### Creating a Map embeddable from state
```
const factory = new MapEmbeddableFactory();
const state = {
layerList: [], // where layerList is same as saved object layerListJSON property (unstringified)
title: 'my map',
}
const input = {
hideFilterActions: true,
isLayerTOCOpen: false,
openTOCDetails: ['tfi3f', 'edh66'],
mapCenter: { lat: 0.0, lon: 0.0, zoom: 7 }
}
const mapEmbeddable = await factory.createFromState(state, input, parent);
```

View file

@ -18,7 +18,6 @@ import { I18nContext } from 'ui/i18n';
import { GisMap } from '../connected_components/gis_map';
import { createMapStore } from '../reducers/store';
import { getInitialLayers } from '../angular/get_initial_layers';
import {
setGotoWithCenter,
replaceLayerList,
@ -26,7 +25,6 @@ import {
setRefreshConfig,
disableScrollZoom,
} from '../actions/map_actions';
import { DEFAULT_IS_LAYER_TOC_OPEN } from '../reducers/ui';
import {
setReadOnly,
setIsLayerTOCOpen,
@ -47,11 +45,11 @@ export class MapEmbeddable extends Embeddable {
editUrl: config.editUrl,
indexPatterns: config.indexPatterns,
editable: config.editable,
defaultTitle: config.savedMap.title
defaultTitle: config.title
},
parent);
this._savedMap = config.savedMap;
this._layerList = config.layerList;
this._store = createMapStore();
this._subscription = this.getInput$().subscribe((input) => this.onContainerStateChanged(input));
@ -103,16 +101,10 @@ export class MapEmbeddable extends Embeddable {
if (_.has(this.input, 'isLayerTOCOpen')) {
this._store.dispatch(setIsLayerTOCOpen(this.input.isLayerTOCOpen));
} else if (this._savedMap.uiStateJSON) {
const uiState = JSON.parse(this._savedMap.uiStateJSON);
this._store.dispatch(setIsLayerTOCOpen(_.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN)));
}
if (_.has(this.input, 'openTOCDetails')) {
this._store.dispatch(setOpenTOCDetails(this.input.openTOCDetails));
} else if (this._savedMap.uiStateJSON) {
const uiState = JSON.parse(this._savedMap.uiStateJSON);
this._store.dispatch(setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])));
}
if (this.input.mapCenter) {
@ -121,16 +113,9 @@ export class MapEmbeddable extends Embeddable {
lon: this.input.mapCenter.lon,
zoom: this.input.mapCenter.zoom,
}));
} else if (this._savedMap.mapStateJSON) {
const mapState = JSON.parse(this._savedMap.mapStateJSON);
this._store.dispatch(setGotoWithCenter({
lat: mapState.center.lat,
lon: mapState.center.lon,
zoom: mapState.zoom,
}));
}
const layerList = getInitialLayers(this._savedMap.layerListJSON);
this._store.dispatch(replaceLayerList(layerList));
this._store.dispatch(replaceLayerList(this._layerList));
this._dispatchSetQuery(this.input);
this._dispatchSetRefreshConfig(this.input);
@ -162,7 +147,7 @@ export class MapEmbeddable extends Embeddable {
if (this._unsubscribeFromStore) {
this._unsubscribeFromStore();
}
this._savedMap.destroy();
if (this._domNode) {
unmountComponentAtNode(this._domNode);
}

View file

@ -20,6 +20,8 @@ import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/con
import { createMapStore } from '../reducers/store';
import { addLayerWithoutDataSync } from '../actions/map_actions';
import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors';
import { getInitialLayers } from '../angular/get_initial_layers';
import { mergeInputWithSavedMap } from './merge_input_with_saved_map';
import '../angular/services/gis_map_saved_object_loader';
import 'ui/vis/map/service_settings';
@ -50,19 +52,20 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
});
}
async _getIndexPatterns(layerListJSON) {
async _getIndexPatterns(layerList) {
// Need to extract layerList from store to get queryable index pattern ids
const store = createMapStore();
let queryableIndexPatternIds;
try {
JSON.parse(layerListJSON).forEach(layerDescriptor => {
layerList.forEach(layerDescriptor => {
store.dispatch(addLayerWithoutDataSync(layerDescriptor));
});
queryableIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState());
} catch (error) {
throw new Error(i18n.translate('xpack.maps.mapEmbeddableFactory', {
defaultMessage: 'Unable to load map, malformed saved object',
throw new Error(i18n.translate('xpack.maps.mapEmbeddableFactory.invalidLayerList', {
defaultMessage: 'Unable to load map, malformed layer list',
}));
}
const queryableIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState());
const promises = queryableIndexPatternIds.map(async (indexPatternId) => {
try {
@ -77,20 +80,25 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
return _.compact(indexPatterns);
}
async _fetchSavedMap(savedObjectId) {
const $injector = await chrome.dangerouslyGetActiveInjector();
const savedObjectLoader = $injector.get('gisMapSavedObjectLoader');
return await savedObjectLoader.get(savedObjectId);
}
async createFromSavedObject(
savedObjectId,
input,
parent
) {
const $injector = await chrome.dangerouslyGetActiveInjector();
const savedObjectLoader = $injector.get('gisMapSavedObjectLoader');
const savedMap = await this._fetchSavedMap(savedObjectId);
const layerList = getInitialLayers(savedMap.layerListJSON);
const indexPatterns = await this._getIndexPatterns(layerList);
const savedMap = await savedObjectLoader.get(savedObjectId);
const indexPatterns = await this._getIndexPatterns(savedMap.layerListJSON);
return new MapEmbeddable(
const embeddable = new MapEmbeddable(
{
savedMap,
layerList,
title: savedMap.title,
editUrl: chrome.addBasePath(createMapPath(savedObjectId)),
indexPatterns,
editable: this.isEditable(),
@ -98,11 +106,42 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
input,
parent
);
try {
embeddable.updateInput(mergeInputWithSavedMap(input, savedMap));
} catch (error) {
throw new Error(i18n.translate('xpack.maps.mapEmbeddableFactory.invalidSavedObject', {
defaultMessage: 'Unable to load map, malformed saved object',
}));
}
return embeddable;
}
async createFromState(
state,
input,
parent
) {
const layerList = state && state.layerList ? state.layerList : getInitialLayers();
const indexPatterns = await this._getIndexPatterns(layerList);
return new MapEmbeddable(
{
layerList,
title: state && state.title ? state.title : '',
editUrl: null,
indexPatterns,
editable: false,
},
input,
parent
);
}
async create(input) {
window.location.href = chrome.addBasePath(createMapPath(''));
return new ErrorEmbeddable('Maps can only be created from a saved object', input);
return new ErrorEmbeddable('Maps can only be created with createFromSavedObject or createFromState', input);
}
}

View file

@ -1,27 +0,0 @@
### MapEmbeddableFactory
Use to load map saved object and create MapEmbeddable instance.
### MapEmbeddable
Renders map saved object. State comes from saved object.
#### Customizable parameters
- **hideFilterActions:** (Boolean) Set to true to hide all filtering controls.
- **isLayerTOCOpen:** (Boolean) Set to false to render map with legend in collapsed state.
- **openTOCDetails:** (Array of Strings) Array of layer ids. Add layer id to show layer details on initial render.
- **mapCenter:** ({lat, lon, zoom }) Provide mapCenter to customize initial map location.
Customize embeddable state by providing properties for `MapEmbeddableFactory.createFromSavedObject` `input` parameter.
```
const factory = new MapEmbeddableFactory();
const mapEmbeddable = await factory.createFromSavedObject(
'de71f4f0-1902-11e9-919b-ffe5949a18d2',
{
hideFilterActions: true,
isLayerTOCOpen: false,
openTOCDetails: ['tfi3f', 'edh66'],
mapCenter: { lat: 0.0, lon: 0.0, zoom: 7 }
}
);
```

View file

@ -0,0 +1,42 @@
/*
* 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 { DEFAULT_IS_LAYER_TOC_OPEN } from '../reducers/ui';
const MAP_EMBEDDABLE_INPUT_KEYS = [
'hideFilterActions',
'isLayerTOCOpen',
'openTOCDetails',
'mapCenter'
];
export function mergeInputWithSavedMap(input, savedMap) {
const mergedInput = _.pick(input, MAP_EMBEDDABLE_INPUT_KEYS);
if (!_.has(input, 'isLayerTOCOpen') && savedMap.uiStateJSON) {
const uiState = JSON.parse(savedMap.uiStateJSON);
mergedInput.isLayerTOCOpen = _.get(uiState, 'isLayerTOCOpen', DEFAULT_IS_LAYER_TOC_OPEN);
}
if (!_.has(input, 'openTOCDetails') && savedMap.uiStateJSON) {
const uiState = JSON.parse(savedMap.uiStateJSON);
if (_.has(uiState, 'openTOCDetails')) {
mergedInput.openTOCDetails = _.get(uiState, 'openTOCDetails', []);
}
}
if (!input.mapCenter && savedMap.mapStateJSON) {
const mapState = JSON.parse(savedMap.mapStateJSON);
mergedInput.mapCenter = {
lat: mapState.center.lat,
lon: mapState.center.lon,
zoom: mapState.zoom,
};
}
return mergedInput;
}

View file

@ -5501,7 +5501,6 @@
"xpack.maps.mapController.saveMapButtonLabel": "保存",
"xpack.maps.mapController.saveMapDescription": "マップを保存",
"xpack.maps.mapController.saveSuccessMessage": "「{title}」が保存されました",
"xpack.maps.mapEmbeddableFactory": "マップを読み込めません。奇形の保存されたオブジェクト",
"xpack.maps.mapListing.advancedSettingsLinkText": "高度な設定",
"xpack.maps.mapListing.cancelTitle": "キャンセル",
"xpack.maps.mapListing.createMapButtonLabel": "マップを作成",

View file

@ -5644,7 +5644,6 @@
"xpack.maps.mapController.saveMapButtonLabel": "保存",
"xpack.maps.mapController.saveMapDescription": "保存地图",
"xpack.maps.mapController.saveSuccessMessage": "已保存“{title}”",
"xpack.maps.mapEmbeddableFactory": "无法加载地图,已保存对象格式错误",
"xpack.maps.mapListing.advancedSettingsLinkText": "高级设置",
"xpack.maps.mapListing.cancelTitle": "取消",
"xpack.maps.mapListing.createMapButtonLabel": "创建地图",