From 806a39531cb616ff39459e09331b0e67c7471546 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 4 Nov 2020 17:07:07 +0100 Subject: [PATCH] Add moving average function (#82122) (#82601) --- ...ns-public.expressionfunctiondefinitions.md | 1 + ...ssionfunctiondefinitions.moving_average.md | 11 + ...ns-server.expressionfunctiondefinitions.md | 1 + ...ssionfunctiondefinitions.moving_average.md | 11 + .../expression_functions/specs/index.ts | 3 + .../specs/moving_average.ts | 153 ++++++ .../specs/tests/moving_average.test.ts | 478 ++++++++++++++++++ .../common/expression_functions/types.ts | 2 + src/plugins/expressions/public/public.api.md | 4 + src/plugins/expressions/server/server.api.md | 4 + 10 files changed, 668 insertions(+) create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md create mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md create mode 100644 src/plugins/expressions/common/expression_functions/specs/moving_average.ts create mode 100644 src/plugins/expressions/common/expression_functions/specs/tests/moving_average.test.ts diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md index 0e180d1fabe3..53f090ea30c3 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md @@ -22,6 +22,7 @@ export interface ExpressionFunctionDefinitions | [font](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | | [kibana\_context](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | | [kibana](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | +| [moving\_average](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md) | ExpressionFunctionMovingAverage | | | [theme](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.theme.md) | ExpressionFunctionTheme | | | [var\_set](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.var_set.md) | ExpressionFunctionVarSet | | | [var](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.var.md) | ExpressionFunctionVar | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md new file mode 100644 index 000000000000..59d05ab6dbfc --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md) > [moving\_average](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.moving_average.md) + +## ExpressionFunctionDefinitions.moving\_average property + +Signature: + +```typescript +moving_average: ExpressionFunctionMovingAverage; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md index d4b71a36e0de..6f152bb10b37 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md @@ -22,6 +22,7 @@ export interface ExpressionFunctionDefinitions | [font](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | | [kibana\_context](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | | [kibana](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | +| [moving\_average](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md) | ExpressionFunctionMovingAverage | | | [theme](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.theme.md) | ExpressionFunctionTheme | | | [var\_set](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.var_set.md) | ExpressionFunctionVarSet | | | [var](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.var.md) | ExpressionFunctionVar | | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md new file mode 100644 index 000000000000..9e3b5299f5f9 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md) > [moving\_average](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.moving_average.md) + +## ExpressionFunctionDefinitions.moving\_average property + +Signature: + +```typescript +moving_average: ExpressionFunctionMovingAverage; +``` diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts index d414057598f1..11706f65dbd3 100644 --- a/src/plugins/expressions/common/expression_functions/specs/index.ts +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -27,6 +27,7 @@ import { AnyExpressionFunctionDefinition } from '../types'; import { theme } from './theme'; import { cumulativeSum } from './cumulative_sum'; import { derivative } from './derivative'; +import { movingAverage } from './moving_average'; export const functionSpecs: AnyExpressionFunctionDefinition[] = [ clog, @@ -38,6 +39,7 @@ export const functionSpecs: AnyExpressionFunctionDefinition[] = [ theme, cumulativeSum, derivative, + movingAverage, ]; export * from './clog'; @@ -49,3 +51,4 @@ export * from './var'; export * from './theme'; export * from './cumulative_sum'; export * from './derivative'; +export * from './moving_average'; diff --git a/src/plugins/expressions/common/expression_functions/specs/moving_average.ts b/src/plugins/expressions/common/expression_functions/specs/moving_average.ts new file mode 100644 index 000000000000..00a4d8c45839 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/moving_average.ts @@ -0,0 +1,153 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../types'; +import { Datatable } from '../../expression_types'; +import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; + +export interface MovingAverageArgs { + by?: string[]; + inputColumnId: string; + outputColumnId: string; + outputColumnName?: string; + window: number; +} + +export type ExpressionFunctionMovingAverage = ExpressionFunctionDefinition< + 'moving_average', + Datatable, + MovingAverageArgs, + Datatable +>; + +/** + * Calculates the moving average of a specified column in the data table. + * + * Also supports multiple series in a single data table - use the `by` argument + * to specify the columns to split the calculation by. + * For each unique combination of all `by` columns a separate moving average will be calculated. + * The order of rows won't be changed - this function is not modifying any existing columns, it's only + * adding the specified `outputColumnId` column to every row of the table without adding or removing rows. + * + * Behavior: + * * Will write the moving average of `inputColumnId` into `outputColumnId` + * * If provided will use `outputColumnName` as name for the newly created column. Otherwise falls back to `outputColumnId` + * * Moving average always starts with an undefined value for the first row of a series. Each next cell will contain sum of the last + * * [window] of values divided by [window] excluding the current bucket. + * If either of window edges moves outside the borders of data series, the window shrinks to include available values only. + * + * Edge cases: + * * Will return the input table if `inputColumnId` does not exist + * * Will throw an error if `outputColumnId` exists already in provided data table + * * If null or undefined value is encountered, skip the current row and do not change the window + * * For all values besides `null` and `undefined`, the value will be cast to a number before it's used in the + * calculation of the current series even if this results in `NaN` (like in case of objects). + * * To determine separate series defined by the `by` columns, the values of these columns will be cast to strings + * before comparison. If the values are objects, the return value of their `toString` method will be used for comparison. + */ +export const movingAverage: ExpressionFunctionMovingAverage = { + name: 'moving_average', + type: 'datatable', + + inputTypes: ['datatable'], + + help: i18n.translate('expressions.functions.movingAverage.help', { + defaultMessage: 'Calculates the moving average of a column in a data table', + }), + + args: { + by: { + help: i18n.translate('expressions.functions.movingAverage.args.byHelpText', { + defaultMessage: 'Column to split the moving average calculation by', + }), + multi: true, + types: ['string'], + required: false, + }, + inputColumnId: { + help: i18n.translate('expressions.functions.movingAverage.args.inputColumnIdHelpText', { + defaultMessage: 'Column to calculate the moving average of', + }), + types: ['string'], + required: true, + }, + outputColumnId: { + help: i18n.translate('expressions.functions.movingAverage.args.outputColumnIdHelpText', { + defaultMessage: 'Column to store the resulting moving average in', + }), + types: ['string'], + required: true, + }, + outputColumnName: { + help: i18n.translate('expressions.functions.movingAverage.args.outputColumnNameHelpText', { + defaultMessage: 'Name of the column to store the resulting moving average in', + }), + types: ['string'], + required: false, + }, + window: { + help: i18n.translate('expressions.functions.movingAverage.args.windowHelpText', { + defaultMessage: 'The size of window to "slide" across the histogram.', + }), + types: ['number'], + default: 5, + }, + }, + + fn(input, { by, inputColumnId, outputColumnId, outputColumnName, window }) { + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName + ); + + if (!resultColumns) { + return input; + } + + const lastNValuesByBucket: Partial> = {}; + return { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + const bucketIdentifier = getBucketIdentifier(row, by); + const lastNValues = lastNValuesByBucket[bucketIdentifier]; + const currentValue = newRow[inputColumnId]; + if (lastNValues != null && currentValue != null) { + const sum = lastNValues.reduce((acc, current) => acc + current, 0); + newRow[outputColumnId] = sum / lastNValues.length; + } else { + newRow[outputColumnId] = undefined; + } + + if (currentValue != null) { + lastNValuesByBucket[bucketIdentifier] = [ + ...(lastNValues || []), + Number(currentValue), + ].slice(-window); + } + + return newRow; + }), + }; + }, +}; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/moving_average.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/moving_average.test.ts new file mode 100644 index 000000000000..01bf1bad8161 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/tests/moving_average.test.ts @@ -0,0 +1,478 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { functionWrapper } from './utils'; +import { movingAverage, MovingAverageArgs } from '../moving_average'; +import { ExecutionContext } from '../../../execution/types'; +import { Datatable } from '../../../expression_types/specs/datatable'; + +const defaultArgs = { window: 5, inputColumnId: 'val', outputColumnId: 'output' }; + +describe('interpreter/functions#movingAverage', () => { + const fn = functionWrapper(movingAverage); + const runFn = (input: Datatable, args: MovingAverageArgs) => + fn(input, args, {} as ExecutionContext) as Datatable; + + it('calculates movingAverage', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }], + }, + defaultArgs + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + 5, + (5 + 7) / 2, + (5 + 7 + 3) / 3, + ]); + }); + + it('skips null or undefined values until there is real data', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [ + {}, + { val: null }, + { val: undefined }, + { val: 1 }, + { val: 2 }, + { val: undefined }, + { val: undefined }, + { val: 4 }, + { val: 8 }, + ], + }, + defaultArgs + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 1, + undefined, + undefined, + (1 + 2) / 2, + (1 + 2 + 4) / 3, + ]); + }); + + it('treats 0 as real data', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [ + {}, + { val: null }, + { val: undefined }, + { val: 1 }, + { val: 2 }, + { val: 0 }, + { val: undefined }, + { val: 0 }, + { val: undefined }, + { val: 0 }, + { val: 8 }, + { val: 0 }, + ], + }, + { ...defaultArgs, window: 3 } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 1, + (1 + 2) / 2, + undefined, + (1 + 2 + 0) / 3, + undefined, + (2 + 0 + 0) / 3, + (0 + 0 + 0) / 3, + (8 + 0 + 0) / 3, + ]); + }); + + it('calculates movingAverage for multiple series', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3, split: 'B' }, + { val: 4, split: 'A' }, + { val: 5, split: 'A' }, + { val: 6, split: 'A' }, + { val: 7, split: 'B' }, + { val: 8, split: 'B' }, + ], + }, + { ...defaultArgs, by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + 2, + 1, + (1 + 4) / 2, + (1 + 4 + 5) / 3, + (2 + 3) / 2, + (2 + 3 + 7) / 3, + ]); + }); + + it('treats missing split column as separate series', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3 }, + { val: 4, split: 'A' }, + { val: 5 }, + { val: 6, split: 'A' }, + { val: 7, split: 'B' }, + { val: 8, split: 'B' }, + ], + }, + { ...defaultArgs, by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 1, + 3, + (1 + 4) / 2, + 2, + (2 + 7) / 2, + ]); + }); + + it('treats null like undefined and empty string for split columns', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3 }, + { val: 4, split: 'A' }, + { val: 5 }, + { val: 6, split: 'A' }, + { val: 7, split: null }, + { val: 8, split: 'B' }, + { val: 9, split: '' }, + ], + }, + { ...defaultArgs, by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 1, + 3, + (1 + 4) / 2, + (3 + 5) / 2, + 2, + (3 + 5 + 7) / 3, + ]); + }); + + it('calculates movingAverage for multiple series by multiple split columns', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + { id: 'split2', name: 'split2', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A', split2: 'C' }, + { val: 2, split: 'B', split2: 'C' }, + { val: 3, split2: 'C' }, + { val: 4, split: 'A', split2: 'C' }, + { val: 5 }, + { val: 6, split: 'A', split2: 'D' }, + { val: 7, split: 'B', split2: 'D' }, + { val: 8, split: 'B', split2: 'D' }, + ], + }, + { ...defaultArgs, by: ['split', 'split2'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 1, + undefined, + undefined, + undefined, + 7, + ]); + }); + + it('splits separate series by the string representation of the cell values', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: { anObj: 3 } }, + { val: 2, split: { anotherObj: 5 } }, + { val: 10, split: 5 }, + { val: 11, split: '5' }, + ], + }, + { ...defaultArgs, by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([undefined, 1, undefined, 10]); + }); + + it('casts values to number before calculating movingAverage', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: '3' }, { val: 2 }], + }, + defaultArgs + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + 5, + (5 + 7) / 2, + (5 + 7 + 3) / 3, + ]); + }); + + it('skips NaN like values', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: {} }, { val: 3 }, { val: 5 }], + }, + defaultArgs + ); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 5, (5 + 7) / 2, NaN, NaN]); + }); + + it('copies over meta information from the source column', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + + field: 'afield', + index: 'anindex', + params: { id: 'number', params: { pattern: '000' } }, + source: 'synthetic', + sourceParams: { + some: 'params', + }, + }, + }, + ], + rows: [{ val: 5 }], + }, + defaultArgs + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { + type: 'number', + + field: 'afield', + index: 'anindex', + params: { id: 'number', params: { pattern: '000' } }, + source: 'synthetic', + sourceParams: { + some: 'params', + }, + }, + }); + }); + + it('sets output name on output column if specified', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }, + { ...defaultArgs, outputColumnName: 'Output name' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'Output name', + meta: { type: 'number' }, + }); + }); + + it('returns source table if input column does not exist', () => { + const input: Datatable = { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }; + expect( + runFn(input, { ...defaultArgs, inputColumnId: 'nonexisting', outputColumnId: 'output' }) + ).toBe(input); + }); + + it('throws an error if output column exists already', () => { + expect(() => + runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }, + { ...defaultArgs, inputColumnId: 'val', outputColumnId: 'val' } + ) + ).toThrow(); + }); + + it('calculates moving average for window equal to 1', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [ + { val: 5 }, + { val: '7' }, + { val: 0 }, + { val: 3 }, + { val: -10 }, + { val: 2 }, + { val: 8 }, + { val: undefined }, + { val: null }, + { val: 5 }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', window: 1 } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + 5, + 7, + 0, + 3, + -10, + 2, + undefined, + undefined, + 8, + ]); + }); + + it('calculates moving average for window bigger than array', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 1 }, { val: 2 }, { val: 0 }, { val: 5 }, { val: {} }, { val: {} }], + }, + { inputColumnId: 'val', outputColumnId: 'output', window: 15 } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + 1, + (1 + 2) / 2, + (1 + 2 + 0) / 3, + (1 + 2 + 0 + 5) / 4, + NaN, + ]); + }); +}); diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts index 134e9e3a6350..4a93cfa9211f 100644 --- a/src/plugins/expressions/common/expression_functions/types.ts +++ b/src/plugins/expressions/common/expression_functions/types.ts @@ -31,6 +31,7 @@ import { ExpressionFunctionTheme, ExpressionFunctionCumulativeSum, ExpressionFunctionDerivative, + ExpressionFunctionMovingAverage, } from './specs'; import { ExpressionAstFunction } from '../ast'; import { PersistableStateDefinition } from '../../../kibana_utils/common'; @@ -135,4 +136,5 @@ export interface ExpressionFunctionDefinitions { theme: ExpressionFunctionTheme; cumulative_sum: ExpressionFunctionCumulativeSum; derivative: ExpressionFunctionDerivative; + moving_average: ExpressionFunctionMovingAverage; } diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 06b7bc214244..454c3030aa07 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -403,6 +403,10 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) kibana_context: ExpressionFunctionKibanaContext; + // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionMovingAverage" needs to be exported by the entry point index.d.ts + // + // (undocumented) + moving_average: ExpressionFunctionMovingAverage; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionTheme" needs to be exported by the entry point index.d.ts // // (undocumented) diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index cacd61a8638a..742322b1b5a4 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -375,6 +375,10 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) kibana_context: ExpressionFunctionKibanaContext; + // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionMovingAverage" needs to be exported by the entry point index.d.ts + // + // (undocumented) + moving_average: ExpressionFunctionMovingAverage; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionTheme" needs to be exported by the entry point index.d.ts // // (undocumented)