mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Maps] Move sort out of top hits configuration for ES documents source (#47361)
* [Maps] Move sort out of top hits configuration for ES documents source * add migration script to convert topHitsTimeField to sortField * update i18n translations * add jest test for es docs source UpdateSourceEditor component * remove time configuration from top hits docs * update migrations integration expect statement * review feedback * reverse hits list so top documents by sort are drawn on top * update functional test expect to account for reversing hits order * update another functional test expect clause for reversing hits
This commit is contained in:
parent
8936e21a3c
commit
c17081ad4d
13 changed files with 568 additions and 92 deletions
|
@ -36,8 +36,6 @@ To enable most recent entities, click "Show most recent documents by entity" and
|
|||
|
||||
. 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 *Time* to the date field that puts your documents in chronological order.
|
||||
This field will be used to sort your documents in the top hits aggregation.
|
||||
. Set *Documents per entity* to configure the maximum number of documents accumulated per entity.
|
||||
|
||||
[role="xpack"]
|
||||
|
|
|
@ -39,6 +39,11 @@ export const LAYER_TYPE = {
|
|||
HEATMAP: 'HEATMAP'
|
||||
};
|
||||
|
||||
export const SORT_ORDER = {
|
||||
ASC: 'asc',
|
||||
DESC: 'desc',
|
||||
};
|
||||
|
||||
export const EMS_TMS = 'EMS_TMS';
|
||||
export const EMS_FILE = 'EMS_FILE';
|
||||
export const ES_GEO_GRID = 'ES_GEO_GRID';
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { ES_SEARCH, SORT_ORDER } from '../constants';
|
||||
|
||||
function isEsDocumentSource(layerDescriptor) {
|
||||
const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type');
|
||||
return sourceType === ES_SEARCH;
|
||||
}
|
||||
|
||||
export function topHitsTimeToSort({ attributes }) {
|
||||
if (!attributes.layerListJSON) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
const layerList = JSON.parse(attributes.layerListJSON);
|
||||
layerList.forEach((layerDescriptor) => {
|
||||
if (isEsDocumentSource(layerDescriptor)) {
|
||||
if (_.has(layerDescriptor, 'sourceDescriptor.topHitsTimeField')) {
|
||||
layerDescriptor.sourceDescriptor.sortField = layerDescriptor.sourceDescriptor.topHitsTimeField;
|
||||
layerDescriptor.sourceDescriptor.sortOrder = SORT_ORDER.DESC;
|
||||
delete layerDescriptor.sourceDescriptor.topHitsTimeField;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...attributes,
|
||||
layerListJSON: JSON.stringify(layerList),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint max-len: 0 */
|
||||
|
||||
import { topHitsTimeToSort } from './top_hits_time_to_sort';
|
||||
|
||||
describe('topHitsTimeToSort', () => {
|
||||
|
||||
test('Should handle missing layerListJSON attribute', () => {
|
||||
const attributes = {
|
||||
title: 'my map',
|
||||
};
|
||||
expect(topHitsTimeToSort({ attributes })).toEqual({
|
||||
title: 'my map',
|
||||
});
|
||||
});
|
||||
|
||||
test('Should move topHitsTimeField to sortField for ES documents sources', () => {
|
||||
const layerListJSON = JSON.stringify([
|
||||
{
|
||||
sourceDescriptor: {
|
||||
type: 'ES_SEARCH',
|
||||
topHitsSplitField: 'gpsId',
|
||||
topHitsTimeField: '@timestamp',
|
||||
}
|
||||
}
|
||||
]);
|
||||
const attributes = {
|
||||
title: 'my map',
|
||||
layerListJSON
|
||||
};
|
||||
expect(topHitsTimeToSort({ attributes })).toEqual({
|
||||
title: 'my map',
|
||||
layerListJSON: '[{\"sourceDescriptor\":{\"type\":\"ES_SEARCH\",\"topHitsSplitField\":\"gpsId\",\"sortField\":\"@timestamp\",\"sortOrder\":\"desc\"}}]',
|
||||
});
|
||||
});
|
||||
|
||||
test('Should handle ES documents sources without topHitsTimeField', () => {
|
||||
const layerListJSON = JSON.stringify([
|
||||
{
|
||||
sourceDescriptor: {
|
||||
type: 'ES_SEARCH',
|
||||
topHitsSplitField: 'gpsId',
|
||||
}
|
||||
}
|
||||
]);
|
||||
const attributes = {
|
||||
title: 'my map',
|
||||
layerListJSON
|
||||
};
|
||||
expect(topHitsTimeToSort({ attributes })).toEqual({
|
||||
title: 'my map',
|
||||
layerListJSON,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { extractReferences } from './common/migrations/references';
|
||||
import { emsRasterTileToEmsVectorTile } from './common/migrations/ems_raster_tile_to_ems_vector_tile';
|
||||
import { topHitsTimeToSort } from './common/migrations/top_hits_time_to_sort';
|
||||
|
||||
export const migrations = {
|
||||
'map': {
|
||||
|
@ -21,6 +22,14 @@ export const migrations = {
|
|||
'7.4.0': (doc) => {
|
||||
const attributes = emsRasterTileToEmsVectorTile(doc);
|
||||
|
||||
return {
|
||||
...doc,
|
||||
attributes,
|
||||
};
|
||||
},
|
||||
'7.5.0': (doc) => {
|
||||
const attributes = topHitsTimeToSort(doc);
|
||||
|
||||
return {
|
||||
...doc,
|
||||
attributes,
|
||||
|
|
|
@ -0,0 +1,292 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should enable sort order select when sort field provided 1`] = `
|
||||
<Fragment>
|
||||
<TooltipSelector
|
||||
fields={null}
|
||||
onChange={[Function]}
|
||||
value={Array []}
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Sort"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SingleFieldSelect
|
||||
compressed={true}
|
||||
fields={null}
|
||||
filterField={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder="Select sort field"
|
||||
value="@timestamp"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiSelect
|
||||
compressed={true}
|
||||
disabled={false}
|
||||
fullWidth={false}
|
||||
hasNoInitialSelection={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"text": "ASC",
|
||||
"value": "asc",
|
||||
},
|
||||
Object {
|
||||
"text": "DESC",
|
||||
"value": "desc",
|
||||
},
|
||||
]
|
||||
}
|
||||
value="DESC"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
label="Show top documents based on sort order"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
label="Dynamically filter for data in the visible map area"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`should render top hits form when useTopHits is true 1`] = `
|
||||
<Fragment>
|
||||
<TooltipSelector
|
||||
fields={null}
|
||||
onChange={[Function]}
|
||||
value={Array []}
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Sort"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SingleFieldSelect
|
||||
compressed={true}
|
||||
fields={null}
|
||||
filterField={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder="Select sort field"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiSelect
|
||||
compressed={true}
|
||||
disabled={true}
|
||||
fullWidth={false}
|
||||
hasNoInitialSelection={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"text": "ASC",
|
||||
"value": "asc",
|
||||
},
|
||||
Object {
|
||||
"text": "DESC",
|
||||
"value": "desc",
|
||||
},
|
||||
]
|
||||
}
|
||||
value="DESC"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
label="Show top documents based on sort order"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="rowCompressed"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Entity"
|
||||
labelType="label"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
compressed={true}
|
||||
fields={null}
|
||||
filterField={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder="Select entity field"
|
||||
value="trackId"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="rowCompressed"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Documents per entity"
|
||||
labelType="label"
|
||||
>
|
||||
<ValidatedRange
|
||||
compressed={true}
|
||||
data-test-subj="layerPanelTopHitsSize"
|
||||
max={100}
|
||||
min={1}
|
||||
onChange={[Function]}
|
||||
showInput={true}
|
||||
showLabels={true}
|
||||
showRange={true}
|
||||
step={1}
|
||||
value={1}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
label="Dynamically filter for data in the visible map area"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`should render update source editor 1`] = `
|
||||
<Fragment>
|
||||
<TooltipSelector
|
||||
fields={null}
|
||||
onChange={[Function]}
|
||||
value={Array []}
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Sort"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SingleFieldSelect
|
||||
compressed={true}
|
||||
fields={null}
|
||||
filterField={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder="Select sort field"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiSelect
|
||||
compressed={true}
|
||||
disabled={true}
|
||||
fullWidth={false}
|
||||
hasNoInitialSelection={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"text": "ASC",
|
||||
"value": "asc",
|
||||
},
|
||||
Object {
|
||||
"text": "DESC",
|
||||
"value": "desc",
|
||||
},
|
||||
]
|
||||
}
|
||||
value="DESC"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
label="Show top documents based on sort order"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
label="Dynamically filter for data in the visible map area"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</Fragment>
|
||||
`;
|
|
@ -14,7 +14,7 @@ import { SearchSource } from '../../../kibana_services';
|
|||
import { hitsToGeoJson } from '../../../elasticsearch_geo_utils';
|
||||
import { CreateSourceEditor } from './create_source_editor';
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
import { ES_SEARCH, ES_GEO_FIELD_TYPE, ES_SIZE_LIMIT } from '../../../../common/constants';
|
||||
import { ES_SEARCH, ES_GEO_FIELD_TYPE, ES_SIZE_LIMIT, SORT_ORDER } from '../../../../common/constants';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { ESTooltipProperty } from '../../tooltips/es_tooltip_property';
|
||||
|
@ -56,9 +56,10 @@ export class ESSearchSource extends AbstractESSource {
|
|||
geoField: descriptor.geoField,
|
||||
filterByMapBounds: _.get(descriptor, 'filterByMapBounds', DEFAULT_FILTER_BY_MAP_BOUNDS),
|
||||
tooltipProperties: _.get(descriptor, 'tooltipProperties', []),
|
||||
sortField: _.get(descriptor, 'sortField', ''),
|
||||
sortOrder: _.get(descriptor, 'sortOrder', SORT_ORDER.DESC),
|
||||
useTopHits: _.get(descriptor, 'useTopHits', false),
|
||||
topHitsSplitField: descriptor.topHitsSplitField,
|
||||
topHitsTimeField: descriptor.topHitsTimeField,
|
||||
topHitsSize: _.get(descriptor, 'topHitsSize', 1),
|
||||
}, inspectorAdapters);
|
||||
}
|
||||
|
@ -70,9 +71,10 @@ export class ESSearchSource extends AbstractESSource {
|
|||
onChange={onChange}
|
||||
filterByMapBounds={this._descriptor.filterByMapBounds}
|
||||
tooltipProperties={this._descriptor.tooltipProperties}
|
||||
sortField={this._descriptor.sortField}
|
||||
sortOrder={this._descriptor.sortOrder}
|
||||
useTopHits={this._descriptor.useTopHits}
|
||||
topHitsSplitField={this._descriptor.topHitsSplitField}
|
||||
topHitsTimeField={this._descriptor.topHitsTimeField}
|
||||
topHitsSize={this._descriptor.topHitsSize}
|
||||
/>
|
||||
);
|
||||
|
@ -135,10 +137,24 @@ export class ESSearchSource extends AbstractESSource {
|
|||
];
|
||||
}
|
||||
|
||||
// Returns sort content for an Elasticsearch search body
|
||||
_buildEsSort() {
|
||||
const {
|
||||
sortField,
|
||||
sortOrder,
|
||||
} = this._descriptor;
|
||||
return [
|
||||
{
|
||||
[sortField]: {
|
||||
order: sortOrder
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async _getTopHits(layerName, searchFilters, registerCancelCallback) {
|
||||
const {
|
||||
topHitsSplitField,
|
||||
topHitsTimeField,
|
||||
topHitsSize,
|
||||
} = this._descriptor;
|
||||
|
||||
|
@ -158,7 +174,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
});
|
||||
|
||||
const searchSource = await this._makeSearchSource(searchFilters, 0);
|
||||
searchSource.setField('aggs', {
|
||||
const aggs = {
|
||||
entitySplit: {
|
||||
terms: {
|
||||
field: topHitsSplitField,
|
||||
|
@ -167,13 +183,6 @@ export class ESSearchSource extends AbstractESSource {
|
|||
aggs: {
|
||||
entityHits: {
|
||||
top_hits: {
|
||||
sort: [
|
||||
{
|
||||
[topHitsTimeField]: {
|
||||
order: 'desc'
|
||||
}
|
||||
}
|
||||
],
|
||||
_source: {
|
||||
includes: searchFilters.fieldNames
|
||||
},
|
||||
|
@ -183,7 +192,11 @@ export class ESSearchSource extends AbstractESSource {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (this._hasSort()) {
|
||||
aggs.entitySplit.aggs.entityHits.top_hits.sort = this._buildEsSort();
|
||||
}
|
||||
searchSource.setField('aggs', aggs);
|
||||
|
||||
const resp = await this._runEsQuery(layerName, searchSource, registerCancelCallback, 'Elasticsearch document top hits request');
|
||||
|
||||
|
@ -193,7 +206,7 @@ export class ESSearchSource extends AbstractESSource {
|
|||
entityBuckets.forEach(entityBucket => {
|
||||
const total = _.get(entityBucket, 'entityHits.hits.total', 0);
|
||||
const hits = _.get(entityBucket, 'entityHits.hits.hits', []);
|
||||
// Reverse hits list so they are drawn from oldest to newest (per entity) so newest events are on top
|
||||
// Reverse hits list so top documents by sort are drawn on top
|
||||
allHits.push(...hits.reverse());
|
||||
if (total > hits.length) {
|
||||
hasTrimmedResults = true;
|
||||
|
@ -218,10 +231,14 @@ export class ESSearchSource extends AbstractESSource {
|
|||
// By setting "fields", SearchSource removes all of defaults
|
||||
searchSource.setField('fields', searchFilters.fieldNames);
|
||||
|
||||
if (this._hasSort()) {
|
||||
searchSource.setField('sort', this._buildEsSort());
|
||||
}
|
||||
|
||||
const resp = await this._runEsQuery(layerName, searchSource, registerCancelCallback, 'Elasticsearch document request');
|
||||
|
||||
return {
|
||||
hits: resp.hits.hits,
|
||||
hits: resp.hits.hits.reverse(), // Reverse hits so top documents by sort are drawn on top
|
||||
meta: {
|
||||
areResultsTrimmed: resp.hits.total > resp.hits.hits.length
|
||||
}
|
||||
|
@ -229,8 +246,13 @@ export class ESSearchSource extends AbstractESSource {
|
|||
}
|
||||
|
||||
_isTopHits() {
|
||||
const { useTopHits, topHitsSplitField, topHitsTimeField } = this._descriptor;
|
||||
return !!(useTopHits && topHitsSplitField && topHitsTimeField);
|
||||
const { useTopHits, topHitsSplitField } = this._descriptor;
|
||||
return !!(useTopHits && topHitsSplitField);
|
||||
}
|
||||
|
||||
_hasSort() {
|
||||
const { sortField, sortOrder } = this._descriptor;
|
||||
return !!sortField && !!sortOrder;
|
||||
}
|
||||
|
||||
async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) {
|
||||
|
@ -406,9 +428,10 @@ export class ESSearchSource extends AbstractESSource {
|
|||
|
||||
getSyncMeta() {
|
||||
return {
|
||||
sortField: this._descriptor.sortField,
|
||||
sortOrder: this._descriptor.sortOrder,
|
||||
useTopHits: this._descriptor.useTopHits,
|
||||
topHitsSplitField: this._descriptor.topHitsSplitField,
|
||||
topHitsTimeField: this._descriptor.topHitsTimeField,
|
||||
topHitsSize: this._descriptor.topHitsSize,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiFormRow, EuiSwitch } from '@elastic/eui';
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiSwitch,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSelect,
|
||||
} from '@elastic/eui';
|
||||
import { SingleFieldSelect } from '../../../components/single_field_select';
|
||||
import { TooltipSelector } from '../../../components/tooltip_selector';
|
||||
|
||||
|
@ -14,6 +20,7 @@ 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';
|
||||
|
||||
export class UpdateSourceEditor extends Component {
|
||||
static propTypes = {
|
||||
|
@ -21,16 +28,17 @@ export class UpdateSourceEditor extends Component {
|
|||
onChange: PropTypes.func.isRequired,
|
||||
filterByMapBounds: PropTypes.bool.isRequired,
|
||||
tooltipProperties: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
sortField: PropTypes.string,
|
||||
sortOrder: PropTypes.string.isRequired,
|
||||
useTopHits: PropTypes.bool.isRequired,
|
||||
topHitsSplitField: PropTypes.string,
|
||||
topHitsTimeField: PropTypes.string,
|
||||
topHitsSize: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
tooltipFields: null,
|
||||
termFields: null,
|
||||
dateFields: null,
|
||||
sortFields: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -64,27 +72,11 @@ export class UpdateSourceEditor extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
const dateFields = indexPattern.fields.filter(field => {
|
||||
return field.type === 'date';
|
||||
});
|
||||
|
||||
this.setState({
|
||||
dateFields,
|
||||
tooltipFields: getSourceFields(indexPattern.fields),
|
||||
termFields: getTermsFields(indexPattern.fields),
|
||||
sortFields: indexPattern.fields.filter(field => field.sortable),
|
||||
});
|
||||
|
||||
if (!this.props.topHitsTimeField) {
|
||||
// prefer default time field
|
||||
if (indexPattern.timeFieldName) {
|
||||
this.onTopHitsTimeFieldChange(indexPattern.timeFieldName);
|
||||
} else {
|
||||
// fall back to first date field in index
|
||||
if (dateFields.length > 0) {
|
||||
this.onTopHitsTimeFieldChange(dateFields[0].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_onTooltipPropertiesChange = propertyNames => {
|
||||
this.props.onChange({ propName: 'tooltipProperties', value: propertyNames });
|
||||
|
@ -102,10 +94,14 @@ export class UpdateSourceEditor extends Component {
|
|||
this.props.onChange({ propName: 'topHitsSplitField', value: topHitsSplitField });
|
||||
};
|
||||
|
||||
onTopHitsTimeFieldChange = topHitsTimeField => {
|
||||
this.props.onChange({ propName: 'topHitsTimeField', value: topHitsTimeField });
|
||||
onSortFieldChange = sortField => {
|
||||
this.props.onChange({ propName: 'sortField', value: sortField });
|
||||
};
|
||||
|
||||
onSortOrderChange = e => {
|
||||
this.props.onChange({ propName: 'sortOrder', value: e.target.value });
|
||||
}
|
||||
|
||||
onTopHitsSizeChange = size => {
|
||||
this.props.onChange({ propName: 'topHitsSize', value: size });
|
||||
};
|
||||
|
@ -115,31 +111,8 @@ export class UpdateSourceEditor extends Component {
|
|||
return null;
|
||||
}
|
||||
|
||||
let timeFieldSelect;
|
||||
let sizeSlider;
|
||||
if (this.props.topHitsSplitField) {
|
||||
timeFieldSelect = (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.source.esSearch.topHitsTimeFieldLabel', {
|
||||
defaultMessage: 'Time',
|
||||
})}
|
||||
display="rowCompressed"
|
||||
>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate(
|
||||
'xpack.maps.source.esSearch.topHitsTimeFieldSelectPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Select time field',
|
||||
}
|
||||
)}
|
||||
value={this.props.topHitsTimeField}
|
||||
onChange={this.onTopHitsTimeFieldChange}
|
||||
fields={this.state.dateFields}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
||||
sizeSlider = (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.source.esSearch.topHitsSizeLabel', {
|
||||
|
@ -185,29 +158,12 @@ export class UpdateSourceEditor extends Component {
|
|||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{timeFieldSelect}
|
||||
|
||||
{sizeSlider}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let topHitsCheckbox;
|
||||
if (this.state.dateFields && this.state.dateFields.length) {
|
||||
topHitsCheckbox = (
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.maps.source.esSearch.useTopHitsLabel', {
|
||||
defaultMessage: `Show most recent documents by entity`,
|
||||
})}
|
||||
checked={this.props.useTopHits}
|
||||
onChange={this.onUseTopHitsChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TooltipSelector
|
||||
|
@ -216,6 +172,56 @@ export class UpdateSourceEditor extends Component {
|
|||
fields={this.state.tooltipFields}
|
||||
/>
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.maps.source.esSearch.sortLabel', {
|
||||
defaultMessage: `Sort`,
|
||||
})}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate(
|
||||
'xpack.maps.source.esSearch.sortFieldSelectPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Select sort field',
|
||||
}
|
||||
)}
|
||||
value={this.props.sortField}
|
||||
onChange={this.onSortFieldChange}
|
||||
fields={this.state.sortFields}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
disabled={!this.props.sortField}
|
||||
options={[
|
||||
{ text: 'ASC', value: SORT_ORDER.ASC },
|
||||
{ text: 'DESC', value: SORT_ORDER.DESC }
|
||||
]}
|
||||
value={this.props.sortOrder}
|
||||
onChange={this.onSortOrderChange}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.maps.source.esSearch.useTopHitsLabel', {
|
||||
defaultMessage: `Show top documents based on sort order`,
|
||||
})}
|
||||
checked={this.props.useTopHits}
|
||||
onChange={this.onUseTopHitsChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{this.renderTopHitsForm()}
|
||||
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.maps.source.esSearch.extentFilterLabel', {
|
||||
|
@ -226,9 +232,6 @@ export class UpdateSourceEditor extends Component {
|
|||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{topHitsCheckbox}
|
||||
|
||||
{this.renderTopHitsForm()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
jest.mock('../../../kibana_services', () => ({}));
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { UpdateSourceEditor } from './update_source_editor';
|
||||
|
||||
const defaultProps = {
|
||||
indexPatternId: 'indexPattern1',
|
||||
onChange: () => {},
|
||||
filterByMapBounds: true,
|
||||
tooltipProperties: [],
|
||||
sortOrder: 'DESC',
|
||||
useTopHits: false,
|
||||
topHitsSplitField: 'trackId',
|
||||
topHitsSize: 1,
|
||||
};
|
||||
|
||||
test('should render update source editor', async () => {
|
||||
const component = shallow(
|
||||
<UpdateSourceEditor
|
||||
{...defaultProps}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should enable sort order select when sort field provided', async () => {
|
||||
const component = shallow(
|
||||
<UpdateSourceEditor
|
||||
{...defaultProps}
|
||||
sortField="@timestamp"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render top hits form when useTopHits is true', async () => {
|
||||
const component = shallow(
|
||||
<UpdateSourceEditor
|
||||
{...defaultProps}
|
||||
useTopHits={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
|
@ -5737,8 +5737,6 @@
|
|||
"xpack.maps.source.esSearch.topHitsSizeLabel": "エンティティごとのドキュメント数",
|
||||
"xpack.maps.source.esSearch.topHitsSplitFieldLabel": "エンティティ",
|
||||
"xpack.maps.source.esSearch.topHitsSplitFieldSelectPlaceholder": "エンティティフィールドを選択",
|
||||
"xpack.maps.source.esSearch.topHitsTimeFieldLabel": "時間",
|
||||
"xpack.maps.source.esSearch.topHitsTimeFieldSelectPlaceholder": "タイムフィールドを選択",
|
||||
"xpack.maps.source.esSearch.useTopHitsLabel": "エンティティによる最も最近のドキュメントを表示",
|
||||
"xpack.maps.source.esSearchDescription": "Kibana インデックスパターンの地理空間データ",
|
||||
"xpack.maps.source.esSearchTitle": "ドキュメント",
|
||||
|
|
|
@ -5740,8 +5740,6 @@
|
|||
"xpack.maps.source.esSearch.topHitsSizeLabel": "每个实体的文档",
|
||||
"xpack.maps.source.esSearch.topHitsSplitFieldLabel": "实体",
|
||||
"xpack.maps.source.esSearch.topHitsSplitFieldSelectPlaceholder": "选择实体字段",
|
||||
"xpack.maps.source.esSearch.topHitsTimeFieldLabel": "时间",
|
||||
"xpack.maps.source.esSearch.topHitsTimeFieldSelectPlaceholder": "选择时间字段",
|
||||
"xpack.maps.source.esSearch.useTopHitsLabel": "按实体显示最近的文档",
|
||||
"xpack.maps.source.esSearchDescription": "Kibana 索引模式的地理空间数据",
|
||||
"xpack.maps.source.esSearchTitle": "文档",
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function ({ getService }) {
|
|||
type: 'index-pattern'
|
||||
}
|
||||
]);
|
||||
expect(resp.body.migrationVersion).to.eql({ map: '7.4.0' });
|
||||
expect(resp.body.migrationVersion).to.eql({ map: '7.5.0' });
|
||||
expect(resp.body.attributes.layerListJSON.includes('indexPatternRefName')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -105,7 +105,7 @@ export default function ({ getPageObjects, getService }) {
|
|||
return feature.properties.__kbn__isvisible__;
|
||||
});
|
||||
|
||||
expect(visibilitiesOfFeatures).to.eql([true, true, true, false]);
|
||||
expect(visibilitiesOfFeatures).to.eql([false, true, true, true]);
|
||||
});
|
||||
|
||||
|
||||
|
@ -182,7 +182,7 @@ export default function ({ getPageObjects, getService }) {
|
|||
return feature.properties.__kbn__isvisible__;
|
||||
});
|
||||
|
||||
expect(visibilitiesOfFeatures).to.eql([false, false, true, false]);
|
||||
expect(visibilitiesOfFeatures).to.eql([false, true, false, false]);
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue