[Maps] surface geo_shape clustering gold feature (#68666)

* [Maps] surface geo_shape clustering gold feature

* show gold in scaling form

* tslint

* more tslint changes

* fix jest test

* fix functional test by handling fields prop being undefined

* tslint fixes - that thing is slow to run

* review feedback

* update doc_values missing copy

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2020-06-11 21:01:06 -06:00 committed by GitHub
parent ae8f6e3195
commit 7d9378aa22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 376 additions and 242 deletions

View file

@ -99,6 +99,9 @@ export enum ES_GEO_FIELD_TYPE {
GEO_SHAPE = 'geo_shape',
}
// Using strings instead of ES_GEO_FIELD_TYPE enum to avoid typeing errors where IFieldType.type is compared to value
export const ES_GEO_FIELD_TYPES = ['geo_point', 'geo_shape'];
export enum ES_SPATIAL_RELATIONS {
INTERSECTS = 'INTERSECTS',
DISJOINT = 'DISJOINT',

View file

@ -8,15 +8,25 @@ import _ from 'lodash';
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { ES_GEO_FIELD_TYPES } from '../../../../common/constants';
import { SingleFieldSelect } from '../../../components/single_field_select';
import { getIndexPatternService, getIndexPatternSelectComponent } from '../../../kibana_services';
import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout';
import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiSpacer } from '@elastic/eui';
import { getAggregatableGeoFieldTypes, getFieldsWithGeoTileAgg } from '../../../index_pattern_util';
import {
getFieldsWithGeoTileAgg,
getGeoFields,
getGeoTileAggNotSupportedReason,
supportsGeoTileAgg,
} from '../../../index_pattern_util';
import { RenderAsSelect } from './render_as_select';
function doesNotSupportGeoTileAgg(field) {
return !supportsGeoTileAgg(field);
}
export class CreateSourceEditor extends Component {
static propTypes = {
onSourceConfigChange: PropTypes.func.isRequired,
@ -87,9 +97,9 @@ export class CreateSourceEditor extends Component {
});
//make default selection
const geoFields = getFieldsWithGeoTileAgg(indexPattern.fields);
if (geoFields[0]) {
this._onGeoFieldSelect(geoFields[0].name);
const geoFieldsWithGeoTileAgg = getFieldsWithGeoTileAgg(indexPattern.fields);
if (geoFieldsWithGeoTileAgg[0]) {
this._onGeoFieldSelect(geoFieldsWithGeoTileAgg[0].name);
}
}, 300);
@ -141,10 +151,10 @@ export class CreateSourceEditor extends Component {
value={this.state.geoField}
onChange={this._onGeoFieldSelect}
fields={
this.state.indexPattern
? getFieldsWithGeoTileAgg(this.state.indexPattern.fields)
: undefined
this.state.indexPattern ? getGeoFields(this.state.indexPattern.fields) : undefined
}
isFieldDisabled={doesNotSupportGeoTileAgg}
getFieldDisabledReason={getGeoTileAggNotSupportedReason}
/>
</EuiFormRow>
);
@ -176,7 +186,7 @@ export class CreateSourceEditor extends Component {
placeholder={i18n.translate('xpack.maps.source.esGeoGrid.indexPatternPlaceholder', {
defaultMessage: 'Select index pattern',
})}
fieldTypes={getAggregatableGeoFieldTypes()}
fieldTypes={ES_GEO_FIELD_TYPES}
onNoIndexPatterns={this._onNoIndexPatterns}
/>
</EuiFormRow>

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should not render clusters option when clustering is not supported 1`] = `
exports[`should disable clusters option when clustering is not supported 1`] = `
<Fragment>
<EuiTitle
size="xs"
@ -24,22 +24,33 @@ exports[`should not render clusters option when clustering is not supported 1`]
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiRadioGroup
idSelected="LIMIT"
onChange={[Function]}
options={
Array [
Object {
"id": "LIMIT",
"label": "Limit results to 10000.",
},
Object {
"id": "TOP_HITS",
"label": "Show top hits per entity.",
},
]
}
/>
<div>
<EuiRadio
checked={true}
id="LIMIT"
label="Limit results to 10000."
onChange={[Function]}
/>
<EuiRadio
checked={false}
id="TOP_HITS"
label="Show top hits per entity."
onChange={[Function]}
/>
<EuiToolTip
content="Simulated clustering disabled"
delay="regular"
position="left"
>
<EuiRadio
checked={false}
disabled={true}
id="CLUSTERS"
label="Show clusters when results exceed 10000."
onChange={[Function]}
/>
</EuiToolTip>
</div>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
@ -83,26 +94,27 @@ exports[`should render 1`] = `
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiRadioGroup
idSelected="LIMIT"
onChange={[Function]}
options={
Array [
Object {
"id": "LIMIT",
"label": "Limit results to 10000.",
},
Object {
"id": "TOP_HITS",
"label": "Show top hits per entity.",
},
Object {
"id": "CLUSTERS",
"label": "Show clusters when results exceed 10000.",
},
]
}
/>
<div>
<EuiRadio
checked={true}
id="LIMIT"
label="Limit results to 10000."
onChange={[Function]}
/>
<EuiRadio
checked={false}
id="TOP_HITS"
label="Show top hits per entity."
onChange={[Function]}
/>
<EuiRadio
checked={false}
disabled={false}
id="CLUSTERS"
label="Show clusters when results exceed 10000."
onChange={[Function]}
/>
</div>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
@ -146,26 +158,27 @@ exports[`should render top hits form when scaling type is TOP_HITS 1`] = `
hasEmptyLabelSpace={false}
labelType="label"
>
<EuiRadioGroup
idSelected="TOP_HITS"
onChange={[Function]}
options={
Array [
Object {
"id": "LIMIT",
"label": "Limit results to 10000.",
},
Object {
"id": "TOP_HITS",
"label": "Show top hits per entity.",
},
Object {
"id": "CLUSTERS",
"label": "Show clusters when results exceed 10000.",
},
]
}
/>
<div>
<EuiRadio
checked={false}
id="LIMIT"
label="Limit results to 10000."
onChange={[Function]}
/>
<EuiRadio
checked={true}
id="TOP_HITS"
label="Show top hits per entity."
onChange={[Function]}
/>
<EuiRadio
checked={false}
disabled={false}
id="CLUSTERS"
label="Show clusters when results exceed 10000."
onChange={[Function]}
/>
</div>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}

View file

@ -13,20 +13,15 @@ import { SingleFieldSelect } from '../../../components/single_field_select';
import { getIndexPatternService, getIndexPatternSelectComponent } from '../../../kibana_services';
import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout';
import { i18n } from '@kbn/i18n';
import { ES_GEO_FIELD_TYPE, SCALING_TYPES } from '../../../../common/constants';
import { ES_GEO_FIELD_TYPES, SCALING_TYPES } from '../../../../common/constants';
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
import { indexPatterns } from '../../../../../../../src/plugins/data/public';
import { ScalingForm } from './scaling_form';
import { getTermsFields, supportsGeoTileAgg } from '../../../index_pattern_util';
function getGeoFields(fields) {
return fields.filter((field) => {
return (
!indexPatterns.isNestedField(field) &&
[ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE].includes(field.type)
);
});
}
import {
getGeoFields,
getTermsFields,
getGeoTileAggNotSupportedReason,
supportsGeoTileAgg,
} from '../../../index_pattern_util';
function doesGeoFieldSupportGeoTileAgg(indexPattern, geoFieldName) {
return indexPattern ? supportsGeoTileAgg(indexPattern.fields.getByName(geoFieldName)) : false;
@ -217,6 +212,13 @@ export class CreateSourceEditor extends Component {
this.state.indexPattern,
this.state.geoFieldName
)}
clusteringDisabledReason={
this.state.indexPattern
? getGeoTileAggNotSupportedReason(
this.state.indexPattern.fields.getByName(this.state.geoFieldName)
)
: null
}
termFields={getTermsFields(this.state.indexPattern.fields)}
topHitsSplitField={this.state.topHitsSplitField}
topHitsSize={this.state.topHitsSize}
@ -260,7 +262,7 @@ export class CreateSourceEditor extends Component {
defaultMessage: 'Select index pattern',
}
)}
fieldTypes={[ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE]}
fieldTypes={ES_GEO_FIELD_TYPES}
onNoIndexPatterns={this._onNoIndexPatterns}
/>
</EuiFormRow>

View file

@ -34,8 +34,14 @@ test('should render', async () => {
expect(component).toMatchSnapshot();
});
test('should not render clusters option when clustering is not supported', async () => {
const component = shallow(<ScalingForm {...defaultProps} supportsClustering={false} />);
test('should disable clusters option when clustering is not supported', async () => {
const component = shallow(
<ScalingForm
{...defaultProps}
supportsClustering={false}
clusteringDisabledReason="Simulated clustering disabled"
/>
);
expect(component).toMatchSnapshot();
});

View file

@ -12,11 +12,11 @@ import {
EuiTitle,
EuiSpacer,
EuiHorizontalRule,
EuiRadioGroup,
EuiRadio,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
// @ts-ignore
import { SingleFieldSelect } from '../../../components/single_field_select';
import { getIndexPatternService } from '../../../kibana_services';
// @ts-ignore
@ -38,6 +38,7 @@ interface Props {
onChange: (args: OnSourceChangeArgs) => void;
scalingType: SCALING_TYPES;
supportsClustering: boolean;
clusteringDisabledReason?: string | null;
termFields: IFieldType[];
topHitsSplitField?: string;
topHitsSize: number;
@ -88,7 +89,7 @@ export class ScalingForm extends Component<Props, State> {
this.props.onChange({ propName: 'filterByMapBounds', value: event.target.checked });
};
_onTopHitsSplitFieldChange = (topHitsSplitField: string) => {
_onTopHitsSplitFieldChange = (topHitsSplitField?: string) => {
this.props.onChange({ propName: 'topHitsSplitField', value: topHitsSplitField });
};
@ -149,32 +150,30 @@ export class ScalingForm extends Component<Props, State> {
);
}
render() {
const scalingOptions = [
{
id: SCALING_TYPES.LIMIT,
label: i18n.translate('xpack.maps.source.esSearch.limitScalingLabel', {
defaultMessage: 'Limit results to {maxResultWindow}.',
values: { maxResultWindow: this.state.maxResultWindow },
}),
},
{
id: SCALING_TYPES.TOP_HITS,
label: i18n.translate('xpack.maps.source.esSearch.useTopHitsLabel', {
defaultMessage: 'Show top hits per entity.',
}),
},
];
if (this.props.supportsClustering) {
scalingOptions.push({
id: SCALING_TYPES.CLUSTERS,
label: i18n.translate('xpack.maps.source.esSearch.clusterScalingLabel', {
_renderClusteringRadio() {
const clusteringRadio = (
<EuiRadio
id={SCALING_TYPES.CLUSTERS}
label={i18n.translate('xpack.maps.source.esSearch.clusterScalingLabel', {
defaultMessage: 'Show clusters when results exceed {maxResultWindow}.',
values: { maxResultWindow: this.state.maxResultWindow },
}),
});
}
})}
checked={this.props.scalingType === SCALING_TYPES.CLUSTERS}
onChange={() => this._onScalingTypeChange(SCALING_TYPES.CLUSTERS)}
disabled={!this.props.supportsClustering}
/>
);
return this.props.clusteringDisabledReason ? (
<EuiToolTip position="left" content={this.props.clusteringDisabledReason}>
{clusteringRadio}
</EuiToolTip>
) : (
clusteringRadio
);
}
render() {
let filterByBoundsSwitch;
if (this.props.scalingType !== SCALING_TYPES.CLUSTERS) {
filterByBoundsSwitch = (
@ -212,11 +211,26 @@ export class ScalingForm extends Component<Props, State> {
<EuiSpacer size="m" />
<EuiFormRow>
<EuiRadioGroup
options={scalingOptions}
idSelected={this.props.scalingType}
onChange={this._onScalingTypeChange}
/>
<div>
<EuiRadio
id={SCALING_TYPES.LIMIT}
label={i18n.translate('xpack.maps.source.esSearch.limitScalingLabel', {
defaultMessage: 'Limit results to {maxResultWindow}.',
values: { maxResultWindow: this.state.maxResultWindow },
})}
checked={this.props.scalingType === SCALING_TYPES.LIMIT}
onChange={() => this._onScalingTypeChange(SCALING_TYPES.LIMIT)}
/>
<EuiRadio
id={SCALING_TYPES.TOP_HITS}
label={i18n.translate('xpack.maps.source.esSearch.useTopHitsLabel', {
defaultMessage: 'Show top hits per entity.',
})}
checked={this.props.scalingType === SCALING_TYPES.TOP_HITS}
onChange={() => this._onScalingTypeChange(SCALING_TYPES.TOP_HITS)}
/>
{this._renderClusteringRadio()}
</div>
</EuiFormRow>
{filterByBoundsSwitch}

View file

@ -12,7 +12,12 @@ import { TooltipSelector } from '../../../components/tooltip_selector';
import { getIndexPatternService } from '../../../kibana_services';
import { i18n } from '@kbn/i18n';
import { getTermsFields, getSourceFields, supportsGeoTileAgg } from '../../../index_pattern_util';
import {
getGeoTileAggNotSupportedReason,
getTermsFields,
getSourceFields,
supportsGeoTileAgg,
} from '../../../index_pattern_util';
import { SORT_ORDER } from '../../../../common/constants';
import { ESDocField } from '../../fields/es_doc_field';
import { FormattedMessage } from '@kbn/i18n/react';
@ -91,6 +96,7 @@ export class UpdateSourceEditor extends Component {
this.setState({
supportsClustering: supportsGeoTileAgg(geoField),
clusteringDisabledReason: getGeoTileAggNotSupportedReason(geoField),
sourceFields: sourceFields,
termFields: getTermsFields(indexPattern.fields), //todo change term fields to use fields
sortFields: indexPattern.fields.filter(
@ -201,6 +207,7 @@ export class UpdateSourceEditor extends Component {
onChange={this.props.onChange}
scalingType={this.props.scalingType}
supportsClustering={this.state.supportsClustering}
clusteringDisabledReason={this.state.clusteringDisabledReason}
termFields={this.state.termFields}
topHitsSplitField={this.props.topHitsSplitField}
topHitsSize={this.props.topHitsSize}

View file

@ -1,68 +0,0 @@
/*
* 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 PropTypes from 'prop-types';
import React from 'react';
import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FieldIcon } from '../../../../../src/plugins/kibana_react/public';
function fieldsToOptions(fields) {
if (!fields) {
return [];
}
return fields
.map((field) => {
return {
value: field,
label: 'label' in field ? field.label : field.name,
};
})
.sort((a, b) => {
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
});
}
function renderOption(option, searchValue, contentClassName) {
return (
<EuiFlexGroup className={contentClassName} gutterSize="s" alignItems="center">
<EuiFlexItem grow={null}>
<FieldIcon type={option.value.type} fill="none" />
</EuiFlexItem>
<EuiFlexItem>
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
</EuiFlexItem>
</EuiFlexGroup>
);
}
export function SingleFieldSelect({ fields, onChange, value, placeholder, ...rest }) {
const onSelection = (selectedOptions) => {
onChange(_.get(selectedOptions, '0.value.name'));
};
return (
<EuiComboBox
placeholder={placeholder}
singleSelection={true}
options={fieldsToOptions(fields)}
selectedOptions={value ? [{ value: value, label: value }] : []}
onChange={onSelection}
isDisabled={!fields}
renderOption={renderOption}
{...rest}
/>
);
}
SingleFieldSelect.propTypes = {
placeholder: PropTypes.string,
fields: PropTypes.array,
onChange: PropTypes.func.isRequired,
value: PropTypes.string, // fieldName
};

View file

@ -0,0 +1,118 @@
/*
* 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 React from 'react';
import {
EuiComboBox,
EuiComboBoxProps,
EuiComboBoxOptionOption,
EuiHighlight,
EuiFlexGroup,
EuiFlexItem,
EuiToolTip,
} from '@elastic/eui';
import { IFieldType } from 'src/plugins/data/public';
import { FieldIcon } from '../../../../../src/plugins/kibana_react/public';
function fieldsToOptions(
fields?: IFieldType[],
isFieldDisabled?: (field: IFieldType) => boolean
): Array<EuiComboBoxOptionOption<IFieldType>> {
if (!fields) {
return [];
}
return fields
.map((field) => {
const option: EuiComboBoxOptionOption<IFieldType> = {
value: field,
label: field.name,
};
if (isFieldDisabled && isFieldDisabled(field)) {
option.disabled = true;
}
return option;
})
.sort((a, b) => {
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
});
}
type Props = Omit<
EuiComboBoxProps<IFieldType>,
'isDisabled' | 'onChange' | 'options' | 'renderOption' | 'selectedOptions' | 'singleSelection'
> & {
fields?: IFieldType[];
onChange: (fieldName?: string) => void;
value?: string; // index pattern field name
isFieldDisabled?: (field: IFieldType) => boolean;
getFieldDisabledReason?: (field: IFieldType) => string | null;
};
export function SingleFieldSelect({
fields,
getFieldDisabledReason,
isFieldDisabled,
onChange,
value,
...rest
}: Props) {
function renderOption(
option: EuiComboBoxOptionOption<IFieldType>,
searchValue: string,
contentClassName: string
) {
const content = (
<EuiFlexGroup className={contentClassName} gutterSize="s" alignItems="center">
<EuiFlexItem grow={null}>
<FieldIcon type={option.value!.type} fill="none" />
</EuiFlexItem>
<EuiFlexItem>
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
</EuiFlexItem>
</EuiFlexGroup>
);
const disabledReason =
option.disabled && getFieldDisabledReason ? getFieldDisabledReason(option.value!) : null;
return disabledReason ? (
<EuiToolTip position="left" content={disabledReason}>
{content}
</EuiToolTip>
) : (
content
);
}
const onSelection = (selectedOptions: Array<EuiComboBoxOptionOption<IFieldType>>) => {
onChange(_.get(selectedOptions, '0.value.name'));
};
const selectedOptions: Array<EuiComboBoxOptionOption<IFieldType>> = [];
if (value && fields) {
const selectedField = fields.find((field: IFieldType) => {
return field.name === value;
});
if (selectedField) {
selectedOptions.push({ value: selectedField, label: value });
}
}
return (
<EuiComboBox
singleSelection={true}
options={fieldsToOptions(fields, isFieldDisabled)}
selectedOptions={selectedOptions}
onChange={onSelection}
isDisabled={!fields}
renderOption={renderOption}
{...rest}
/>
);
}

View file

@ -1,61 +0,0 @@
/*
* 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 { getIndexPatternService, getIsGoldPlus } from './kibana_services';
import { indexPatterns } from '../../../../src/plugins/data/public';
import { ES_GEO_FIELD_TYPE } from '../common/constants';
export async function getIndexPatternsFromIds(indexPatternIds = []) {
const promises = [];
indexPatternIds.forEach((id) => {
const indexPatternPromise = getIndexPatternService().get(id);
if (indexPatternPromise) {
promises.push(indexPatternPromise);
}
});
return await Promise.all(promises);
}
export function getTermsFields(fields) {
return fields.filter((field) => {
return (
field.aggregatable &&
!indexPatterns.isNestedField(field) &&
['number', 'boolean', 'date', 'ip', 'string'].includes(field.type)
);
});
}
export function getAggregatableGeoFieldTypes() {
const aggregatableFieldTypes = [ES_GEO_FIELD_TYPE.GEO_POINT];
if (getIsGoldPlus()) {
aggregatableFieldTypes.push(ES_GEO_FIELD_TYPE.GEO_SHAPE);
}
return aggregatableFieldTypes;
}
export function getFieldsWithGeoTileAgg(fields) {
return fields.filter(supportsGeoTileAgg);
}
export function supportsGeoTileAgg(field) {
return (
field &&
field.aggregatable &&
!indexPatterns.isNestedField(field) &&
getAggregatableGeoFieldTypes().includes(field.type)
);
}
// Returns filtered fields list containing only fields that exist in _source.
export function getSourceFields(fields) {
return fields.filter((field) => {
// Multi fields are not stored in _source and only exist in index.
const isMultiField = field.subType && field.subType.multi;
return !isMultiField && !indexPatterns.isNestedField(field);
});
}

View file

@ -18,6 +18,7 @@ describe('getSourceFields', () => {
const fields = [
{
name: 'agent',
type: 'string',
},
{
name: 'agent.keyword',
@ -26,10 +27,11 @@ describe('getSourceFields', () => {
parent: 'agent',
},
},
type: 'string',
},
];
const sourceFields = getSourceFields(fields);
expect(sourceFields).toEqual([{ name: 'agent' }]);
expect(sourceFields).toEqual([{ name: 'agent', type: 'string' }]);
});
});
@ -37,6 +39,7 @@ describe('Gold+ licensing', () => {
const testStubs = [
{
field: {
name: 'location',
type: 'geo_point',
aggregatable: true,
},
@ -45,6 +48,7 @@ describe('Gold+ licensing', () => {
},
{
field: {
name: 'location',
type: 'geo_shape',
aggregatable: false,
},
@ -53,6 +57,7 @@ describe('Gold+ licensing', () => {
},
{
field: {
name: 'location',
type: 'geo_shape',
aggregatable: true,
},

View file

@ -0,0 +1,85 @@
/*
* 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 { IFieldType, IndexPattern } from 'src/plugins/data/public';
import { i18n } from '@kbn/i18n';
import { getIndexPatternService, getIsGoldPlus } from './kibana_services';
import { indexPatterns } from '../../../../src/plugins/data/public';
import { ES_GEO_FIELD_TYPE, ES_GEO_FIELD_TYPES } from '../common/constants';
export function getGeoTileAggNotSupportedReason(field: IFieldType): string | null {
if (!field.aggregatable) {
return i18n.translate('xpack.maps.geoTileAgg.disabled.docValues', {
defaultMessage:
'Clustering requires aggregations. Enable aggregations by setting doc_values to true.',
});
}
if (field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE && !getIsGoldPlus()) {
return i18n.translate('xpack.maps.geoTileAgg.disabled.license', {
defaultMessage: 'Geo_shape clustering requires a Gold license.',
});
}
return null;
}
export async function getIndexPatternsFromIds(
indexPatternIds: string[] = []
): Promise<IndexPattern[]> {
const promises: Array<Promise<IndexPattern>> = [];
indexPatternIds.forEach((id) => {
promises.push(getIndexPatternService().get(id));
});
return await Promise.all(promises);
}
export function getTermsFields(fields: IFieldType[]): IFieldType[] {
return fields.filter((field) => {
return (
field.aggregatable &&
!indexPatterns.isNestedField(field) &&
['number', 'boolean', 'date', 'ip', 'string'].includes(field.type)
);
});
}
export function getAggregatableGeoFieldTypes(): string[] {
const aggregatableFieldTypes = [ES_GEO_FIELD_TYPE.GEO_POINT];
if (getIsGoldPlus()) {
aggregatableFieldTypes.push(ES_GEO_FIELD_TYPE.GEO_SHAPE);
}
return aggregatableFieldTypes;
}
export function getGeoFields(fields: IFieldType[]): IFieldType[] {
return fields.filter((field) => {
return !indexPatterns.isNestedField(field) && ES_GEO_FIELD_TYPES.includes(field.type);
});
}
export function getFieldsWithGeoTileAgg(fields: IFieldType[]): IFieldType[] {
return fields.filter(supportsGeoTileAgg);
}
export function supportsGeoTileAgg(field?: IFieldType): boolean {
return (
!!field &&
!!field.aggregatable &&
!indexPatterns.isNestedField(field) &&
getAggregatableGeoFieldTypes().includes(field.type)
);
}
// Returns filtered fields list containing only fields that exist in _source.
export function getSourceFields(fields: IFieldType[]): IFieldType[] {
return fields.filter((field) => {
// Multi fields are not stored in _source and only exist in index.
const isMultiField = field.subType && field.subType.multi;
return !isMultiField && !indexPatterns.isNestedField(field);
});
}