[Transform] Support for the top_metrics aggregation (#101152)

* [ML] init top_metrics agg

* [ML] support sort

* [ML] support _score sorting

* [ML] support sort mode

* [ML] support numeric type sorting

* [ML] update field label, hide additional sorting controls

* [ML] preserve advanced config

* [ML] update agg fields after runtime fields edit

* [ML] fix TS issue with EuiButtonGroup

* [ML] fix Field label

* [ML] refactor setUiConfig

* [ML] update unit tests

* [ML] wrap advanced sorting settings with accordion

* [ML] config validation with tests

* [ML] fix preserving of the unsupported config

* [ML] update translation message

* [ML] fix level of the custom config

* [ML] preserve unsupported config for sorting
This commit is contained in:
Dima Arnautov 2021-06-04 16:04:53 +02:00 committed by GitHub
parent 93df9a32a4
commit 9810a72720
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 693 additions and 29 deletions

View file

@ -17,6 +17,7 @@ export const PIVOT_SUPPORTED_AGGS = {
SUM: 'sum',
VALUE_COUNT: 'value_count',
FILTER: 'filter',
TOP_METRICS: 'top_metrics',
} as const;
export type PivotSupportedAggs = typeof PIVOT_SUPPORTED_AGGS[keyof typeof PIVOT_SUPPORTED_AGGS];

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { getAggConfigFromEsAgg } from './pivot_aggs';
import { getAggConfigFromEsAgg, isSpecialSortField } from './pivot_aggs';
import {
FilterAggForm,
FilterTermForm,
@ -67,3 +67,12 @@ describe('getAggConfigFromEsAgg', () => {
});
});
});
describe('isSpecialSortField', () => {
test('detects special sort field', () => {
expect(isSpecialSortField('_score')).toBe(true);
});
test('rejects special fields that not supported yet', () => {
expect(isSpecialSortField('_doc')).toBe(false);
});
});

View file

@ -7,7 +7,7 @@
import { FC } from 'react';
import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/common';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/common';
import type { AggName } from '../../../common/types/aggregations';
import type { Dictionary } from '../../../common/types/common';
@ -43,6 +43,7 @@ export const pivotAggsFieldSupport = {
PIVOT_SUPPORTED_AGGS.CARDINALITY,
PIVOT_SUPPORTED_AGGS.VALUE_COUNT,
PIVOT_SUPPORTED_AGGS.FILTER,
PIVOT_SUPPORTED_AGGS.TOP_METRICS,
],
[KBN_FIELD_TYPES.MURMUR3]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
[KBN_FIELD_TYPES.NUMBER]: [
@ -54,17 +55,78 @@ export const pivotAggsFieldSupport = {
PIVOT_SUPPORTED_AGGS.SUM,
PIVOT_SUPPORTED_AGGS.VALUE_COUNT,
PIVOT_SUPPORTED_AGGS.FILTER,
PIVOT_SUPPORTED_AGGS.TOP_METRICS,
],
[KBN_FIELD_TYPES.STRING]: [
PIVOT_SUPPORTED_AGGS.CARDINALITY,
PIVOT_SUPPORTED_AGGS.VALUE_COUNT,
PIVOT_SUPPORTED_AGGS.FILTER,
PIVOT_SUPPORTED_AGGS.TOP_METRICS,
],
[KBN_FIELD_TYPES._SOURCE]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
[KBN_FIELD_TYPES.UNKNOWN]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
[KBN_FIELD_TYPES.CONFLICT]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
};
export const TOP_METRICS_SORT_FIELD_TYPES = [
KBN_FIELD_TYPES.NUMBER,
KBN_FIELD_TYPES.DATE,
KBN_FIELD_TYPES.GEO_POINT,
];
export const SORT_DIRECTION = {
ASC: 'asc',
DESC: 'desc',
} as const;
export type SortDirection = typeof SORT_DIRECTION[keyof typeof SORT_DIRECTION];
export const SORT_MODE = {
MIN: 'min',
MAX: 'max',
AVG: 'avg',
SUM: 'sum',
MEDIAN: 'median',
} as const;
export const NUMERIC_TYPES_OPTIONS = {
[KBN_FIELD_TYPES.NUMBER]: [ES_FIELD_TYPES.DOUBLE, ES_FIELD_TYPES.LONG],
[KBN_FIELD_TYPES.DATE]: [ES_FIELD_TYPES.DATE, ES_FIELD_TYPES.DATE_NANOS],
};
export type KbnNumericType = typeof KBN_FIELD_TYPES.NUMBER | typeof KBN_FIELD_TYPES.DATE;
const SORT_NUMERIC_FIELD_TYPES = [
ES_FIELD_TYPES.DOUBLE,
ES_FIELD_TYPES.LONG,
ES_FIELD_TYPES.DATE,
ES_FIELD_TYPES.DATE_NANOS,
] as const;
export type SortNumericFieldType = typeof SORT_NUMERIC_FIELD_TYPES[number];
export type SortMode = typeof SORT_MODE[keyof typeof SORT_MODE];
export const TOP_METRICS_SPECIAL_SORT_FIELDS = {
_SCORE: '_score',
} as const;
export const isSpecialSortField = (sortField: unknown) => {
return Object.values(TOP_METRICS_SPECIAL_SORT_FIELDS).some((v) => v === sortField);
};
export const isValidSortDirection = (arg: unknown): arg is SortDirection => {
return Object.values(SORT_DIRECTION).some((v) => v === arg);
};
export const isValidSortMode = (arg: unknown): arg is SortMode => {
return Object.values(SORT_MODE).some((v) => v === arg);
};
export const isValidSortNumericType = (arg: unknown): arg is SortNumericFieldType => {
return SORT_NUMERIC_FIELD_TYPES.some((v) => v === arg);
};
/**
* The maximum level of sub-aggregations
*/
@ -75,6 +137,10 @@ export interface PivotAggsConfigBase {
agg: PivotSupportedAggs;
aggName: AggName;
dropDownName: string;
/**
* Indicates if aggregation supports multiple fields
*/
isMultiField?: boolean;
/** Indicates if aggregation supports sub-aggregations */
isSubAggsSupported?: boolean;
/** Dictionary of the sub-aggregations */
@ -130,7 +196,7 @@ export function getAggConfigFromEsAgg(
}
export interface PivotAggsConfigWithUiBase extends PivotAggsConfigBase {
field: EsFieldName;
field: EsFieldName | EsFieldName[];
}
export interface PivotAggsConfigWithExtra<T> extends PivotAggsConfigWithUiBase {

View file

@ -46,7 +46,7 @@ export const AdvancedRuntimeMappingsSettings: FC<StepDefineFormHook> = (props) =
},
} = props.runtimeMappingsEditor;
const {
actions: { deleteAggregation, deleteGroupBy },
actions: { deleteAggregation, deleteGroupBy, updateAggregation },
state: { groupByList, aggList },
} = props.pivotConfig;
@ -55,6 +55,9 @@ export const AdvancedRuntimeMappingsSettings: FC<StepDefineFormHook> = (props) =
advancedRuntimeMappingsConfig === '' ? {} : JSON.parse(advancedRuntimeMappingsConfig);
const previousConfig = runtimeMappings;
const isFieldDeleted = (field: string) =>
previousConfig?.hasOwnProperty(field) && !nextConfig.hasOwnProperty(field);
applyRuntimeMappingsEditorChanges();
// If the user updates the name of the runtime mapping fields
@ -71,13 +74,16 @@ export const AdvancedRuntimeMappingsSettings: FC<StepDefineFormHook> = (props) =
});
Object.keys(aggList).forEach((aggName) => {
const agg = aggList[aggName] as PivotAggsConfigWithUiSupport;
if (
isPivotAggConfigWithUiSupport(agg) &&
agg.field !== undefined &&
previousConfig?.hasOwnProperty(agg.field) &&
!nextConfig.hasOwnProperty(agg.field)
) {
deleteAggregation(aggName);
if (isPivotAggConfigWithUiSupport(agg)) {
if (Array.isArray(agg.field)) {
const newFields = agg.field.filter((f) => !isFieldDeleted(f));
updateAggregation(aggName, { ...agg, field: newFields });
} else {
if (agg.field !== undefined && isFieldDeleted(agg.field)) {
deleteAggregation(aggName);
}
}
}
});
};

View file

@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n';
import {
EuiButton,
EuiCodeEditor,
EuiComboBox,
EuiFieldText,
EuiForm,
EuiFormRow,
@ -79,7 +80,7 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
const [aggName, setAggName] = useState(defaultData.aggName);
const [agg, setAgg] = useState(defaultData.agg);
const [field, setField] = useState(
const [field, setField] = useState<string | string[]>(
isPivotAggsConfigWithUiSupport(defaultData) ? defaultData.field : ''
);
@ -148,13 +149,21 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
if (!isUnsupportedAgg) {
const optionsArr = dictionaryToArray(options);
optionsArr
.filter((o) => o.agg === defaultData.agg)
.forEach((o) => {
availableFields.push({ text: o.field });
});
optionsArr
.filter((o) => isPivotAggsConfigWithUiSupport(defaultData) && o.field === defaultData.field)
.filter(
(o) =>
isPivotAggsConfigWithUiSupport(defaultData) &&
(Array.isArray(defaultData.field)
? defaultData.field.includes(o.field as string)
: o.field === defaultData.field)
)
.forEach((o) => {
availableAggs.push({ text: o.agg });
});
@ -217,20 +226,48 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
data-test-subj="transformAggName"
/>
</EuiFormRow>
{availableFields.length > 0 && (
<EuiFormRow
label={i18n.translate('xpack.transform.agg.popoverForm.fieldLabel', {
defaultMessage: 'Field',
})}
>
<EuiSelect
options={availableFields}
value={field}
onChange={(e) => setField(e.target.value)}
data-test-subj="transformAggField"
/>
</EuiFormRow>
)}
{availableFields.length > 0 ? (
aggConfigDef.isMultiField ? (
<EuiFormRow
label={i18n.translate('xpack.transform.agg.popoverForm.fieldsLabel', {
defaultMessage: 'Fields',
})}
>
<EuiComboBox
fullWidth
options={availableFields.map((v) => {
return {
value: v.text,
label: v.text as string,
};
})}
selectedOptions={(typeof field === 'string' ? [field] : field).map((v) => ({
value: v,
label: v,
}))}
onChange={(e) => {
const res = e.map((v) => v.value as string);
setField(res);
}}
isClearable={false}
data-test-subj="transformAggFields"
/>
</EuiFormRow>
) : (
<EuiFormRow
label={i18n.translate('xpack.transform.agg.popoverForm.fieldLabel', {
defaultMessage: 'Field',
})}
>
<EuiSelect
options={availableFields}
value={field as string}
onChange={(e) => setField(e.target.value)}
data-test-subj="transformAggField"
/>
</EuiFormRow>
)
) : null}
{availableAggs.length > 0 && (
<EuiFormRow
label={i18n.translate('xpack.transform.agg.popoverForm.aggLabel', {
@ -248,7 +285,7 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
{isPivotAggsWithExtendedForm(aggConfigDef) && (
<aggConfigDef.AggFormComponent
aggConfig={aggConfigDef.aggConfig}
selectedField={field}
selectedField={field as string}
onChange={(update) => {
setAggConfigDef({
...aggConfigDef,

View file

@ -44,6 +44,7 @@ describe('Transform: Define Pivot Common', () => {
{ label: 'sum( the-f[i]e>ld )' },
{ label: 'value_count( the-f[i]e>ld )' },
{ label: 'filter( the-f[i]e>ld )' },
{ label: 'top_metrics( the-f[i]e>ld )' },
],
},
],
@ -133,6 +134,7 @@ describe('Transform: Define Pivot Common', () => {
{ label: 'sum( the-f[i]e>ld )' },
{ label: 'value_count( the-f[i]e>ld )' },
{ label: 'filter( the-f[i]e>ld )' },
{ label: 'top_metrics( the-f[i]e>ld )' },
],
},
{
@ -146,6 +148,7 @@ describe('Transform: Define Pivot Common', () => {
{ label: 'sum(rt_bytes_bigger)' },
{ label: 'value_count(rt_bytes_bigger)' },
{ label: 'filter(rt_bytes_bigger)' },
{ label: 'top_metrics(rt_bytes_bigger)' },
],
},
],

View file

@ -12,6 +12,7 @@ import {
import { PivotAggsConfigBase, PivotAggsConfigWithUiBase } from '../../../../../common/pivot_aggs';
import { getFilterAggConfig } from './filter_agg/config';
import { getTopMetricsAggConfig } from './top_metrics_agg/config';
/**
* Gets form configuration for provided aggregation type.
@ -23,6 +24,8 @@ export function getAggFormConfig(
switch (agg) {
case PIVOT_SUPPORTED_AGGS.FILTER:
return getFilterAggConfig(commonConfig);
case PIVOT_SUPPORTED_AGGS.TOP_METRICS:
return getTopMetricsAggConfig(commonConfig);
default:
return commonConfig;
}

View file

@ -15,6 +15,7 @@ import {
PivotAggsConfigWithUiSupport,
} from '../../../../../common';
import { getFilterAggConfig } from './filter_agg/config';
import { getTopMetricsAggConfig } from './top_metrics_agg/config';
/**
* Provides a configuration based on the aggregation type.
@ -41,6 +42,8 @@ export function getDefaultAggregationConfig(
};
case PIVOT_SUPPORTED_AGGS.FILTER:
return getFilterAggConfig(commonConfig);
case PIVOT_SUPPORTED_AGGS.TOP_METRICS:
return getTopMetricsAggConfig(commonConfig);
default:
return commonConfig;
}

View file

@ -141,6 +141,7 @@ export function getPivotDropdownOptions(
});
return {
fields: combinedFields,
groupByOptions,
groupByOptionsData,
aggOptions,

View file

@ -0,0 +1,195 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useCallback, useContext } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiFormRow, EuiSelect, EuiButtonGroup, EuiAccordion, EuiSpacer } from '@elastic/eui';
import { PivotAggsConfigTopMetrics, TopMetricsAggConfig } from '../types';
import { PivotConfigurationContext } from '../../../../pivot_configuration/pivot_configuration';
import {
isSpecialSortField,
KbnNumericType,
NUMERIC_TYPES_OPTIONS,
SORT_DIRECTION,
SORT_MODE,
SortDirection,
SortMode,
SortNumericFieldType,
TOP_METRICS_SORT_FIELD_TYPES,
TOP_METRICS_SPECIAL_SORT_FIELDS,
} from '../../../../../../../common/pivot_aggs';
export const TopMetricsAggForm: PivotAggsConfigTopMetrics['AggFormComponent'] = ({
onChange,
aggConfig,
}) => {
const {
state: { fields },
} = useContext(PivotConfigurationContext)!;
const sortFieldOptions = fields
.filter((v) => TOP_METRICS_SORT_FIELD_TYPES.includes(v.type))
.map(({ name }) => ({ text: name, value: name }));
Object.values(TOP_METRICS_SPECIAL_SORT_FIELDS).forEach((v) => {
sortFieldOptions.unshift({ text: v, value: v });
});
sortFieldOptions.unshift({ text: '', value: '' });
const isSpecialFieldSelected = isSpecialSortField(aggConfig.sortField);
const sortDirectionOptions = Object.values(SORT_DIRECTION).map((v) => ({
id: v,
label: v,
}));
const sortModeOptions = Object.values(SORT_MODE).map((v) => ({
id: v,
label: v,
}));
const sortFieldType = fields.find((f) => f.name === aggConfig.sortField)?.type;
const sortSettings = aggConfig.sortSettings ?? {};
const updateSortSettings = useCallback(
(update: Partial<TopMetricsAggConfig['sortSettings']>) => {
onChange({
...aggConfig,
sortSettings: {
...(aggConfig.sortSettings ?? {}),
...update,
},
});
},
[aggConfig, onChange]
);
return (
<>
<EuiFormRow
label={
<FormattedMessage
id="xpack.transform.agg.popoverForm.sortFieldTopMetricsLabel"
defaultMessage="Sort field"
/>
}
>
<EuiSelect
options={sortFieldOptions}
value={aggConfig.sortField}
onChange={(e) => {
onChange({ ...aggConfig, sortField: e.target.value });
}}
data-test-subj="transformSortFieldTopMetricsLabel"
/>
</EuiFormRow>
{aggConfig.sortField ? (
<>
{isSpecialFieldSelected ? null : (
<>
<EuiFormRow
label={
<FormattedMessage
id="xpack.transform.agg.popoverForm.sortDirectionTopMetricsLabel"
defaultMessage="Sort direction"
/>
}
>
<EuiButtonGroup
type="single"
legend={i18n.translate(
'xpack.transform.agg.popoverForm.sortDirectionTopMetricsLabel',
{
defaultMessage: 'Sort direction',
}
)}
options={sortDirectionOptions}
idSelected={sortSettings.order ?? ''}
onChange={(id: string) => {
updateSortSettings({ order: id as SortDirection });
}}
color="text"
/>
</EuiFormRow>
<EuiSpacer size="s" />
<EuiAccordion
id="sortAdvancedSettings"
buttonContent={
<FormattedMessage
id="xpack.transform.agg.popoverForm.advancedSortingSettingsLabel"
defaultMessage="Advanced sorting settings"
/>
}
>
<EuiFormRow
label={
<FormattedMessage
id="xpack.transform.agg.popoverForm.sortModeTopMetricsLabel"
defaultMessage="Sort mode"
/>
}
helpText={
<FormattedMessage
id="xpack.transform.agg.popoverForm.sortModeTopMetricsHelpText"
defaultMessage="Only relevant if the sorting field is an array."
/>
}
>
<EuiButtonGroup
type="single"
legend={i18n.translate(
'xpack.transform.agg.popoverForm.sortModeTopMetricsLabel',
{
defaultMessage: 'Sort mode',
}
)}
options={sortModeOptions}
idSelected={sortSettings.mode ?? ''}
onChange={(id: string) => {
updateSortSettings({ mode: id as SortMode });
}}
color="text"
/>
</EuiFormRow>
{sortFieldType && NUMERIC_TYPES_OPTIONS.hasOwnProperty(sortFieldType) ? (
<EuiFormRow
label={
<FormattedMessage
id="xpack.transform.agg.popoverForm.numericSortFieldTopMetricsLabel"
defaultMessage="Numeric field"
/>
}
>
<EuiSelect
options={NUMERIC_TYPES_OPTIONS[sortFieldType as KbnNumericType].map((v) => ({
text: v,
name: v,
}))}
value={sortSettings.numericType}
onChange={(e) => {
updateSortSettings({
numericType: e.target.value as SortNumericFieldType,
});
}}
data-test-subj="transformSortNumericTypeTopMetricsLabel"
/>
</EuiFormRow>
) : null}
</EuiAccordion>
</>
)}
</>
) : null}
</>
);
};

View file

@ -0,0 +1,196 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getTopMetricsAggConfig } from './config';
import { PivotAggsConfigTopMetrics } from './types';
describe('top metrics agg config', () => {
let config: PivotAggsConfigTopMetrics;
beforeEach(() => {
config = getTopMetricsAggConfig({
agg: 'top_metrics',
aggName: 'test-agg',
field: ['test-field'],
dropDownName: 'test-agg',
});
});
describe('#setUiConfigFromEs', () => {
test('sets config with a special field', () => {
// act
config.setUiConfigFromEs({
metrics: {
field: 'test-field-01',
},
sort: '_score',
});
// assert
expect(config.field).toEqual(['test-field-01']);
expect(config.aggConfig).toEqual({
sortField: '_score',
});
});
test('sets config with a simple sort direction definition', () => {
// act
config.setUiConfigFromEs({
metrics: [
{
field: 'test-field-01',
},
{
field: 'test-field-02',
},
],
sort: {
'sort-field': 'asc',
},
});
// assert
expect(config.field).toEqual(['test-field-01', 'test-field-02']);
expect(config.aggConfig).toEqual({
sortField: 'sort-field',
sortSettings: {
order: 'asc',
},
});
});
test('sets config with a sort definition params not supported by the UI', () => {
// act
config.setUiConfigFromEs({
metrics: [
{
field: 'test-field-01',
},
],
sort: {
'offer.price': {
order: 'desc',
mode: 'avg',
nested: {
path: 'offer',
filter: {
term: { 'offer.color': 'blue' },
},
},
},
},
});
// assert
expect(config.field).toEqual(['test-field-01']);
expect(config.aggConfig).toEqual({
sortField: 'offer.price',
sortSettings: {
order: 'desc',
mode: 'avg',
nested: {
path: 'offer',
filter: {
term: { 'offer.color': 'blue' },
},
},
},
});
});
});
describe('#getEsAggConfig', () => {
test('rejects invalid config', () => {
// arrange
config.field = ['field-01', 'field-02'];
config.aggConfig = {
sortSettings: {
order: 'asc',
},
};
// act and assert
expect(config.getEsAggConfig()).toEqual(null);
});
test('rejects invalid config with missing sort direction', () => {
// arrange
config.field = ['field-01', 'field-02'];
config.aggConfig = {
sortField: 'sort-field',
};
// act and assert
expect(config.getEsAggConfig()).toEqual(null);
});
test('converts valid config', () => {
// arrange
config.field = ['field-01', 'field-02'];
config.aggConfig = {
sortField: 'sort-field',
sortSettings: {
order: 'asc',
},
};
// act and assert
expect(config.getEsAggConfig()).toEqual({
metrics: [{ field: 'field-01' }, { field: 'field-02' }],
sort: {
'sort-field': 'asc',
},
});
});
test('preserves unsupported config', () => {
// arrange
config.field = ['field-01', 'field-02'];
config.aggConfig = {
sortField: 'sort-field',
sortSettings: {
order: 'asc',
// @ts-ignore
nested: {
path: 'order',
},
},
// @ts-ignore
size: 2,
};
// act and assert
expect(config.getEsAggConfig()).toEqual({
metrics: [{ field: 'field-01' }, { field: 'field-02' }],
sort: {
'sort-field': {
order: 'asc',
nested: {
path: 'order',
},
},
},
size: 2,
});
});
test('converts configs with a special sorting field', () => {
// arrange
config.field = ['field-01', 'field-02'];
config.aggConfig = {
sortField: '_score',
};
// act and assert
expect(config.getEsAggConfig()).toEqual({
metrics: [{ field: 'field-01' }, { field: 'field-02' }],
sort: '_score',
});
});
});
});

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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
isPivotAggsConfigWithUiSupport,
isSpecialSortField,
isValidSortDirection,
isValidSortMode,
isValidSortNumericType,
PivotAggsConfigBase,
PivotAggsConfigWithUiBase,
} from '../../../../../../common/pivot_aggs';
import { PivotAggsConfigTopMetrics } from './types';
import { TopMetricsAggForm } from './components/top_metrics_agg_form';
import { isPopulatedObject } from '../../../../../../../../common/shared_imports';
/**
* Gets initial basic configuration of the top_metrics aggregation.
*/
export function getTopMetricsAggConfig(
commonConfig: PivotAggsConfigWithUiBase | PivotAggsConfigBase
): PivotAggsConfigTopMetrics {
return {
...commonConfig,
isSubAggsSupported: false,
isMultiField: true,
field: isPivotAggsConfigWithUiSupport(commonConfig) ? commonConfig.field : '',
AggFormComponent: TopMetricsAggForm,
aggConfig: {},
getEsAggConfig() {
// ensure the configuration has been completed
if (!this.isValid()) {
return null;
}
const { sortField, sortSettings = {}, ...unsupportedConfig } = this.aggConfig;
let sort = null;
if (isSpecialSortField(sortField)) {
sort = sortField;
} else {
const { mode, numericType, order, ...rest } = sortSettings;
if (mode || numericType || isPopulatedObject(rest)) {
sort = {
[sortField!]: {
...rest,
order,
...(mode ? { mode } : {}),
...(numericType ? { numeric_type: numericType } : {}),
},
};
} else {
sort = { [sortField!]: sortSettings.order };
}
}
return {
metrics: (Array.isArray(this.field) ? this.field : [this.field]).map((f) => ({ field: f })),
sort,
...(unsupportedConfig ?? {}),
};
},
setUiConfigFromEs(esAggDefinition) {
const { metrics, sort, ...unsupportedConfig } = esAggDefinition;
this.field = (Array.isArray(metrics) ? metrics : [metrics]).map((v) => v.field);
if (isSpecialSortField(sort)) {
this.aggConfig.sortField = sort;
return;
}
const sortField = Object.keys(sort)[0];
this.aggConfig.sortField = sortField;
const sortDefinition = sort[sortField];
this.aggConfig.sortSettings = this.aggConfig.sortSettings ?? {};
if (isValidSortDirection(sortDefinition)) {
this.aggConfig.sortSettings.order = sortDefinition;
}
if (isPopulatedObject(sortDefinition)) {
const { order, mode, numeric_type: numType, ...rest } = sortDefinition;
this.aggConfig.sortSettings = rest;
if (isValidSortDirection(order)) {
this.aggConfig.sortSettings.order = order;
}
if (isValidSortMode(mode)) {
this.aggConfig.sortSettings.mode = mode;
}
if (isValidSortNumericType(numType)) {
this.aggConfig.sortSettings.numericType = numType;
}
}
this.aggConfig = {
...this.aggConfig,
...(unsupportedConfig ?? {}),
};
},
isValid() {
return (
!!this.aggConfig.sortField &&
(isSpecialSortField(this.aggConfig.sortField) ? true : !!this.aggConfig.sortSettings?.order)
);
},
};
}

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type {
PivotAggsConfigWithExtra,
SortDirection,
SortMode,
SortNumericFieldType,
} from '../../../../../../common/pivot_aggs';
export interface TopMetricsAggConfig {
sortField: string;
sortSettings?: {
order?: SortDirection;
mode?: SortMode;
numericType?: SortNumericFieldType;
};
}
export type PivotAggsConfigTopMetrics = PivotAggsConfigWithExtra<TopMetricsAggConfig>;

View file

@ -97,7 +97,7 @@ export const usePivotConfig = (
) => {
const toastNotifications = useToastNotifications();
const { aggOptions, aggOptionsData, groupByOptions, groupByOptionsData } = useMemo(
const { aggOptions, aggOptionsData, groupByOptions, groupByOptionsData, fields } = useMemo(
() => getPivotDropdownOptions(indexPattern, defaults.runtimeMappings),
[defaults.runtimeMappings, indexPattern]
);
@ -347,6 +347,7 @@ export const usePivotConfig = (
pivotGroupByArr,
validationStatus,
requestPayload,
fields,
},
};
}, [
@ -361,6 +362,7 @@ export const usePivotConfig = (
pivotGroupByArr,
validationStatus,
requestPayload,
fields,
]);
};