mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Maps] Do not wait for meta call to complete for bootstrapping the app (#29514)
This commit is contained in:
parent
cbefe90d50
commit
98a77ab9db
22 changed files with 453 additions and 199 deletions
|
@ -9,3 +9,5 @@ export const GIS_API_PATH = 'api/gis';
|
|||
export const DECIMAL_DEGREES_PRECISION = 5; // meters precision
|
||||
|
||||
export const ZOOM_PRECISION = 2;
|
||||
|
||||
export const DEFAULT_EMS_TILE_LAYER = 'road_map';
|
||||
|
|
|
@ -6,12 +6,25 @@
|
|||
import _ from 'lodash';
|
||||
import { KibanaTilemapSource } from '../shared/layers/sources/kibana_tilemap_source';
|
||||
import { EMSTMSSource } from '../shared/layers/sources/ems_tms_source';
|
||||
import { isMetaDataLoaded, getDataSourcesSync } from '../meta';
|
||||
import { DEFAULT_EMS_TILE_LAYER } from '../../common/constants';
|
||||
|
||||
export function getInitialLayers(savedMapLayerListJSON) {
|
||||
|
||||
export function getInitialLayers(savedMapLayerListJSON, dataSources) {
|
||||
if (savedMapLayerListJSON) {
|
||||
return JSON.parse(savedMapLayerListJSON);
|
||||
}
|
||||
|
||||
if (!isMetaDataLoaded()) {
|
||||
const descriptor = EMSTMSSource.createDescriptor(DEFAULT_EMS_TILE_LAYER);
|
||||
const source = new EMSTMSSource(descriptor);
|
||||
const layer = source.createDefaultLayer();
|
||||
return [
|
||||
layer.toLayerDescriptor()
|
||||
];
|
||||
}
|
||||
|
||||
const dataSources = getDataSourcesSync();
|
||||
const kibanaTilemapUrl = _.get(dataSources, 'kibana.tilemap.url');
|
||||
if (kibanaTilemapUrl) {
|
||||
const sourceDescriptor = KibanaTilemapSource.createDescriptor(kibanaTilemapUrl);
|
||||
|
@ -32,7 +45,5 @@ export function getInitialLayers(savedMapLayerListJSON, dataSources) {
|
|||
];
|
||||
}
|
||||
|
||||
// TODO display (or throw) warning that no tile layers are available and map.tilemap needs to be configured
|
||||
// because EMS is unreachable or has been turned off on purpose.
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('../meta', () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
import { getInitialLayers } from './get_initial_layers';
|
||||
|
||||
const mockKibanaDataSource = {
|
||||
|
@ -28,22 +32,65 @@ describe('Saved object has layer list', () => {
|
|||
}
|
||||
];
|
||||
const layerListJSON = JSON.stringify(layerListFromSavedObject);
|
||||
const dataSources = {
|
||||
kibana: mockKibanaDataSource,
|
||||
ems: mockEmsDataSource
|
||||
};
|
||||
expect(getInitialLayers(layerListJSON, dataSources)).toEqual(layerListFromSavedObject);
|
||||
expect((getInitialLayers(layerListJSON))).toEqual(layerListFromSavedObject);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('Saved object does not have layer list', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
require('../meta').isMetaDataLoaded = () => {
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
function mockDataSourceResponse(dataSources) {
|
||||
require('../meta').getDataSourcesSync = () => {
|
||||
return dataSources;
|
||||
};
|
||||
require('../meta').isMetaDataLoaded = () => {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
it('should get the default EMS layer when metadata has not loaded yet', () => {
|
||||
mockDataSourceResponse();
|
||||
require('../meta').isMetaDataLoaded = () => {
|
||||
return false;
|
||||
};
|
||||
const layers = getInitialLayers(null);
|
||||
expect(layers).toEqual([{
|
||||
"alpha": 1,
|
||||
"dataRequests": [],
|
||||
"id": layers[0].id,
|
||||
"label": null,
|
||||
"maxZoom": 24,
|
||||
"minZoom": 0,
|
||||
"sourceDescriptor": {
|
||||
"type": "EMS_TMS",
|
||||
"id": "road_map",
|
||||
},
|
||||
"style": {
|
||||
"properties": {},
|
||||
"type": "TILE",
|
||||
},
|
||||
"temporary": false,
|
||||
"type": "TILE",
|
||||
"visible": true,
|
||||
}]);
|
||||
});
|
||||
|
||||
|
||||
it('Should get initial layer from Kibana tilemap data source when Kibana tilemap is configured ', () => {
|
||||
const dataSources = {
|
||||
|
||||
mockDataSourceResponse({
|
||||
kibana: mockKibanaDataSource,
|
||||
ems: mockEmsDataSource
|
||||
};
|
||||
});
|
||||
|
||||
const layers = getInitialLayers(null, dataSources);
|
||||
const layers = getInitialLayers(null);
|
||||
expect(layers).toEqual([{
|
||||
"alpha": 1,
|
||||
dataRequests: [],
|
||||
|
@ -70,8 +117,9 @@ describe('Saved object does not have layer list', () => {
|
|||
const dataSources = {
|
||||
ems: mockEmsDataSource
|
||||
};
|
||||
mockDataSourceResponse(dataSources);
|
||||
|
||||
const layers = getInitialLayers(null, dataSources);
|
||||
const layers = getInitialLayers(null);
|
||||
expect(layers).toEqual([{
|
||||
"alpha": 1,
|
||||
dataRequests: [],
|
||||
|
@ -100,10 +148,12 @@ describe('Saved object does not have layer list', () => {
|
|||
tms: []
|
||||
}
|
||||
};
|
||||
expect(getInitialLayers(null, dataSources)).toEqual([]);
|
||||
mockDataSourceResponse(dataSources);
|
||||
expect((getInitialLayers(null))).toEqual([]);
|
||||
});
|
||||
|
||||
it('Should return empty list when no dataSoures are provided', () => {
|
||||
expect(getInitialLayers(null, null)).toEqual([]);
|
||||
mockDataSourceResponse(null);
|
||||
expect((getInitialLayers(null))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
setQuery,
|
||||
} from '../actions/store_actions';
|
||||
import { updateFlyout, FLYOUT_STATE } from '../store/ui';
|
||||
import { getDataSources, getUniqueIndexPatternIds } from '../selectors/map_selectors';
|
||||
import { getUniqueIndexPatternIds } from '../selectors/map_selectors';
|
||||
import { Inspector } from 'ui/inspector';
|
||||
import { inspectorAdapters, indexPatternService } from '../kibana_services';
|
||||
import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
|
||||
|
@ -139,7 +139,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage
|
|||
}));
|
||||
}
|
||||
|
||||
const layerList = getInitialLayers(savedMap.layerListJSON, getDataSources(store.getState()));
|
||||
const layerList = getInitialLayers(savedMap.layerListJSON);
|
||||
store.dispatch(replaceLayerList(layerList));
|
||||
|
||||
store.dispatch(setRefreshConfig($scope.refreshConfig));
|
||||
|
|
|
@ -8,7 +8,7 @@ import { connect } from 'react-redux';
|
|||
import { AddLayerPanel } from './view';
|
||||
import { getFlyoutDisplay, updateFlyout, FLYOUT_STATE }
|
||||
from '../../store/ui';
|
||||
import { getTemporaryLayers, getDataSources } from "../../selectors/map_selectors";
|
||||
import { getTemporaryLayers } from "../../selectors/map_selectors";
|
||||
import {
|
||||
addLayer,
|
||||
removeLayer,
|
||||
|
@ -19,14 +19,12 @@ import _ from 'lodash';
|
|||
|
||||
function mapStateToProps(state = {}) {
|
||||
|
||||
const dataSourceMeta = getDataSources(state);
|
||||
function isLoading() {
|
||||
const tmp = getTemporaryLayers(state);
|
||||
return tmp.some((layer) => layer.isLayerLoading());
|
||||
}
|
||||
return {
|
||||
flyoutVisible: getFlyoutDisplay(state) !== FLYOUT_STATE.NONE,
|
||||
dataSourcesMeta: dataSourceMeta,
|
||||
layerLoading: isLoading(),
|
||||
temporaryLayers: !_.isEmpty(getTemporaryLayers(state))
|
||||
};
|
||||
|
|
|
@ -108,8 +108,7 @@ export class AddLayerPanel extends Component {
|
|||
|
||||
_renderSourceEditor() {
|
||||
const editorProperties = {
|
||||
onPreviewSource: this._previewLayer,
|
||||
dataSourcesMeta: this.props.dataSourcesMeta
|
||||
onPreviewSource: this._previewLayer
|
||||
};
|
||||
|
||||
const Source = ALL_SOURCES.find((Source) => {
|
||||
|
|
60
x-pack/plugins/gis/public/meta.js
Normal file
60
x-pack/plugins/gis/public/meta.js
Normal file
|
@ -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 { GIS_API_PATH } from '../common/constants';
|
||||
import _ from 'lodash';
|
||||
|
||||
const GIS_API_RELATIVE = `../${GIS_API_PATH}`;
|
||||
|
||||
let meta = null;
|
||||
let isLoaded = false;
|
||||
export async function getDataSources() {
|
||||
if (!meta) {
|
||||
meta = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const meta = await fetch(`${GIS_API_RELATIVE}/meta`);
|
||||
const metaJson = await meta.json();
|
||||
isLoaded = true;
|
||||
resolve(metaJson.data_sources);
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
export function getDataSourcesSync() {
|
||||
if (!isLoaded) {
|
||||
throw new Error('Metadata is not loaded yet. Use isMetadataLoaded first before calling this function.');
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
export function isMetaDataLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
export async function getEmsVectorFilesMeta() {
|
||||
const dataSource = await getDataSources();
|
||||
return _.get(dataSource, 'ems.file', []);
|
||||
}
|
||||
|
||||
export async function getEmsTMSServices() {
|
||||
const dataSource = await getDataSources();
|
||||
return _.get(dataSource, 'ems.tms', []);
|
||||
}
|
||||
|
||||
export async function getKibanaRegionList() {
|
||||
const dataSource = await getDataSources();
|
||||
return _.get(dataSource, 'kibana.regionmap', []);
|
||||
}
|
||||
|
||||
export async function getKibanaTileMap() {
|
||||
const dataSource = await getDataSources();
|
||||
return _.get(dataSource, 'kibana.tilemap', []);
|
||||
}
|
|
@ -15,8 +15,8 @@ import { HeatmapStyle } from '../shared/layers/styles/heatmap_style';
|
|||
import { TileStyle } from '../shared/layers/styles/tile_style';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
function createLayerInstance(layerDescriptor, dataSources) {
|
||||
const source = createSourceInstance(layerDescriptor.sourceDescriptor, dataSources);
|
||||
function createLayerInstance(layerDescriptor) {
|
||||
const source = createSourceInstance(layerDescriptor.sourceDescriptor);
|
||||
const style = createStyleInstance(layerDescriptor.style);
|
||||
switch (layerDescriptor.type) {
|
||||
case TileLayer.type:
|
||||
|
@ -30,21 +30,14 @@ function createLayerInstance(layerDescriptor, dataSources) {
|
|||
}
|
||||
}
|
||||
|
||||
function createSourceInstance(sourceDescriptor, dataSources) {
|
||||
|
||||
const dataMeta = {
|
||||
emsTmsServices: _.get(dataSources, 'ems.tms', []),
|
||||
emsFileLayers: _.get(dataSources, 'ems.file', []),
|
||||
ymlFileLayers: _.get(dataSources, 'kibana.regionmap', [])
|
||||
};
|
||||
|
||||
function createSourceInstance(sourceDescriptor) {
|
||||
const Source = ALL_SOURCES.find(Source => {
|
||||
return Source.type === sourceDescriptor.type;
|
||||
});
|
||||
if (!Source) {
|
||||
throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`);
|
||||
}
|
||||
return new Source(sourceDescriptor, dataMeta);
|
||||
return new Source(sourceDescriptor);
|
||||
}
|
||||
|
||||
|
||||
|
@ -66,8 +59,6 @@ function createStyleInstance(styleDescriptor) {
|
|||
}
|
||||
}
|
||||
|
||||
export const getMapState = ({ map }) => map && map.mapState;
|
||||
|
||||
export const getMapReady = ({ map }) => map && map.ready;
|
||||
|
||||
export const getGoto = ({ map }) => map && map.goto;
|
||||
|
@ -137,14 +128,12 @@ export const getDataFilters = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const getDataSources = createSelector(getMetadata, metadata => metadata ? metadata.data_sources : null);
|
||||
|
||||
export const getLayerList = createSelector(
|
||||
getLayerListRaw,
|
||||
getDataSources,
|
||||
(layerDescriptorList, dataSources) => {
|
||||
(layerDescriptorList) => {
|
||||
return layerDescriptorList.map(layerDescriptor =>
|
||||
createLayerInstance(layerDescriptor, dataSources));
|
||||
createLayerInstance(layerDescriptor));
|
||||
});
|
||||
|
||||
export const getSelectedLayer = createSelector(
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiSelect,
|
||||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { getEmsVectorFilesMeta } from '../../../../meta';
|
||||
|
||||
|
||||
export class EMSFileCreateSourceEditor extends React.Component {
|
||||
|
||||
|
||||
state = {
|
||||
emsFileOptionsRaw: null
|
||||
};
|
||||
|
||||
_loadFileOptions = async () => {
|
||||
const options = await getEmsVectorFilesMeta();
|
||||
if (this._isMounted) {
|
||||
this.setState({
|
||||
emsFileOptionsRaw: options
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadFileOptions();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (!this.state.emsFileOptionsRaw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const emsVectorOptions = this.state.emsFileOptionsRaw ? this.state.emsFileOptionsRaw.map((file) => ({
|
||||
value: file.id,
|
||||
text: file.name
|
||||
})) : [];
|
||||
|
||||
|
||||
return (
|
||||
<EuiFormRow label="Layer">
|
||||
<EuiSelect
|
||||
hasNoInitialSelection
|
||||
options={emsVectorOptions}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,15 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AbstractVectorSource } from './vector_source';
|
||||
import { AbstractVectorSource } from '../vector_source';
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiSelect,
|
||||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { GIS_API_PATH } from '../../../../common/constants';
|
||||
import { emsServiceSettings } from '../../../kibana_services';
|
||||
import { GIS_API_PATH } from '../../../../../common/constants';
|
||||
import { emsServiceSettings } from '../../../../kibana_services';
|
||||
import { getEmsVectorFilesMeta } from '../../../../meta';
|
||||
import { EMSFileCreateSourceEditor } from './create_source_editor';
|
||||
|
||||
export class EMSFileSource extends AbstractVectorSource {
|
||||
|
||||
|
@ -28,38 +25,23 @@ export class EMSFileSource extends AbstractVectorSource {
|
|||
};
|
||||
}
|
||||
|
||||
static renderEditor({ dataSourcesMeta, onPreviewSource }) {
|
||||
|
||||
const emsVectorOptionsRaw = (dataSourcesMeta) ? dataSourcesMeta.ems.file : [];
|
||||
const emsVectorOptions = emsVectorOptionsRaw ? emsVectorOptionsRaw.map((file) => ({
|
||||
value: file.id,
|
||||
text: file.name
|
||||
})) : [];
|
||||
|
||||
static renderEditor({ onPreviewSource }) {
|
||||
const onChange = ({ target }) => {
|
||||
const selectedId = target.options[target.selectedIndex].value;
|
||||
const emsFileSourceDescriptor = EMSFileSource.createDescriptor(selectedId);
|
||||
const emsFileSource = new EMSFileSource(emsFileSourceDescriptor, emsVectorOptionsRaw);
|
||||
const emsFileSource = new EMSFileSource(emsFileSourceDescriptor);
|
||||
onPreviewSource(emsFileSource);
|
||||
};
|
||||
return (
|
||||
<EuiFormRow label="Layer">
|
||||
<EuiSelect
|
||||
hasNoInitialSelection
|
||||
options={emsVectorOptions}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
return <EMSFileCreateSourceEditor onChange={onChange}/>;
|
||||
}
|
||||
|
||||
constructor(descriptor, { emsFileLayers }) {
|
||||
constructor(descriptor) {
|
||||
super(descriptor);
|
||||
this._emsFiles = emsFileLayers;
|
||||
}
|
||||
|
||||
async getGeoJsonWithMeta() {
|
||||
const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id));
|
||||
const emsFiles = await getEmsVectorFilesMeta();
|
||||
const fileSource = emsFiles.find((source => source.id === this._descriptor.id));
|
||||
const fetchUrl = `../${GIS_API_PATH}/data/ems?id=${encodeURIComponent(this._descriptor.id)}`;
|
||||
const featureCollection = await AbstractVectorSource.getGeoJson(fileSource, fetchUrl);
|
||||
return {
|
||||
|
@ -77,24 +59,24 @@ export class EMSFileSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getDisplayName() {
|
||||
const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id));
|
||||
const emsFiles = await getEmsVectorFilesMeta();
|
||||
const fileSource = emsFiles.find((source => source.id === this._descriptor.id));
|
||||
return fileSource.name;
|
||||
}
|
||||
|
||||
async getAttributions() {
|
||||
const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id));
|
||||
const emsFiles = await getEmsVectorFilesMeta();
|
||||
const fileSource = emsFiles.find((source => source.id === this._descriptor.id));
|
||||
return fileSource.attributions;
|
||||
}
|
||||
|
||||
|
||||
async getStringFields() {
|
||||
//todo: use map/service-settings instead.
|
||||
const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id));
|
||||
|
||||
const emsFiles = await getEmsVectorFilesMeta();
|
||||
const fileSource = emsFiles.find((source => source.id === this._descriptor.id));
|
||||
return fileSource.fields.map(f => {
|
||||
return { name: f.name, label: f.description };
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
canFormatFeatureProperties() {
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { EMSFileSource } from './ems_file_source';
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiSelect,
|
||||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { getEmsTMSServices } from '../../../../meta';
|
||||
|
||||
|
||||
export class EMSTMSCreateSourceEditor extends React.Component {
|
||||
|
||||
|
||||
state = {
|
||||
emsTmsOptionsRaw: null
|
||||
};
|
||||
|
||||
_loadTmsOptions = async () => {
|
||||
const options = await getEmsTMSServices();
|
||||
if (this._isMounted) {
|
||||
this.setState({
|
||||
emsTmsOptionsRaw: options
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadTmsOptions();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (!this.state.emsTmsOptionsRaw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const emsTileOptions = this.state.emsTmsOptionsRaw.map((service) => ({
|
||||
value: service.id,
|
||||
text: service.id //due to not having human readable names
|
||||
}));
|
||||
|
||||
|
||||
return (
|
||||
<EuiFormRow label="Tile service">
|
||||
<EuiSelect
|
||||
hasNoInitialSelection
|
||||
options={emsTileOptions}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -6,11 +6,9 @@
|
|||
import React from 'react';
|
||||
import { AbstractTMSSource } from '../tms_source';
|
||||
import { TileLayer } from '../../tile_layer';
|
||||
import {
|
||||
EuiSelect,
|
||||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getEmsTMSServices } from '../../../../meta';
|
||||
import { EMSTMSCreateSourceEditor } from './create_source_editor';
|
||||
|
||||
|
||||
export class EMSTMSSource extends AbstractTMSSource {
|
||||
|
@ -27,34 +25,20 @@ export class EMSTMSSource extends AbstractTMSSource {
|
|||
};
|
||||
}
|
||||
|
||||
static renderEditor({ dataSourcesMeta, onPreviewSource }) {
|
||||
|
||||
const emsTmsOptionsRaw = _.get(dataSourcesMeta, "ems.tms", []);
|
||||
const emsTileOptions = emsTmsOptionsRaw.map((service) => ({
|
||||
value: service.id,
|
||||
text: service.id //due to not having human readable names
|
||||
}));
|
||||
static renderEditor({ onPreviewSource }) {
|
||||
|
||||
const onChange = ({ target }) => {
|
||||
const selectedId = target.options[target.selectedIndex].value;
|
||||
const emsTMSSourceDescriptor = EMSTMSSource.createDescriptor(selectedId);
|
||||
const emsTMSSource = new EMSTMSSource(emsTMSSourceDescriptor, emsTmsOptionsRaw);
|
||||
const emsTMSSource = new EMSTMSSource(emsTMSSourceDescriptor);
|
||||
onPreviewSource(emsTMSSource);
|
||||
};
|
||||
return (
|
||||
<EuiFormRow label="Tile service">
|
||||
<EuiSelect
|
||||
hasNoInitialSelection
|
||||
options={emsTileOptions}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
||||
return <EMSTMSCreateSourceEditor onChange={onChange}/>;
|
||||
}
|
||||
|
||||
constructor(descriptor, { emsTmsServices }) {
|
||||
constructor(descriptor) {
|
||||
super(descriptor);
|
||||
this._emsTileServices = emsTmsServices;
|
||||
}
|
||||
|
||||
async getImmutableProperties() {
|
||||
|
@ -64,12 +48,13 @@ export class EMSTMSSource extends AbstractTMSSource {
|
|||
];
|
||||
}
|
||||
|
||||
_getTMSOptions() {
|
||||
if(!this._emsTileServices) {
|
||||
async _getTMSOptions() {
|
||||
const emsTileServices = await getEmsTMSServices();
|
||||
if(!emsTileServices) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this._emsTileServices.find(service => {
|
||||
return emsTileServices.find(service => {
|
||||
return service.id === this._descriptor.id;
|
||||
});
|
||||
}
|
||||
|
@ -93,7 +78,7 @@ export class EMSTMSSource extends AbstractTMSSource {
|
|||
}
|
||||
|
||||
async getAttributions() {
|
||||
const service = this._getTMSOptions();
|
||||
const service = await this._getTMSOptions();
|
||||
if (!service || !service.attributionMarkdown) {
|
||||
return [];
|
||||
}
|
||||
|
@ -110,10 +95,10 @@ export class EMSTMSSource extends AbstractTMSSource {
|
|||
});
|
||||
}
|
||||
|
||||
getUrlTemplate() {
|
||||
const service = this._getTMSOptions();
|
||||
async getUrlTemplate() {
|
||||
const service = await this._getTMSOptions();
|
||||
if (!service || !service.url) {
|
||||
throw new Error('Can not generate EMS TMS url template');
|
||||
throw new Error('Cannot generate EMS TMS url template');
|
||||
}
|
||||
return service.url;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,21 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('../../../../meta', () => {
|
||||
return {
|
||||
getEmsTMSServices: async () => {
|
||||
return [
|
||||
{
|
||||
id: 'road_map',
|
||||
attributionMarkdown: '[foobar](http://foobar.org) | [foobaz](http://foobaz.org)'
|
||||
}, {
|
||||
id: 'satellite',
|
||||
attributionMarkdown: '[satellite](http://satellite.org)'
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
import {
|
||||
EMSTMSSource,
|
||||
|
@ -15,21 +30,10 @@ describe('EMSTMSSource', () => {
|
|||
|
||||
const emsTmsSource = new EMSTMSSource({
|
||||
id: 'road_map'
|
||||
}, {
|
||||
emsTmsServices: [
|
||||
{
|
||||
id: 'road_map',
|
||||
attributionMarkdown: '[foobar](http://foobar.org) | [foobaz](http://foobaz.org)'
|
||||
}, {
|
||||
id: 'satellite',
|
||||
attributionMarkdown: '[satellite](http://satellite.org)'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
const attributions = await emsTmsSource.getAttributions();
|
||||
|
||||
expect(attributions).toEqual([
|
||||
{
|
||||
label: 'foobar',
|
||||
|
|
|
@ -10,48 +10,70 @@ import {
|
|||
EuiSelect,
|
||||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
import { getKibanaRegionList } from '../../../../meta';
|
||||
|
||||
const NO_REGIONMAP_LAYERS_MSG =
|
||||
'No vector layers are available.' +
|
||||
' Ask your system administrator to set "map.regionmap" in kibana.yml.';
|
||||
|
||||
export function CreateSourceEditor({ onSelect, regionmapLayers }) {
|
||||
export class CreateSourceEditor extends React.Component {
|
||||
|
||||
const regionmapOptions = regionmapLayers.map(({ name, url }) => {
|
||||
return {
|
||||
value: url,
|
||||
text: name
|
||||
};
|
||||
});
|
||||
state = {
|
||||
regionmapLayers: []
|
||||
}
|
||||
|
||||
const onChange = ({ target }) => {
|
||||
const selectedName = target.options[target.selectedIndex].text;
|
||||
onSelect({ name: selectedName });
|
||||
_loadList = async () => {
|
||||
const list = await getKibanaRegionList();
|
||||
if (this._isMounted) {
|
||||
this.setState({
|
||||
regionmapLayers: list
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label="Vector layer"
|
||||
helpText={regionmapLayers.length === 0 ? NO_REGIONMAP_LAYERS_MSG : null}
|
||||
>
|
||||
<EuiSelect
|
||||
hasNoInitialSelection
|
||||
options={regionmapOptions}
|
||||
onChange={onChange}
|
||||
disabled={regionmapLayers.length === 0}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadList();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
|
||||
|
||||
const onChange = ({ target }) => {
|
||||
const selectedName = target.options[target.selectedIndex].text;
|
||||
this.props.onSelect({ name: selectedName });
|
||||
};
|
||||
|
||||
const regionmapOptions = this.state.regionmapLayers.map(({ name, url }) => {
|
||||
return {
|
||||
value: url,
|
||||
text: name
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label="Vector layer"
|
||||
helpText={this.state.regionmapLayers.length === 0 ? NO_REGIONMAP_LAYERS_MSG : null}
|
||||
>
|
||||
<EuiSelect
|
||||
hasNoInitialSelection
|
||||
options={regionmapOptions}
|
||||
onChange={onChange}
|
||||
disabled={this.state.regionmapLayers.length === 0}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CreateSourceEditor.propTypes = {
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
regionmapLayers: PropTypes.arrayOf(PropTypes.shape({
|
||||
url: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
})),
|
||||
onSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
CreateSourceEditor.defaultProps = {
|
||||
regionmapLayers: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { AbstractVectorSource } from '../vector_source';
|
||||
import React from 'react';
|
||||
import { CreateSourceEditor } from './create_source_editor';
|
||||
|
||||
import { getKibanaRegionList } from '../../../../meta';
|
||||
|
||||
export class KibanaRegionmapSource extends AbstractVectorSource {
|
||||
|
||||
static type = 'REGIONMAP_FILE';
|
||||
|
@ -16,9 +17,8 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
static description = 'Vector shapes from static files configured in kibana.yml';
|
||||
static icon = 'logoKibana';
|
||||
|
||||
constructor(descriptor, { ymlFileLayers }) {
|
||||
constructor(descriptor) {
|
||||
super(descriptor);
|
||||
this._regionList = ymlFileLayers;
|
||||
}
|
||||
|
||||
static createDescriptor(options) {
|
||||
|
@ -28,19 +28,16 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
};
|
||||
}
|
||||
|
||||
static renderEditor = ({ dataSourcesMeta, onPreviewSource }) => {
|
||||
const regionmapLayers = _.get(dataSourcesMeta, 'kibana.regionmap', []);
|
||||
|
||||
static renderEditor = ({ onPreviewSource }) => {
|
||||
const onSelect = (layerConfig) => {
|
||||
const sourceDescriptor = KibanaRegionmapSource.createDescriptor(layerConfig);
|
||||
const source = new KibanaRegionmapSource(sourceDescriptor, { ymlFileLayers: regionmapLayers });
|
||||
const source = new KibanaRegionmapSource(sourceDescriptor);
|
||||
onPreviewSource(source);
|
||||
};
|
||||
|
||||
return (
|
||||
<CreateSourceEditor
|
||||
onSelect={onSelect}
|
||||
regionmapLayers={regionmapLayers}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -53,7 +50,8 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getGeoJsonWithMeta() {
|
||||
const fileSource = this._regionList.find(source => source.name === this._descriptor.name);
|
||||
const regionList = await getKibanaRegionList();
|
||||
const fileSource = regionList.find(source => source.name === this._descriptor.name);
|
||||
const featureCollection = await AbstractVectorSource.getGeoJson(fileSource, fileSource.url);
|
||||
return {
|
||||
data: featureCollection
|
||||
|
@ -61,8 +59,8 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
|
|||
}
|
||||
|
||||
async getStringFields() {
|
||||
//todo: use map/service-settings instead.
|
||||
const fileSource = this._regionList.find((source => source.name === this._descriptor.name));
|
||||
const regionList = await getKibanaRegionList();
|
||||
const fileSource = regionList.find((source => source.name === this._descriptor.name));
|
||||
|
||||
return fileSource.fields.map(f => {
|
||||
return { name: f.name, label: f.description };
|
||||
|
|
|
@ -11,27 +11,54 @@ import {
|
|||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { getKibanaTileMap } from '../../../../meta';
|
||||
|
||||
const NO_TILEMAP_LAYER_MSG =
|
||||
'No tilemap layer is available.' +
|
||||
' Ask your system administrator to set "map.tilemap.url" in kibana.yml.';
|
||||
|
||||
|
||||
|
||||
export class CreateSourceEditor extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.url) {
|
||||
|
||||
state = {
|
||||
url: null
|
||||
}
|
||||
|
||||
_loadUrl = async () => {
|
||||
const tilemap = await getKibanaTileMap();
|
||||
if (this._isMounted) {
|
||||
this.setState({
|
||||
url: tilemap.url
|
||||
});
|
||||
this.props.previewTilemap(this.props.url);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadUrl();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (this.state.url === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label="Tilemap url"
|
||||
helpText={this.props.url ? null : NO_TILEMAP_LAYER_MSG}
|
||||
helpText={this.state.url ? null : NO_TILEMAP_LAYER_MSG}
|
||||
>
|
||||
<EuiFieldText
|
||||
readOnly
|
||||
value={this.props.url}
|
||||
value={this.state.url}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
@ -39,6 +66,5 @@ export class CreateSourceEditor extends Component {
|
|||
}
|
||||
|
||||
CreateSourceEditor.propTypes = {
|
||||
previewTilemap: PropTypes.func.isRequired,
|
||||
url: PropTypes.string,
|
||||
previewTilemap: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@ import React from 'react';
|
|||
import { AbstractTMSSource } from '../tms_source';
|
||||
import { TileLayer } from '../../tile_layer';
|
||||
import { CreateSourceEditor } from './create_source_editor';
|
||||
|
||||
export class KibanaTilemapSource extends AbstractTMSSource {
|
||||
|
||||
static type = 'KIBANA_TILEMAP';
|
||||
|
@ -22,20 +21,19 @@ export class KibanaTilemapSource extends AbstractTMSSource {
|
|||
};
|
||||
}
|
||||
|
||||
static renderEditor = ({ dataSourcesMeta, onPreviewSource }) => {
|
||||
const { url } = dataSourcesMeta ? dataSourcesMeta.kibana.tilemap : {};
|
||||
static renderEditor = ({ onPreviewSource }) => {
|
||||
const previewTilemap = (urlTemplate) => {
|
||||
const sourceDescriptor = KibanaTilemapSource.createDescriptor(urlTemplate);
|
||||
const source = new KibanaTilemapSource(sourceDescriptor);
|
||||
onPreviewSource(source);
|
||||
};
|
||||
return (<CreateSourceEditor previewTilemap={previewTilemap} url={url} />);
|
||||
return (<CreateSourceEditor previewTilemap={previewTilemap} />);
|
||||
};
|
||||
|
||||
async getImmutableProperties() {
|
||||
return [
|
||||
{ label: 'Data source', value: KibanaTilemapSource.title },
|
||||
{ label: 'Tilemap url', value: this.getUrlTemplate() },
|
||||
{ label: 'Tilemap url', value: (await this.getUrlTemplate()) },
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -54,11 +52,11 @@ export class KibanaTilemapSource extends AbstractTMSSource {
|
|||
}
|
||||
|
||||
|
||||
getUrlTemplate() {
|
||||
async getUrlTemplate() {
|
||||
return this._descriptor.url;
|
||||
}
|
||||
|
||||
async getDisplayName() {
|
||||
return this.getUrlTemplate();
|
||||
return await this.getUrlTemplate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { AbstractSource } from './source';
|
||||
|
||||
export class AbstractTMSSource extends AbstractSource {
|
||||
getUrlTemplate() {
|
||||
async getUrlTemplate() {
|
||||
throw new Error('Should implement TMSSource#getUrlTemplate');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,23 @@ export class TileLayer extends AbstractLayer {
|
|||
});
|
||||
}
|
||||
|
||||
async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) {
|
||||
if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) {
|
||||
return;
|
||||
}
|
||||
const sourceDataId = 'source';
|
||||
const requestToken = Symbol(`layer-source-refresh:${ this.getId()} - source`);
|
||||
startLoading(sourceDataId, requestToken, dataFilters);
|
||||
try {
|
||||
const url = await this._source.getUrlTemplate();
|
||||
stopLoading(sourceDataId, requestToken, url, {});
|
||||
} catch(error) {
|
||||
onLoadError(sourceDataId, requestToken, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async syncLayerWithMB(mbMap) {
|
||||
|
||||
const source = mbMap.getSource(this.getId());
|
||||
const mbLayerId = this.getId() + '_raster';
|
||||
|
||||
|
@ -74,7 +90,12 @@ export class TileLayer extends AbstractLayer {
|
|||
return;
|
||||
}
|
||||
|
||||
const url = this._source.getUrlTemplate();
|
||||
const sourceDataRquest = this.getSourceDataRequest();
|
||||
const url = sourceDataRquest.getData();
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceId = this.getId();
|
||||
mbMap.addSource(sourceId, {
|
||||
type: 'raster',
|
||||
|
|
|
@ -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 { SET_META } from '../actions/store_actions';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
meta: null
|
||||
};
|
||||
|
||||
// Reducer
|
||||
function config(state = INITIAL_STATE, action) {
|
||||
switch (action.type) {
|
||||
case SET_META:
|
||||
return { ...state, meta: action.meta };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default config;
|
|
@ -8,16 +8,13 @@ import { combineReducers, applyMiddleware, createStore, compose } from 'redux';
|
|||
import thunk from 'redux-thunk';
|
||||
import ui from './ui';
|
||||
import { map } from './map';
|
||||
import { loadMetaResources } from "../actions/store_actions";
|
||||
import config from './config';
|
||||
|
||||
// TODO this should not be exported and all access to the store be via getStore
|
||||
export let store;
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
map,
|
||||
ui,
|
||||
config
|
||||
ui
|
||||
});
|
||||
|
||||
const enhancers = [ applyMiddleware(thunk) ];
|
||||
|
@ -36,6 +33,5 @@ export const getStore = async function () {
|
|||
storeConfig,
|
||||
composeEnhancers(...enhancers)
|
||||
);
|
||||
await loadMetaResources(store.dispatch);
|
||||
return store;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue