From e881d15cb1e595bf7c7b8e210ea57ffd6da18569 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 29 Jun 2020 15:12:09 -0600 Subject: [PATCH] [Maps] choropleth layer wizard (#69699) * [Maps] choropleth layer wizard * add boundaries radio group * geo_index_pattern_select * consolidate more logic into geo_index_pattern_select * small clean-up * left geo field and join field * move EuiPanel into render wizard * cleanup * right panel * createEmsChoroplethLayerDescriptor * createEsChoroplethLayerDescriptor * i18n cleanup * tslint * snapshot update * review feedback * review feedback * update snapshot * make EMS default source * tslint Co-authored-by: Elastic Machine --- .../layer_template.test.tsx.snap | 107 ++++ .../choropleth_layer_wizard.tsx | 25 + .../create_choropleth_layer_descriptor.ts | 144 ++++++ .../layers/choropleth_layer_wizard/index.ts | 7 + .../layer_template.test.tsx | 43 ++ .../layer_template.tsx | 459 ++++++++++++++++++ .../classes/layers/load_layer_wizards.ts | 2 + .../observability_layer_template.tsx | 7 +- .../security/security_layer_template.tsx | 7 +- .../create_client_file_source_editor.tsx | 21 +- .../ems_file_source/create_source_editor.tsx | 80 +-- .../ems_tms_source/tile_service_select.js | 40 +- .../create_source_editor.js | 131 +---- .../es_pew_pew_source/create_source_editor.js | 6 +- .../__snapshots__/scaling_form.test.tsx.snap | 2 + .../es_search_source/create_source_editor.js | 143 ++---- .../es_documents_layer_wizard.tsx | 2 +- .../es_search_source/scaling_form.test.tsx | 1 + .../sources/es_search_source/scaling_form.tsx | 6 +- .../create_source_editor.js | 30 +- .../create_source_editor.js | 34 +- .../mvt_single_layer_vector_source_editor.tsx | 8 +- .../wms_source/wms_create_source_editor.js | 6 +- .../sources/xyz_tms_source/xyz_tms_editor.tsx | 8 +- .../geo_index_pattern_select.test.tsx.snap | 104 ++++ .../public/components/ems_file_select.tsx | 105 ++++ .../ems_unavailable_message.tsx} | 2 +- .../geo_index_pattern_select.test.tsx | 42 ++ .../components/geo_index_pattern_select.tsx | 139 ++++++ .../components/no_index_pattern_callout.js | 52 -- .../public/components/single_field_select.tsx | 2 +- .../flyout_body/flyout_body.tsx | 4 +- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 34 files changed, 1337 insertions(+), 440 deletions(-) create mode 100644 x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/__snapshots__/layer_template.test.tsx.snap create mode 100644 x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/choropleth_layer_wizard.tsx create mode 100644 x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts create mode 100644 x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/index.ts create mode 100644 x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.test.tsx create mode 100644 x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx create mode 100644 x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap create mode 100644 x-pack/plugins/maps/public/components/ems_file_select.tsx rename x-pack/plugins/maps/public/{classes/sources/ems_unavailable_message.ts => components/ems_unavailable_message.tsx} (93%) create mode 100644 x-pack/plugins/maps/public/components/geo_index_pattern_select.test.tsx create mode 100644 x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx delete mode 100644 x-pack/plugins/maps/public/components/no_index_pattern_callout.js diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/__snapshots__/layer_template.test.tsx.snap b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/__snapshots__/layer_template.test.tsx.snap new file mode 100644 index 0000000000000..3a301a951ed57 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/__snapshots__/layer_template.test.tsx.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render EMS UI when left source is BOUNDARIES_SOURCE.EMS 1`] = ` + + + +
+ +
+
+ + + + + +
+ +
+`; + +exports[`should render elasticsearch UI when left source is BOUNDARIES_SOURCE.ELASTICSEARCH 1`] = ` + + + +
+ +
+
+ + + + + +
+ +
+`; diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/choropleth_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/choropleth_layer_wizard.tsx new file mode 100644 index 0000000000000..6e806f4530df2 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/choropleth_layer_wizard.tsx @@ -0,0 +1,25 @@ +/* + * 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 { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; +import { LayerWizard, RenderWizardArguments } from '../layer_wizard_registry'; +import { LayerTemplate } from './layer_template'; + +export const choroplethLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], + description: i18n.translate('xpack.maps.choropleth.desc', { + defaultMessage: 'Shaded areas to compare statistics across boundaries', + }), + icon: 'logoElasticsearch', + renderWizard: (renderWizardArguments: RenderWizardArguments) => { + return ; + }, + title: i18n.translate('xpack.maps.choropleth.title', { + defaultMessage: 'Choropleth', + }), +}; diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts new file mode 100644 index 0000000000000..61fb6ef54c207 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts @@ -0,0 +1,144 @@ +/* + * 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 uuid from 'uuid/v4'; +import { + AGG_TYPE, + COLOR_MAP_TYPE, + FIELD_ORIGIN, + SCALING_TYPES, + SOURCE_TYPES, + STYLE_TYPE, + VECTOR_STYLES, +} from '../../../../common/constants'; +import { getJoinAggKey } from '../../../../common/get_agg_key'; +import { + AggDescriptor, + ColorDynamicOptions, + EMSFileSourceDescriptor, + ESSearchSourceDescriptor, +} from '../../../../common/descriptor_types'; +import { VectorStyle } from '../../styles/vector/vector_style'; +import { VectorLayer } from '../vector_layer/vector_layer'; +import { EMSFileSource } from '../../sources/ems_file_source'; +// @ts-ignore +import { ESSearchSource } from '../../sources/es_search_source'; +import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; + +const defaultDynamicProperties = getDefaultDynamicProperties(); + +function createChoroplethLayerDescriptor({ + sourceDescriptor, + leftField, + rightIndexPatternId, + rightIndexPatternTitle, + rightTermField, +}: { + sourceDescriptor: EMSFileSourceDescriptor | ESSearchSourceDescriptor; + leftField: string; + rightIndexPatternId: string; + rightIndexPatternTitle: string; + rightTermField: string; +}) { + const metricsDescriptor: AggDescriptor = { type: AGG_TYPE.COUNT }; + const joinId = uuid(); + const joinKey = getJoinAggKey({ + aggType: metricsDescriptor.type, + aggFieldName: metricsDescriptor.field ? metricsDescriptor.field : '', + rightSourceId: joinId, + }); + return VectorLayer.createDescriptor({ + joins: [ + { + leftField, + right: { + type: SOURCE_TYPES.ES_TERM_SOURCE, + id: joinId, + indexPatternId: rightIndexPatternId, + indexPatternTitle: rightIndexPatternTitle, + term: rightTermField, + metrics: [metricsDescriptor], + }, + }, + ], + sourceDescriptor, + style: VectorStyle.createDescriptor({ + [VECTOR_STYLES.FILL_COLOR]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + field: { + name: joinKey, + origin: FIELD_ORIGIN.JOIN, + }, + color: 'Yellow to Red', + type: COLOR_MAP_TYPE.ORDINAL, + }, + }, + [VECTOR_STYLES.LINE_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { + color: '#3d3d3d', + }, + }, + }), + }); +} + +export function createEmsChoroplethLayerDescriptor({ + leftEmsFileId, + leftEmsField, + rightIndexPatternId, + rightIndexPatternTitle, + rightTermField, +}: { + leftEmsFileId: string; + leftEmsField: string; + rightIndexPatternId: string; + rightIndexPatternTitle: string; + rightTermField: string; +}) { + return createChoroplethLayerDescriptor({ + sourceDescriptor: EMSFileSource.createDescriptor({ + id: leftEmsFileId, + tooltipProperties: [leftEmsField], + }), + leftField: leftEmsField, + rightIndexPatternId, + rightIndexPatternTitle, + rightTermField, + }); +} + +export function createEsChoroplethLayerDescriptor({ + leftIndexPatternId, + leftGeoField, + leftJoinField, + rightIndexPatternId, + rightIndexPatternTitle, + rightTermField, +}: { + leftIndexPatternId: string; + leftGeoField: string; + leftJoinField: string; + rightIndexPatternId: string; + rightIndexPatternTitle: string; + rightTermField: string; +}) { + return createChoroplethLayerDescriptor({ + sourceDescriptor: ESSearchSource.createDescriptor({ + indexPatternId: leftIndexPatternId, + geoField: leftGeoField, + scalingType: SCALING_TYPES.LIMIT, + tooltipProperties: [leftJoinField], + applyGlobalQuery: false, + }), + leftField: leftJoinField, + rightIndexPatternId, + rightIndexPatternTitle, + rightTermField, + }); +} diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/index.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/index.ts new file mode 100644 index 0000000000000..afabfea0c8d46 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/index.ts @@ -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 { choroplethLayerWizardConfig } from './choropleth_layer_wizard'; diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.test.tsx b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.test.tsx new file mode 100644 index 0000000000000..ecb86756e1ca7 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.test.tsx @@ -0,0 +1,43 @@ +/* + * 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. + */ + +jest.mock('../../../kibana_services', () => { + const MockIndexPatternSelect = (props: unknown) => { + return
; + }; + return { + getIndexPatternSelectComponent: () => { + return MockIndexPatternSelect; + }, + }; +}); + +import React from 'react'; +import { shallow } from 'enzyme'; +import { BOUNDARIES_SOURCE, LayerTemplate } from './layer_template'; + +const renderWizardArguments = { + previewLayers: () => {}, + mapColors: [], + currentStepId: null, + enableNextBtn: () => {}, + disableNextBtn: () => {}, + startStepLoading: () => {}, + stopStepLoading: () => {}, + advanceToNextStep: () => {}, +}; + +test('should render elasticsearch UI when left source is BOUNDARIES_SOURCE.ELASTICSEARCH', async () => { + const component = shallow(); + component.setState({ leftSource: BOUNDARIES_SOURCE.ELASTICSEARCH }); + expect(component).toMatchSnapshot(); +}); + +test('should render EMS UI when left source is BOUNDARIES_SOURCE.EMS', async () => { + const component = shallow(); + component.setState({ leftSource: BOUNDARIES_SOURCE.EMS }); + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx new file mode 100644 index 0000000000000..72618781902d2 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx @@ -0,0 +1,459 @@ +/* + * 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, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { FileLayer } from '@elastic/ems-client'; +import { + EuiComboBox, + EuiComboBoxOptionOption, + EuiFormRow, + EuiPanel, + EuiRadioGroup, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { IFieldType, IndexPattern } from 'src/plugins/data/public'; +import { RenderWizardArguments } from '../layer_wizard_registry'; +import { EMSFileSelect } from '../../../components/ems_file_select'; +import { GeoIndexPatternSelect } from '../../../components/geo_index_pattern_select'; +import { SingleFieldSelect } from '../../../components/single_field_select'; +import { getGeoFields, getSourceFields, getTermsFields } from '../../../index_pattern_util'; +import { getEmsFileLayers } from '../../../meta'; +import { getIndexPatternSelectComponent, getIndexPatternService } from '../../../kibana_services'; +import { + createEmsChoroplethLayerDescriptor, + createEsChoroplethLayerDescriptor, +} from './create_choropleth_layer_descriptor'; + +export enum BOUNDARIES_SOURCE { + ELASTICSEARCH = 'ELASTICSEARCH', + EMS = 'EMS', +} + +const BOUNDARIES_OPTIONS = [ + { + id: BOUNDARIES_SOURCE.EMS, + label: i18n.translate('xpack.maps.choropleth.boundaries.ems', { + defaultMessage: 'Administrative boundaries from Elastic Maps Service', + }), + }, + { + id: BOUNDARIES_SOURCE.ELASTICSEARCH, + label: i18n.translate('xpack.maps.choropleth.boundaries.elasticsearch', { + defaultMessage: 'Points, lines, and polygons from Elasticsearch', + }), + }, +]; + +interface State { + leftSource: BOUNDARIES_SOURCE; + leftEmsFileId: string | null; + leftEmsFields: Array>; + leftIndexPattern: IndexPattern | null; + leftGeoFields: IFieldType[]; + leftJoinFields: IFieldType[]; + leftGeoField: string | null; + leftEmsJoinField: string | null; + leftElasticsearchJoinField: string | null; + rightIndexPatternId: string | null; + rightIndexPatternTitle: string | null; + rightTermsFields: IFieldType[]; + rightJoinField: string | null; +} + +export class LayerTemplate extends Component { + private _isMounted: boolean = false; + + state = { + leftSource: BOUNDARIES_SOURCE.EMS, + leftEmsFileId: null, + leftEmsFields: [], + leftIndexPattern: null, + leftGeoFields: [], + leftJoinFields: [], + leftGeoField: null, + leftEmsJoinField: null, + leftElasticsearchJoinField: null, + rightIndexPatternId: null, + rightIndexPatternTitle: null, + rightTermsFields: [], + rightJoinField: null, + }; + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + } + + _loadRightFields = async (indexPatternId: string) => { + this.setState({ rightTermsFields: [], rightIndexPatternTitle: null }); + + let indexPattern; + try { + indexPattern = await getIndexPatternService().get(indexPatternId); + } catch (err) { + return; + } + + // method may be called again before 'get' returns + // ignore response when fetched index pattern does not match active index pattern + if (!this._isMounted || indexPatternId !== this.state.rightIndexPatternId) { + return; + } + + this.setState({ + rightTermsFields: getTermsFields(indexPattern.fields), + rightIndexPatternTitle: indexPattern.title, + }); + }; + + _loadEmsFileFields = async () => { + const emsFileLayers = await getEmsFileLayers(); + const emsFileLayer = emsFileLayers.find((fileLayer: FileLayer) => { + return fileLayer.getId() === this.state.leftEmsFileId; + }); + + if (!this._isMounted || !emsFileLayer) { + return; + } + + const leftEmsFields = emsFileLayer + .getFieldsInLanguage() + .filter((field) => { + return field.type === 'id'; + }) + .map((field) => { + return { + value: field.name, + label: field.description, + }; + }); + this.setState( + { + leftEmsFields, + leftEmsJoinField: leftEmsFields.length ? leftEmsFields[0].value : null, + }, + this._previewLayer + ); + }; + + _onLeftSourceChange = (optionId: string) => { + this.setState( + { leftSource: optionId as BOUNDARIES_SOURCE, rightJoinField: null }, + this._previewLayer + ); + }; + + _onLeftIndexPatternChange = (indexPattern: IndexPattern) => { + this.setState( + { + leftIndexPattern: indexPattern, + leftGeoFields: getGeoFields(indexPattern.fields), + leftJoinFields: getSourceFields(indexPattern.fields), + leftGeoField: null, + leftElasticsearchJoinField: null, + rightJoinField: null, + }, + () => { + // make default geo field selection + if (this.state.leftGeoFields.length) { + // @ts-expect-error - avoid wrong "Property 'name' does not exist on type 'never'." compile error + this._onLeftGeoFieldSelect(this.state.leftGeoFields[0].name); + } + } + ); + }; + + _onLeftGeoFieldSelect = (geoField?: string) => { + if (!geoField) { + return; + } + this.setState({ leftGeoField: geoField }, this._previewLayer); + }; + + _onLeftJoinFieldSelect = (joinField?: string) => { + if (!joinField) { + return; + } + this.setState({ leftElasticsearchJoinField: joinField }, this._previewLayer); + }; + + _onLeftEmsFileChange = (emFileId: string) => { + this.setState({ leftEmsFileId: emFileId, leftEmsJoinField: null, rightJoinField: null }, () => { + this._previewLayer(); + this._loadEmsFileFields(); + }); + }; + + _onLeftEmsFieldChange = (selectedOptions: Array>) => { + if (selectedOptions.length === 0) { + return; + } + + this.setState({ leftEmsJoinField: selectedOptions[0].value! }, this._previewLayer); + }; + + _onRightIndexPatternChange = (indexPatternId: string) => { + if (!indexPatternId) { + return; + } + + this.setState( + { + rightIndexPatternId: indexPatternId, + rightJoinField: null, + }, + () => { + this._previewLayer(); + this._loadRightFields(indexPatternId); + } + ); + }; + + _onRightJoinFieldSelect = (joinField?: string) => { + if (!joinField) { + return; + } + this.setState({ rightJoinField: joinField }, this._previewLayer); + }; + + _isLeftConfigComplete() { + if (this.state.leftSource === BOUNDARIES_SOURCE.ELASTICSEARCH) { + return ( + !!this.state.leftIndexPattern && + !!this.state.leftGeoField && + !!this.state.leftElasticsearchJoinField + ); + } else { + return !!this.state.leftEmsFileId && !!this.state.leftEmsJoinField; + } + } + + _isRightConfigComplete() { + return !!this.state.rightIndexPatternId && !!this.state.rightJoinField; + } + + _previewLayer() { + if (!this._isLeftConfigComplete() || !this._isRightConfigComplete()) { + this.props.previewLayers([]); + return; + } + + const layerDescriptor = + this.state.leftSource === BOUNDARIES_SOURCE.ELASTICSEARCH + ? createEsChoroplethLayerDescriptor({ + // @ts-expect-error - avoid wrong "Property 'id' does not exist on type 'never'." compile error + leftIndexPatternId: this.state.leftIndexPattern!.id, + leftGeoField: this.state.leftGeoField!, + leftJoinField: this.state.leftElasticsearchJoinField!, + rightIndexPatternId: this.state.rightIndexPatternId!, + rightIndexPatternTitle: this.state.rightIndexPatternTitle!, + rightTermField: this.state.rightJoinField!, + }) + : createEmsChoroplethLayerDescriptor({ + leftEmsFileId: this.state.leftEmsFileId!, + leftEmsField: this.state.leftEmsJoinField!, + rightIndexPatternId: this.state.rightIndexPatternId!, + rightIndexPatternTitle: this.state.rightIndexPatternTitle!, + rightTermField: this.state.rightJoinField!, + }); + + this.props.previewLayers([layerDescriptor]); + } + + _renderLeftSourceForm() { + if (this.state.leftSource === BOUNDARIES_SOURCE.ELASTICSEARCH) { + let geoFieldSelect; + if (this.state.leftGeoFields.length) { + geoFieldSelect = ( + + + + ); + } + let joinFieldSelect; + if (this.state.leftJoinFields.length) { + joinFieldSelect = ( + + + + ); + } + return ( + <> + + {geoFieldSelect} + {joinFieldSelect} + + ); + } else { + let emsFieldSelect; + if (this.state.leftEmsFields.length) { + let selectedOption; + if (this.state.leftEmsJoinField) { + selectedOption = this.state.leftEmsFields.find( + (option: EuiComboBoxOptionOption) => { + return this.state.leftEmsJoinField === option.value; + } + ); + } + emsFieldSelect = ( + + + + ); + } + return ( + <> + + {emsFieldSelect} + + ); + } + } + + _renderLeftPanel() { + return ( + + +
+ +
+
+ + + + + + + + {this._renderLeftSourceForm()} +
+ ); + } + + _renderRightPanel() { + if (!this._isLeftConfigComplete()) { + return null; + } + const IndexPatternSelect = getIndexPatternSelectComponent(); + + let joinFieldSelect; + if (this.state.rightTermsFields.length) { + joinFieldSelect = ( + + + + ); + } + + return ( + + +
+ +
+
+ + + + + + + + {joinFieldSelect} +
+ ); + } + + render() { + return ( + <> + {this._renderLeftPanel()} + + + + {this._renderRightPanel()} + + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts index 8357971a3778f..37bcb3b23c072 100644 --- a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts @@ -26,6 +26,7 @@ import { wmsLayerWizardConfig } from '../sources/wms_source'; import { mvtVectorSourceWizardConfig } from '../sources/mvt_single_layer_vector_source'; import { ObservabilityLayerWizardConfig } from './solution_layers/observability'; import { SecurityLayerWizardConfig } from './solution_layers/security'; +import { choroplethLayerWizardConfig } from './choropleth_layer_wizard'; import { getEnableVectorTiles } from '../../kibana_services'; let registered = false; @@ -41,6 +42,7 @@ export function registerLayerWizards() { // @ts-ignore registerLayerWizard(esDocumentsLayerWizardConfig); // @ts-ignore + registerLayerWizard(choroplethLayerWizardConfig); registerLayerWizard(clustersLayerWizardConfig); // @ts-ignore registerLayerWizard(heatmapLayerWizardConfig); diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_template.tsx b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_template.tsx index 3f3c556dcae1e..3fe640a135aa6 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_template.tsx +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_template.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; +import { EuiPanel } from '@elastic/eui'; import { RenderWizardArguments } from '../../layer_wizard_registry'; import { LayerSelect, OBSERVABILITY_LAYER_TYPE } from './layer_select'; import { getMetricOptionsForLayer, MetricSelect, OBSERVABILITY_METRIC_TYPE } from './metric_select'; @@ -63,7 +64,7 @@ export class ObservabilityLayerTemplate extends Component + - + ); } } diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_template.tsx b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_template.tsx index eda489c88fda2..b20f57f4c276d 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_template.tsx +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_template.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; +import { EuiPanel } from '@elastic/eui'; import { RenderWizardArguments } from '../../layer_wizard_registry'; import { IndexPatternSelect } from './index_pattern_select'; import { createSecurityLayerDescriptors } from './create_layer_descriptors'; @@ -44,12 +45,12 @@ export class SecurityLayerTemplate extends Component + - + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/client_file_source/create_client_file_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/client_file_source/create_client_file_source_editor.tsx index 344bdc92489e0..cf2692393192d 100644 --- a/x-pack/plugins/maps/public/classes/sources/client_file_source/create_client_file_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/client_file_source/create_client_file_source_editor.tsx @@ -5,6 +5,7 @@ */ import React, { Component } from 'react'; +import { EuiPanel } from '@elastic/eui'; import { IFieldType } from 'src/plugins/data/public'; import { ES_GEO_FIELD_TYPE, @@ -146,15 +147,17 @@ export class ClientFileCreateSourceEditor extends Component + + + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/create_source_editor.tsx index a78a49032503b..ef15a8d786607 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/create_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/create_source_editor.tsx @@ -5,95 +5,33 @@ */ import React, { Component } from 'react'; -import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FileLayer } from '@elastic/ems-client'; -import { getEmsFileLayers } from '../../../meta'; -import { getEmsUnavailableMessage } from '../ems_unavailable_message'; +import { EuiPanel } from '@elastic/eui'; import { EMSFileSourceDescriptor } from '../../../../common/descriptor_types'; +import { EMSFileSelect } from '../../../components/ems_file_select'; interface Props { onSourceConfigChange: (sourceConfig: Partial) => void; } interface State { - hasLoadedOptions: boolean; - emsFileOptions: Array>; - selectedOption: EuiComboBoxOptionOption | null; + emsFileId: string | null; } export class EMSFileCreateSourceEditor extends Component { - private _isMounted: boolean = false; - state = { - hasLoadedOptions: false, - emsFileOptions: [], - selectedOption: null, - }; - - _loadFileOptions = async () => { - const fileLayers: FileLayer[] = await getEmsFileLayers(); - const options = fileLayers.map((fileLayer) => { - return { - value: fileLayer.getId(), - label: fileLayer.getDisplayName(), - }; - }); - if (this._isMounted) { - this.setState({ - hasLoadedOptions: true, - emsFileOptions: options, - }); - } + emsFileId: null, }; - componentWillUnmount() { - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - this._loadFileOptions(); - } - - _onChange = (selectedOptions: Array>) => { - if (selectedOptions.length === 0) { - return; - } - - this.setState({ selectedOption: selectedOptions[0] }); - - const emsFileId = selectedOptions[0].value; + _onChange = (emsFileId: string) => { + this.setState({ emsFileId }); this.props.onSourceConfigChange({ id: emsFileId }); }; render() { - if (!this.state.hasLoadedOptions) { - // TODO display loading message - return null; - } - return ( - - - + + + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.js index 3931e441ff254..2b54e00cae739 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.js +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.js @@ -5,10 +5,10 @@ */ import React from 'react'; -import { EuiSelect, EuiFormRow } from '@elastic/eui'; +import { EuiSelect, EuiFormRow, EuiPanel } from '@elastic/eui'; import { getEmsTmsServices } from '../../../meta'; -import { getEmsUnavailableMessage } from '../ems_unavailable_message'; +import { getEmsUnavailableMessage } from '../../../components/ems_unavailable_message'; import { i18n } from '@kbn/i18n'; export const AUTO_SELECT = 'auto_select'; @@ -71,23 +71,25 @@ export class TileServiceSelect extends React.Component { } return ( - - - + + + + + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/create_source_editor.js index 24edf0251c153..ebe312d73ecce 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/create_source_editor.js @@ -4,17 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; -import React, { Fragment, Component } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { ES_GEO_FIELD_TYPES } from '../../../../common/constants'; import { SingleFieldSelect } from '../../../components/single_field_select'; -import { getIndexPatternService, getIndexPatternSelectComponent } from '../../../kibana_services'; -import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout'; +import { GeoIndexPatternSelect } from '../../../components/geo_index_pattern_select'; import { i18n } from '@kbn/i18n'; -import { EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { EuiFormRow, EuiPanel } from '@elastic/eui'; import { getFieldsWithGeoTileAgg, getGeoFields, @@ -33,76 +30,26 @@ export class CreateSourceEditor extends Component { }; state = { - isLoadingIndexPattern: false, - indexPatternId: '', + indexPattern: null, geoField: '', requestType: this.props.requestType, - noGeoIndexPatternsExist: false, }; - componentWillUnmount() { - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - } - - onIndexPatternSelect = (indexPatternId) => { + onIndexPatternSelect = (indexPattern) => { this.setState( { - indexPatternId, + indexPattern, }, - this.loadIndexPattern.bind(null, indexPatternId) + () => { + //make default selection + const geoFieldsWithGeoTileAgg = getFieldsWithGeoTileAgg(indexPattern.fields); + if (geoFieldsWithGeoTileAgg[0]) { + this._onGeoFieldSelect(geoFieldsWithGeoTileAgg[0].name); + } + } ); }; - loadIndexPattern = (indexPatternId) => { - this.setState( - { - isLoadingIndexPattern: true, - indexPattern: undefined, - geoField: undefined, - }, - this.debouncedLoad.bind(null, indexPatternId) - ); - }; - - debouncedLoad = _.debounce(async (indexPatternId) => { - if (!indexPatternId || indexPatternId.length === 0) { - return; - } - - let indexPattern; - try { - indexPattern = await getIndexPatternService().get(indexPatternId); - } catch (err) { - // index pattern no longer exists - return; - } - - if (!this._isMounted) { - return; - } - - // props.indexPatternId may be updated before getIndexPattern returns - // ignore response when fetched index pattern does not match active index pattern - if (indexPattern.id !== indexPatternId) { - return; - } - - this.setState({ - isLoadingIndexPattern: false, - indexPattern: indexPattern, - }); - - //make default selection - const geoFieldsWithGeoTileAgg = getFieldsWithGeoTileAgg(indexPattern.fields); - if (geoFieldsWithGeoTileAgg[0]) { - this._onGeoFieldSelect(geoFieldsWithGeoTileAgg[0].name); - } - }, 300); - _onGeoFieldSelect = (geoField) => { this.setState( { @@ -122,17 +69,13 @@ export class CreateSourceEditor extends Component { }; previewLayer = () => { - const { indexPatternId, geoField, requestType } = this.state; + const { indexPattern, geoField, requestType } = this.state; const sourceConfig = - indexPatternId && geoField ? { indexPatternId, geoField, requestType } : null; + indexPattern && geoField ? { indexPatternId: indexPattern.id, geoField, requestType } : null; this.props.onSourceConfigChange(sourceConfig); }; - _onNoIndexPatterns = () => { - this.setState({ noGeoIndexPatternsExist: true }); - }; - _renderGeoSelect() { if (!this.state.indexPattern) { return null; @@ -170,50 +113,16 @@ export class CreateSourceEditor extends Component { ); } - _renderIndexPatternSelect() { - const IndexPatternSelect = getIndexPatternSelectComponent(); - + render() { return ( - - + - - ); - } - - _renderNoIndexPatternWarning() { - if (!this.state.noGeoIndexPatternsExist) { - return null; - } - - return ( - - - - - ); - } - - render() { - return ( - - {this._renderNoIndexPatternWarning()} - {this._renderIndexPatternSelect()} {this._renderGeoSelect()} {this._renderRenderAsSelect()} - + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/create_source_editor.js index 38a5850537200..3fe3d536ff809 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/create_source_editor.js @@ -13,7 +13,7 @@ import { getIndexPatternService, getIndexPatternSelectComponent } from '../../.. import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFormRow, EuiCallOut } from '@elastic/eui'; +import { EuiFormRow, EuiCallOut, EuiPanel } from '@elastic/eui'; import { getFieldsWithGeoTileAgg } from '../../../index_pattern_util'; import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; @@ -200,11 +200,11 @@ export class CreateSourceEditor extends Component { } return ( - + {callout} {this._renderIndexPatternSelect()} {this._renderGeoSelects()} - + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap index 2b04da9251756..8ebb389472f74 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap @@ -210,8 +210,10 @@ exports[`should render top hits form when scaling type is TOP_HITS 1`] = ` diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/create_source_editor.js index 73abfd87e4797..0423d70b3f427 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/create_source_editor.js @@ -4,16 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; import React, { Fragment, Component } from 'react'; import PropTypes from 'prop-types'; -import { EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { EuiFormRow, EuiPanel, EuiSpacer } from '@elastic/eui'; import { SingleFieldSelect } from '../../../components/single_field_select'; -import { getIndexPatternService, getIndexPatternSelectComponent } from '../../../kibana_services'; -import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout'; +import { GeoIndexPatternSelect } from '../../../components/geo_index_pattern_select'; import { i18n } from '@kbn/i18n'; -import { ES_GEO_FIELD_TYPES, SCALING_TYPES } from '../../../../common/constants'; +import { SCALING_TYPES } from '../../../../common/constants'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { ScalingForm } from './scaling_form'; import { @@ -45,80 +43,33 @@ export class CreateSourceEditor extends Component { }; state = { - isLoadingIndexPattern: false, - noGeoIndexPatternsExist: false, ...RESET_INDEX_PATTERN_STATE, }; - componentWillUnmount() { - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - } - - _onIndexPatternSelect = (indexPatternId) => { - this.setState( - { - indexPatternId, - }, - this._loadIndexPattern(indexPatternId) - ); - }; + _onIndexPatternSelect = (indexPattern) => { + const geoFields = getGeoFields(indexPattern.fields); - _loadIndexPattern = (indexPatternId) => { this.setState( { - isLoadingIndexPattern: true, ...RESET_INDEX_PATTERN_STATE, + indexPattern, + geoFields, }, - this._debouncedLoad.bind(null, indexPatternId) + () => { + if (geoFields.length) { + // make default selection, prefer aggregatable field over the first available + const firstAggregatableGeoField = geoFields.find((geoField) => { + return geoField.aggregatable; + }); + const defaultGeoFieldName = firstAggregatableGeoField + ? firstAggregatableGeoField + : geoFields[0]; + this._onGeoFieldSelect(defaultGeoFieldName.name); + } + } ); }; - _debouncedLoad = _.debounce(async (indexPatternId) => { - if (!indexPatternId || indexPatternId.length === 0) { - return; - } - - let indexPattern; - try { - indexPattern = await getIndexPatternService().get(indexPatternId); - } catch (err) { - // index pattern no longer exists - return; - } - - if (!this._isMounted) { - return; - } - - // props.indexPatternId may be updated before getIndexPattern returns - // ignore response when fetched index pattern does not match active index pattern - if (indexPattern.id !== indexPatternId) { - return; - } - - const geoFields = getGeoFields(indexPattern.fields); - this.setState({ - isLoadingIndexPattern: false, - indexPattern: indexPattern, - geoFields, - }); - - if (geoFields.length) { - // make default selection, prefer aggregatable field over the first available - const firstAggregatableGeoField = geoFields.find((geoField) => { - return geoField.aggregatable; - }); - const defaultGeoFieldName = firstAggregatableGeoField - ? firstAggregatableGeoField - : geoFields[0]; - this._onGeoFieldSelect(defaultGeoFieldName.name); - } - }, 300); - _onGeoFieldSelect = (geoFieldName) => { // Respect previous scaling type selection unless newly selected geo field does not support clustering. const scalingType = @@ -146,7 +97,7 @@ export class CreateSourceEditor extends Component { _previewLayer = () => { const { - indexPatternId, + indexPattern, geoFieldName, filterByMapBounds, scalingType, @@ -155,9 +106,9 @@ export class CreateSourceEditor extends Component { } = this.state; const sourceConfig = - indexPatternId && geoFieldName + indexPattern && geoFieldName ? { - indexPatternId, + indexPatternId: indexPattern.id, geoField: geoFieldName, filterByMapBounds, scalingType, @@ -168,10 +119,6 @@ export class CreateSourceEditor extends Component { this.props.onSourceConfigChange(sourceConfig); }; - _onNoIndexPatterns = () => { - this.setState({ noGeoIndexPatternsExist: true }); - }; - _renderGeoSelect() { if (!this.state.indexPattern) { return; @@ -205,7 +152,7 @@ export class CreateSourceEditor extends Component { - - - - ); - } - render() { - const IndexPatternSelect = getIndexPatternSelectComponent(); - return ( - - {this._renderNoIndexPatternWarning()} - - - - + + {this._renderGeoSelect()} {this._renderScalingPanel()} - + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx index 4598b1467229d..1ec6d2a1ff671 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx @@ -26,7 +26,7 @@ export function createDefaultLayerDescriptor(sourceConfig: unknown, mapColors: s export const esDocumentsLayerWizardConfig: LayerWizard = { categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], description: i18n.translate('xpack.maps.source.esSearchDescription', { - defaultMessage: 'Vector data from a Kibana index pattern', + defaultMessage: 'Points, lines, and polygons from Elasticsearch', }), icon: 'logoElasticsearch', renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx index 3ec746223c7cf..6e56c179b4ead 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx @@ -25,6 +25,7 @@ const defaultProps = { scalingType: SCALING_TYPES.LIMIT, supportsClustering: true, termFields: [], + topHitsSplitField: null, topHitsSize: 1, }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx index a998fe3569835..816db6a98d593 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx @@ -40,7 +40,7 @@ interface Props { supportsClustering: boolean; clusteringDisabledReason?: string | null; termFields: IFieldType[]; - topHitsSplitField?: string; + topHitsSplitField: string | null; topHitsSize: number; } @@ -90,6 +90,9 @@ export class ScalingForm extends Component { }; _onTopHitsSplitFieldChange = (topHitsSplitField?: string) => { + if (!topHitsSplitField) { + return; + } this.props.onChange({ propName: 'topHitsSplitField', value: topHitsSplitField }); }; @@ -141,6 +144,7 @@ export class ScalingForm extends Component { value={this.props.topHitsSplitField} onChange={this._onTopHitsSplitFieldChange} fields={this.props.termFields} + isClearable={false} compressed /> diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js index 5e28916e79f3f..82f80e7fe484b 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiSelect, EuiFormRow } from '@elastic/eui'; +import { EuiSelect, EuiFormRow, EuiPanel } from '@elastic/eui'; import { getKibanaRegionList } from '../../../meta'; import { i18n } from '@kbn/i18n'; @@ -31,19 +31,21 @@ export function CreateSourceEditor({ onSourceConfigChange }) { : null; return ( - - - + + + + + ); } diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js index a0a507ff9d32d..1cbf4c1a87de3 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; +import { EuiFieldText, EuiFormRow, EuiPanel } from '@elastic/eui'; import { getKibanaTileMap } from '../../../meta'; import { i18n } from '@kbn/i18n'; @@ -19,21 +19,23 @@ export function CreateSourceEditor({ onSourceConfigChange }) { } return ( - - - + + + + + ); } diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx index 7a4b8d43811da..760b8c676cb37 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor.tsx @@ -5,9 +5,9 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import React, { Fragment, Component, ChangeEvent } from 'react'; +import React, { Component, ChangeEvent } from 'react'; import _ from 'lodash'; -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; +import { EuiFieldText, EuiFormRow, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants'; import { ValidatedDualRange, Value } from '../../../../../../../src/plugins/kibana_react/public'; @@ -85,7 +85,7 @@ export class MVTSingleLayerVectorSourceEditor extends Component { render() { return ( - + { } )} /> - + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_create_source_editor.js index df00faf43daa3..ce9af42117683 100644 --- a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_create_source_editor.js @@ -13,7 +13,7 @@ import { EuiComboBox, EuiFieldText, EuiFormRow, - EuiForm, + EuiPanel, EuiSpacer, } from '@elastic/eui'; import { WmsClient } from './wms_client'; @@ -289,7 +289,7 @@ export class WMSCreateSourceEditor extends Component { render() { return ( - + + ); } } diff --git a/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_editor.tsx b/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_editor.tsx index 715ff0e4c2fdd..bf5f2c3dfe04d 100644 --- a/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/xyz_tms_editor.tsx @@ -5,9 +5,9 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import React, { Fragment, Component, ChangeEvent } from 'react'; +import React, { Component, ChangeEvent } from 'react'; import _ from 'lodash'; -import { EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { EuiFormRow, EuiFieldText, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AttributionDescriptor } from '../../../../common/descriptor_types'; @@ -77,7 +77,7 @@ export class XYZTMSEditor extends Component { render() { const { attributionText, attributionUrl } = this.state; return ( - + { } /> - + ); } } diff --git a/x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap b/x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap new file mode 100644 index 0000000000000..809fe61862513 --- /dev/null +++ b/x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap @@ -0,0 +1,104 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + + + + + +`; + +exports[`should render no index pattern warning when there are no matching index patterns 1`] = ` + + +

+ + + + + +

+

+ + + + +

+
+ + + + +
+`; diff --git a/x-pack/plugins/maps/public/components/ems_file_select.tsx b/x-pack/plugins/maps/public/components/ems_file_select.tsx new file mode 100644 index 0000000000000..f66e813608ce1 --- /dev/null +++ b/x-pack/plugins/maps/public/components/ems_file_select.tsx @@ -0,0 +1,105 @@ +/* + * 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, { Component } from 'react'; +import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FileLayer } from '@elastic/ems-client'; +import { getEmsFileLayers } from '../meta'; +import { getEmsUnavailableMessage } from './ems_unavailable_message'; + +interface Props { + onChange: (emsFileId: string) => void; + value: string | null; +} + +interface State { + hasLoadedOptions: boolean; + emsFileOptions: Array>; +} + +export class EMSFileSelect extends Component { + private _isMounted: boolean = false; + + state = { + hasLoadedOptions: false, + emsFileOptions: [], + }; + + _loadFileOptions = async () => { + const fileLayers: FileLayer[] = await getEmsFileLayers(); + const options = fileLayers.map((fileLayer) => { + return { + value: fileLayer.getId(), + label: fileLayer.getDisplayName(), + }; + }); + if (this._isMounted) { + this.setState({ + hasLoadedOptions: true, + emsFileOptions: options, + }); + } + }; + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadFileOptions(); + } + + _onChange = (selectedOptions: Array>) => { + if (selectedOptions.length === 0) { + return; + } + + this.props.onChange(selectedOptions[0].value!); + }; + + _renderSelect() { + if (!this.state.hasLoadedOptions) { + return ; + } + + const selectedOption = this.state.emsFileOptions.find( + (option: EuiComboBoxOptionOption) => { + return option.value === this.props.value; + } + ); + + return ( + + ); + } + + render() { + return ( + + {this._renderSelect()} + + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/sources/ems_unavailable_message.ts b/x-pack/plugins/maps/public/components/ems_unavailable_message.tsx similarity index 93% rename from x-pack/plugins/maps/public/classes/sources/ems_unavailable_message.ts rename to x-pack/plugins/maps/public/components/ems_unavailable_message.tsx index 748016cf889e2..dea161fafd609 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_unavailable_message.ts +++ b/x-pack/plugins/maps/public/components/ems_unavailable_message.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore -import { getIsEmsEnabled } from '../../kibana_services'; +import { getIsEmsEnabled } from '../kibana_services'; export function getEmsUnavailableMessage(): string { const isEmsEnabled = getIsEmsEnabled(); diff --git a/x-pack/plugins/maps/public/components/geo_index_pattern_select.test.tsx b/x-pack/plugins/maps/public/components/geo_index_pattern_select.test.tsx new file mode 100644 index 0000000000000..74d29e7d7e59a --- /dev/null +++ b/x-pack/plugins/maps/public/components/geo_index_pattern_select.test.tsx @@ -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. + */ + +jest.mock('../kibana_services', () => { + const MockIndexPatternSelect = (props: unknown) => { + return
; + }; + const MockHttp = { + basePath: { + prepend: (path: string) => { + return `abc/${path}`; + }, + }, + }; + return { + getIndexPatternSelectComponent: () => { + return MockIndexPatternSelect; + }, + getHttp: () => { + return MockHttp; + }, + }; +}); + +import React from 'react'; +import { shallow } from 'enzyme'; +import { GeoIndexPatternSelect } from './geo_index_pattern_select'; + +test('should render', async () => { + const component = shallow( {}} value={'indexPatternId'} />); + + expect(component).toMatchSnapshot(); +}); + +test('should render no index pattern warning when there are no matching index patterns', async () => { + const component = shallow( {}} value={'indexPatternId'} />); + component.setState({ noGeoIndexPatternsExist: true }); + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx b/x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx new file mode 100644 index 0000000000000..ae23d9d97de86 --- /dev/null +++ b/x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx @@ -0,0 +1,139 @@ +/* + * 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, { Component } from 'react'; +import { EuiCallOut, EuiFormRow, EuiLink, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { IndexPattern } from 'src/plugins/data/public'; +import { + getIndexPatternSelectComponent, + getIndexPatternService, + getHttp, +} from '../kibana_services'; +import { ES_GEO_FIELD_TYPES } from '../../common/constants'; + +interface Props { + onChange: (indexPattern: IndexPattern) => void; + value: string | null; +} + +interface State { + noGeoIndexPatternsExist: boolean; +} + +export class GeoIndexPatternSelect extends Component { + private _isMounted: boolean = false; + + state = { + noGeoIndexPatternsExist: false, + }; + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + } + + _onIndexPatternSelect = async (indexPatternId: string) => { + if (!indexPatternId || indexPatternId.length === 0) { + return; + } + + let indexPattern; + try { + indexPattern = await getIndexPatternService().get(indexPatternId); + } catch (err) { + return; + } + + // method may be called again before 'get' returns + // ignore response when fetched index pattern does not match active index pattern + if (this._isMounted && indexPattern.id === indexPatternId) { + this.props.onChange(indexPattern); + } + }; + + _onNoIndexPatterns = () => { + this.setState({ noGeoIndexPatternsExist: true }); + }; + + _renderNoIndexPatternWarning() { + if (!this.state.noGeoIndexPatternsExist) { + return null; + } + + return ( + <> + +

+ + + + + +

+

+ + + + +

+
+ + + ); + } + + render() { + const IndexPatternSelect = getIndexPatternSelectComponent(); + return ( + <> + {this._renderNoIndexPatternWarning()} + + + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/components/no_index_pattern_callout.js b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js deleted file mode 100644 index 2daab8c6322dd..0000000000000 --- a/x-pack/plugins/maps/public/components/no_index_pattern_callout.js +++ /dev/null @@ -1,52 +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 { getHttp } from '../kibana_services'; -import React from 'react'; -import { EuiCallOut, EuiLink } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export function NoIndexPatternCallout() { - const http = getHttp(); - return ( - -

- - - - - -

-

- - - - -

-
- ); -} diff --git a/x-pack/plugins/maps/public/components/single_field_select.tsx b/x-pack/plugins/maps/public/components/single_field_select.tsx index eb3a28be0efc0..2895479c4fd0e 100644 --- a/x-pack/plugins/maps/public/components/single_field_select.tsx +++ b/x-pack/plugins/maps/public/components/single_field_select.tsx @@ -49,7 +49,7 @@ type Props = Omit< > & { fields?: IFieldType[]; onChange: (fieldName?: string) => void; - value?: string; // index pattern field name + value: string | null; // index pattern field name isFieldDisabled?: (field: IFieldType) => boolean; getFieldDisabledReason?: (field: IFieldType) => string | null; }; diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/flyout_body.tsx b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/flyout_body.tsx index 38474b84114fa..3f493ef7d4355 100644 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/flyout_body.tsx +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/flyout_body.tsx @@ -5,7 +5,7 @@ */ import React, { Fragment } from 'react'; -import { EuiButtonEmpty, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { LayerWizardSelect } from './layer_wizard_select'; import { LayerWizard, RenderWizardArguments } from '../../../classes/layers/layer_wizard_registry'; @@ -50,7 +50,7 @@ export const FlyoutBody = (props: Props) => { return ( {backButton} - {props.layerWizard.renderWizard(renderWizardArgs)} + {props.layerWizard.renderWizard(renderWizardArgs)} ); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 29ea4426ac5f6..cb2d3e60be9ac 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9155,7 +9155,6 @@ "xpack.maps.source.ems.disabledDescription": "Elastic Maps Service へのアクセスが無効になっています。システム管理者に問い合わせるか、kibana.yml で「map.includeElasticMapsService」を設定してください。", "xpack.maps.source.ems.noAccessDescription": "Kibana が Elastic Maps Service にアクセスできません。システム管理者にお問い合わせください", "xpack.maps.source.emsFile.layerLabel": "レイヤー", - "xpack.maps.source.emsFile.selectPlaceholder": "EMS ベクターシェイプを選択", "xpack.maps.source.emsFile.unableToFindIdErrorMessage": "ID {id} の EMS ベクターシェイプが見つかりません", "xpack.maps.source.emsFileDescription": "Elastic Maps Service の行政区画のベクターシェイプ", "xpack.maps.source.emsFileTitle": "ベクターシェイプ", @@ -9171,8 +9170,6 @@ "xpack.maps.source.esGeoGrid.geofieldLabel": "地理空間フィールド", "xpack.maps.source.esGeoGrid.geofieldPlaceholder": "ジオフィールドを選択", "xpack.maps.source.esGeoGrid.gridRectangleDropdownOption": "グリッド", - "xpack.maps.source.esGeoGrid.indexPatternLabel": "インデックスパターン", - "xpack.maps.source.esGeoGrid.indexPatternPlaceholder": "インデックスパターンを選択", "xpack.maps.source.esGeoGrid.pointsDropdownOption": "クラスター", "xpack.maps.source.esGeoGrid.showAsLabel": "表示形式", "xpack.maps.source.esGrid.coarseDropdownOption": "粗い", @@ -9206,7 +9203,6 @@ "xpack.maps.source.esSearch.limitScalingLabel": "結果を {maxResultWindow} に限定。", "xpack.maps.source.esSearch.loadErrorMessage": "インデックスパターン {id} が見つかりません", "xpack.maps.source.esSearch.loadTooltipPropertiesErrorMsg": "ドキュメントが見つかりません。_id: {docId}", - "xpack.maps.source.esSearch.selectIndexPatternPlaceholder": "インデックスパターンを選択", "xpack.maps.source.esSearch.selectLabel": "ジオフィールドを選択", "xpack.maps.source.esSearch.sortFieldLabel": "フィールド", "xpack.maps.source.esSearch.sortFieldSelectPlaceholder": "ソートフィールドを選択", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 84a4fb41cdd76..b63212b11b1d6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9159,7 +9159,6 @@ "xpack.maps.source.ems.disabledDescription": "已禁用对 Elastic 地图服务的访问。让您的系统管理员在 kibana.yml 中设置“map.includeElasticMapsService”。", "xpack.maps.source.ems.noAccessDescription": "Kibana 无法访问 Elastic 地图服务。请联系您的系统管理员", "xpack.maps.source.emsFile.layerLabel": "图层", - "xpack.maps.source.emsFile.selectPlaceholder": "选择 EMS 矢量形状", "xpack.maps.source.emsFile.unableToFindIdErrorMessage": "找不到 ID {id} 的 EMS 矢量形状", "xpack.maps.source.emsFileDescription": "来自 Elastic 地图服务的管理边界的矢量形状", "xpack.maps.source.emsFileTitle": "矢量形状", @@ -9175,8 +9174,6 @@ "xpack.maps.source.esGeoGrid.geofieldLabel": "地理空间字段", "xpack.maps.source.esGeoGrid.geofieldPlaceholder": "选择地理字段", "xpack.maps.source.esGeoGrid.gridRectangleDropdownOption": "网格", - "xpack.maps.source.esGeoGrid.indexPatternLabel": "索引模式", - "xpack.maps.source.esGeoGrid.indexPatternPlaceholder": "选择索引模式", "xpack.maps.source.esGeoGrid.pointsDropdownOption": "集群", "xpack.maps.source.esGeoGrid.showAsLabel": "显示为", "xpack.maps.source.esGrid.coarseDropdownOption": "粗糙", @@ -9210,7 +9207,6 @@ "xpack.maps.source.esSearch.limitScalingLabel": "将结果数限制到 {maxResultWindow}。", "xpack.maps.source.esSearch.loadErrorMessage": "找不到索引模式 {id}", "xpack.maps.source.esSearch.loadTooltipPropertiesErrorMsg": "找不到文档,_id:{docId}", - "xpack.maps.source.esSearch.selectIndexPatternPlaceholder": "选择索引模式", "xpack.maps.source.esSearch.selectLabel": "选择地理字段", "xpack.maps.source.esSearch.sortFieldLabel": "字段", "xpack.maps.source.esSearch.sortFieldSelectPlaceholder": "选择排序字段",