[Maps] pew pew source (#41504)
* [Maps][POC] pew pew source * refetch data on zoom level change * add metric aggs to request * fix bug where initial draw did not have styles set up * make tooltips work * fix import broken with merging master * use custom labels in tooltips * remove duplicate keys and clean up title and desc wording * update source description * update references migration to extract references for pew pew source * add percy visual test to ensure pew pew map data is fetch, processed, and visualized as expected * update title and description * use GEO_FIELD_TYPES in filterGeoField function * remove unneeded Fragment wrapper * fix typo * update inspector description id and message
This commit is contained in:
parent
ce2ba6d5f5
commit
e42cfacb9d
|
@ -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';
|
||||
|
|
|
@ -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 = [] }) {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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 (
|
||||
<Fragment>
|
||||
<EuiFormRow label={i18n.translate('xpack.maps.source.pewPew.sourceGeoFieldLabel', {
|
||||
defaultMessage: 'Source'
|
||||
})}
|
||||
>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate('xpack.maps.source.pewPew.sourceGeoFieldPlaceholder', {
|
||||
defaultMessage: 'Select source geo field'
|
||||
})}
|
||||
value={this.state.sourceGeoField}
|
||||
onChange={this._onSourceGeoSelect}
|
||||
filterField={filterGeoField}
|
||||
fields={this.state.indexPattern ? this.state.indexPattern.fields : undefined}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow label={i18n.translate('xpack.maps.source.pewPew.destGeoFieldLabel', {
|
||||
defaultMessage: 'Destination'
|
||||
})}
|
||||
>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate('xpack.maps.source.pewPew.destGeoFieldPlaceholder', {
|
||||
defaultMessage: 'Select destination geo field'
|
||||
})}
|
||||
value={this.state.destGeoField}
|
||||
onChange={this._onDestGeoSelect}
|
||||
filterField={filterGeoField}
|
||||
fields={this.state.indexPattern ? this.state.indexPattern.fields : undefined}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
_renderIndexPatternSelect() {
|
||||
return (
|
||||
<EuiFormRow label={i18n.translate('xpack.maps.source.pewPew.indexPatternLabel', {
|
||||
defaultMessage: 'Index pattern'
|
||||
})}
|
||||
>
|
||||
<IndexPatternSelect
|
||||
indexPatternId={this.state.indexPatternId}
|
||||
onChange={this.onIndexPatternSelect}
|
||||
placeholder={i18n.translate('xpack.maps.source.pewPew.indexPatternPlaceholder', {
|
||||
defaultMessage: 'Select index pattern'
|
||||
})}
|
||||
fieldTypes={GEO_FIELD_TYPES}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let callout;
|
||||
if (this.state.indexPattern && !this.state.indexPatternHasMultipleGeoFields) {
|
||||
callout = (
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.maps.source.pewPew.noSourceAndDestDetails"
|
||||
defaultMessage="Selected index pattern does not contain source and destination fields."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{callout}
|
||||
{this._renderIndexPatternSelect()}
|
||||
{this._renderGeoSelects()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 (<CreateSourceEditor onSourceConfigChange={onSourceConfigChange}/>);
|
||||
}
|
||||
|
||||
renderSourceSettingsEditor({ onChange }) {
|
||||
return (
|
||||
<UpdateSourceEditor
|
||||
indexPatternId={this._descriptor.indexPatternId}
|
||||
onChange={onChange}
|
||||
metrics={this._descriptor.metrics}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.source.pewPew.metricsLabel', {
|
||||
defaultMessage: 'Metrics'
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<MetricsEditor
|
||||
allowMultipleMetrics={true}
|
||||
fields={this.state.fields}
|
||||
metrics={this.props.metrics}
|
||||
onChange={this._onMetricsChange}
|
||||
/>
|
||||
</div>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue