[AggConfigs] Add TopMetrics agg (#125936)

This commit is contained in:
Anton Dosov 2022-03-07 16:10:41 +01:00 committed by GitHub
parent a79562a67e
commit eca203ce73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 679 additions and 3 deletions

View file

@ -196,6 +196,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
std_dev: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-extendedstats-aggregation.html`, std_dev: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-extendedstats-aggregation.html`,
sum: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-sum-aggregation.html`, sum: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-sum-aggregation.html`,
top_hits: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-top-hits-aggregation.html`, top_hits: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-top-hits-aggregation.html`,
top_metrics: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-top-metrics.html`,
}, },
runtimeFields: { runtimeFields: {
overview: `${ELASTICSEARCH_DOCS}runtime.html`, overview: `${ELASTICSEARCH_DOCS}runtime.html`,

View file

@ -36,6 +36,7 @@ export const getAggTypes = () => ({
{ name: METRIC_TYPES.PERCENTILES, fn: metrics.getPercentilesMetricAgg }, { name: METRIC_TYPES.PERCENTILES, fn: metrics.getPercentilesMetricAgg },
{ name: METRIC_TYPES.PERCENTILE_RANKS, fn: metrics.getPercentileRanksMetricAgg }, { name: METRIC_TYPES.PERCENTILE_RANKS, fn: metrics.getPercentileRanksMetricAgg },
{ name: METRIC_TYPES.TOP_HITS, fn: metrics.getTopHitMetricAgg }, { name: METRIC_TYPES.TOP_HITS, fn: metrics.getTopHitMetricAgg },
{ name: METRIC_TYPES.TOP_METRICS, fn: metrics.getTopMetricsMetricAgg },
{ name: METRIC_TYPES.DERIVATIVE, fn: metrics.getDerivativeMetricAgg }, { name: METRIC_TYPES.DERIVATIVE, fn: metrics.getDerivativeMetricAgg },
{ name: METRIC_TYPES.CUMULATIVE_SUM, fn: metrics.getCumulativeSumMetricAgg }, { name: METRIC_TYPES.CUMULATIVE_SUM, fn: metrics.getCumulativeSumMetricAgg },
{ name: METRIC_TYPES.MOVING_FN, fn: metrics.getMovingAvgMetricAgg }, { name: METRIC_TYPES.MOVING_FN, fn: metrics.getMovingAvgMetricAgg },
@ -109,4 +110,5 @@ export const getAggTypesFunctions = () => [
metrics.aggStdDeviation, metrics.aggStdDeviation,
metrics.aggSum, metrics.aggSum,
metrics.aggTopHit, metrics.aggTopHit,
metrics.aggTopMetrics,
]; ];

View file

@ -95,6 +95,7 @@ describe('Aggs service', () => {
"percentiles", "percentiles",
"percentile_ranks", "percentile_ranks",
"top_hits", "top_hits",
"top_metrics",
"derivative", "derivative",
"cumulative_sum", "cumulative_sum",
"moving_avg", "moving_avg",
@ -147,6 +148,7 @@ describe('Aggs service', () => {
"percentiles", "percentiles",
"percentile_ranks", "percentile_ranks",
"top_hits", "top_hits",
"top_metrics",
"derivative", "derivative",
"cumulative_sum", "cumulative_sum",
"moving_avg", "moving_avg",

View file

@ -56,3 +56,5 @@ export * from './sum_fn';
export * from './sum'; export * from './sum';
export * from './top_hit_fn'; export * from './top_hit_fn';
export * from './top_hit'; export * from './top_hit';
export * from './top_metrics';
export * from './top_metrics_fn';

View file

@ -15,6 +15,7 @@ import { parentPipelineAggWriter } from './parent_pipeline_agg_writer';
const metricAggFilter = [ const metricAggFilter = [
'!top_hits', '!top_hits',
'!top_metrics',
'!percentiles', '!percentiles',
'!percentile_ranks', '!percentile_ranks',
'!median', '!median',

View file

@ -13,6 +13,7 @@ import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type';
const metricAggFilter: string[] = [ const metricAggFilter: string[] = [
'!top_hits', '!top_hits',
'!top_metrics',
'!percentiles', '!percentiles',
'!percentile_ranks', '!percentile_ranks',
'!median', '!median',

View file

@ -22,6 +22,7 @@ export interface MetricAggParam<TMetricAggConfig extends AggConfig>
extends AggParamType<TMetricAggConfig> { extends AggParamType<TMetricAggConfig> {
filterFieldTypes?: FieldTypes; filterFieldTypes?: FieldTypes;
onlyAggregatable?: boolean; onlyAggregatable?: boolean;
scriptable?: boolean;
} }
const metricType = 'metrics'; const metricType = 'metrics';

View file

@ -27,6 +27,7 @@ export enum METRIC_TYPES {
SERIAL_DIFF = 'serial_diff', SERIAL_DIFF = 'serial_diff',
SUM = 'sum', SUM = 'sum',
TOP_HITS = 'top_hits', TOP_HITS = 'top_hits',
TOP_METRICS = 'top_metrics',
PERCENTILES = 'percentiles', PERCENTILES = 'percentiles',
PERCENTILE_RANKS = 'percentile_ranks', PERCENTILE_RANKS = 'percentile_ranks',
STD_DEV = 'std_dev', STD_DEV = 'std_dev',

View file

@ -0,0 +1,194 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { getTopMetricsMetricAgg } from './top_metrics';
import { AggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { IMetricAggConfig } from './metric_agg_type';
import { KBN_FIELD_TYPES } from '../../../../common';
describe('Top metrics metric', () => {
let aggConfig: IMetricAggConfig;
const init = ({
fieldName = 'field',
fieldType = KBN_FIELD_TYPES.NUMBER,
sortFieldName = 'sortField',
sortFieldType = KBN_FIELD_TYPES.NUMBER,
sortOrder = 'desc',
size = 1,
}: any) => {
const typesRegistry = mockAggTypesRegistry();
const field = {
name: fieldName,
displayName: fieldName,
type: fieldType,
};
const sortField = {
name: sortFieldName,
displayName: sortFieldName,
type: sortFieldType,
};
const params = {
size,
field: field.name,
sortField: sortField.name,
sortOrder: {
value: sortOrder,
},
};
const indexPattern = {
id: '1234',
title: 'logstash-*',
fields: {
getByName: (name: string) => {
if (name === sortFieldName) return sortField;
if (name === fieldName) return field;
return null;
},
filter: () => [field, sortField],
},
} as any;
const aggConfigs = new AggConfigs(
indexPattern,
[
{
id: '1',
type: 'top_metrics',
schema: 'metric',
params,
},
],
{ typesRegistry }
);
// Grab the aggConfig off the vis (we don't actually use the vis for anything else)
aggConfig = aggConfigs.aggs[0] as IMetricAggConfig;
};
it('should return a label prefixed with Last if sorting in descending order', () => {
init({ fieldName: 'bytes', sortFieldName: '@timestamp' });
expect(getTopMetricsMetricAgg().makeLabel(aggConfig)).toEqual(
'Last "bytes" value by "@timestamp"'
);
});
it('should return a label prefixed with First if sorting in ascending order', () => {
init({
fieldName: 'bytes',
sortFieldName: '@timestamp',
sortOrder: 'asc',
});
expect(getTopMetricsMetricAgg().makeLabel(aggConfig)).toEqual(
'First "bytes" value by "@timestamp"'
);
});
it('should return a label with size if larger then 1', () => {
init({
fieldName: 'bytes',
sortFieldName: '@timestamp',
sortOrder: 'asc',
size: 3,
});
expect(getTopMetricsMetricAgg().makeLabel(aggConfig)).toEqual(
'First 3 "bytes" values by "@timestamp"'
);
});
it('should return a fieldName in getValueBucketPath', () => {
init({
fieldName: 'bytes',
sortFieldName: '@timestamp',
sortOrder: 'asc',
size: 3,
});
expect(getTopMetricsMetricAgg().getValueBucketPath(aggConfig)).toEqual('1[bytes]');
});
it('produces the expected expression ast', () => {
init({ fieldName: 'machine.os', sortFieldName: '@timestamp' });
expect(aggConfig.toExpressionAst()).toMatchInlineSnapshot(`
Object {
"chain": Array [
Object {
"arguments": Object {
"enabled": Array [
true,
],
"field": Array [
"machine.os",
],
"id": Array [
"1",
],
"schema": Array [
"metric",
],
"size": Array [
1,
],
"sortField": Array [
"@timestamp",
],
"sortOrder": Array [
"desc",
],
},
"function": "aggTopMetrics",
"type": "function",
},
],
"type": "expression",
}
`);
});
describe('gets value from top metrics bucket', () => {
it('should return null if there is no hits', () => {
const bucket = {
'1': {
top: [],
},
};
init({ fieldName: 'bytes' });
expect(getTopMetricsMetricAgg().getValue(aggConfig, bucket)).toBe(null);
});
it('should return a single value if there is a single hit', () => {
const bucket = {
'1': {
top: [{ sort: [3], metrics: { bytes: 1024 } }],
},
};
init({ fieldName: 'bytes' });
expect(getTopMetricsMetricAgg().getValue(aggConfig, bucket)).toBe(1024);
});
it('should return an array of values if there is a multiple results', () => {
const bucket = {
'1': {
top: [
{ sort: [3], metrics: { bytes: 1024 } },
{ sort: [2], metrics: { bytes: 512 } },
{ sort: [1], metrics: { bytes: 256 } },
],
},
};
init({ fieldName: 'bytes' });
expect(getTopMetricsMetricAgg().getValue(aggConfig, bucket)).toEqual([1024, 512, 256]);
});
});
});

View file

@ -0,0 +1,155 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import _ from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { i18n } from '@kbn/i18n';
import { aggTopMetricsFnName } from './top_metrics_fn';
import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types';
import { KBN_FIELD_TYPES } from '../../../../common';
import { BaseAggParams } from '../types';
export interface AggParamsTopMetrics extends BaseAggParams {
field: string;
sortField?: string;
sortOrder?: 'desc' | 'asc';
size?: number;
}
export const getTopMetricsMetricAgg = () => {
return new MetricAggType({
name: METRIC_TYPES.TOP_METRICS,
expressionName: aggTopMetricsFnName,
title: i18n.translate('data.search.aggs.metrics.topMetricsTitle', {
defaultMessage: 'Top metrics',
}),
makeLabel(aggConfig) {
const isDescOrder = aggConfig.getParam('sortOrder').value === 'desc';
const size = aggConfig.getParam('size');
const field = aggConfig.getParam('field');
const sortField = aggConfig.getParam('sortField');
if (isDescOrder) {
if (size > 1) {
return i18n.translate('data.search.aggs.metrics.topMetrics.descWithSizeLabel', {
defaultMessage: `Last {size} "{fieldName}" values by "{sortField}"`,
values: {
size,
fieldName: field?.displayName,
sortField: sortField?.displayName ?? '_score',
},
});
} else {
return i18n.translate('data.search.aggs.metrics.topMetrics.descNoSizeLabel', {
defaultMessage: `Last "{fieldName}" value by "{sortField}"`,
values: {
fieldName: field?.displayName,
sortField: sortField?.displayName ?? '_score',
},
});
}
} else {
if (size > 1) {
return i18n.translate('data.search.aggs.metrics.topMetrics.ascWithSizeLabel', {
defaultMessage: `First {size} "{fieldName}" values by "{sortField}"`,
values: {
size,
fieldName: field?.displayName,
sortField: sortField?.displayName ?? '_score',
},
});
} else {
return i18n.translate('data.search.aggs.metrics.topMetrics.ascNoSizeLabel', {
defaultMessage: `First "{fieldName}" value by "{sortField}"`,
values: {
fieldName: field?.displayName,
sortField: sortField?.displayName ?? '_score',
},
});
}
}
},
params: [
{
name: 'field',
type: 'field',
scriptable: false,
filterFieldTypes: [
KBN_FIELD_TYPES.STRING,
KBN_FIELD_TYPES.IP,
KBN_FIELD_TYPES.BOOLEAN,
KBN_FIELD_TYPES.NUMBER,
KBN_FIELD_TYPES.DATE,
],
write(agg, output) {
const field = agg.getParam('field');
output.params.metrics = { field: field.name };
},
},
{
name: 'size',
default: 1,
},
{
name: 'sortField',
type: 'field',
scriptable: false,
filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE],
default(agg: IMetricAggConfig) {
return agg.getIndexPattern().timeFieldName;
},
write: _.noop, // prevent default write, it is handled below
},
{
name: 'sortOrder',
type: 'optioned',
default: 'desc',
options: [
{
text: i18n.translate('data.search.aggs.metrics.topMetrics.descendingLabel', {
defaultMessage: 'Descending',
}),
value: 'desc',
},
{
text: i18n.translate('data.search.aggs.metrics.topMetrics.ascendingLabel', {
defaultMessage: 'Ascending',
}),
value: 'asc',
},
],
write(agg, output) {
const sortField = agg.params.sortField;
const sortOrder = agg.params.sortOrder;
if (sortField && sortOrder) {
output.params.sort = {
[sortField.name]: sortOrder.value,
};
} else {
output.params.sort = '_score';
}
},
},
],
// override is needed to support top_metrics as an orderAgg of terms agg
getValueBucketPath(agg) {
const field = agg.getParam('field').name;
return `${agg.id}[${field}]`;
},
getValue(agg, aggregate: Record<string, estypes.AggregationsTopMetricsAggregate | undefined>) {
const metricFieldName = agg.getParam('field').name;
const results = aggregate[agg.id]?.top.map((result) => result.metrics[metricFieldName]) ?? [];
if (results.length === 0) return null;
if (results.length === 1) return results[0];
return results;
},
});
};

View file

@ -0,0 +1,79 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { functionWrapper } from '../test_helpers';
import { aggTopMetrics } from './top_metrics_fn';
describe('agg_expression_functions', () => {
describe('aggTopMetrics', () => {
const fn = functionWrapper(aggTopMetrics());
test('fills in defaults when only required args are provided', () => {
const actual = fn({
field: 'machine.os.keyword',
});
expect(actual).toMatchInlineSnapshot(`
Object {
"type": "agg_type",
"value": Object {
"enabled": true,
"id": undefined,
"params": Object {
"customLabel": undefined,
"field": "machine.os.keyword",
"json": undefined,
"size": undefined,
"sortField": undefined,
"sortOrder": undefined,
},
"schema": undefined,
"type": "top_metrics",
},
}
`);
});
test('includes optional params when they are provided', () => {
const actual = fn({
id: '1',
enabled: false,
schema: 'whatever',
field: 'machine.os.keyword',
sortOrder: 'asc',
size: 6,
sortField: 'bytes',
});
expect(actual.value).toMatchInlineSnapshot(`
Object {
"enabled": false,
"id": "1",
"params": Object {
"customLabel": undefined,
"field": "machine.os.keyword",
"json": undefined,
"size": 6,
"sortField": "bytes",
"sortOrder": "asc",
},
"schema": "whatever",
"type": "top_metrics",
}
`);
});
test('correctly parses json string argument', () => {
const actual = fn({
field: 'machine.os.keyword',
json: '{ "foo": true }',
});
expect(actual.value.params.json).toEqual('{ "foo": true }');
});
});
});

View file

@ -0,0 +1,106 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { AggExpressionType, AggExpressionFunctionArgs, METRIC_TYPES } from '../';
export const aggTopMetricsFnName = 'aggTopMetrics';
type Input = any;
type AggArgs = AggExpressionFunctionArgs<typeof METRIC_TYPES.TOP_METRICS>;
type Output = AggExpressionType;
type FunctionDefinition = ExpressionFunctionDefinition<
typeof aggTopMetricsFnName,
Input,
AggArgs,
Output
>;
export const aggTopMetrics = (): FunctionDefinition => ({
name: aggTopMetricsFnName,
help: i18n.translate('data.search.aggs.function.metrics.topMetrics.help', {
defaultMessage: 'Generates a serialized aggregation configuration for Top metrics.',
}),
type: 'agg_type',
args: {
id: {
types: ['string'],
help: i18n.translate('data.search.aggs.metrics.topMetrics.id.help', {
defaultMessage: 'ID for this aggregation',
}),
},
enabled: {
types: ['boolean'],
default: true,
help: i18n.translate('data.search.aggs.metrics.topMetrics.enabled.help', {
defaultMessage: 'Specifies whether this aggregation should be enabled',
}),
},
schema: {
types: ['string'],
help: i18n.translate('data.search.aggs.metrics.topMetrics.schema.help', {
defaultMessage: 'Schema to use for this aggregation',
}),
},
field: {
types: ['string'],
required: true,
help: i18n.translate('data.search.aggs.metrics.topMetrics.field.help', {
defaultMessage: 'Field to use for this aggregation',
}),
},
size: {
types: ['number'],
help: i18n.translate('data.search.aggs.metrics.topMetrics.size.help', {
defaultMessage: 'Number of top values to retrieve',
}),
},
sortOrder: {
types: ['string'],
options: ['desc', 'asc'],
help: i18n.translate('data.search.aggs.metrics.topMetrics.sortOrder.help', {
defaultMessage: 'Order in which to return the results: asc or desc',
}),
},
sortField: {
types: ['string'],
help: i18n.translate('data.search.aggs.metrics.topMetrics.sortField.help', {
defaultMessage: 'Field to order results by',
}),
},
json: {
types: ['string'],
help: i18n.translate('data.search.aggs.metrics.topMetrics.json.help', {
defaultMessage: 'Advanced JSON to include when the aggregation is sent to Elasticsearch',
}),
},
customLabel: {
types: ['string'],
help: i18n.translate('data.search.aggs.metrics.topMetrics.customLabel.help', {
defaultMessage: 'Represents a custom label for this aggregation',
}),
},
},
fn: (input, args) => {
const { id, enabled, schema, ...rest } = args;
return {
type: 'agg_type',
value: {
id,
enabled,
schema,
type: METRIC_TYPES.TOP_METRICS,
params: {
...rest,
},
},
};
},
});

View file

@ -43,6 +43,7 @@ export class FieldParamType extends BaseParamType {
this.filterFieldTypes = config.filterFieldTypes || '*'; this.filterFieldTypes = config.filterFieldTypes || '*';
this.onlyAggregatable = config.onlyAggregatable !== false; this.onlyAggregatable = config.onlyAggregatable !== false;
this.scriptable = config.scriptable !== false;
this.filterField = config.filterField; this.filterField = config.filterField;
if (!config.write) { if (!config.write) {

View file

@ -93,6 +93,8 @@ import {
import { AggParamsSampler } from './buckets/sampler'; import { AggParamsSampler } from './buckets/sampler';
import { AggParamsDiversifiedSampler } from './buckets/diversified_sampler'; import { AggParamsDiversifiedSampler } from './buckets/diversified_sampler';
import { AggParamsSignificantText } from './buckets/significant_text'; import { AggParamsSignificantText } from './buckets/significant_text';
import { AggParamsTopMetrics } from './metrics/top_metrics';
import { aggTopMetrics } from './metrics/top_metrics_fn';
export type { IAggConfig, AggConfigSerialized } from './agg_config'; export type { IAggConfig, AggConfigSerialized } from './agg_config';
export type { CreateAggConfigParams, IAggConfigs } from './agg_configs'; export type { CreateAggConfigParams, IAggConfigs } from './agg_configs';
@ -187,6 +189,7 @@ export interface AggParamsMapping {
[METRIC_TYPES.PERCENTILES]: AggParamsPercentiles; [METRIC_TYPES.PERCENTILES]: AggParamsPercentiles;
[METRIC_TYPES.SERIAL_DIFF]: AggParamsSerialDiff; [METRIC_TYPES.SERIAL_DIFF]: AggParamsSerialDiff;
[METRIC_TYPES.TOP_HITS]: AggParamsTopHit; [METRIC_TYPES.TOP_HITS]: AggParamsTopHit;
[METRIC_TYPES.TOP_METRICS]: AggParamsTopMetrics;
} }
/** /**
@ -229,4 +232,5 @@ export interface AggFunctionsMapping {
aggStdDeviation: ReturnType<typeof aggStdDeviation>; aggStdDeviation: ReturnType<typeof aggStdDeviation>;
aggSum: ReturnType<typeof aggSum>; aggSum: ReturnType<typeof aggSum>;
aggTopHit: ReturnType<typeof aggTopHit>; aggTopHit: ReturnType<typeof aggTopHit>;
aggTopMetrics: ReturnType<typeof aggTopMetrics>;
} }

View file

@ -54,7 +54,7 @@ describe('AggsService - public', () => {
service.setup(setupDeps); service.setup(setupDeps);
const start = service.start(startDeps); const start = service.start(startDeps);
expect(start.types.getAll().buckets.length).toBe(16); expect(start.types.getAll().buckets.length).toBe(16);
expect(start.types.getAll().metrics.length).toBe(23); expect(start.types.getAll().metrics.length).toBe(24);
}); });
test('registers custom agg types', () => { test('registers custom agg types', () => {
@ -71,7 +71,7 @@ describe('AggsService - public', () => {
const start = service.start(startDeps); const start = service.start(startDeps);
expect(start.types.getAll().buckets.length).toBe(17); expect(start.types.getAll().buckets.length).toBe(17);
expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true); expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true);
expect(start.types.getAll().metrics.length).toBe(24); expect(start.types.getAll().metrics.length).toBe(25);
expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true); expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true);
}); });
}); });

View file

@ -67,6 +67,11 @@ const metrics = {
sortField: controls.TopSortFieldParamEditor, sortField: controls.TopSortFieldParamEditor,
sortOrder: controls.OrderParamEditor, sortOrder: controls.OrderParamEditor,
}, },
[METRIC_TYPES.TOP_METRICS]: {
field: controls.FieldParamEditor,
sortField: controls.TopSortFieldParamEditor,
sortOrder: controls.OrderParamEditor,
},
[METRIC_TYPES.PERCENTILES]: { [METRIC_TYPES.PERCENTILES]: {
percents: controls.PercentilesEditor, percents: controls.PercentilesEditor,
}, },

View file

@ -13,7 +13,14 @@ import { i18n } from '@kbn/i18n';
import { useAvailableOptions, useFallbackMetric, useValidation } from './utils'; import { useAvailableOptions, useFallbackMetric, useValidation } from './utils';
import { AggParamEditorProps } from '../agg_param_props'; import { AggParamEditorProps } from '../agg_param_props';
const aggFilter = ['!top_hits', '!percentiles', '!percentile_ranks', '!median', '!std_dev']; const aggFilter = [
'!top_hits',
'!top_metrics',
'!percentiles',
'!percentile_ranks',
'!median',
'!std_dev',
];
const EMPTY_VALUE = 'EMPTY_VALUE'; const EMPTY_VALUE = 'EMPTY_VALUE';
const DEFAULT_OPTIONS = [{ text: '', value: EMPTY_VALUE, hidden: true }]; const DEFAULT_OPTIONS = [{ text: '', value: EMPTY_VALUE, hidden: true }];

View file

@ -0,0 +1,112 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { ExpectExpression, expectExpressionProvider } from './helpers';
import { FtrProviderContext } from '../../../functional/ftr_provider_context';
export default function ({
getService,
updateBaselines,
}: FtrProviderContext & { updateBaselines: boolean }) {
let expectExpression: ExpectExpression;
describe('esaggs_topmetrics', () => {
before(() => {
expectExpression = expectExpressionProvider({ getService, updateBaselines });
});
const timeRange = {
from: '2015-09-21T00:00:00Z',
to: '2015-09-22T00:00:00Z',
};
describe('aggTopMetrics', () => {
it('can execute aggTopMetrics', async () => {
const expression = `
kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
| esaggs index={indexPatternLoad id='logstash-*'}
aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw"}
aggs={aggTopMetrics id="2" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="desc" size=3 }
`;
const result = await expectExpression('aggTopMetrics', expression).getResponse();
expect(result.rows.map((r: { 'col-0-1': string }) => r['col-0-1'])).to.eql([
'jpg',
'css',
'png',
'gif',
'php',
]);
result.rows.forEach((r: { 'col-1-2': number[] }) => {
expect(r['col-1-2'].length).to.be(3);
expect(
r['col-1-2'].forEach((metric) => {
expect(typeof metric).to.be('number');
})
);
});
});
it('can execute aggTopMetrics with different sortOrder and size', async () => {
const expression = `
kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
| esaggs index={indexPatternLoad id='logstash-*'}
aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw"}
aggs={aggTopMetrics id="2" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="asc" size=1 }
`;
const result = await expectExpression('aggTopMetrics', expression).getResponse();
expect(result.rows.map((r: { 'col-0-1': string }) => r['col-0-1'])).to.eql([
'jpg',
'css',
'png',
'gif',
'php',
]);
result.rows.forEach((r: { 'col-1-2': number[] }) => {
expect(typeof r['col-1-2']).to.be('number');
});
});
it('can use aggTopMetrics as an orderAgg of aggTerms', async () => {
const expressionSortBytesAsc = `
kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
| esaggs index={indexPatternLoad id='logstash-*'}
aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw" size=1 orderAgg={aggTopMetrics id="order" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="asc" size=1}}
aggs={aggCount id="2" enabled=true schema="metric"}
`;
const resultSortBytesAsc = await expectExpression(
'sortBytesAsc',
expressionSortBytesAsc
).getResponse();
const expressionSortBytesDesc = `
kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
| esaggs index={indexPatternLoad id='logstash-*'}
aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw" size=1 orderAgg={aggTopMetrics id="order" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="desc" size=1}}
aggs={aggCount id="2" enabled=true schema="metric"}
`;
const resultSortBytesDesc = await expectExpression(
'sortBytesDesc',
expressionSortBytesDesc
).getResponse();
expect(resultSortBytesAsc.rows.length).to.be(1);
expect(resultSortBytesAsc.rows[0]['col-0-1']).to.be('jpg');
expect(resultSortBytesDesc.rows.length).to.be(1);
expect(resultSortBytesDesc.rows[0]['col-0-1']).to.be('php');
});
});
});
}

View file

@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
await kibanaServer.uiSettings.replace({ await kibanaServer.uiSettings.replace({
'dateFormat:tz': 'Australia/North', 'dateFormat:tz': 'Australia/North',
defaultIndex: 'logstash-*', defaultIndex: 'logstash-*',
'bfetch:disableCompression': true, // makes it easier to debug while developing tests
}); });
await browser.setWindowSize(1300, 900); await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings'); await PageObjects.common.navigateToApp('settings');
@ -47,5 +48,6 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
loadTestFile(require.resolve('./esaggs_sampler')); loadTestFile(require.resolve('./esaggs_sampler'));
loadTestFile(require.resolve('./esaggs_significanttext')); loadTestFile(require.resolve('./esaggs_significanttext'));
loadTestFile(require.resolve('./esaggs_rareterms')); loadTestFile(require.resolve('./esaggs_rareterms'));
loadTestFile(require.resolve('./esaggs_topmetrics'));
}); });
} }