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)