[Maps] Add filter actions to tooltips (#33635)

This commit is contained in:
Thomas Neirynck 2019-04-08 13:03:56 -04:00 committed by GitHub
parent f39339d9d3
commit ae977f8636
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 686 additions and 84 deletions

View file

@ -174,9 +174,7 @@ export class FilterEditor extends Component {
/>
</h5>
</EuiTitle>
{this._renderQuery()}
{this._renderQueryPopover()}
</Fragment>
);

View file

@ -0,0 +1,237 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FeatureTooltip should not show close button and not show filter button 1`] = `
<Fragment>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexItem>
<EuiFlexGroup
direction="column"
gutterSize="none"
/>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
`;
exports[`FeatureTooltip should show both filter buttons and close button 1`] = `
<Fragment>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexItem
grow={true}
>
<EuiFlexGroup
alignItems="flexEnd"
direction="row"
justifyContent="flexEnd"
>
<EuiFlexItem
grow={false}
>
<EuiButtonIcon
aria-label="Close tooltip"
color="primary"
iconSize="m"
iconType="cross"
onClick={[Function]}
type="button"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexGroup
key="0"
>
<EuiFlexItem
grow={4}
>
<strong>
foo
</strong>
</EuiFlexItem>
<EuiFlexItem
grow={6}
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "bar",
}
}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiButtonIcon
aria-label="Filter on property"
className="mapFeatureTooltipFilterButton"
color="primary"
iconSize="m"
iconType="plusInCircle"
onClick={[Function]}
title="Filter on property"
type="button"
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup
key="1"
>
<EuiFlexItem
grow={4}
>
<strong>
foo
</strong>
</EuiFlexItem>
<EuiFlexItem
grow={6}
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "bar",
}
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
`;
exports[`FeatureTooltip should show close button, but not filter button 1`] = `
<Fragment>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexItem
grow={true}
>
<EuiFlexGroup
alignItems="flexEnd"
direction="row"
justifyContent="flexEnd"
>
<EuiFlexItem
grow={false}
>
<EuiButtonIcon
aria-label="Close tooltip"
color="primary"
iconSize="m"
iconType="cross"
onClick={[Function]}
type="button"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup
direction="column"
gutterSize="none"
/>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
`;
exports[`FeatureTooltip should show only filter button for filterable properties 1`] = `
<Fragment>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexItem>
<EuiFlexGroup
direction="column"
gutterSize="none"
>
<EuiFlexGroup
key="0"
>
<EuiFlexItem
grow={4}
>
<strong>
foo
</strong>
</EuiFlexItem>
<EuiFlexItem
grow={6}
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "bar",
}
}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiButtonIcon
aria-label="Filter on property"
className="mapFeatureTooltipFilterButton"
color="primary"
iconSize="m"
iconType="plusInCircle"
onClick={[Function]}
title="Filter on property"
type="button"
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup
key="1"
>
<EuiFlexItem
grow={4}
>
<strong>
foo
</strong>
</EuiFlexItem>
<EuiFlexItem
grow={6}
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "bar",
}
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
`;

View file

@ -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 (
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconType="plusInCircle"
title={i18n.translate('xpack.maps.tooltip.filterOnPropertyTitle', {
defaultMessage: 'Filter on property'
})}
onClick={() => {
this.props.closeTooltip();
const filterAction = tooltipProperty.getFilterAction();
filterAction();
}}
aria-label={i18n.translate('xpack.maps.tooltip.filterOnPropertyAriaLabel', {
defaultMessage: 'Filter on property'
})}
className="mapFeatureTooltipFilterButton"
/>
</EuiFlexItem>
);
}
_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 = (<span
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: this.props.properties[propertyName]
__html: tooltipProperty.getHtmlDisplayValue()
}}
/>);
return (
<div key={propertyName}>
<span><strong>{propertyName}</strong></span>
<span>{' '}</span>
{htmlValue}
</div>
<EuiFlexGroup key={index}>
<EuiFlexItem grow={4}>
<strong>{tooltipProperty.getPropertyName()}</strong>
</EuiFlexItem>
<EuiFlexItem grow={6}>
{htmlValue}
</EuiFlexItem>
{this._renderFilterButton(tooltipProperty, hasFilters)}
</EuiFlexGroup>
);
});
}
_renderCloseButton() {
if (!this.props.showCloseButton) {
return null;
}
return (
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem grow={true}>
<EuiFlexGroup alignItems="flexEnd" direction="row" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonIcon
onClick={this.props.closeTooltip}
iconType="cross"
aria-label={i18n.translate('xpack.maps.tooltip.closeAriaLabel', {
defaultMessage: 'Close tooltip'
})}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
);
}
render() {
return (
<Fragment>
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem grow={true}>
<EuiFlexGroup alignItems="flexEnd" direction="row" justifyContent="spaceBetween">
<EuiFlexItem>&nbsp;</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
onClick={this.props.onCloseClick}
iconType="cross"
aria-label={i18n.translate('xpack.maps.tooltip.closeAreaLabel', {
defaultMessage: 'Close tooltip'
})}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{this._renderCloseButton()}
<EuiFlexItem>
{this._renderProperties()}
<EuiFlexGroup direction="column" gutterSize="none">
{this._renderProperties()}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>

View file

@ -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(
<FeatureTooltip
{...defaultProps}
/>
);
expect(component)
.toMatchSnapshot();
});
test('should show close button, but not filter button', () => {
const component = shallowWithIntl(
<FeatureTooltip
{...defaultProps}
showCloseButton={true}
/>
);
expect(component)
.toMatchSnapshot();
});
test('should show only filter button for filterable properties', () => {
const component = shallowWithIntl(
<FeatureTooltip
{...defaultProps}
showFilterButtons={true}
properties={mockTooltipProperties}
/>
);
expect(component)
.toMatchSnapshot();
});
test('should show both filter buttons and close button', () => {
const component = shallowWithIntl(
<FeatureTooltip
{...defaultProps}
showFilterButtons={true}
showCloseButton={true}
properties={mockTooltipProperties}
/>
);
expect(component)
.toMatchSnapshot();
});
});

View file

@ -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));
}
};
}

View file

@ -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((<FeatureTooltip properties={content} onCloseClick={this._onTooltipClose}/>), this._tooltipContainer);
const isLocked = this.props.tooltipState.type === TOOLTIP_TYPE.LOCKED;
ReactDOM.render((
<FeatureTooltip
properties={content}
closeTooltip={this._onTooltipClose}
showFilterButtons={this.props.isFilterable && isLocked}
showCloseButton={isLocked}
/>
), 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();
}
}

View file

@ -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({

View file

@ -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;

View file

@ -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);
});

View file

@ -67,7 +67,7 @@ export class LeftInnerJoin {
return { ...featureCollection };
}
getJoinSource() {
getRightJoinSource() {
return this._rightSource;
}

View file

@ -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;
}
}
}

View file

@ -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() {

View file

@ -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;
}

View file

@ -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() {

View file

@ -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);
}
}

View file

@ -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]);
};
}
}

View file

@ -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);
};
}
}

View file

@ -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');
}
}

View file

@ -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() {

View file

@ -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;