diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js index 65234bcad669..5af3f8cfc071 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.js @@ -43,6 +43,7 @@ export const EMS_TMS = 'EMS_TMS'; export const EMS_FILE = 'EMS_FILE'; export const ES_GEO_GRID = 'ES_GEO_GRID'; export const ES_SEARCH = 'ES_SEARCH'; +export const ES_PEW_PEW = 'ES_PEW_PEW'; export const SOURCE_DATA_ID_ORIGIN = 'source'; export const GEOJSON_FILE = 'GEOJSON_FILE'; diff --git a/x-pack/legacy/plugins/maps/common/migrations/references.js b/x-pack/legacy/plugins/maps/common/migrations/references.js index a86abdf27a1f..d235427f54e6 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/references.js +++ b/x-pack/legacy/plugins/maps/common/migrations/references.js @@ -7,11 +7,11 @@ // Can not use public Layer classes to extract references since this logic must run in both client and server. import _ from 'lodash'; -import { ES_GEO_GRID, ES_SEARCH } from '../constants'; +import { ES_GEO_GRID, ES_SEARCH, ES_PEW_PEW } from '../constants'; function doesSourceUseIndexPattern(layerDescriptor) { const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type'); - return sourceType === ES_GEO_GRID || sourceType === ES_SEARCH; + return sourceType === ES_GEO_GRID || sourceType === ES_SEARCH || sourceType === ES_PEW_PEW; } export function extractReferences({ attributes, references = [] }) { diff --git a/x-pack/legacy/plugins/maps/common/migrations/references.test.js b/x-pack/legacy/plugins/maps/common/migrations/references.test.js index 362d6ed718e2..cede1fd6e4da 100644 --- a/x-pack/legacy/plugins/maps/common/migrations/references.test.js +++ b/x-pack/legacy/plugins/maps/common/migrations/references.test.js @@ -7,7 +7,7 @@ /* eslint max-len: 0 */ import { extractReferences, injectReferences } from './references'; -import { ES_GEO_GRID, ES_SEARCH } from '../constants'; +import { ES_GEO_GRID, ES_SEARCH, ES_PEW_PEW } from '../constants'; const layerListJSON = { esSearchSource: { @@ -21,6 +21,10 @@ const layerListJSON = { join: { withIndexPatternId: '[{\"joins\":[{\"right\":{\"indexPatternId\":\"e20b2a30-f735-11e8-8ce0-9723965e01e3\"}}]}]', withIndexPatternRef: '[{\"joins\":[{\"right\":{\"indexPatternRefName\":\"layer_0_join_0_index_pattern\"}}]}]', + }, + pewPewSource: { + withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${ES_PEW_PEW}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`, + withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${ES_PEW_PEW}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`, } }; @@ -78,6 +82,26 @@ describe('extractReferences', () => { }); }); + test('Should extract index-pattern reference from ES pew pew source descriptor', () => { + const attributes = { + title: 'my map', + layerListJSON: layerListJSON.pewPewSource.withIndexPatternId, + }; + expect(extractReferences({ attributes })).toEqual({ + attributes: { + title: 'my map', + layerListJSON: layerListJSON.pewPewSource.withIndexPatternRef, + }, + references: [ + { + id: 'c698b940-e149-11e8-a35a-370a8516603a', + name: 'layer_0_source_index_pattern', + type: 'index-pattern', + } + ], + }); + }); + test('Should extract index-pattern reference from joins', () => { const attributes = { title: 'my map', @@ -151,6 +175,26 @@ describe('injectReferences', () => { }); }); + test('Should inject index-pattern reference into ES pew pew source descriptor', () => { + const attributes = { + title: 'my map', + layerListJSON: layerListJSON.pewPewSource.withIndexPatternRef, + }; + const references = [ + { + id: 'c698b940-e149-11e8-a35a-370a8516603a', + name: 'layer_0_source_index_pattern', + type: 'index-pattern', + } + ]; + expect(injectReferences({ attributes, references })).toEqual({ + attributes: { + title: 'my map', + layerListJSON: layerListJSON.pewPewSource.withIndexPatternId, + } + }); + }); + test('Should inject index-pattern reference into joins', () => { const attributes = { title: 'my map', diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/all_sources.js b/x-pack/legacy/plugins/maps/public/layers/sources/all_sources.js index d81d218910cc..6a518609dd77 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/all_sources.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/all_sources.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ - import { EMSFileSource } from './ems_file_source'; import { GeojsonFileSource } from './client_file_source'; import { KibanaRegionmapSource } from './kibana_regionmap_source'; @@ -14,12 +13,13 @@ import { WMSSource } from './wms_source'; import { KibanaTilemapSource } from './kibana_tilemap_source'; import { ESGeoGridSource } from './es_geo_grid_source'; import { ESSearchSource } from './es_search_source'; - +import { ESPewPewSource } from './es_pew_pew_source/es_pew_pew_source'; export const ALL_SOURCES = [ GeojsonFileSource, ESSearchSource, ESGeoGridSource, + ESPewPewSource, EMSFileSource, EMSTMSSource, KibanaRegionmapSource, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js new file mode 100644 index 000000000000..ce5efb3b4417 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; + +const LAT_INDEX = 0; +const LON_INDEX = 1; + +function parsePointFromKey(key) { + const split = key.split(','); + const lat = parseFloat(split[LAT_INDEX]); + const lon = parseFloat(split[LON_INDEX]); + return [lon, lat]; +} + +export function convertToLines(esResponse) { + + const lineFeatures = []; + + const destBuckets = _.get(esResponse, 'aggregations.destSplit.buckets', []); + for (let i = 0; i < destBuckets.length; i++) { + const destBucket = destBuckets[i]; + const dest = parsePointFromKey(destBucket.key); + const sourceBuckets = _.get(destBucket, 'sourceGrid.buckets', []); + for (let j = 0; j < sourceBuckets.length; j++) { + const { + key, // eslint-disable-line no-unused-vars + sourceCentroid, + ...rest + } = sourceBuckets[j]; + + // flatten metrics + Object.keys(rest).forEach(key => { + if (_.has(rest[key], 'value')) { + rest[key] = rest[key].value; + } + }); + + lineFeatures.push({ + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [[sourceCentroid.location.lon, sourceCentroid.location.lat], dest] + }, + properties: { + ...rest + } + }); + } + } + + return { + featureCollection: { + type: 'FeatureCollection', + features: lineFeatures + } + }; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js new file mode 100644 index 000000000000..9f9789374274 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React, { Fragment, Component } from 'react'; +import PropTypes from 'prop-types'; + +import { IndexPatternSelect } from 'ui/index_patterns'; +import { SingleFieldSelect } from '../../../components/single_field_select'; +import { indexPatternService } from '../../../kibana_services'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiFormRow, + EuiCallOut, +} from '@elastic/eui'; +import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; + +const GEO_FIELD_TYPES = [ES_GEO_FIELD_TYPE.GEO_POINT]; + +function filterGeoField({ type }) { + return GEO_FIELD_TYPES.includes(type); +} + +export class CreateSourceEditor extends Component { + + static propTypes = { + onSourceConfigChange: PropTypes.func.isRequired, + }; + + state = { + isLoadingIndexPattern: false, + indexPattern: undefined, + indexPatternId: undefined, + sourceGeoField: undefined, + destGeoField: undefined, + indexPatternHasMultipleGeoFields: false, + } + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + } + + onIndexPatternSelect = (indexPatternId) => { + this.setState({ + indexPatternId, + }, this.loadIndexPattern.bind(null, indexPatternId)); + }; + + loadIndexPattern = (indexPatternId) => { + this.setState({ + isLoadingIndexPattern: true, + indexPattern: undefined, + sourceGeoField: undefined, + destGeoField: undefined, + indexPatternHasMultipleGeoFields: false, + }, this.debouncedLoad.bind(null, indexPatternId)); + }; + + debouncedLoad = _.debounce(async (indexPatternId) => { + if (!indexPatternId || indexPatternId.length === 0) { + return; + } + + let indexPattern; + try { + indexPattern = await indexPatternService.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 (this.state.indexPatternId !== indexPatternId) { + return; + } + + const geoFields = indexPattern.fields.filter(filterGeoField); + + this.setState({ + isLoadingIndexPattern: false, + indexPattern: indexPattern, + indexPatternHasMultipleGeoFields: geoFields.length >= 2, + }); + }, 300); + + _onSourceGeoSelect = (sourceGeoField) => { + this.setState({ + sourceGeoField + }, this.previewLayer); + }; + + _onDestGeoSelect = (destGeoField) => { + this.setState({ + destGeoField + }, this.previewLayer); + }; + + previewLayer = () => { + const { + indexPatternId, + sourceGeoField, + destGeoField, + } = this.state; + + const sourceConfig = (indexPatternId && sourceGeoField && destGeoField) + ? { indexPatternId, sourceGeoField, destGeoField } + : null; + this.props.onSourceConfigChange(sourceConfig); + }; + + _renderGeoSelects() { + if (!this.state.indexPattern || !this.state.indexPatternHasMultipleGeoFields) { + return null; + } + + return ( + + + + + + + + + + ); + } + + _renderIndexPatternSelect() { + return ( + + + + ); + } + + render() { + let callout; + if (this.state.indexPattern && !this.state.indexPatternHasMultipleGeoFields) { + callout = ( + +

