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] automatically disable filter by bounds for indexes with small doc counts #34456

Merged
merged 5 commits into from Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,9 @@
/*
* 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.
*/

export const DEFAULT_ES_DOC_LIMIT = 2048;

export const DEFAULT_FILTER_BY_MAP_BOUNDS = true;
Expand Up @@ -7,17 +7,27 @@
import _ from 'lodash';
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { EuiFormRow, EuiSpacer } from '@elastic/eui';
import { EuiFormRow, EuiSpacer, EuiSwitch, EuiCallOut } from '@elastic/eui';

import { IndexPatternSelect } from 'ui/index_patterns/components/index_pattern_select';
import { SingleFieldSelect } from '../../../components/single_field_select';
import { indexPatternService } from '../../../../kibana_services';
import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { kfetch } from 'ui/kfetch';
import { GIS_API_PATH } from '../../../../../common/constants';
import { DEFAULT_ES_DOC_LIMIT, DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';

function filterGeoField(field) {
return ['geo_point', 'geo_shape'].includes(field.type);
}
const RESET_INDEX_PATTERN_STATE = {
indexPattern: undefined,
geoField: undefined,
filterByMapBounds: DEFAULT_FILTER_BY_MAP_BOUNDS,
showFilterByBoundsSwitch: false,
};

export class CreateSourceEditor extends Component {

Expand All @@ -27,9 +37,8 @@ export class CreateSourceEditor extends Component {

state = {
isLoadingIndexPattern: false,
indexPatternId: '',
geoField: '',
noGeoIndexPatternsExist: false,
...RESET_INDEX_PATTERN_STATE,
};

componentWillUnmount() {
Expand All @@ -50,11 +59,20 @@ export class CreateSourceEditor extends Component {
loadIndexPattern = (indexPatternId) => {
this.setState({
isLoadingIndexPattern: true,
nreese marked this conversation as resolved.
Show resolved Hide resolved
indexPattern: undefined,
geoField: undefined,
...RESET_INDEX_PATTERN_STATE
}, this.debouncedLoad.bind(null, indexPatternId));
};

loadIndexDocCount = async (indexPatternTitle) => {
const { count } = await kfetch({
pathname: `../${GIS_API_PATH}/indexCount`,
query: {
index: indexPatternTitle
}
});
return count;
}

debouncedLoad = _.debounce(async (indexPatternId) => {
if (!indexPatternId || indexPatternId.length === 0) {
return;
Expand All @@ -68,6 +86,15 @@ export class CreateSourceEditor extends Component {
return;
}

let indexHasSmallDocCount = false;
try {
const indexDocCount = await this.loadIndexDocCount(indexPattern.title);
indexHasSmallDocCount = indexDocCount <= DEFAULT_ES_DOC_LIMIT;
} catch (error) {
// retrieving index count is a nice to have and is not essential
// do not interrupt user flow if unable to retrieve count
}

if (!this._isMounted) {
return;
}
Expand All @@ -80,7 +107,9 @@ export class CreateSourceEditor extends Component {

this.setState({
isLoadingIndexPattern: false,
indexPattern: indexPattern
indexPattern: indexPattern,
filterByMapBounds: !indexHasSmallDocCount, // Turn off filterByMapBounds when index contains a limited number of documents
showFilterByBoundsSwitch: indexHasSmallDocCount
});

//make default selection
Expand All @@ -97,21 +126,21 @@ export class CreateSourceEditor extends Component {
}, this.previewLayer);
};

onLimitChange = e => {
const sanitizedValue = parseInt(e.target.value, 10);
onFilterByMapBoundsChange = event => {
this.setState({
limit: isNaN(sanitizedValue) ? '' : sanitizedValue,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx for removing cruft

filterByMapBounds: event.target.checked,
}, this.previewLayer);
}
};

previewLayer = () => {
const {
indexPatternId,
geoField,
filterByMapBounds,
} = this.state;

const sourceConfig = (indexPatternId && geoField)
? { indexPatternId, geoField }
? { indexPatternId, geoField, filterByMapBounds }
: null;
this.props.onSelect(sourceConfig);
}
Expand Down Expand Up @@ -144,6 +173,53 @@ export class CreateSourceEditor extends Component {
);
}

_renderFilterByMapBounds() {
if (!this.state.showFilterByBoundsSwitch) {
return null;
}

return (
<Fragment>
<EuiCallOut
title={
i18n.translate('xpack.maps.source.esSearch.disableFilterByMapBoundsTitle', {
defaultMessage: `Dynamic data filter disabled`
})
}
>
<p>
<FormattedMessage
id="xpack.maps.source.esSearch.disableFilterByMapBoundsExplainMsg"
defaultMessage="Index '{indexPatternTitle}' has a small number of documents and does not require dynamic filtering."
values={{
indexPatternTitle: this.state.indexPattern ? this.state.indexPattern.title : this.state.indexPatternId,
}}
/>
</p>
<p>
<FormattedMessage
id="xpack.maps.source.esSearch.disableFilterByMapBoundsTurnOnMsg"
defaultMessage="Turn on dynamic filtering if you expect the number of documents to increase."
/>
</p>
</EuiCallOut>
<EuiSpacer size="s" />
<EuiFormRow>
<EuiSwitch
label={
i18n.translate('xpack.maps.source.esSearch.extentFilterLabel', {
defaultMessage: `Dynamically filter for data in the visible map area.`
})

}
checked={this.props.filterByMapBounds}
onChange={this.onFilterByMapBoundsChange}
/>
</EuiFormRow>
</Fragment>
);
}

_renderNoIndexPatternWarning() {
if (!this.state.noGeoIndexPatternsExist) {
return null;
Expand Down Expand Up @@ -183,6 +259,8 @@ export class CreateSourceEditor extends Component {

{this._renderGeoSelect()}

{this._renderFilterByMapBounds()}

</Fragment>
);
}
Expand Down
Expand Up @@ -15,8 +15,7 @@ import { UpdateSourceEditor } from './update_source_editor';
import { ES_SEARCH } from '../../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';

const DEFAULT_LIMIT = 2048;
import { DEFAULT_ES_DOC_LIMIT, DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';

export class ESSearchSource extends AbstractESSource {

Expand Down Expand Up @@ -50,8 +49,8 @@ export class ESSearchSource extends AbstractESSource {
type: ESSearchSource.type,
indexPatternId: descriptor.indexPatternId,
geoField: descriptor.geoField,
limit: _.get(descriptor, 'limit', DEFAULT_LIMIT),
filterByMapBounds: _.get(descriptor, 'filterByMapBounds', true),
limit: _.get(descriptor, 'limit', DEFAULT_ES_DOC_LIMIT),
filterByMapBounds: _.get(descriptor, 'filterByMapBounds', DEFAULT_FILTER_BY_MAP_BOUNDS),
tooltipProperties: _.get(descriptor, 'tooltipProperties', []),
}, inspectorAdapters);
}
Expand Down
21 changes: 21 additions & 0 deletions x-pack/plugins/maps/server/routes.js
Expand Up @@ -78,6 +78,27 @@ export function initRoutes(server, licenseUid) {
}
});

server.route({
method: 'GET',
path: `${ROOT}/indexCount`,
handler: async (request, h) => {
const { server, query } = request;

if (!query.index) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log a warning to the server-logs? This should never really occur, and if it does, it's unexpected and there is an issue with the stack deployment. Might be helpful to show feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could happen if someone uses the API and does not supply index query param. Is it worth logging? Consumer of API would get 400 and know something is wrong with the request

return h.response().code(400);
}

const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');

try {
const { count } = await callWithRequest(request, 'count', { index: query.index });
return { count };
} catch(error) {
return h.response().code(400);
}
}
});

async function getEMSResources(licenseUid) {

if (!mapConfig.includeElasticMapsService) {
Expand Down