diff --git a/x-pack/plugins/maps/public/components/layer_panel/filter_editor/filter_editor.js b/x-pack/plugins/maps/public/components/layer_panel/filter_editor/filter_editor.js
index c260b67d7de71..6d5cfe28c490a 100644
--- a/x-pack/plugins/maps/public/components/layer_panel/filter_editor/filter_editor.js
+++ b/x-pack/plugins/maps/public/components/layer_panel/filter_editor/filter_editor.js
@@ -174,9 +174,7 @@ export class FilterEditor extends Component {
/>
-
{this._renderQuery()}
-
{this._renderQueryPopover()}
);
diff --git a/x-pack/plugins/maps/public/components/map/__snapshots__/feature_tooltip.test.js.snap b/x-pack/plugins/maps/public/components/map/__snapshots__/feature_tooltip.test.js.snap
new file mode 100644
index 0000000000000..2ac044bf0fa14
--- /dev/null
+++ b/x-pack/plugins/maps/public/components/map/__snapshots__/feature_tooltip.test.js.snap
@@ -0,0 +1,237 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FeatureTooltip should not show close button and not show filter button 1`] = `
+
+
+
+
+
+
+
+`;
+
+exports[`FeatureTooltip should show both filter buttons and close button 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ foo
+
+
+
+
+
+
+
+
+
+
+
+
+ foo
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`FeatureTooltip should show close button, but not filter button 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`FeatureTooltip should show only filter button for filterable properties 1`] = `
+
+
+
+
+
+
+
+ foo
+
+
+
+
+
+
+
+
+
+
+
+
+ foo
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/maps/public/components/map/feature_tooltip.js b/x-pack/plugins/maps/public/components/map/feature_tooltip.js
index 9410800e48c2f..0b0a902e845e9 100644
--- a/x-pack/plugins/maps/public/components/map/feature_tooltip.js
+++ b/x-pack/plugins/maps/public/components/map/feature_tooltip.js
@@ -11,52 +11,93 @@ import { i18n } from '@kbn/i18n';
export class FeatureTooltip extends React.Component {
+ _renderFilterButton(tooltipProperty) {
+ if (!this.props.showFilterButtons || !tooltipProperty.isFilterable()) {
+ return null;
+ }
- _renderProperties() {
- return Object.keys(this.props.properties).map(propertyName => {
+ return (
+
+ {
+ this.props.closeTooltip();
+ const filterAction = tooltipProperty.getFilterAction();
+ filterAction();
+ }}
+ aria-label={i18n.translate('xpack.maps.tooltip.filterOnPropertyAriaLabel', {
+ defaultMessage: 'Filter on property'
+ })}
+ className="mapFeatureTooltipFilterButton"
+ />
+
+ );
+ }
+
+ _renderProperties(hasFilters) {
+ return this.props.properties.map((tooltipProperty, index) => {
/*
* Justification for dangerouslySetInnerHTML:
- * Propery value contains value generated by Field formatter
+ * Property value contains value generated by Field formatter
* Since these formatters produce raw HTML, this component needs to be able to render them as-is, relying
* on the field formatter to only produce safe HTML.
*/
-
const htmlValue = ();
+
return (
-
- {propertyName}
- {' '}
- {htmlValue}
-
+
+
+ {tooltipProperty.getPropertyName()}
+
+
+ {htmlValue}
+
+ {this._renderFilterButton(tooltipProperty, hasFilters)}
+
);
});
}
+ _renderCloseButton() {
+ if (!this.props.showCloseButton) {
+ return null;
+ }
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+
render() {
return (
-
-
-
-
-
-
-
-
+ {this._renderCloseButton()}
- {this._renderProperties()}
+
+ {this._renderProperties()}
+
diff --git a/x-pack/plugins/maps/public/components/map/feature_tooltip.test.js b/x-pack/plugins/maps/public/components/map/feature_tooltip.test.js
new file mode 100644
index 0000000000000..324798fa1c2d8
--- /dev/null
+++ b/x-pack/plugins/maps/public/components/map/feature_tooltip.test.js
@@ -0,0 +1,101 @@
+/*
+ * 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 { shallowWithIntl } from 'test_utils/enzyme_helpers';
+import { FeatureTooltip } from './feature_tooltip';
+
+class MockTooltipProperty {
+ constructor(key, value, isFilterable) {
+ this._key = key;
+ this._value = value;
+ this._isFilterable = isFilterable;
+ }
+
+ isFilterable() {
+ return this._isFilterable;
+ }
+
+ getFilterAction() {
+ return () => {};
+ }
+
+ getHtmlDisplayValue() {
+ return this._value;
+ }
+
+ getPropertyName() {
+ return this._key;
+ }
+}
+
+const defaultProps = {
+ properties: [],
+ closeTooltip: () => {},
+ showFilterButtons: false,
+ showCloseButton: false
+};
+
+
+const mockTooltipProperties = [
+ new MockTooltipProperty('foo', 'bar', true),
+ new MockTooltipProperty('foo', 'bar', false)
+];
+
+describe('FeatureTooltip', () => {
+
+ test('should not show close button and not show filter button', () => {
+ const component = shallowWithIntl(
+
+ );
+
+ expect(component)
+ .toMatchSnapshot();
+ });
+
+ test('should show close button, but not filter button', () => {
+ const component = shallowWithIntl(
+
+ );
+
+ expect(component)
+ .toMatchSnapshot();
+ });
+
+ test('should show only filter button for filterable properties', () => {
+ const component = shallowWithIntl(
+
+ );
+
+ expect(component)
+ .toMatchSnapshot();
+ });
+
+ test('should show both filter buttons and close button', () => {
+ const component = shallowWithIntl(
+
+ );
+
+ expect(component)
+ .toMatchSnapshot();
+ });
+
+
+});
diff --git a/x-pack/plugins/maps/public/components/map/mb/index.js b/x-pack/plugins/maps/public/components/map/mb/index.js
index 7e57eaa7ee336..b356eb015f4bf 100644
--- a/x-pack/plugins/maps/public/components/map/mb/index.js
+++ b/x-pack/plugins/maps/public/components/map/mb/index.js
@@ -16,10 +16,12 @@ import {
setTooltipState
} from '../../../actions/store_actions';
import { getTooltipState, getLayerList, getMapReady, getGoto } from '../../../selectors/map_selectors';
+import { getIsFilterable } from '../../../store/ui';
import { getInspectorAdapters } from '../../../store/non_serializable_instances';
function mapStateToProps(state = {}) {
return {
+ isFilterable: getIsFilterable(state),
isMapReady: getMapReady(state),
layerList: getLayerList(state),
goto: getGoto(state),
@@ -53,7 +55,6 @@ function mapDispatchToProps(dispatch) {
setTooltipState(tooltipState) {
dispatch(setTooltipState(tooltipState));
}
-
};
}
diff --git a/x-pack/plugins/maps/public/components/map/mb/view.js b/x-pack/plugins/maps/public/components/map/mb/view.js
index 53d6473cbc69b..0e129ac6f9aee 100644
--- a/x-pack/plugins/maps/public/components/map/mb/view.js
+++ b/x-pack/plugins/maps/public/components/map/mb/view.js
@@ -62,7 +62,6 @@ export class MBMapContainer extends React.Component {
featureId: targetFeature.properties[FEATURE_ID_PROPERTY_NAME],
location: popupAnchorLocation
});
-
};
_updateHoverTooltipState = _.debounce((e) => {
@@ -251,7 +250,15 @@ export class MBMapContainer extends React.Component {
if (!this._isMounted) {
return;
}
- ReactDOM.render((), this._tooltipContainer);
+ const isLocked = this.props.tooltipState.type === TOOLTIP_TYPE.LOCKED;
+ ReactDOM.render((
+
+ ), this._tooltipContainer);
this._mbPopup.setLngLat(location)
.setDOMContent(this._tooltipContainer)
@@ -270,8 +277,10 @@ export class MBMapContainer extends React.Component {
_syncTooltipState() {
if (this.props.tooltipState) {
+ this._mbMap.getCanvas().style.cursor = 'pointer';
this._showTooltip();
} else {
+ this._mbMap.getCanvas().style.cursor = '';
this._hideTooltip();
}
}
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/plugins/maps/public/embeddable/map_embeddable.js
index e3d9c52ab4740..5227b53f05273 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable.js
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.js
@@ -20,9 +20,9 @@ import {
setGotoWithCenter,
replaceLayerList,
setQuery,
- setRefreshConfig,
+ setRefreshConfig
} from '../actions/store_actions';
-import { setReadOnly } from '../store/ui';
+import { setReadOnly, setFilterable } from '../store/ui';
import { getInspectorAdapters } from '../store/non_serializable_instances';
import { getMapCenter, getMapZoom } from '../selectors/map_selectors';
@@ -82,6 +82,7 @@ export class MapEmbeddable extends Embeddable {
*/
render(domNode, containerState) {
this._store.dispatch(setReadOnly(true));
+ this._store.dispatch(setFilterable(true));
if (this._embeddableConfig.mapCenter) {
this._store.dispatch(setGotoWithCenter({
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js
index 8ab8efde42d97..50e5ce35d6eb6 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js
@@ -10,7 +10,7 @@ import { EmbeddableFactory } from 'ui/embeddable';
import { MapEmbeddable } from './map_embeddable';
import { indexPatternService } from '../kibana_services';
import { i18n } from '@kbn/i18n';
-import { createMapPath, MAP_SAVED_OBJECT_TYPE } from '../../common/constants';
+import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants';
export class MapEmbeddableFactory extends EmbeddableFactory {
@@ -22,7 +22,7 @@ export class MapEmbeddableFactory extends EmbeddableFactory {
defaultMessage: 'Map',
}),
type: MAP_SAVED_OBJECT_TYPE,
- getIconForSavedObject: () => 'gisApp',
+ getIconForSavedObject: () => APP_ICON
},
});
this._savedObjectLoader = gisMapSavedObjectLoader;
diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js
index d8d395ef414b1..6c80774383b2b 100644
--- a/x-pack/plugins/maps/public/kibana_services.js
+++ b/x-pack/plugins/maps/public/kibana_services.js
@@ -6,12 +6,12 @@
import { uiModules } from 'ui/modules';
import { SearchSourceProvider } from 'ui/courier';
-import { timefilter } from 'ui/timefilter/timefilter';
+import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
import { getRequestInspectorStats, getResponseInspectorStats } from 'ui/courier/utils/courier_inspector_utils';
-export const timeService = timefilter;
export let indexPatternService;
export let SearchSource;
+export let filterBarQueryFilter;
export async function fetchSearchSourceAndRecordWithInspector({ searchSource, requestId, requestName, requestDesc, inspectorAdapters }) {
const inspectorRequest = inspectorAdapters.requests.start(
@@ -39,4 +39,5 @@ uiModules.get('app/maps').run(($injector) => {
indexPatternService = $injector.get('indexPatterns');
const Private = $injector.get('Private');
SearchSource = Private(SearchSourceProvider);
+ filterBarQueryFilter = Private(FilterBarQueryFilterProvider);
});
diff --git a/x-pack/plugins/maps/public/shared/layers/joins/left_inner_join.js b/x-pack/plugins/maps/public/shared/layers/joins/left_inner_join.js
index d64b4806d0123..7034612335291 100644
--- a/x-pack/plugins/maps/public/shared/layers/joins/left_inner_join.js
+++ b/x-pack/plugins/maps/public/shared/layers/joins/left_inner_join.js
@@ -67,7 +67,7 @@ export class LeftInnerJoin {
return { ...featureCollection };
}
- getJoinSource() {
+ getRightJoinSource() {
return this._rightSource;
}
diff --git a/x-pack/plugins/maps/public/shared/layers/sources/es_join_source.js b/x-pack/plugins/maps/public/shared/layers/sources/es_join_source.js
index de911fdb8dda4..c141ab28e0f54 100644
--- a/x-pack/plugins/maps/public/shared/layers/sources/es_join_source.js
+++ b/x-pack/plugins/maps/public/shared/layers/sources/es_join_source.js
@@ -10,6 +10,7 @@ import { AbstractESSource } from './es_source';
import { Schemas } from 'ui/vis/editors/default/schemas';
import { AggConfigs } from 'ui/vis/agg_configs';
import { i18n } from '@kbn/i18n';
+import { ESTooltipProperty } from '../tooltips/es_tooltip_property';
const TERMS_AGG_NAME = 'join';
@@ -74,6 +75,10 @@ export class ESJoinSource extends AbstractESSource {
return [this._descriptor.indexPatternId];
}
+ getTerm() {
+ return this._descriptor.term;
+ }
+
_formatMetricKey(metric) {
const metricKey = metric.type !== 'count' ? `${metric.type}_of_${metric.field}` : metric.type;
return `__kbnjoin__${metricKey}_groupby_${this._descriptor.indexPatternTitle}.${this._descriptor.term}`;
@@ -180,4 +185,16 @@ export class ESJoinSource extends AbstractESSource {
async filterAndFormatPropertiesToHtml(properties) {
return await this.filterAndFormatPropertiesToHtmlForMetricFields(properties);
}
+
+ async createESTooltipProperty(propertyName, rawValue) {
+ try {
+ const indexPattern = await this._getIndexPattern();
+ if (!indexPattern) {
+ return null;
+ }
+ return new ESTooltipProperty(propertyName, rawValue, indexPattern);
+ } catch (e) {
+ return null;
+ }
+ }
}
diff --git a/x-pack/plugins/maps/public/shared/layers/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/shared/layers/sources/es_search_source/es_search_source.js
index e49520fdb029d..03d598c029de9 100644
--- a/x-pack/plugins/maps/public/shared/layers/sources/es_search_source/es_search_source.js
+++ b/x-pack/plugins/maps/public/shared/layers/sources/es_search_source/es_search_source.js
@@ -15,6 +15,8 @@ import { UpdateSourceEditor } from './update_source_editor';
import { ES_SEARCH } from '../../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
+import { ESTooltipProperty } from '../../tooltips/es_tooltip_property';
+
import { DEFAULT_ES_DOC_LIMIT, DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
export class ESSearchSource extends AbstractESSource {
@@ -167,30 +169,19 @@ export class ESSearchSource extends AbstractESSource {
}
async filterAndFormatPropertiesToHtml(properties) {
- const filteredProperties = {};
- this._descriptor.tooltipProperties.forEach(propertyName => {
- filteredProperties[propertyName] = _.get(properties, propertyName, '-');
- });
-
+ const tooltipProps = [];
let indexPattern;
try {
indexPattern = await this._getIndexPattern();
} catch(error) {
console.warn(`Unable to find Index pattern ${this._descriptor.indexPatternId}, values are not formatted`);
- return filteredProperties;
+ return [];
}
this._descriptor.tooltipProperties.forEach(propertyName => {
- const field = indexPattern.fields.byName[propertyName];
- if (!field) {
- return;
- }
- const htmlConverter = field.format.getConverterFor('html');
- filteredProperties[propertyName] = (htmlConverter) ? htmlConverter(filteredProperties[propertyName]) :
- field.format.convert(filteredProperties[propertyName]);
+ tooltipProps.push(new ESTooltipProperty(propertyName, properties[propertyName], indexPattern));
});
-
- return filteredProperties;
+ return tooltipProps;
}
isFilterByMapBounds() {
diff --git a/x-pack/plugins/maps/public/shared/layers/sources/es_source.js b/x-pack/plugins/maps/public/shared/layers/sources/es_source.js
index 54a83360fea3a..c348d22c33fb0 100644
--- a/x-pack/plugins/maps/public/shared/layers/sources/es_source.js
+++ b/x-pack/plugins/maps/public/shared/layers/sources/es_source.js
@@ -15,6 +15,8 @@ import { timefilter } from 'ui/timefilter/timefilter';
import _ from 'lodash';
import { AggConfigs } from 'ui/vis/agg_configs';
import { i18n } from '@kbn/i18n';
+import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_property';
+
import uuid from 'uuid/v4';
import { copyPersistentState } from '../../../store/util';
@@ -99,35 +101,24 @@ export class AbstractESSource extends AbstractVectorSource {
return properties;
}
- function formatMetricValue(metricField, propertyValue) {
- if (metricField.type === 'count') {
- return propertyValue;
- }
-
- const indexPatternField = indexPattern.fields.byName[metricField.field];
- if (!indexPatternField) {
- return propertyValue;
- }
-
- const htmlConverter = indexPatternField.format.getConverterFor('html');
- return (htmlConverter)
- ? htmlConverter(propertyValue)
- : indexPatternField.format.convert(propertyValue);
- }
const metricFields = this.getMetricFields();
- const tooltipProps = {};
+ const tooltipProperties = [];
metricFields.forEach((metricField) => {
let value;
for (const key in properties) {
if (properties.hasOwnProperty(key) && metricField.propertyKey === key) {
- value = formatMetricValue(metricField, properties[key]);
+ value = properties[key];
break;
}
}
- tooltipProps[metricField.propertyLabel] = (typeof value === 'undefined') ? '-' : value;
+
+ const tooltipProperty = new ESAggMetricTooltipProperty(metricField.propertyLabel, value, indexPattern, metricField);
+ tooltipProperties.push(tooltipProperty);
});
- return tooltipProps;
+
+ return tooltipProperties;
+
}
diff --git a/x-pack/plugins/maps/public/shared/layers/sources/vector_source.js b/x-pack/plugins/maps/public/shared/layers/sources/vector_source.js
index 5b2467a9da23e..7c1e8f14072f7 100644
--- a/x-pack/plugins/maps/public/shared/layers/sources/vector_source.js
+++ b/x-pack/plugins/maps/public/shared/layers/sources/vector_source.js
@@ -6,6 +6,7 @@
import { VectorLayer } from '../vector_layer';
+import { TooltipProperty } from '../tooltips/tooltip_property';
import { VectorStyle } from '../styles/vector_style';
import { AbstractSource } from './source';
import * as topojson from 'topojson-client';
@@ -96,15 +97,14 @@ export class AbstractVectorSource extends AbstractSource {
// Allow source to filter and format feature properties before displaying to user
async filterAndFormatPropertiesToHtml(properties) {
- //todo :this is quick hack... should revise (should model proeprties explicitly in vector_layer
- const props = {};
+ const tooltipProperties = [];
for (const key in properties) {
if (key.startsWith('__kbn')) {//these are system properties and should be ignored
continue;
}
- props[key] = _.escape(properties[key]);
+ tooltipProperties.push(new TooltipProperty(key, properties[key]));
}
- return props;
+ return tooltipProperties;
}
async isTimeAware() {
diff --git a/x-pack/plugins/maps/public/shared/layers/tooltips/es_aggmetric_tooltip_property.js b/x-pack/plugins/maps/public/shared/layers/tooltips/es_aggmetric_tooltip_property.js
new file mode 100644
index 0000000000000..ba8131f349542
--- /dev/null
+++ b/x-pack/plugins/maps/public/shared/layers/tooltips/es_aggmetric_tooltip_property.js
@@ -0,0 +1,39 @@
+/*
+ * 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 { ESTooltipProperty } from './es_tooltip_property';
+
+export class ESAggMetricTooltipProperty extends ESTooltipProperty {
+
+ constructor(propertyName, rawValue, indexPattern, metricField) {
+ super(propertyName, rawValue, indexPattern);
+ this._metricField = metricField;
+ }
+ isFilterable() {
+ return false;
+ }
+
+ getHtmlDisplayValue() {
+ if (typeof this._rawValue === 'undefined') {
+ return '-';
+ }
+ if (this._metricField.type === 'count') {
+ return this._rawValue;
+ }
+ const indexPatternField = this._indexPattern.fields.byName[this._metricField.field];
+ if (!indexPatternField) {
+ return this._rawValue;
+ }
+ const htmlConverter = indexPatternField.format.getConverterFor('html');
+
+ return (htmlConverter)
+ ? htmlConverter(this._rawValue)
+ : indexPatternField.format.convert(this._rawValue);
+
+ }
+
+}
diff --git a/x-pack/plugins/maps/public/shared/layers/tooltips/es_tooltip_property.js b/x-pack/plugins/maps/public/shared/layers/tooltips/es_tooltip_property.js
new file mode 100644
index 0000000000000..f3df3a198c0b0
--- /dev/null
+++ b/x-pack/plugins/maps/public/shared/layers/tooltips/es_tooltip_property.js
@@ -0,0 +1,51 @@
+/*
+ * 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 { buildPhraseFilter } from '@kbn/es-query';
+import { filterBarQueryFilter } from '../../../kibana_services';
+import { TooltipProperty } from './tooltip_property';
+import _ from 'lodash';
+
+export class ESTooltipProperty extends TooltipProperty {
+
+ constructor(propertyName, rawValue, indexPattern) {
+ super(propertyName, rawValue);
+ this._indexPattern = indexPattern;
+ }
+
+ getHtmlDisplayValue() {
+
+ if (typeof this._rawValue === 'undefined') {
+ return '-';
+ }
+
+ const field = this._indexPattern.fields.byName[this._propertyName];
+ if (!field) {
+ return _.escape(this._rawValue);
+ }
+ const htmlConverter = field.format.getConverterFor('html');
+ return htmlConverter ? htmlConverter(this._rawValue) : field.format.convert(this._rawValue);
+ }
+
+ isFilterable() {
+ const field = this._indexPattern.fields.byName[this._propertyName];
+ return field && (field.type === 'string' || field.type === 'date' || field.type === 'ip' || field.type === 'number');
+ }
+
+ getESFilter() {
+ return buildPhraseFilter(
+ this._indexPattern.fields.byName[this._propertyName],
+ this._rawValue,
+ this._indexPattern);
+ }
+
+ getFilterAction() {
+ return () => {
+ const phraseFilter = this.getESFilter();
+ filterBarQueryFilter.addFilters([phraseFilter]);
+ };
+ }
+}
diff --git a/x-pack/plugins/maps/public/shared/layers/tooltips/join_tooltip_property.js b/x-pack/plugins/maps/public/shared/layers/tooltips/join_tooltip_property.js
new file mode 100644
index 0000000000000..db48c94c1616d
--- /dev/null
+++ b/x-pack/plugins/maps/public/shared/layers/tooltips/join_tooltip_property.js
@@ -0,0 +1,56 @@
+/*
+ * 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 { filterBarQueryFilter } from '../../../kibana_services';
+import { TooltipProperty } from './tooltip_property';
+
+export class JoinTooltipProperty extends TooltipProperty {
+
+ constructor(tooltipProperty, leftInnerJoins) {
+ super();
+ this._tooltipProperty = tooltipProperty;
+ this._leftInnerJoins = leftInnerJoins;
+ }
+
+ isFilterable() {
+ return true;
+ }
+
+ getPropertyName() {
+ return this._tooltipProperty.getPropertyName();
+ }
+
+ getHtmlDisplayValue() {
+ return this._tooltipProperty.getHtmlDisplayValue();
+ }
+
+ getFilterAction() {
+ //dispatch all the filter actions to the query bar
+ //this relies on the de-duping of filterBarQueryFilter
+ return async () => {
+ const esFilters = [];
+ if (this._tooltipProperty.isFilterable()) {
+ esFilters.push(this._tooltipProperty.getESFilter());
+ }
+
+ for (let i = 0; i < this._leftInnerJoins.length; i++) {
+ const rightSource = this._leftInnerJoins[i].getRightJoinSource();
+ const esTooltipProperty = await rightSource.createESTooltipProperty(
+ rightSource.getTerm(),
+ this._tooltipProperty.getRawValue()
+ );
+ if (esTooltipProperty) {
+ const filter = esTooltipProperty.getESFilter();
+ esFilters.push(filter);
+ }
+ }
+ filterBarQueryFilter.addFilters(esFilters);
+ };
+ }
+
+
+}
diff --git a/x-pack/plugins/maps/public/shared/layers/tooltips/tooltip_property.js b/x-pack/plugins/maps/public/shared/layers/tooltips/tooltip_property.js
new file mode 100644
index 0000000000000..3f3a9ca2829bf
--- /dev/null
+++ b/x-pack/plugins/maps/public/shared/layers/tooltips/tooltip_property.js
@@ -0,0 +1,35 @@
+/*
+ * 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';
+
+export class TooltipProperty {
+
+ constructor(propertyName, rawValue) {
+ this._propertyName = propertyName;
+ this._rawValue = rawValue;
+ }
+
+ getPropertyName() {
+ return this._propertyName;
+ }
+
+ getHtmlDisplayValue() {
+ return _.escape(this._rawValue);
+ }
+
+ getRawValue() {
+ return this._rawValue;
+ }
+
+ isFilterable() {
+ return false;
+ }
+
+ getFilterAction() {
+ throw new Error('This property is not filterable');
+ }
+}
diff --git a/x-pack/plugins/maps/public/shared/layers/vector_layer.js b/x-pack/plugins/maps/public/shared/layers/vector_layer.js
index abd9e02e6a36d..f96ccda3d5457 100644
--- a/x-pack/plugins/maps/public/shared/layers/vector_layer.js
+++ b/x-pack/plugins/maps/public/shared/layers/vector_layer.js
@@ -10,6 +10,7 @@ import { VectorStyle } from './styles/vector_style';
import { LeftInnerJoin } from './joins/left_inner_join';
import { FEATURE_ID_PROPERTY_NAME, SOURCE_DATA_ID_ORIGIN } from '../../../common/constants';
import _ from 'lodash';
+import { JoinTooltipProperty } from './tooltips/join_tooltip_property';
const EMPTY_FEATURE_COLLECTION = {
type: 'FeatureCollection',
@@ -159,6 +160,7 @@ export class VectorLayer extends AbstractLayer {
}
async _canSkipSourceUpdate(source, sourceDataId, searchFilters) {
+
const timeAware = await source.isTimeAware();
const refreshTimerAware = await source.isRefreshTimerAware();
const extentAware = source.isFilterByMapBounds();
@@ -230,7 +232,7 @@ export class VectorLayer extends AbstractLayer {
async _syncJoin({ join, startLoading, stopLoading, onLoadError, dataFilters }) {
- const joinSource = join.getJoinSource();
+ const joinSource = join.getRightJoinSource();
const sourceDataId = join.getSourceId();
const requestToken = Symbol(`layer-join-refresh:${ this.getId()} - ${sourceDataId}`);
@@ -517,13 +519,34 @@ export class VectorLayer extends AbstractLayer {
return [this._getMbPointLayerId(), this._getMbLineLayerId(), this._getMbPolygonLayerId()];
}
+
+ _addJoinsToSourceTooltips(tooltipsFromSource) {
+ for (let i = 0; i < tooltipsFromSource.length; i++) {
+ const tooltipProperty = tooltipsFromSource[i];
+ const matchingJoins = [];
+ for (let j = 0; j < this._joins.length; j++) {
+ if (this._joins[j].getLeftFieldName() === tooltipProperty.getPropertyName()) {
+ matchingJoins.push(this._joins[j]);
+ }
+ }
+ if (matchingJoins.length) {
+ tooltipsFromSource[i] = new JoinTooltipProperty(tooltipProperty, matchingJoins);
+ }
+ }
+ }
+
+
async getPropertiesForTooltip(properties) {
- const tooltipsFromSource = await this._source.filterAndFormatPropertiesToHtml(properties);
+
+ let allTooltips = await this._source.filterAndFormatPropertiesToHtml(properties);
+ this._addJoinsToSourceTooltips(allTooltips);
+
+
for (let i = 0; i < this._joins.length; i++) {
const propsFromJoin = await this._joins[i].filterAndFormatPropertiesForTooltip(properties);
- Object.assign(tooltipsFromSource, propsFromJoin);
+ allTooltips = [...allTooltips, ...propsFromJoin];
}
- return tooltipsFromSource;
+ return allTooltips;
}
canShowTooltip() {
diff --git a/x-pack/plugins/maps/public/store/ui.js b/x-pack/plugins/maps/public/store/ui.js
index 7804fe26b6fe5..6334d49b0a320 100644
--- a/x-pack/plugins/maps/public/store/ui.js
+++ b/x-pack/plugins/maps/public/store/ui.js
@@ -3,13 +3,12 @@
* 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';
-
export const UPDATE_FLYOUT = 'UPDATE_FLYOUT';
export const CLOSE_SET_VIEW = 'CLOSE_SET_VIEW';
export const OPEN_SET_VIEW = 'OPEN_SET_VIEW';
export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
export const SET_READ_ONLY = 'SET_READ_ONLY';
+export const SET_FILTERABLE = 'IS_FILTERABLE';
export const FLYOUT_STATE = {
NONE: 'NONE',
LAYER_PANEL: 'LAYER_PANEL',
@@ -20,6 +19,7 @@ const INITIAL_STATE = {
flyoutDisplay: FLYOUT_STATE.NONE,
isFullScreen: false,
isReadOnly: false,
+ isFilterable: false
};
// Reducer
@@ -35,6 +35,8 @@ export function ui(state = INITIAL_STATE, action) {
return { ...state, isFullScreen: action.isFullScreen };
case SET_READ_ONLY:
return { ...state, isReadOnly: action.isReadOnly };
+ case SET_FILTERABLE:
+ return { ...state, isFilterable: action.isFilterable };
default:
return state;
}
@@ -76,9 +78,17 @@ export function setReadOnly(isReadOnly) {
};
}
+export function setFilterable(isFilterable) {
+ return {
+ type: SET_FILTERABLE,
+ isFilterable
+ };
+}
+
// Selectors
export const getFlyoutDisplay = ({ ui }) => ui && ui.flyoutDisplay
|| INITIAL_STATE.flyoutDisplay;
-export const getIsSetViewOpen = ({ ui }) => _.get(ui, 'isSetViewOpen', false);
-export const getIsFullScreen = ({ ui }) => _.get(ui, 'isFullScreen', false);
-export const getIsReadOnly = ({ ui }) => _.get(ui, 'isReadOnly', true);
+export const getIsSetViewOpen = ({ ui }) => ui.isSetViewOpen;
+export const getIsFullScreen = ({ ui }) => ui.isFullScreen;
+export const getIsReadOnly = ({ ui }) => ui.isReadOnly;
+export const getIsFilterable = ({ ui }) => ui.isFilterable;