mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[data.search.aggs] Add support for rate aggregation (#143487)
## Summary Resolves https://github.com/elastic/kibana/issues/131386. Adds support for the rate aggregation. Usage may look something like the following: ```ts const aggs = [ { type: 'date_histogram', params: { field: '@timestamp', interval: '1h', }, }, { type: 'rate', params: { field: 'bytes', // optional unit: 'hour', }, }, ]; const aggsDsl = data.search.aggs.createAggConfigs(dataView, aggs).toDsl(); // Which generates the following DSL: { "1": { "date_histogram": { "field": "@timestamp", "calendar_interval": "1h", "time_zone": "America/Phoenix", "min_doc_count": 1 }, "aggs": { "2": { "rate": { "field": "bytes", "unit": "hour" } } } } } ``` ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) Co-authored-by: Peter Pisljar <peter.pisljar@elastic.co>
This commit is contained in:
parent
c99f40f4b2
commit
5a6bf1dacd
10 changed files with 293 additions and 2 deletions
|
@ -39,6 +39,7 @@ export const getAggTypes = () => ({
|
|||
{ name: METRIC_TYPES.VALUE_COUNT, fn: metrics.getValueCountMetricAgg },
|
||||
{ name: METRIC_TYPES.PERCENTILES, fn: metrics.getPercentilesMetricAgg },
|
||||
{ name: METRIC_TYPES.PERCENTILE_RANKS, fn: metrics.getPercentileRanksMetricAgg },
|
||||
{ name: METRIC_TYPES.RATE, fn: metrics.getRateMetricAgg },
|
||||
{ name: METRIC_TYPES.TOP_HITS, fn: metrics.getTopHitMetricAgg },
|
||||
{ name: METRIC_TYPES.TOP_METRICS, fn: metrics.getTopMetricsMetricAgg },
|
||||
{ name: METRIC_TYPES.DERIVATIVE, fn: metrics.getDerivativeMetricAgg },
|
||||
|
@ -112,6 +113,7 @@ export const getAggTypesFunctions = () => [
|
|||
metrics.aggMovingAvg,
|
||||
metrics.aggPercentileRanks,
|
||||
metrics.aggPercentiles,
|
||||
metrics.aggRate,
|
||||
metrics.aggSerialDiff,
|
||||
metrics.aggStdDeviation,
|
||||
metrics.aggSum,
|
||||
|
|
|
@ -87,6 +87,7 @@ describe('Aggs service', () => {
|
|||
"value_count",
|
||||
"percentiles",
|
||||
"percentile_ranks",
|
||||
"rate",
|
||||
"top_hits",
|
||||
"top_metrics",
|
||||
"derivative",
|
||||
|
@ -140,6 +141,7 @@ describe('Aggs service', () => {
|
|||
"value_count",
|
||||
"percentiles",
|
||||
"percentile_ranks",
|
||||
"rate",
|
||||
"top_hits",
|
||||
"top_metrics",
|
||||
"derivative",
|
||||
|
|
|
@ -50,6 +50,8 @@ export * from './percentile_ranks_fn';
|
|||
export * from './percentile_ranks';
|
||||
export * from './percentiles_fn';
|
||||
export * from './percentiles';
|
||||
export * from './rate_fn';
|
||||
export * from './rate';
|
||||
export * from './single_percentile_rank_fn';
|
||||
export * from './single_percentile_rank';
|
||||
export * from './serial_diff_fn';
|
||||
|
|
|
@ -32,5 +32,6 @@ export enum METRIC_TYPES {
|
|||
TOP_METRICS = 'top_metrics',
|
||||
PERCENTILES = 'percentiles',
|
||||
PERCENTILE_RANKS = 'percentile_ranks',
|
||||
RATE = 'rate',
|
||||
STD_DEV = 'std_dev',
|
||||
}
|
||||
|
|
104
src/plugins/data/common/search/aggs/metrics/rate.ts
Normal file
104
src/plugins/data/common/search/aggs/metrics/rate.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 { aggRateFnName } from './rate_fn';
|
||||
import { MetricAggType } from './metric_agg_type';
|
||||
import { METRIC_TYPES } from './metric_agg_types';
|
||||
import { KBN_FIELD_TYPES } from '../../..';
|
||||
import { BaseAggParams } from '../types';
|
||||
|
||||
const rateTitle = i18n.translate('data.search.aggs.metrics.rateTitle', {
|
||||
defaultMessage: 'Rate',
|
||||
});
|
||||
|
||||
export interface AggParamsRate extends BaseAggParams {
|
||||
unit: string;
|
||||
field?: string;
|
||||
}
|
||||
|
||||
export const getRateMetricAgg = () => {
|
||||
return new MetricAggType({
|
||||
name: METRIC_TYPES.RATE,
|
||||
expressionName: aggRateFnName,
|
||||
title: rateTitle,
|
||||
valueType: 'number',
|
||||
makeLabel: (aggConfig) => {
|
||||
return i18n.translate('data.search.aggs.metrics.rateLabel', {
|
||||
defaultMessage: 'Rate of {field} per {unit}',
|
||||
values: { field: aggConfig.getFieldDisplayName(), unit: aggConfig.getParam('unit') },
|
||||
});
|
||||
},
|
||||
params: [
|
||||
{
|
||||
name: 'field',
|
||||
type: 'field',
|
||||
required: false,
|
||||
filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM],
|
||||
},
|
||||
{
|
||||
name: 'unit',
|
||||
type: 'string',
|
||||
displayName: i18n.translate('data.search.aggs.metrics.rate.unit.displayName', {
|
||||
defaultMessage: 'Unit',
|
||||
}),
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.second', {
|
||||
defaultMessage: 'Second',
|
||||
}),
|
||||
value: 'second',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.minute', {
|
||||
defaultMessage: 'Minute',
|
||||
}),
|
||||
value: 'minute',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.hour', {
|
||||
defaultMessage: 'Hour',
|
||||
}),
|
||||
value: 'hour',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.day', {
|
||||
defaultMessage: 'Day',
|
||||
}),
|
||||
value: 'day',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.week', {
|
||||
defaultMessage: 'Week',
|
||||
}),
|
||||
value: 'week',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.month', {
|
||||
defaultMessage: 'Month',
|
||||
}),
|
||||
value: 'month',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.quarter', {
|
||||
defaultMessage: 'Quarter',
|
||||
}),
|
||||
value: 'quarter',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('data.search.aggs.metrics.rate.unit.year', {
|
||||
defaultMessage: 'Year',
|
||||
}),
|
||||
value: 'year',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
75
src/plugins/data/common/search/aggs/metrics/rate_fn.test.ts
Normal file
75
src/plugins/data/common/search/aggs/metrics/rate_fn.test.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { aggRate } from './rate_fn';
|
||||
|
||||
describe('agg_expression_functions', () => {
|
||||
describe('aggRate', () => {
|
||||
const fn = functionWrapper(aggRate());
|
||||
|
||||
test('without field', () => {
|
||||
const actual = fn({
|
||||
unit: 'second',
|
||||
});
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"type": "agg_type",
|
||||
"value": Object {
|
||||
"enabled": true,
|
||||
"id": undefined,
|
||||
"params": Object {
|
||||
"customLabel": undefined,
|
||||
"field": undefined,
|
||||
"json": undefined,
|
||||
"timeShift": undefined,
|
||||
"unit": "second",
|
||||
},
|
||||
"schema": undefined,
|
||||
"type": "rate",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('with field', () => {
|
||||
const actual = fn({
|
||||
field: 'bytes',
|
||||
unit: 'second',
|
||||
});
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"type": "agg_type",
|
||||
"value": Object {
|
||||
"enabled": true,
|
||||
"id": undefined,
|
||||
"params": Object {
|
||||
"customLabel": undefined,
|
||||
"field": "bytes",
|
||||
"json": undefined,
|
||||
"timeShift": undefined,
|
||||
"unit": "second",
|
||||
},
|
||||
"schema": undefined,
|
||||
"type": "rate",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('correctly parses json string argument', () => {
|
||||
const actual = fn({
|
||||
field: 'machine.os.keyword',
|
||||
unit: 'month',
|
||||
json: '{ "foo": true }',
|
||||
});
|
||||
|
||||
expect(actual.value.params.json).toMatchInlineSnapshot(`"{ \\"foo\\": true }"`);
|
||||
});
|
||||
});
|
||||
});
|
101
src/plugins/data/common/search/aggs/metrics/rate_fn.ts
Normal file
101
src/plugins/data/common/search/aggs/metrics/rate_fn.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 '@kbn/expressions-plugin/common';
|
||||
import { AggExpressionType, AggExpressionFunctionArgs, METRIC_TYPES } from '..';
|
||||
|
||||
export const aggRateFnName = 'aggRate';
|
||||
|
||||
type Input = any;
|
||||
type AggArgs = AggExpressionFunctionArgs<typeof METRIC_TYPES.RATE>;
|
||||
type Output = AggExpressionType;
|
||||
type FunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof aggRateFnName,
|
||||
Input,
|
||||
AggArgs,
|
||||
Output
|
||||
>;
|
||||
|
||||
export const aggRate = (): FunctionDefinition => ({
|
||||
name: aggRateFnName,
|
||||
help: i18n.translate('data.search.aggs.function.metrics.rate.help', {
|
||||
defaultMessage: 'Generates a serialized agg config for a Rate agg',
|
||||
}),
|
||||
type: 'agg_type',
|
||||
args: {
|
||||
id: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.rate.id.help', {
|
||||
defaultMessage: 'ID for this aggregation',
|
||||
}),
|
||||
},
|
||||
enabled: {
|
||||
types: ['boolean'],
|
||||
default: true,
|
||||
help: i18n.translate('data.search.aggs.metrics.rate.enabled.help', {
|
||||
defaultMessage: 'Specifies whether this aggregation should be enabled',
|
||||
}),
|
||||
},
|
||||
schema: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.rate.schema.help', {
|
||||
defaultMessage: 'Schema to use for this aggregation',
|
||||
}),
|
||||
},
|
||||
field: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.rate.field.help', {
|
||||
defaultMessage: 'Field to use for this aggregation',
|
||||
}),
|
||||
},
|
||||
unit: {
|
||||
types: ['string'],
|
||||
required: true,
|
||||
options: ['second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'],
|
||||
help: i18n.translate('data.search.aggs.metrics.rate.unit.help', {
|
||||
defaultMessage: 'Unit to use for this aggregation',
|
||||
}),
|
||||
},
|
||||
json: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.rate.json.help', {
|
||||
defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch',
|
||||
}),
|
||||
},
|
||||
customLabel: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.rate.customLabel.help', {
|
||||
defaultMessage: 'Represents a custom label for this aggregation',
|
||||
}),
|
||||
},
|
||||
timeShift: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('data.search.aggs.metrics.timeShift.help', {
|
||||
defaultMessage:
|
||||
'Shift the time range for the metric by a set time, for example 1h or 7d. "previous" will use the closest time range from the date histogram or time range filter.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: (input, args) => {
|
||||
const { id, enabled, schema, ...rest } = args;
|
||||
|
||||
return {
|
||||
type: 'agg_type',
|
||||
value: {
|
||||
id,
|
||||
enabled,
|
||||
schema,
|
||||
type: METRIC_TYPES.RATE,
|
||||
params: {
|
||||
...rest,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -48,6 +48,7 @@ export class FieldParamType extends BaseParamType {
|
|||
const field = aggConfig.getField();
|
||||
|
||||
if (!field) {
|
||||
if (config.required === false) return;
|
||||
throw new TypeError(
|
||||
i18n.translate('data.search.aggs.paramTypes.field.requiredFieldParameterErrorMessage', {
|
||||
defaultMessage: '{fieldParameter} is a required parameter',
|
||||
|
|
|
@ -69,6 +69,7 @@ import {
|
|||
AggParamsPercentileRanks,
|
||||
AggParamsPercentiles,
|
||||
AggParamsRange,
|
||||
AggParamsRate,
|
||||
AggParamsSerialDiff,
|
||||
AggParamsSignificantTerms,
|
||||
AggParamsStdDeviation,
|
||||
|
@ -209,6 +210,7 @@ interface SerializedAggParamsMapping {
|
|||
[METRIC_TYPES.MOVING_FN]: AggParamsMovingAvgSerialized;
|
||||
[METRIC_TYPES.PERCENTILE_RANKS]: AggParamsPercentileRanks;
|
||||
[METRIC_TYPES.PERCENTILES]: AggParamsPercentiles;
|
||||
[METRIC_TYPES.RATE]: AggParamsRate;
|
||||
[METRIC_TYPES.SERIAL_DIFF]: AggParamsSerialDiffSerialized;
|
||||
[METRIC_TYPES.TOP_HITS]: AggParamsTopHitSerialized;
|
||||
[METRIC_TYPES.TOP_METRICS]: AggParamsTopMetricsSerialized;
|
||||
|
@ -254,6 +256,7 @@ export interface AggParamsMapping {
|
|||
[METRIC_TYPES.MOVING_FN]: AggParamsMovingAvg;
|
||||
[METRIC_TYPES.PERCENTILE_RANKS]: AggParamsPercentileRanks;
|
||||
[METRIC_TYPES.PERCENTILES]: AggParamsPercentiles;
|
||||
[METRIC_TYPES.RATE]: AggParamsRate;
|
||||
[METRIC_TYPES.SERIAL_DIFF]: AggParamsSerialDiff;
|
||||
[METRIC_TYPES.TOP_HITS]: AggParamsTopHit;
|
||||
[METRIC_TYPES.TOP_METRICS]: AggParamsTopMetrics;
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('AggsService - public', () => {
|
|||
service.setup(setupDeps);
|
||||
const start = service.start(startDeps);
|
||||
expect(start.types.getAll().buckets.length).toBe(16);
|
||||
expect(start.types.getAll().metrics.length).toBe(26);
|
||||
expect(start.types.getAll().metrics.length).toBe(27);
|
||||
});
|
||||
|
||||
test('registers custom agg types', () => {
|
||||
|
@ -70,7 +70,7 @@ describe('AggsService - public', () => {
|
|||
const start = service.start(startDeps);
|
||||
expect(start.types.getAll().buckets.length).toBe(17);
|
||||
expect(start.types.getAll().buckets.some(({ name }) => name === 'foo')).toBe(true);
|
||||
expect(start.types.getAll().metrics.length).toBe(27);
|
||||
expect(start.types.getAll().metrics.length).toBe(28);
|
||||
expect(start.types.getAll().metrics.some(({ name }) => name === 'bar')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue