Skip to content

Commit

Permalink
[Maps] add Where clause to terms joins (#39593)
Browse files Browse the repository at this point in the history
* [Maps] add Where clause to terms joins

* add functional test

* rename layerQuery to sourceQuery
  • Loading branch information
nreese committed Jun 26, 2019
1 parent ebd656c commit 51ae0f4
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/maps/search.asciidoc
Expand Up @@ -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[]
Expand Down
Expand Up @@ -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,
Expand All @@ -29,6 +30,7 @@ export class Join extends Component {
leftFields: null,
leftSourceName: '',
rightFields: undefined,
indexPattern: undefined,
loadError: undefined,
prevIndexPatternId: getIndexPatternId(this.props),
};
Expand Down Expand Up @@ -92,7 +94,8 @@ export class Join extends Component {
}

this.setState({
rightFields: indexPattern.fields
rightFields: indexPattern.fields,
indexPattern,
});
}

Expand Down Expand Up @@ -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,
Expand All @@ -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 = (
<EuiFlexItem grow={false}>
<MetricsExpression
Expand All @@ -181,6 +196,19 @@ export class Join extends Component {
);
}

let whereExpression;
if (indexPattern && isJoinConfigComplete) {
whereExpression = (
<EuiFlexItem grow={false}>
<WhereExpression
indexPattern={indexPattern}
whereQuery={join.right.whereQuery}
onChange={this._onWhereQueryChange}
/>
</EuiFlexItem>
);
}

return (
<div className="mapJoinItem">
<EuiFlexGroup className="mapJoinItem__inner" responsive={false} wrap={true} gutterSize="s">
Expand All @@ -204,6 +232,8 @@ export class Join extends Component {

{metricsExpression}

{whereExpression}

<EuiButtonIcon
className="mapJoinItem__delete"
iconType="trash"
Expand Down
@@ -0,0 +1,98 @@
/*
* 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 chrome from 'ui/chrome';
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiButton,
EuiPopover,
EuiExpression,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { data } from 'plugins/data/setup';
const { QueryBar } = data.query.ui;
import { Storage } from 'ui/storage';

const settings = chrome.getUiSettingsClient();
const localStorage = new Storage(window.localStorage);

export class WhereExpression extends Component {

state = {
isPopoverOpen: false,
};

_togglePopover = () => {
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 (
<EuiPopover
id="whereClausePopover"
isOpen={this.state.isPopoverOpen}
closePopover={this._closePopover}
ownFocus
withTitle
anchorPosition="leftCenter"
button={
<EuiExpression
onClick={this._togglePopover}
description={i18n.translate('xpack.maps.layerPanel.whereExpression.expressionDescription', {
defaultMessage: 'where'
})}
uppercase={false}
value={expressionValue}
data-test-subj="mapJoinWhereExpressionButton"
/>
}
>
<div className="mapFilterEditor" data-test-subj="mapJoinWhereFilterEditor">
<QueryBar
query={whereQuery ? whereQuery : { language: settings.get('search:queryLanguage'), query: '' }}
onSubmit={this._onQueryChange}
appName="maps"
showDatePicker={false}
indexPatterns={[indexPattern]}
store={localStorage}
customSubmitButton={
<EuiButton
fill
data-test-subj="mapWhereFilterEditorSubmitButton"
>
<FormattedMessage
id="xpack.maps.layerPanel.whereExpression.queryBarSubmitButtonLabel"
defaultMessage="Set filter"
/>
</EuiButton>
}
/>
</div>
</EuiPopover>
);
}
}
Expand Up @@ -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);
Expand All @@ -151,7 +151,7 @@ export class HeatmapLayer extends AbstractLayer {
&& !updateDueToExtent
&& !updateDueToRefreshTimer
&& !updateDueToQuery
&& !updateDueToLayerQuery
&& !updateDueToSourceQuery
&& !updateDueToApplyGlobalQuery
&& !updateDueToFilters
&& !updateDueToMetricChange
Expand All @@ -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()
Expand Down
Expand Up @@ -82,5 +82,8 @@ export class LeftInnerJoin {
return this._rightSource.getIndexPatternIds();
}

getWhereQuery() {
return this._rightSource.getWhereQuery();
}
}

Expand Up @@ -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}`;
Expand Down
Expand Up @@ -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();

Expand Down
Expand Up @@ -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);
Expand All @@ -273,7 +273,7 @@ export class VectorLayer extends AbstractLayer {
&& !updateDueToFields
&& !updateDueToQuery
&& !updateDueToFilters
&& !updateDueToLayerQuery
&& !updateDueToSourceQuery
&& !updateDueToApplyGlobalQuery
&& !updateDueToPrecisionChange
&& !updateDueToSourceMetaChange;
Expand All @@ -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);
Expand Down Expand Up @@ -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(),
};
Expand Down
30 changes: 30 additions & 0 deletions x-pack/test/functional/apps/maps/joins.js
Expand Up @@ -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 () => {
Expand Down
13 changes: 13 additions & 0 deletions x-pack/test/functional/page_objects/gis_page.js
Expand Up @@ -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');
Expand Down

0 comments on commit 51ae0f4

Please sign in to comment.