+ +

+
+ ); + } + + return ( + + {callout} + {this._renderIndexPatternSelect()} + {this._renderGeoSelects()} + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js new file mode 100644 index 000000000000..e1b930c8ab39 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -0,0 +1,276 @@ +/* + * 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 uuid from 'uuid/v4'; + +import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; +import { AbstractESSource } from '../es_source'; +import { VectorLayer } from '../../vector_layer'; +import { CreateSourceEditor } from './create_source_editor'; +import { UpdateSourceEditor } from './update_source_editor'; +import { VectorStyle } from '../../styles/vector_style'; +import { i18n } from '@kbn/i18n'; +import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW } from '../../../../common/constants'; +import { getDataSourceLabel } from '../../../../common/i18n_getters'; +import { convertToLines } from './convert_to_lines'; +import { Schemas } from 'ui/vis/editors/default/schemas'; +import { AggConfigs } from 'ui/vis/agg_configs'; + +const COUNT_PROP_LABEL = 'count'; +const COUNT_PROP_NAME = 'doc_count'; +const MAX_GEOTILE_LEVEL = 29; + +const aggSchemas = new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Value', + min: 1, + max: Infinity, + aggFilter: ['avg', 'count', 'max', 'min', 'sum'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + } +]); + +export class ESPewPewSource extends AbstractESSource { + + static type = ES_PEW_PEW; + static title = i18n.translate('xpack.maps.source.pewPewTitle', { + defaultMessage: 'Source-destination connections' + }); + static description = i18n.translate('xpack.maps.source.pewPewDescription', { + defaultMessage: 'Aggregated data paths between the origin and destinations.' + }); + + static createDescriptor({ indexPatternId, sourceGeoField, destGeoField }) { + return { + type: ESPewPewSource.type, + id: uuid(), + indexPatternId: indexPatternId, + sourceGeoField, + destGeoField + }; + } + + static renderEditor({ onPreviewSource, inspectorAdapters }) { + const onSourceConfigChange = (sourceConfig) => { + if (!sourceConfig) { + onPreviewSource(null); + return; + } + + const sourceDescriptor = ESPewPewSource.createDescriptor(sourceConfig); + const source = new ESPewPewSource(sourceDescriptor, inspectorAdapters); + onPreviewSource(source); + }; + + return (); + } + + renderSourceSettingsEditor({ onChange }) { + return ( + + ); + } + + isFilterByMapBounds() { + return true; + } + + isJoinable() { + return false; + } + + isGeoGridPrecisionAware() { + return true; + } + + async getNumberFields() { + return this.getMetricFields().map(({ propertyKey: name, propertyLabel: label }) => { + return { label, name }; + }); + } + + async getSupportedShapeTypes() { + return [VECTOR_SHAPE_TYPES.LINE]; + } + + async getImmutableProperties() { + let indexPatternTitle = this._descriptor.indexPatternId; + try { + const indexPattern = await this._getIndexPattern(); + indexPatternTitle = indexPattern.title; + } catch (error) { + // ignore error, title will just default to id + } + + return [ + { + label: getDataSourceLabel(), + value: ESPewPewSource.title + }, + { + label: i18n.translate('xpack.maps.source.pewPew.indexPatternLabel', { + defaultMessage: 'Index pattern' + }), + value: indexPatternTitle }, + { + label: i18n.translate('xpack.maps.source.pewPew.sourceGeoFieldLabel', { + defaultMessage: 'Source' + }), + value: this._descriptor.sourceGeoField + }, + { + label: i18n.translate('xpack.maps.source.pewPew.destGeoFieldLabel', { + defaultMessage: 'Destination' + }), + value: this._descriptor.destGeoField + }, + ]; + } + + createDefaultLayer(options) { + const styleDescriptor = VectorStyle.createDescriptor({ + lineColor: { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options: { + field: { + label: COUNT_PROP_LABEL, + name: COUNT_PROP_NAME, + origin: SOURCE_DATA_ID_ORIGIN + }, + color: 'Blues' + } + }, + lineWidth: { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options: { + field: { + label: COUNT_PROP_LABEL, + name: COUNT_PROP_NAME, + origin: SOURCE_DATA_ID_ORIGIN + }, + minSize: 4, + maxSize: 32, + } + } + }); + + return new VectorLayer({ + layerDescriptor: VectorLayer.createDescriptor({ + ...options, + sourceDescriptor: this._descriptor, + style: styleDescriptor + }), + source: this, + style: new VectorStyle(styleDescriptor, this) + }); + } + + getGeoGridPrecision(zoom) { + const targetGeotileLevel = Math.ceil(zoom) + 2; + return Math.min(targetGeotileLevel, MAX_GEOTILE_LEVEL); + } + + async getGeoJsonWithMeta(layerName, searchFilters) { + const indexPattern = await this._getIndexPattern(); + const metricAggConfigs = this.getMetricFields().map(metric => { + const metricAggConfig = { + id: metric.propertyKey, + enabled: true, + type: metric.type, + schema: 'metric', + params: {} + }; + if (metric.type !== 'count') { + metricAggConfig.params = { field: metric.field }; + } + return metricAggConfig; + }); + const aggConfigs = new AggConfigs(indexPattern, metricAggConfigs, aggSchemas.all); + + const searchSource = await this._makeSearchSource(searchFilters, 0); + searchSource.setField('aggs', { + destSplit: { + terms: { + script: { + source: `doc['${this._descriptor.destGeoField}'].value.toString()`, + lang: 'painless' + }, + order: { + _count: 'desc' + }, + size: 100 + }, + aggs: { + sourceGrid: { + geotile_grid: { + field: this._descriptor.sourceGeoField, + precision: searchFilters.geogridPrecision, + size: 500, + }, + aggs: { + sourceCentroid: { + geo_centroid: { + field: this._descriptor.sourceGeoField + } + }, + ...aggConfigs.toDsl() + } + } + } + } + }); + + const esResponse = await this._runEsQuery(layerName, searchSource, i18n.translate('xpack.maps.source.pewPew.inspectorDescription', { + defaultMessage: 'Source-destination connections request' + })); + + const { featureCollection } = convertToLines(esResponse); + + return { + data: featureCollection, + meta: { + areResultsTrimmed: false + } + }; + } + + _formatMetricKey(metric) { + return metric.type !== 'count' ? `${metric.type}_of_${metric.field}` : COUNT_PROP_NAME; + } + + _formatMetricLabel(metric) { + return metric.type !== 'count' ? `${metric.type} of ${metric.field}` : COUNT_PROP_LABEL; + } + + async _getGeoField() { + const indexPattern = await this._getIndexPattern(); + const geoField = indexPattern.fields.byName[this._descriptor.destGeoField]; + if (!geoField) { + throw new Error(i18n.translate('xpack.maps.source.esSource.noGeoFieldErrorMessage', { + defaultMessage: `Index pattern {indexPatternTitle} no longer contains the geo field {geoField}`, + values: { indexPatternTitle: indexPattern.title, geoField: this._descriptor.geoField } + })); + } + return geoField; + } + + canFormatFeatureProperties() { + return true; + } + + async filterAndFormatPropertiesToHtml(properties) { + return await this.filterAndFormatPropertiesToHtmlForMetricFields(properties); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js new file mode 100644 index 000000000000..573366b5ef84 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js @@ -0,0 +1,76 @@ +/* + * 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 { MetricsEditor } from '../../../components/metrics_editor'; +import { indexPatternService } from '../../../kibana_services'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow } from '@elastic/eui'; + +export class UpdateSourceEditor extends Component { + + state = { + fields: null, + }; + + componentDidMount() { + this._isMounted = true; + this._loadFields(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + async _loadFields() { + let indexPattern; + try { + indexPattern = await indexPatternService.get(this.props.indexPatternId); + } catch (err) { + if (this._isMounted) { + this.setState({ + loadError: i18n.translate('xpack.maps.source.pewPew.noIndexPatternErrorMessage', { + defaultMessage: `Unable to find Index pattern {id}`, + values: { + id: this.props.indexPatternId + } + }) + }); + } + return; + } + + if (!this._isMounted) { + return; + } + + this.setState({ fields: indexPattern.fields }); + } + + _onMetricsChange = (metrics) => { + this.props.onChange({ propName: 'metrics', value: metrics }); + }; + + render() { + return ( + +
+ +
+
+ ); + } +} diff --git a/x-pack/test/functional/es_archives/maps/data/data.json b/x-pack/test/functional/es_archives/maps/data/data.json index 41f4dda80b5e..e44b19504af3 100644 --- a/x-pack/test/functional/es_archives/maps/data/data.json +++ b/x-pack/test/functional/es_archives/maps/data/data.json @@ -388,3 +388,51 @@ } } } + +{ + "type": "doc", + "value": { + "id": "1", + "index": "connections", + "source": { + "source" : [-65, 40], + "destination" : "drm3btev3e86" + } + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "connections", + "source": { + "source" : [-75.00001, 41], + "destination" : { "lon": -71, "lat": 40 } + } + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "connections", + "source": { + "source" : [-75, 41.00003], + "destination" : [ -71.000000000000000, 40 ] + } + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "connections", + "source": { + "source" : [-75, 41.00004], + "destination" : "40.000,-71" + } + } +} diff --git a/x-pack/test/functional/es_archives/maps/data/mappings.json b/x-pack/test/functional/es_archives/maps/data/mappings.json index 2c394b8e3b75..7e642ca49f3a 100644 --- a/x-pack/test/functional/es_archives/maps/data/mappings.json +++ b/x-pack/test/functional/es_archives/maps/data/mappings.json @@ -109,3 +109,26 @@ } } } + +{ + "type": "index", + "value": { + "index": "connections", + "mappings": { + "properties": { + "source": { + "type": "geo_point" + }, + "destination": { + "type": "geo_point" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index 8dc4f08f59b8..d9baecf23d85 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -109,6 +109,26 @@ } } +{ + "type": "doc", + "value": { + "id": "index-pattern:dedd3180-c8d8-11e9-b36c-81f9f9da524f", + "index": ".kibana", + "source": { + "index-pattern" : { + "title" : "connections", + "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"destination\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + }, + "type" : "index-pattern", + "references" : [ ], + "migrationVersion" : { + "index-pattern" : "6.5.0" + }, + "updated_at" : "2019-08-27T14:42:20.061Z" + } + } +} + { "type": "doc", "value": { @@ -617,6 +637,62 @@ } } +{ + "type": "doc", + "value": { + "id": "map:3c9949f0-c8dc-11e9-9ea1-8b2710d4a86b", + "index": ".kibana", + "source": { + "map" : { + "title" : "pew pew demo", + "description" : "", + "mapStateJSON" : "{\"zoom\":5.5,\"center\":{\"lon\":-71.34293,\"lat\":40.33097},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[]}", + "layerListJSON" : "[{\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#1EA593\"}},\"lineColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineWidth\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"sourceDescriptor\":{\"type\":\"ES_PEW_PEW\",\"id\":\"d7ca27a8-dba2-4f5b-857c-8f529511ad81\",\"sourceGeoField\":\"source\",\"destGeoField\":\"destination\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"id\":\"67c1de2c-2fc5-4425-8983-094b589afe61\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"applyGlobalQuery\":true,\"type\":\"VECTOR\"}]", + "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}", + "bounds" : { + "type" : "Polygon", + "coordinates" : [ + [ + [ + -83.72374, + 44.30172 + ], + [ + -83.72374, + 36.11211 + ], + [ + -58.96211, + 36.11211 + ], + [ + -58.96211, + 44.30172 + ], + [ + -83.72374, + 44.30172 + ] + ] + ] + } + }, + "type" : "map", + "references" : [ + { + "name" : "layer_0_source_index_pattern", + "type" : "index-pattern", + "id" : "dedd3180-c8d8-11e9-b36c-81f9f9da524f" + } + ], + "migrationVersion" : { + "map" : "7.4.0" + }, + "updated_at" : "2019-08-27T15:06:24.654Z" + } + } +} + { "type": "doc", "value": { diff --git a/x-pack/test/visual_regression/tests/maps/vector_styling.js b/x-pack/test/visual_regression/tests/maps/vector_styling.js index 9b398fd56610..25a9e794219a 100644 --- a/x-pack/test/visual_regression/tests/maps/vector_styling.js +++ b/x-pack/test/visual_regression/tests/maps/vector_styling.js @@ -35,5 +35,19 @@ export default function ({ getPageObjects, getService }) { }); }); + + describe('dynamic line coloring', () => { + before(async () => { + await PageObjects.maps.loadSavedMap('pew pew demo'); + await PageObjects.maps.enterFullScreen(); + await PageObjects.maps.closeLegend(); + }); + + // eslint-disable-next-line max-len + it('should symbolize pew pew lines', async () => { + await visualTesting.snapshot(); + }); + + }); }); }