Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps] pew pew source #41504

Merged
merged 21 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
01f87ad
[Maps][POC] pew pew source
nreese Jul 18, 2019
3887d7e
refetch data on zoom level change
nreese Jul 18, 2019
2df390c
Merge branch 'master' of github.com:elastic/kibana into pew-pew
nreese Aug 13, 2019
87ed533
Merge branch 'master' of github.com:elastic/kibana into pew-pew
nreese Aug 16, 2019
f730139
add metric aggs to request
nreese Aug 16, 2019
f8d4a27
fix bug where initial draw did not have styles set up
nreese Aug 16, 2019
771038a
make tooltips work
nreese Aug 16, 2019
f69d68b
Merge branch 'master' of github.com:elastic/kibana into pew-pew
nreese Aug 19, 2019
96394f3
fix import broken with merging master
nreese Aug 19, 2019
f7e6b1d
use custom labels in tooltips
nreese Aug 19, 2019
07667f7
Merge branch 'master' of github.com:elastic/kibana into pew-pew
nreese Aug 22, 2019
564512f
Merge branch 'master' of github.com:elastic/kibana into pew-pew
nreese Aug 26, 2019
b5b18b3
remove duplicate keys and clean up title and desc wording
nreese Aug 27, 2019
2934f2b
update source description
nreese Aug 27, 2019
b2299d3
update references migration to extract references for pew pew source
nreese Aug 27, 2019
d2fd34f
add percy visual test to ensure pew pew map data is fetch, processed,…
nreese Aug 27, 2019
ad5bdca
update title and description
nreese Aug 27, 2019
556c7a8
use GEO_FIELD_TYPES in filterGeoField function
nreese Aug 28, 2019
fa5f8d2
remove unneeded Fragment wrapper
nreese Aug 28, 2019
6ee0e0d
fix typo
nreese Aug 28, 2019
67028c6
update inspector description id and message
nreese Aug 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/maps/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions x-pack/legacy/plugins/maps/common/migrations/references.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [] }) {
Expand Down
46 changes: 45 additions & 1 deletion x-pack/legacy/plugins/maps/common/migrations/references.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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\"}}]`,
}
};

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
};
}
Original file line number Diff line number Diff line change
@@ -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>
);
}
}
Loading