[Maps] add Where clause to terms joins (#39593)

* [Maps] add Where clause to terms joins

* add functional test

* rename layerQuery to sourceQuery
This commit is contained in:
Nathan Reese 2019-06-26 09:56:23 -06:00 committed by GitHub
parent ebd656cb23
commit 51ae0f45af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 194 additions and 14 deletions

View file

@ -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[]

View file

@ -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 = (
<EuiFlexItem grow={false}>
<MetricsExpression
@ -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">
@ -204,6 +232,8 @@ export class Join extends Component {
{metricsExpression}
{whereExpression}
<EuiButtonIcon
className="mapJoinItem__delete"
iconType="trash"

View file

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

View file

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

View file

@ -82,5 +82,8 @@ export class LeftInnerJoin {
return this._rightSource.getIndexPatternIds();
}
getWhereQuery() {
return this._rightSource.getWhereQuery();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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