Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps] add Where clause to terms joins #39593

Merged
merged 3 commits into from Jun 26, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -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 @@ -287,6 +287,7 @@ export class VectorLayer extends AbstractLayer {

const searchFilters = {
...dataFilters,
layerQuery: joinSource.getWhereQuery(),
nreese marked this conversation as resolved.
Show resolved Hide resolved
applyGlobalQuery: this.getApplyGlobalQuery(),
};
const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, searchFilters);
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