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

[7.x] [Maps] get max_result_window and max_inner_result_window from index settings (#53500) #53902

Merged
merged 2 commits into from
Jan 2, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/maps/maps-aggregations.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ To enable top hits:
. Set *Entity* to the field that identifies entities in your documents.
This field will be used in the terms aggregation to group your documents into entity buckets.
. Set *Documents per entity* to configure the maximum number of documents accumulated per entity.
This setting is limited to the `index.max_inner_result_window` index setting, which defaults to 100.

[role="screenshot"]
image::maps/images/top_hits.png[]
Expand Down
2 changes: 1 addition & 1 deletion docs/maps/vector-layer.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ See map.regionmap.* in <<settings>> for details.
*Documents*:: Vector data from a Kibana index pattern.
The index must contain at least one field mapped as {ref}/geo-point.html[geo_point] or {ref}/geo-shape.html[geo_shape].

NOTE: Document results are limited to the first 10000 matching documents.
NOTE: Document results are limited to the `index.max_result_window` index setting, which defaults to 10000.
Use <<maps-aggregations, aggregations>> to plot large data sets.

*Grid aggregation*:: Geospatial data grouped in grids with metrics for each gridded cell.
Expand Down
5 changes: 4 additions & 1 deletion x-pack/legacy/plugins/maps/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const APP_ICON = 'gisApp';

export const MAP_APP_PATH = `app/${APP_ID}`;
export const GIS_API_PATH = `api/${APP_ID}`;
export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`;

export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`;

Expand Down Expand Up @@ -69,7 +70,9 @@ export const MAX_ZOOM = 24;

export const DECIMAL_DEGREES_PRECISION = 5; // meters precision
export const ZOOM_PRECISION = 2;
export const ES_SIZE_LIMIT = 10000;
export const DEFAULT_MAX_RESULT_WINDOW = 10000;
export const DEFAULT_MAX_INNER_RESULT_WINDOW = 100;
export const DEFAULT_MAX_BUCKETS_LIMIT = 10000;

export const FEATURE_ID_PROPERTY_NAME = '__kbn__feature_id__';
export const FEATURE_VISIBLE_PROPERTY_NAME = '__kbn_isvisibleduetojoin__';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

import { AbstractVectorSource } from '../vector_source';
import React from 'react';
import { ES_GEO_FIELD_TYPE, GEOJSON_FILE, ES_SIZE_LIMIT } from '../../../../common/constants';
import {
ES_GEO_FIELD_TYPE,
GEOJSON_FILE,
DEFAULT_MAX_RESULT_WINDOW,
} from '../../../../common/constants';
import { ClientFileCreateSourceEditor } from './create_client_file_source_editor';
import { ESSearchSource } from '../es_search_source';
import uuid from 'uuid/v4';
Expand Down Expand Up @@ -82,7 +86,7 @@ export class GeojsonFileSource extends AbstractVectorSource {
addAndViewSource(null);
} else {
// Only turn on bounds filter for large doc counts
const filterByMapBounds = indexDataResp.docCount > ES_SIZE_LIMIT;
const filterByMapBounds = indexDataResp.docCount > DEFAULT_MAX_RESULT_WINDOW;
const source = new ESSearchSource(
{
id: uuid(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { NoIndexPatternCallout } from '../../../components/no_index_pattern_call
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { kfetch } from 'ui/kfetch';
import { ES_GEO_FIELD_TYPE, GIS_API_PATH, ES_SIZE_LIMIT } from '../../../../common/constants';
import {
ES_GEO_FIELD_TYPE,
GIS_API_PATH,
DEFAULT_MAX_RESULT_WINDOW,
} from '../../../../common/constants';
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';

import { npStart } from 'ui/new_platform';
Expand Down Expand Up @@ -96,7 +100,7 @@ export class CreateSourceEditor extends Component {
let indexHasSmallDocCount = false;
try {
const indexDocCount = await this.loadIndexDocCount(indexPattern.title);
indexHasSmallDocCount = indexDocCount <= ES_SIZE_LIMIT;
indexHasSmallDocCount = indexDocCount <= DEFAULT_MAX_RESULT_WINDOW;
} catch (error) {
// retrieving index count is a nice to have and is not essential
// do not interrupt user flow if unable to retrieve count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import { UpdateSourceEditor } from './update_source_editor';
import {
ES_SEARCH,
ES_GEO_FIELD_TYPE,
ES_SIZE_LIMIT,
DEFAULT_MAX_BUCKETS_LIMIT,
SORT_ORDER,
} from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { getSourceFields } from '../../../index_pattern_util';
import { loadIndexSettings } from './load_index_settings';

import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
import { ESDocField } from '../../fields/es_doc_field';
Expand Down Expand Up @@ -267,8 +268,8 @@ export class ESSearchSource extends AbstractESSource {
entitySplit: {
terms: {
field: topHitsSplitField,
size: ES_SIZE_LIMIT,
shard_size: ES_SIZE_LIMIT,
size: DEFAULT_MAX_BUCKETS_LIMIT,
shard_size: DEFAULT_MAX_BUCKETS_LIMIT,
},
aggs: {
entityHits: {
Expand All @@ -290,7 +291,7 @@ export class ESSearchSource extends AbstractESSource {
const entityBuckets = _.get(resp, 'aggregations.entitySplit.buckets', []);
const totalEntities = _.get(resp, 'aggregations.totalEntities.value', 0);
// can not compare entityBuckets.length to totalEntities because totalEntities is an approximate
const areEntitiesTrimmed = entityBuckets.length >= ES_SIZE_LIMIT;
const areEntitiesTrimmed = entityBuckets.length >= DEFAULT_MAX_BUCKETS_LIMIT;
let areTopHitsTrimmed = false;
entityBuckets.forEach(entityBucket => {
const total = _.get(entityBucket, 'entityHits.hits.total', 0);
Expand All @@ -315,7 +316,7 @@ export class ESSearchSource extends AbstractESSource {

// searchFilters.fieldNames contains geo field and any fields needed for styling features
// Performs Elasticsearch search request being careful to pull back only required fields to minimize response size
async _getSearchHits(layerName, searchFilters, registerCancelCallback) {
async _getSearchHits(layerName, searchFilters, maxResultWindow, registerCancelCallback) {
const initialSearchContext = {
docvalue_fields: await this._getDateDocvalueFields(searchFilters.fieldNames),
};
Expand All @@ -331,7 +332,7 @@ export class ESSearchSource extends AbstractESSource {
);
searchSource = await this._makeSearchSource(
searchFilters,
ES_SIZE_LIMIT,
maxResultWindow,
initialSearchContext
);
searchSource.setField('source', false); // do not need anything from _source
Expand All @@ -340,7 +341,7 @@ export class ESSearchSource extends AbstractESSource {
// geo_shape fields do not support docvalue_fields yet, so still have to be pulled from _source
searchSource = await this._makeSearchSource(
searchFilters,
ES_SIZE_LIMIT,
maxResultWindow,
initialSearchContext
);
// Setting "fields" instead of "source: { includes: []}"
Expand Down Expand Up @@ -382,11 +383,19 @@ export class ESSearchSource extends AbstractESSource {
}

async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) {
const indexPattern = await this.getIndexPattern();

const indexSettings = await loadIndexSettings(indexPattern.title);

const { hits, meta } = this._isTopHits()
? await this._getTopHits(layerName, searchFilters, registerCancelCallback)
: await this._getSearchHits(layerName, searchFilters, registerCancelCallback);
: await this._getSearchHits(
layerName,
searchFilters,
indexSettings.maxResultWindow,
registerCancelCallback
);

const indexPattern = await this.getIndexPattern();
const unusedMetaFields = indexPattern.metaFields.filter(metaField => {
return !['_id', '_index'].includes(metaField);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 {
DEFAULT_MAX_RESULT_WINDOW,
DEFAULT_MAX_INNER_RESULT_WINDOW,
INDEX_SETTINGS_API_PATH,
} from '../../../../common/constants';
import { kfetch } from 'ui/kfetch';
import { toastNotifications } from 'ui/notify';
import { i18n } from '@kbn/i18n';

let toastDisplayed = false;
const indexSettings = new Map();

export async function loadIndexSettings(indexPatternTitle) {
if (indexSettings.has(indexPatternTitle)) {
return indexSettings.get(indexPatternTitle);
}

const fetchPromise = fetchIndexSettings(indexPatternTitle);
indexSettings.set(indexPatternTitle, fetchPromise);
return fetchPromise;
}

async function fetchIndexSettings(indexPatternTitle) {
try {
const indexSettings = await kfetch({
pathname: `../${INDEX_SETTINGS_API_PATH}`,
query: {
indexPatternTitle,
},
});
return indexSettings;
} catch (err) {
const warningMsg = i18n.translate('xpack.maps.indexSettings.fetchErrorMsg', {
defaultMessage: `Unable to fetch index settings for index pattern '{indexPatternTitle}'.
Ensure you have '{viewIndexMetaRole}' role.`,
values: {
indexPatternTitle,
viewIndexMetaRole: 'view_index_metadata',
},
});
if (!toastDisplayed) {
// Only show toast for first failure to avoid flooding user with warnings
toastDisplayed = true;
toastNotifications.addWarning(warningMsg);
}
console.warn(warningMsg);
return {
maxResultWindow: DEFAULT_MAX_RESULT_WINDOW,
maxInnerResultWindow: DEFAULT_MAX_INNER_RESULT_WINDOW,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import { indexPatternService } from '../../../kibana_services';
import { i18n } from '@kbn/i18n';
import { getTermsFields, getSourceFields } from '../../../index_pattern_util';
import { ValidatedRange } from '../../../components/validated_range';
import { SORT_ORDER } from '../../../../common/constants';
import { DEFAULT_MAX_INNER_RESULT_WINDOW, SORT_ORDER } from '../../../../common/constants';
import { ESDocField } from '../../fields/es_doc_field';
import { FormattedMessage } from '@kbn/i18n/react';
import { loadIndexSettings } from './load_index_settings';

export class UpdateSourceEditor extends Component {
static propTypes = {
Expand All @@ -43,17 +44,31 @@ export class UpdateSourceEditor extends Component {
sourceFields: null,
termFields: null,
sortFields: null,
maxInnerResultWindow: DEFAULT_MAX_INNER_RESULT_WINDOW,
};

componentDidMount() {
this._isMounted = true;
this.loadFields();
this.loadIndexSettings();
}

componentWillUnmount() {
this._isMounted = false;
}

async loadIndexSettings() {
try {
const indexPattern = await indexPatternService.get(this.props.indexPatternId);
const { maxInnerResultWindow } = await loadIndexSettings(indexPattern.title);
if (this._isMounted) {
this.setState({ maxInnerResultWindow });
}
} catch (err) {
return;
}
}

async loadFields() {
let indexPattern;
try {
Expand Down Expand Up @@ -149,7 +164,7 @@ export class UpdateSourceEditor extends Component {
>
<ValidatedRange
min={1}
max={100}
max={this.state.maxInnerResultWindow}
step={1}
value={this.props.topHitsSize}
onChange={this.onTopHitsSizeChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

jest.mock('../../../kibana_services', () => ({}));

jest.mock('./load_index_settings', () => ({
loadIndexSettings: async () => {
return { maxInnerResultWindow: 100 };
},
}));

import React from 'react';
import { shallow } from 'enzyme';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import _ from 'lodash';
import { Schemas } from 'ui/vis/editors/default/schemas';
import { AggConfigs } from 'ui/agg_types';
import { i18n } from '@kbn/i18n';
import { ES_SIZE_LIMIT, FIELD_ORIGIN, METRIC_TYPE } from '../../../common/constants';
import { DEFAULT_MAX_BUCKETS_LIMIT, FIELD_ORIGIN, METRIC_TYPE } from '../../../common/constants';
import { ESDocField } from '../fields/es_doc_field';
import { AbstractESAggSource } from './es_agg_source';

Expand Down Expand Up @@ -170,7 +170,7 @@ export class ESTermSource extends AbstractESAggSource {
schema: 'segment',
params: {
field: this._termField.getName(),
size: ES_SIZE_LIMIT,
size: DEFAULT_MAX_BUCKETS_LIMIT,
},
},
];
Expand Down
1 change: 0 additions & 1 deletion x-pack/legacy/plugins/maps/public/layers/vector_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ export class VectorLayer extends AbstractLayer {
const requestToken = Symbol(`layer-${this.getId()}-${SOURCE_DATA_ID_ORIGIN}`);
const searchFilters = this._getSearchFilters(dataFilters);
const prevDataRequest = this.getSourceDataRequest();

const canSkipFetch = await canSkipSourceUpdate({
source: this._source,
prevDataRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 _ from 'lodash';
import { DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_INNER_RESULT_WINDOW } from '../../common/constants';

export function getIndexPatternSettings(indicesSettingsResp) {
let maxResultWindow = Infinity;
let maxInnerResultWindow = Infinity;
Object.values(indicesSettingsResp).forEach(indexSettings => {
const indexMaxResultWindow = _.get(
indexSettings,
'settings.index.max_result_window',
DEFAULT_MAX_RESULT_WINDOW
);
maxResultWindow = Math.min(maxResultWindow, indexMaxResultWindow);

const indexMaxInnerResultWindow = _.get(
indexSettings,
'settings.index.max_inner_result_window',
DEFAULT_MAX_INNER_RESULT_WINDOW
);
maxInnerResultWindow = Math.min(indexMaxInnerResultWindow, indexMaxResultWindow);
});

return { maxResultWindow, maxInnerResultWindow };
}
Loading