From 51ae0f45af4197649de9171d5935b89314b77937 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 26 Jun 2019 09:56:23 -0600 Subject: [PATCH] [Maps] add Where clause to terms joins (#39593) * [Maps] add Where clause to terms joins * add functional test * rename layerQuery to sourceQuery --- docs/maps/search.asciidoc | 1 + .../layer_panel/join_editor/resources/join.js | 34 ++++++- .../join_editor/resources/where_expression.js | 98 +++++++++++++++++++ .../public/shared/layers/heatmap_layer.js | 8 +- .../shared/layers/joins/left_inner_join.js | 3 + .../shared/layers/sources/es_join_source.js | 4 + .../public/shared/layers/sources/es_source.js | 8 +- .../maps/public/shared/layers/vector_layer.js | 9 +- x-pack/test/functional/apps/maps/joins.js | 30 ++++++ .../test/functional/page_objects/gis_page.js | 13 +++ 10 files changed, 194 insertions(+), 14 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/components/layer_panel/join_editor/resources/where_expression.js diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 31583afb9d268c..33a8ba15113d84 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -28,6 +28,7 @@ image::maps/images/global_search_bar.png[] You can apply a search request to individual layers by setting `Filters` in the layer details panel. Click the *Add filter* button to add a filter to a layer. +NOTE: Layer filters are not applied to *term joins*. You can apply a search request to *term joins* by setting the *where* clause in the join definition. [role="screenshot"] image::maps/images/layer_search.png[] diff --git a/x-pack/legacy/plugins/maps/public/components/layer_panel/join_editor/resources/join.js b/x-pack/legacy/plugins/maps/public/components/layer_panel/join_editor/resources/join.js index 50d590aab8c1b0..44987e0d241e35 100644 --- a/x-pack/legacy/plugins/maps/public/components/layer_panel/join_editor/resources/join.js +++ b/x-pack/legacy/plugins/maps/public/components/layer_panel/join_editor/resources/join.js @@ -14,6 +14,7 @@ import { import { i18n } from '@kbn/i18n'; import { JoinExpression } from './join_expression'; import { MetricsExpression } from './metrics_expression'; +import { WhereExpression } from './where_expression'; import { indexPatternService, @@ -29,6 +30,7 @@ export class Join extends Component { leftFields: null, leftSourceName: '', rightFields: undefined, + indexPattern: undefined, loadError: undefined, prevIndexPatternId: getIndexPatternId(this.props), }; @@ -92,7 +94,8 @@ export class Join extends Component { } this.setState({ - rightFields: indexPattern.fields + rightFields: indexPattern.fields, + indexPattern, }); } @@ -155,6 +158,16 @@ export class Join extends Component { }); } + _onWhereQueryChange = (whereQuery) => { + this.props.onChange({ + leftField: this.props.join.leftField, + right: { + ...this.props.join.right, + whereQuery, + }, + }); + } + render() { const { join, @@ -164,12 +177,14 @@ export class Join extends Component { leftSourceName, leftFields, rightFields, + indexPattern, } = this.state; const right = _.get(join, 'right', {}); const rightSourceName = right.indexPatternTitle ? right.indexPatternTitle : right.indexPatternId; + const isJoinConfigComplete = join.leftField && right.indexPatternId && right.term; let metricsExpression; - if (join.leftField && right.indexPatternId && right.term) { + if (isJoinConfigComplete) { metricsExpression = ( + + + ); + } + return (
@@ -204,6 +232,8 @@ export class Join extends Component { {metricsExpression} + {whereExpression} + { + this.setState((prevState) => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); + } + + _closePopover = () => { + this.setState({ + isPopoverOpen: false, + }); + } + + _onQueryChange = ({ query }) => { + this.props.onChange(query); + this._closePopover(); + } + + render() { + const { whereQuery, indexPattern } = this.props; + const expressionValue = whereQuery && whereQuery.query + ? whereQuery.query + : i18n.translate('xpack.maps.layerPanel.whereExpression.expressionValuePlaceholder', { + defaultMessage: '-- add filter --' + }); + + return ( + + } + > +
+ + + + } + /> +
+
+ ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/shared/layers/heatmap_layer.js b/x-pack/legacy/plugins/maps/public/shared/layers/heatmap_layer.js index 0def928dd4963f..61be935863911b 100644 --- a/x-pack/legacy/plugins/maps/public/shared/layers/heatmap_layer.js +++ b/x-pack/legacy/plugins/maps/public/shared/layers/heatmap_layer.js @@ -140,8 +140,8 @@ export class HeatmapLayer extends AbstractLayer { // Exception is "Refresh" query. updateDueToQuery = isRefreshOnlyQuery(meta.query, searchFilters.query); } - const updateDueToLayerQuery = searchFilters.layerQuery - && !_.isEqual(meta.layerQuery, searchFilters.layerQuery); + const updateDueToSourceQuery = searchFilters.sourceQuery + && !_.isEqual(meta.sourceQuery, searchFilters.sourceQuery); const updateDueToApplyGlobalQuery = meta.applyGlobalQuery !== searchFilters.applyGlobalQuery; const updateDueToMetricChange = !_.isEqual(meta.metric, searchFilters.metric); @@ -151,7 +151,7 @@ export class HeatmapLayer extends AbstractLayer { && !updateDueToExtent && !updateDueToRefreshTimer && !updateDueToQuery - && !updateDueToLayerQuery + && !updateDueToSourceQuery && !updateDueToApplyGlobalQuery && !updateDueToFilters && !updateDueToMetricChange @@ -165,7 +165,7 @@ export class HeatmapLayer extends AbstractLayer { _getSearchFilters(dataFilters) { return { ...dataFilters, - layerQuery: this.getQuery(), + sourceQuery: this.getQuery(), applyGlobalQuery: this.getApplyGlobalQuery(), geogridPrecision: this._source.getGeoGridPrecision(dataFilters.zoom), metric: this._getPropKeyOfSelectedMetric() diff --git a/x-pack/legacy/plugins/maps/public/shared/layers/joins/left_inner_join.js b/x-pack/legacy/plugins/maps/public/shared/layers/joins/left_inner_join.js index d53e2e5fd3d186..c8936da381baf7 100644 --- a/x-pack/legacy/plugins/maps/public/shared/layers/joins/left_inner_join.js +++ b/x-pack/legacy/plugins/maps/public/shared/layers/joins/left_inner_join.js @@ -82,5 +82,8 @@ export class LeftInnerJoin { return this._rightSource.getIndexPatternIds(); } + getWhereQuery() { + return this._rightSource.getWhereQuery(); + } } diff --git a/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_join_source.js b/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_join_source.js index c141ab28e0f541..f6c4cce69968d1 100644 --- a/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_join_source.js +++ b/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_join_source.js @@ -79,6 +79,10 @@ export class ESJoinSource extends AbstractESSource { return this._descriptor.term; } + getWhereQuery() { + return this._descriptor.whereQuery; + } + _formatMetricKey(metric) { const metricKey = metric.type !== 'count' ? `${metric.type}_of_${metric.field}` : metric.type; return `__kbnjoin__${metricKey}_groupby_${this._descriptor.indexPatternTitle}.${this._descriptor.term}`; diff --git a/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_source.js index db340a3485557b..e055df8ad0f37b 100644 --- a/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/shared/layers/sources/es_source.js @@ -162,19 +162,19 @@ export class AbstractESSource extends AbstractVectorSource { searchSource.setField('query', searchFilters.query); } - if (searchFilters.layerQuery) { + if (searchFilters.sourceQuery) { const layerSearchSource = new SearchSource(); layerSearchSource.setField('index', indexPattern); - layerSearchSource.setField('query', searchFilters.layerQuery); + layerSearchSource.setField('query', searchFilters.sourceQuery); searchSource.setParent(layerSearchSource); } return searchSource; } - async getBoundsForFilters({ layerQuery, query, timeFilters, filters, applyGlobalQuery }) { + async getBoundsForFilters({ sourceQuery, query, timeFilters, filters, applyGlobalQuery }) { - const searchSource = await this._makeSearchSource({ layerQuery, query, timeFilters, filters, applyGlobalQuery }, 0); + const searchSource = await this._makeSearchSource({ sourceQuery, query, timeFilters, filters, applyGlobalQuery }, 0); const geoField = await this._getGeoField(); const indexPattern = await this._getIndexPattern(); diff --git a/x-pack/legacy/plugins/maps/public/shared/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/shared/layers/vector_layer.js index 5ed9ba00ccb72d..423c22cc1cceda 100644 --- a/x-pack/legacy/plugins/maps/public/shared/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/shared/layers/vector_layer.js @@ -243,11 +243,11 @@ export class VectorLayer extends AbstractLayer { let updateDueToQuery = false; let updateDueToFilters = false; - let updateDueToLayerQuery = false; + let updateDueToSourceQuery = false; let updateDueToApplyGlobalQuery = false; if (isQueryAware) { updateDueToApplyGlobalQuery = prevMeta.applyGlobalQuery !== nextMeta.applyGlobalQuery; - updateDueToLayerQuery = !_.isEqual(prevMeta.layerQuery, nextMeta.layerQuery); + updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextMeta.sourceQuery); if (nextMeta.applyGlobalQuery) { updateDueToQuery = !_.isEqual(prevMeta.query, nextMeta.query); updateDueToFilters = !_.isEqual(prevMeta.filters, nextMeta.filters); @@ -273,7 +273,7 @@ export class VectorLayer extends AbstractLayer { && !updateDueToFields && !updateDueToQuery && !updateDueToFilters - && !updateDueToLayerQuery + && !updateDueToSourceQuery && !updateDueToApplyGlobalQuery && !updateDueToPrecisionChange && !updateDueToSourceMetaChange; @@ -287,6 +287,7 @@ export class VectorLayer extends AbstractLayer { const searchFilters = { ...dataFilters, + sourceQuery: joinSource.getWhereQuery(), applyGlobalQuery: this.getApplyGlobalQuery(), }; const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, searchFilters); @@ -343,7 +344,7 @@ export class VectorLayer extends AbstractLayer { ...dataFilters, fieldNames: _.uniq(fieldNames).sort(), geogridPrecision: this._source.getGeoGridPrecision(dataFilters.zoom), - layerQuery: this.getQuery(), + sourceQuery: this.getQuery(), applyGlobalQuery: this.getApplyGlobalQuery(), sourceMeta: this._source.getSyncMeta(), }; diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index 6272ee560708c2..c59437e236e716 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -127,6 +127,36 @@ export default function ({ getPageObjects, getService }) { }); }); + describe('where clause', () => { + before(async () => { + await PageObjects.maps.setJoinWhereQuery('geo_shapes*', 'prop1 >= 11'); + }); + + after(async () => { + await PageObjects.maps.closeLayerPanel(); + }); + + 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('2'); + const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits'); + expect(hits).to.equal('0'); // aggregation requests do not return any documents + await inspector.close(); + }); + + it('should update dynamic data range in legend with new results', async () => { + const layerTOCDetails = await PageObjects.maps.getLayerTOCDetails('geo_shapes*'); + const split = layerTOCDetails.trim().split('\n'); + + const min = split[0]; + expect(min).to.equal('12'); + + const max = split[2]; + expect(max).to.equal('12'); + }); + }); describe('inspector', () => { afterEach(async () => { diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js index 3d057278b91e9c..878700c7002351 100644 --- a/x-pack/test/functional/page_objects/gis_page.js +++ b/x-pack/test/functional/page_objects/gis_page.js @@ -353,6 +353,19 @@ export function GisPageProvider({ getService, getPageObjects }) { await this.waitForLayersToLoad(); } + async setJoinWhereQuery(layerName, query) { + await this.openLayerPanel(layerName); + await testSubjects.click('mapJoinWhereExpressionButton'); + const filterEditorContainer = await testSubjects.find('mapJoinWhereFilterEditor'); + const queryBarInFilterEditor = await testSubjects.findDescendant('queryInput', filterEditorContainer); + await queryBarInFilterEditor.click(); + const input = await find.activeElement(); + await input.clearValue(); + await input.type(query); + await testSubjects.click('mapWhereFilterEditorSubmitButton'); + await this.waitForLayersToLoad(); + } + async selectVectorSource() { log.debug(`Select vector source`); await testSubjects.click('vectorShapes');