From acb78a60ba48304c812888965f39909a6c55bf16 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Sat, 27 Apr 2019 19:14:37 -0600 Subject: [PATCH] [Maps] ignore global query layer setting (#35542) (#35694) * [Maps] ignore global query layer setting * do not include index pattern id in query bar indexPatterns when applyGlobalQuery is disabled * update how embeddable factory extracts indexPatterns so only queryable index patterns are included in list * support applyGlobalQuery for heatmap and getBounds * add functional tests to join layer * show filter section when layer has join * move checkbox to layer settings panel * text review * set checkbox to false when disabled * do not trigger refetch when global query and global filter changes and applyGlobalQuery is disabled * rename applyGlobalQuery to getApplyGlobalQuery * throw error in map embeddable factory if layerListJSON can not be parsed * remove extra space * update zoom range slider label and remove nested EuiFormRow --- .../maps/public/actions/store_actions.js | 22 +++ .../public/angular/get_initial_layers.test.js | 35 ++-- .../maps/public/angular/map_controller.js | 4 +- .../filter_editor/filter_editor.js | 6 + .../__snapshots__/layer_errors.test.js.snap | 2 +- .../layer_panel/layer_errors/layer_errors.js | 2 +- .../layer_panel/layer_settings/index.js | 5 + .../layer_settings/layer_settings.js | 84 +++++--- .../source_settings.test.js.snap | 2 +- .../source_settings/source_settings.js | 2 +- .../components/layer_panel/style_tabs/view.js | 2 +- .../embeddable/map_embeddable_factory.js | 23 ++- .../maps/public/selectors/map_selectors.js | 13 ++ .../public/shared/layers/heatmap_layer.js | 20 +- .../maps/public/shared/layers/layer.js | 14 ++ .../public/shared/layers/sources/es_source.js | 12 +- .../layers/util/is_refresh_only_query.js | 13 ++ .../maps/public/shared/layers/vector_layer.js | 46 +++-- .../public/shared/layers/vector_layer.test.js | 185 ++++++++++++++++++ x-pack/test/functional/apps/maps/joins.js | 29 ++- .../test/functional/page_objects/gis_page.js | 16 ++ 21 files changed, 458 insertions(+), 79 deletions(-) create mode 100644 x-pack/plugins/maps/public/shared/layers/util/is_refresh_only_query.js create mode 100644 x-pack/plugins/maps/public/shared/layers/vector_layer.test.js diff --git a/x-pack/plugins/maps/public/actions/store_actions.js b/x-pack/plugins/maps/public/actions/store_actions.js index 783b5c2e66a19..1482f9abc7b33 100644 --- a/x-pack/plugins/maps/public/actions/store_actions.js +++ b/x-pack/plugins/maps/public/actions/store_actions.js @@ -156,6 +156,15 @@ export function addLayer(layerDescriptor) { }; } +// Do not use when rendering a map. Method exists to enable selectors for getLayerList when +// rendering is not needed. +export function addLayerWithoutDataSync(layerDescriptor) { + return { + type: ADD_LAYER, + layer: layerDescriptor, + }; +} + function setLayerDataLoadErrorStatus(layerId, errorMessage) { return dispatch => { dispatch({ @@ -511,6 +520,19 @@ export function setLayerQuery(id, query) { }; } +export function setLayerApplyGlobalQuery(id, applyGlobalQuery) { + return (dispatch) => { + dispatch({ + type: UPDATE_LAYER_PROP, + id, + propName: 'applyGlobalQuery', + newValue: applyGlobalQuery, + }); + + dispatch(syncDataForLayer(id)); + }; +} + export function removeSelectedLayer() { return (dispatch, getState) => { const state = getState(); diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js index fe675642b0adf..1ed53f4286857 100644 --- a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js @@ -62,22 +62,23 @@ describe('Saved object does not have layer list', () => { }; const layers = getInitialLayers(null); expect(layers).toEqual([{ - 'alpha': 1, - '__dataRequests': [], - 'id': layers[0].id, - 'label': null, - 'maxZoom': 24, - 'minZoom': 0, - 'sourceDescriptor': { - 'type': 'EMS_TMS', - 'id': 'road_map', + alpha: 1, + __dataRequests: [], + id: layers[0].id, + applyGlobalQuery: true, + label: null, + maxZoom: 24, + minZoom: 0, + sourceDescriptor: { + type: 'EMS_TMS', + id: 'road_map', }, - 'style': { - 'properties': {}, - 'type': 'TILE', + style: { + properties: {}, + type: 'TILE', }, - 'type': 'TILE', - 'visible': true, + type: 'TILE', + visible: true, }]); }); @@ -91,9 +92,10 @@ describe('Saved object does not have layer list', () => { const layers = getInitialLayers(null); expect(layers).toEqual([{ - 'alpha': 1, + alpha: 1, __dataRequests: [], id: layers[0].id, + applyGlobalQuery: true, label: null, maxZoom: 24, minZoom: 0, @@ -117,9 +119,10 @@ describe('Saved object does not have layer list', () => { const layers = getInitialLayers(null); expect(layers).toEqual([{ - 'alpha': 1, + alpha: 1, __dataRequests: [], id: layers[0].id, + applyGlobalQuery: true, label: null, maxZoom: 24, minZoom: 0, diff --git a/x-pack/plugins/maps/public/angular/map_controller.js b/x-pack/plugins/maps/public/angular/map_controller.js index 7603ecdc44aa5..13db2dc3a6e1d 100644 --- a/x-pack/plugins/maps/public/angular/map_controller.js +++ b/x-pack/plugins/maps/public/angular/map_controller.js @@ -35,7 +35,7 @@ import { setReadOnly, setIsLayerTOCOpen } from '../store/ui'; -import { getUniqueIndexPatternIds } from '../selectors/map_selectors'; +import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; import { getInspectorAdapters } from '../store/non_serializable_instances'; import { Inspector } from 'ui/inspector'; import { DocTitleProvider } from 'ui/doc_title'; @@ -197,7 +197,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage }); } - const nextIndexPatternIds = getUniqueIndexPatternIds(store.getState()); + const nextIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState()); if (nextIndexPatternIds !== prevIndexPatternIds) { prevIndexPatternIds = nextIndexPatternIds; updateIndexPatterns(nextIndexPatternIds); 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 6d5cfe28c490a..ab8b5f64f4d89 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 @@ -128,6 +128,7 @@ export class FilterEditor extends Component {

+ ); } @@ -174,8 +175,13 @@ export class FilterEditor extends Component { /> + + + {this._renderQuery()} + {this._renderQueryPopover()} + ); } diff --git a/x-pack/plugins/maps/public/components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap b/x-pack/plugins/maps/public/components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap index d8288ddb57bc7..5e14de8c4a3dd 100644 --- a/x-pack/plugins/maps/public/components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap +++ b/x-pack/plugins/maps/public/components/layer_panel/layer_errors/__snapshots__/layer_errors.test.js.snap @@ -14,7 +14,7 @@ exports[`Should render errors when layer has errors 1`] = `

`; diff --git a/x-pack/plugins/maps/public/components/layer_panel/layer_errors/layer_errors.js b/x-pack/plugins/maps/public/components/layer_panel/layer_errors/layer_errors.js index 755a1ed39ff8d..8ac96bd3ebeea 100644 --- a/x-pack/plugins/maps/public/components/layer_panel/layer_errors/layer_errors.js +++ b/x-pack/plugins/maps/public/components/layer_panel/layer_errors/layer_errors.js @@ -31,7 +31,7 @@ export function LayerErrors({ layer }) { {layer.getErrors()}

- + ); } diff --git a/x-pack/plugins/maps/public/components/layer_panel/layer_settings/index.js b/x-pack/plugins/maps/public/components/layer_panel/layer_settings/index.js index e0a1275155211..c12f0580ca8ab 100644 --- a/x-pack/plugins/maps/public/components/layer_panel/layer_settings/index.js +++ b/x-pack/plugins/maps/public/components/layer_panel/layer_settings/index.js @@ -12,12 +12,14 @@ import { updateLayerMaxZoom, updateLayerMinZoom, updateLayerAlpha, + setLayerApplyGlobalQuery, } from '../../../actions/store_actions'; function mapStateToProps(state = {}) { const selectedLayer = getSelectedLayer(state); return { alpha: selectedLayer.getAlpha(), + applyGlobalQuery: selectedLayer.getApplyGlobalQuery(), label: selectedLayer.getLabel(), layerId: selectedLayer.getId(), maxZoom: selectedLayer.getMaxZoom(), @@ -32,6 +34,9 @@ function mapDispatchToProps(dispatch) { updateMinZoom: (id, minZoom) => dispatch(updateLayerMinZoom(id, minZoom)), updateMaxZoom: (id, maxZoom) => dispatch(updateLayerMaxZoom(id, maxZoom)), updateAlpha: (id, alpha) => dispatch(updateLayerAlpha(id, alpha)), + setLayerApplyGlobalQuery: (layerId, applyGlobalQuery) => { + dispatch(setLayerApplyGlobalQuery(layerId, applyGlobalQuery)); + } }; } diff --git a/x-pack/plugins/maps/public/components/layer_panel/layer_settings/layer_settings.js b/x-pack/plugins/maps/public/components/layer_panel/layer_settings/layer_settings.js index 0a01498986e90..3d626e3083dba 100644 --- a/x-pack/plugins/maps/public/components/layer_panel/layer_settings/layer_settings.js +++ b/x-pack/plugins/maps/public/components/layer_panel/layer_settings/layer_settings.js @@ -14,6 +14,8 @@ import { EuiFormRow, EuiFieldText, EuiSpacer, + EuiSwitch, + EuiToolTip, } from '@elastic/eui'; import { ValidatedRange } from '../../../shared/components/validated_range'; @@ -40,35 +42,26 @@ export function LayerSettings(props) { props.updateAlpha(props.layerId, alpha); }; + const onApplyGlobalQueryChange = event => { + props.setLayerApplyGlobalQuery(props.layerId, event.target.checked); + }; + const renderZoomSliders = () => { return ( - - - - - - - - + ); }; @@ -115,6 +108,43 @@ export function LayerSettings(props) { ); }; + const renderApplyGlobalQueryCheckbox = () => { + const layerSupportsGlobalQuery = props.layer.getIndexPatternIds().length; + + const applyGlobalQueryCheckbox = ( + + + + ); + + if (layerSupportsGlobalQuery) { + return applyGlobalQueryCheckbox; + } + + return ( + + {applyGlobalQueryCheckbox} + + ); + }; + return ( @@ -131,13 +161,15 @@ export function LayerSettings(props) { - + {renderLabel()} {renderZoomSliders()} {renderAlphaSlider()} + + {renderApplyGlobalQueryCheckbox()} diff --git a/x-pack/plugins/maps/public/components/layer_panel/source_settings/__snapshots__/source_settings.test.js.snap b/x-pack/plugins/maps/public/components/layer_panel/source_settings/__snapshots__/source_settings.test.js.snap index d83030e544574..f1bc275380b6a 100644 --- a/x-pack/plugins/maps/public/components/layer_panel/source_settings/__snapshots__/source_settings.test.js.snap +++ b/x-pack/plugins/maps/public/components/layer_panel/source_settings/__snapshots__/source_settings.test.js.snap @@ -23,7 +23,7 @@ exports[`Should render source settings editor 1`] = `
mockSourceEditor diff --git a/x-pack/plugins/maps/public/components/layer_panel/source_settings/source_settings.js b/x-pack/plugins/maps/public/components/layer_panel/source_settings/source_settings.js index 6a1a75327427a..fa6fd90cce0ec 100644 --- a/x-pack/plugins/maps/public/components/layer_panel/source_settings/source_settings.js +++ b/x-pack/plugins/maps/public/components/layer_panel/source_settings/source_settings.js @@ -44,7 +44,7 @@ export function SourceSettings({ layer, updateSourceProp }) { - + {sourceSettingsEditor} diff --git a/x-pack/plugins/maps/public/components/layer_panel/style_tabs/view.js b/x-pack/plugins/maps/public/components/layer_panel/style_tabs/view.js index a692dc20a4788..5aedcc02ecc1c 100644 --- a/x-pack/plugins/maps/public/components/layer_panel/style_tabs/view.js +++ b/x-pack/plugins/maps/public/components/layer_panel/style_tabs/view.js @@ -40,7 +40,7 @@ export function StyleTabs({ layer, updateStyle }) {
{Style.getDisplayName()}
{description} - + {styleEditor}
); 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 50e5ce35d6eb6..ace9d42494783 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js @@ -11,6 +11,9 @@ import { MapEmbeddable } from './map_embeddable'; import { indexPatternService } from '../kibana_services'; import { i18n } from '@kbn/i18n'; import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; +import { createMapStore } from '../store/store'; +import { addLayerWithoutDataSync } from '../actions/store_actions'; +import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; export class MapEmbeddableFactory extends EmbeddableFactory { @@ -28,8 +31,21 @@ export class MapEmbeddableFactory extends EmbeddableFactory { this._savedObjectLoader = gisMapSavedObjectLoader; } - async _getIndexPatterns(indexPatternIds = []) { - const promises = indexPatternIds.map(async (indexPatternId) => { + async _getIndexPatterns(layerListJSON) { + // Need to extract layerList from store to get queryable index pattern ids + const store = createMapStore(); + try { + JSON.parse(layerListJSON).forEach(layerDescriptor => { + store.dispatch(addLayerWithoutDataSync(layerDescriptor)); + }); + } catch (error) { + throw new Error(i18n.translate('xpack.maps.mapEmbeddableFactory', { + defaultMessage: 'Unable to load map, malformed saved object', + })); + } + const queryableIndexPatternIds = getQueryableUniqueIndexPatternIds(store.getState()); + + const promises = queryableIndexPatternIds.map(async (indexPatternId) => { try { return await indexPatternService.get(indexPatternId); } catch (error) { @@ -44,7 +60,8 @@ export class MapEmbeddableFactory extends EmbeddableFactory { async create(panelMetadata, onEmbeddableStateChanged) { const savedMap = await this._savedObjectLoader.get(panelMetadata.id); - const indexPatterns = await this._getIndexPatterns(savedMap.indexPatternIds); + + const indexPatterns = await this._getIndexPatterns(savedMap.layerListJSON); return new MapEmbeddable({ onEmbeddableStateChanged, diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.js b/x-pack/plugins/maps/public/selectors/map_selectors.js index e8f7af3c7dc4d..a83f609d26c2b 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/plugins/maps/public/selectors/map_selectors.js @@ -170,6 +170,7 @@ export const getSelectedLayerJoinDescriptors = createSelector( }); }); +// Get list of unique index patterns used by all layers export const getUniqueIndexPatternIds = createSelector( getLayerList, (layerList) => { @@ -181,6 +182,18 @@ export const getUniqueIndexPatternIds = createSelector( } ); +// Get list of unique index patterns, excluding index patterns from layers that disable applyGlobalQuery +export const getQueryableUniqueIndexPatternIds = createSelector( + getLayerList, + (layerList) => { + const indexPatternIds = []; + layerList.forEach(layer => { + indexPatternIds.push(...layer.getQueryableIndexPatternIds()); + }); + return _.uniq(indexPatternIds); + } +); + export const hasDirtyState = createSelector(getLayerListRaw, (layerListRaw) => { return layerListRaw.some(layerDescriptor => { const currentState = copyPersistentState(layerDescriptor); diff --git a/x-pack/plugins/maps/public/shared/layers/heatmap_layer.js b/x-pack/plugins/maps/public/shared/layers/heatmap_layer.js index 93323f7c96465..fe93b9ffb689d 100644 --- a/x-pack/plugins/maps/public/shared/layers/heatmap_layer.js +++ b/x-pack/plugins/maps/public/shared/layers/heatmap_layer.js @@ -10,6 +10,7 @@ import { AbstractLayer } from './layer'; import { EuiIcon } from '@elastic/eui'; import { HeatmapStyle } from './styles/heatmap_style'; import { SOURCE_DATA_ID_ORIGIN } from '../../../common/constants'; +import { isRefreshOnlyQuery } from './util/is_refresh_only_query'; const SCALED_PROPERTY_NAME = '__kbn_heatmap_weight__';//unique name to store scaled value for weighting @@ -45,7 +46,6 @@ export class HeatmapLayer extends AbstractLayer { return metricfields[0].propertyKey; } - _getMbLayerId() { return this.getId() + '_heatmap'; } @@ -134,13 +134,19 @@ export class HeatmapLayer extends AbstractLayer { const updateDueToExtent = this.updateDueToExtent(this._source, meta, searchFilters); - const updateDueToQuery = searchFilters.query - && !_.isEqual(meta.query, searchFilters.query); + let updateDueToQuery = false; + let updateDueToFilters = false; + if (searchFilters.applyGlobalQuery) { + updateDueToQuery = !_.isEqual(meta.query, searchFilters.query); + updateDueToFilters = !_.isEqual(meta.filters, searchFilters.filters); + } else { + // Global filters and query are not applied to layer search request so no re-fetch required. + // Exception is "Refresh" query. + updateDueToQuery = isRefreshOnlyQuery(meta.query, searchFilters.query); + } const updateDueToLayerQuery = searchFilters.layerQuery && !_.isEqual(meta.layerQuery, searchFilters.layerQuery); - - const updateDueToFilters = searchFilters.filters - && !_.isEqual(meta.filters, searchFilters.filters); + const updateDueToApplyGlobalQuery = meta.applyGlobalQuery !== searchFilters.applyGlobalQuery; const updateDueToMetricChange = !_.isEqual(meta.metric, searchFilters.metric); @@ -150,6 +156,7 @@ export class HeatmapLayer extends AbstractLayer { && !updateDueToRefreshTimer && !updateDueToQuery && !updateDueToLayerQuery + && !updateDueToApplyGlobalQuery && !updateDueToFilters && !updateDueToMetricChange ) { @@ -163,6 +170,7 @@ export class HeatmapLayer extends AbstractLayer { return { ...dataFilters, layerQuery: this.getQuery(), + applyGlobalQuery: this.getApplyGlobalQuery(), geogridPrecision: this._source.getGeoGridPrecision(dataFilters.zoom), metric: this._getPropKeyOfSelectedMetric() }; diff --git a/x-pack/plugins/maps/public/shared/layers/layer.js b/x-pack/plugins/maps/public/shared/layers/layer.js index e32be4554424e..0e72e3960dc86 100644 --- a/x-pack/plugins/maps/public/shared/layers/layer.js +++ b/x-pack/plugins/maps/public/shared/layers/layer.js @@ -42,7 +42,9 @@ export class AbstractLayer { layerDescriptor.maxZoom = _.get(options, 'maxZoom', 24); layerDescriptor.alpha = _.get(options, 'alpha', 0.75); layerDescriptor.visible = _.get(options, 'visible', true); + layerDescriptor.applyGlobalQuery = _.get(options, 'applyGlobalQuery', true); layerDescriptor.style = _.get(options, 'style', {}); + return layerDescriptor; } @@ -144,6 +146,10 @@ export class AbstractLayer { return this._descriptor.query; } + getApplyGlobalQuery() { + return this._descriptor.applyGlobalQuery; + } + getZoomConfig() { return { minZoom: this._descriptor.minZoom, @@ -262,6 +268,14 @@ export class AbstractLayer { return []; } + getQueryableIndexPatternIds() { + if (this.getApplyGlobalQuery()) { + return this.getIndexPatternIds(); + } + + return []; + } + async getOrdinalFields() { return []; } 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 c348d22c33fb0..c089bede1a14d 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 @@ -142,7 +142,9 @@ export class AbstractESSource extends AbstractVectorSource { async _makeSearchSource(searchFilters, limit) { const indexPattern = await this._getIndexPattern(); const isTimeAware = await this.isTimeAware(); - const allFilters = [...searchFilters.filters]; + const applyGlobalQuery = _.get(searchFilters, 'applyGlobalQuery', true); + const globalFilters = applyGlobalQuery ? searchFilters.filters : []; + const allFilters = [...globalFilters]; if (this.isFilterByMapBounds() && searchFilters.buffer) {//buffer can be empty const geoField = await this._getGeoField(); allFilters.push(createExtentFilter(searchFilters.buffer, geoField.name, geoField.type)); @@ -155,7 +157,9 @@ export class AbstractESSource extends AbstractVectorSource { searchSource.setField('index', indexPattern); searchSource.setField('size', limit); searchSource.setField('filter', allFilters); - searchSource.setField('query', searchFilters.query); + if (applyGlobalQuery) { + searchSource.setField('query', searchFilters.query); + } if (searchFilters.layerQuery) { const layerSearchSource = new SearchSource(); @@ -167,9 +171,9 @@ export class AbstractESSource extends AbstractVectorSource { return searchSource; } - async getBoundsForFilters({ layerQuery, query, timeFilters, filters }) { + async getBoundsForFilters({ layerQuery, query, timeFilters, filters, applyGlobalQuery }) { - const searchSource = await this._makeSearchSource({ layerQuery, query, timeFilters, filters }, 0); + const searchSource = await this._makeSearchSource({ layerQuery, query, timeFilters, filters, applyGlobalQuery }, 0); const geoField = await this._getGeoField(); const indexPattern = await this._getIndexPattern(); diff --git a/x-pack/plugins/maps/public/shared/layers/util/is_refresh_only_query.js b/x-pack/plugins/maps/public/shared/layers/util/is_refresh_only_query.js new file mode 100644 index 0000000000000..a8c6341863517 --- /dev/null +++ b/x-pack/plugins/maps/public/shared/layers/util/is_refresh_only_query.js @@ -0,0 +1,13 @@ +/* + * 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. + */ + +// Refresh only query is query where timestamps are different but query is the same. +// Triggered by clicking "Refresh" button in QueryBar +export function isRefreshOnlyQuery(prevQuery, newQuery) { + return prevQuery.queryLastTriggeredAt !== newQuery.queryLastTriggeredAt + && prevQuery.language === newQuery.language + && prevQuery.query === newQuery.query; +} 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 6262e26b81171..1abca50abcc9b 100644 --- a/x-pack/plugins/maps/public/shared/layers/vector_layer.js +++ b/x-pack/plugins/maps/public/shared/layers/vector_layer.js @@ -11,6 +11,7 @@ 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'; +import { isRefreshOnlyQuery } from './util/is_refresh_only_query'; const EMPTY_FEATURE_COLLECTION = { type: 'FeatureCollection', @@ -207,10 +208,18 @@ export class VectorLayer extends AbstractLayer { let updateDueToQuery = false; let updateDueToFilters = false; let updateDueToLayerQuery = false; + let updateDueToApplyGlobalQuery = false; if (isQueryAware) { - updateDueToQuery = !_.isEqual(meta.query, searchFilters.query); - updateDueToFilters = !_.isEqual(meta.filters, searchFilters.filters); + updateDueToApplyGlobalQuery = meta.applyGlobalQuery !== searchFilters.applyGlobalQuery; updateDueToLayerQuery = !_.isEqual(meta.layerQuery, searchFilters.layerQuery); + if (searchFilters.applyGlobalQuery) { + updateDueToQuery = !_.isEqual(meta.query, searchFilters.query); + updateDueToFilters = !_.isEqual(meta.filters, searchFilters.filters); + } else { + // Global filters and query are not applied to layer search request so no re-fetch required. + // Exception is "Refresh" query. + updateDueToQuery = isRefreshOnlyQuery(meta.query, searchFilters.query); + } } let updateDueToPrecisionChange = false; @@ -227,6 +236,7 @@ export class VectorLayer extends AbstractLayer { && !updateDueToQuery && !updateDueToFilters && !updateDueToLayerQuery + && !updateDueToApplyGlobalQuery && !updateDueToPrecisionChange; } @@ -236,22 +246,27 @@ export class VectorLayer extends AbstractLayer { const sourceDataId = join.getSourceId(); const requestToken = Symbol(`layer-join-refresh:${ this.getId()} - ${sourceDataId}`); + const searchFilters = { + ...dataFilters, + applyGlobalQuery: this.getApplyGlobalQuery(), + }; + const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, searchFilters); + if (canSkip) { + const sourceDataRequest = this._findDataRequestForSource(sourceDataId); + const propertiesMap = sourceDataRequest ? sourceDataRequest.getData() : null; + return { + dataHasChanged: false, + join: join, + propertiesMap: propertiesMap + }; + } + try { - const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, dataFilters); - if (canSkip) { - const sourceDataRequest = this._findDataRequestForSource(sourceDataId); - const propertiesMap = sourceDataRequest ? sourceDataRequest.getData() : null; - return { - dataHasChanged: false, - join: join, - propertiesMap: propertiesMap - }; - } - startLoading(sourceDataId, requestToken, dataFilters); + startLoading(sourceDataId, requestToken, searchFilters); const leftSourceName = await this.getSourceName(); const { propertiesMap - } = await joinSource.getPropertiesMap(dataFilters, leftSourceName, join.getLeftFieldName()); + } = await joinSource.getPropertiesMap(searchFilters, leftSourceName, join.getLeftFieldName()); stopLoading(sourceDataId, requestToken, propertiesMap); return { dataHasChanged: true, @@ -289,7 +304,8 @@ export class VectorLayer extends AbstractLayer { ...dataFilters, fieldNames: _.uniq(fieldNames).sort(), geogridPrecision: this._source.getGeoGridPrecision(dataFilters.zoom), - layerQuery: this.getQuery() + layerQuery: this.getQuery(), + applyGlobalQuery: this.getApplyGlobalQuery(), }; } diff --git a/x-pack/plugins/maps/public/shared/layers/vector_layer.test.js b/x-pack/plugins/maps/public/shared/layers/vector_layer.test.js new file mode 100644 index 0000000000000..85937c2783cce --- /dev/null +++ b/x-pack/plugins/maps/public/shared/layers/vector_layer.test.js @@ -0,0 +1,185 @@ +/* + * 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. + */ + +jest.mock('./joins/left_inner_join', () => ({ + LeftInnerJoin: Object +})); + +jest.mock('./tooltips/join_tooltip_property', () => ({ + JoinTooltipProperty: Object +})); + +import { VectorLayer } from './vector_layer'; + +describe('_canSkipSourceUpdate', () => { + const SOURCE_DATA_REQUEST_ID = 'foo'; + + describe('isQueryAware', () => { + + const queryAwareSourceMock = { + isTimeAware: () => { return false; }, + isRefreshTimerAware: () => { return false; }, + isFilterByMapBounds: () => { return false; }, + isFieldAware: () => { return false; }, + isQueryAware: () => { return true; }, + isGeoGridPrecisionAware: () => { return false; }, + }; + const prevFilters = []; + const prevQuery = { + language: 'kuery', + query: 'machine.os.keyword : "win 7"', + queryLastTriggeredAt: '2019-04-25T20:53:22.331Z' + }; + + describe('applyGlobalQuery is false', () => { + + const prevApplyGlobalQuery = false; + + const vectorLayer = new VectorLayer({ + layerDescriptor: { + __dataRequests: [ + { + dataId: SOURCE_DATA_REQUEST_ID, + dataMeta: { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery, + } + } + ] + } + }); + + it('can skip update when filter changes', async () => { + const searchFilters = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: [prevQuery], + query: prevQuery, + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(true); + }); + + it('can skip update when query changes', async () => { + const searchFilters = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + query: 'a new query string', + } + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(true); + }); + + it('can not skip update when query is refreshed', async () => { + const searchFilters = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + queryLastTriggeredAt: 'sometime layer when Refresh button is clicked' + } + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when applyGlobalQuery changes', async () => { + const searchFilters = { + applyGlobalQuery: !prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(false); + }); + }); + + describe('applyGlobalQuery is true', () => { + + const prevApplyGlobalQuery = true; + + const vectorLayer = new VectorLayer({ + layerDescriptor: { + __dataRequests: [ + { + dataId: SOURCE_DATA_REQUEST_ID, + dataMeta: { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery, + } + } + ] + } + }); + + it('can not skip update when filter changes', async () => { + const searchFilters = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: [prevQuery], + query: prevQuery, + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when query changes', async () => { + const searchFilters = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + query: 'a new query string', + } + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when query is refreshed', async () => { + const searchFilters = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + queryLastTriggeredAt: 'sometime layer when Refresh button is clicked' + } + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when applyGlobalQuery changes', async () => { + const searchFilters = { + applyGlobalQuery: !prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery + }; + + const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); + + expect(canSkipUpdate).toBe(false); + }); + }); + }); +}); diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index 4eae5e85dbecf..0e750d5fa83a7 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -82,12 +82,30 @@ export default function ({ getPageObjects, getService }) { }); - describe('inspector', () => { + describe('query bar', () => { + before(async () => { + await PageObjects.maps.setAndSubmitQuery('prop1 < 10 or _index : "geo_shapes*"'); + }); + afterEach(async () => { await inspector.close(); }); - it('should contain terms aggregation elasticsearch request', async () => { + it('should apply query to join request', async () => { + await PageObjects.maps.openInspectorRequest('meta_for_geo_shapes*.shape_name'); + const requestStats = await inspector.getTableData(); + const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)'); + expect(totalHits).to.equal('3'); + const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits'); + expect(hits).to.equal('0'); // aggregation requests do not return any documents + const indexPatternName = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Index pattern'); + expect(indexPatternName).to.equal('meta_for_geo_shapes*'); + }); + + it('should not apply query to join request when apply global query is disabled', async () => { + await PageObjects.maps.openLayerPanel('geo_shapes*'); + await PageObjects.maps.disableApplyGlobalQuery(); + await PageObjects.maps.openInspectorRequest('meta_for_geo_shapes*.shape_name'); const requestStats = await inspector.getTableData(); const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)'); @@ -97,6 +115,13 @@ export default function ({ getPageObjects, getService }) { const indexPatternName = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Index pattern'); expect(indexPatternName).to.equal('meta_for_geo_shapes*'); }); + }); + + + describe('inspector', () => { + afterEach(async () => { + await inspector.close(); + }); it('should not contain any elasticsearch request after layer is deleted', async () => { await PageObjects.maps.removeLayer('geo_shapes*'); diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js index 5c3246ef2516e..6f2ecf4625cf6 100644 --- a/x-pack/test/functional/page_objects/gis_page.js +++ b/x-pack/test/functional/page_objects/gis_page.js @@ -259,6 +259,22 @@ export function GisPageProvider({ getService, getPageObjects }) { await testSubjects.click(`mapOpenLayerButton${layerName}`); } + async disableApplyGlobalQuery() { + const element = await testSubjects.find('mapLayerPanelApplyGlobalQueryCheckbox'); + const isSelected = await element.isSelected(); + if(isSelected) { + await retry.try(async () => { + log.debug(`disabling applyGlobalQuery`); + await testSubjects.click('mapLayerPanelApplyGlobalQueryCheckbox'); + const isStillSelected = await element.isSelected(); + if (isStillSelected) { + throw new Error('applyGlobalQuery not disabled'); + } + }); + await this.waitForLayersToLoad(); + } + } + async doesLayerExist(layerName) { layerName = layerName.replace(' ', '_'); log.debug(`Open layer panel, layer: ${layerName}`);