From 9e8f81fc2177623419725696e7532c1839defdbc Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 9 Nov 2020 12:48:33 +0100 Subject: [PATCH 1/8] [Lens] Implement counter rate expression --- .../common/expression_functions/index.ts | 1 + .../{specs => }/series_calculation_helpers.ts | 2 +- .../specs/cumulative_sum.ts | 2 +- .../expression_functions/specs/derivative.ts | 2 +- .../specs/moving_average.ts | 2 +- .../counter_rate/counter_rate.test.ts | 392 ++++++++++++++++++ .../counter_rate/index.ts | 127 ++++++ .../indexpattern_datasource/time_scale.ts | 35 +- 8 files changed, 532 insertions(+), 31 deletions(-) rename src/plugins/expressions/common/expression_functions/{specs => }/series_calculation_helpers.ts (97%) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts diff --git a/src/plugins/expressions/common/expression_functions/index.ts b/src/plugins/expressions/common/expression_functions/index.ts index b29e6b78b8f4d..094fbe83efd22 100644 --- a/src/plugins/expressions/common/expression_functions/index.ts +++ b/src/plugins/expressions/common/expression_functions/index.ts @@ -22,3 +22,4 @@ export * from './arguments'; export * from './expression_function_parameter'; export * from './expression_function'; export * from './specs'; +export * from './series_calculation_helpers'; diff --git a/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts b/src/plugins/expressions/common/expression_functions/series_calculation_helpers.ts similarity index 97% rename from src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts rename to src/plugins/expressions/common/expression_functions/series_calculation_helpers.ts index 8ba9d527d4c59..99ad2098ab10a 100644 --- a/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts +++ b/src/plugins/expressions/common/expression_functions/series_calculation_helpers.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { Datatable, DatatableRow } from '../../expression_types'; +import { Datatable, DatatableRow } from '../expression_types'; /** * Returns a string identifying the group of a row by a list of columns to group by diff --git a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts index 672abadd3c016..0d9547f70dd3b 100644 --- a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts +++ b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable } from '../../expression_types'; -import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; +import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers'; export interface CumulativeSumArgs { by?: string[]; diff --git a/src/plugins/expressions/common/expression_functions/specs/derivative.ts b/src/plugins/expressions/common/expression_functions/specs/derivative.ts index 44ac198e2d17c..320a254bf94a9 100644 --- a/src/plugins/expressions/common/expression_functions/specs/derivative.ts +++ b/src/plugins/expressions/common/expression_functions/specs/derivative.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable } from '../../expression_types'; -import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; +import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers'; export interface DerivativeArgs { by?: string[]; diff --git a/src/plugins/expressions/common/expression_functions/specs/moving_average.ts b/src/plugins/expressions/common/expression_functions/specs/moving_average.ts index 00a4d8c45839e..c4887e0240ec0 100644 --- a/src/plugins/expressions/common/expression_functions/specs/moving_average.ts +++ b/src/plugins/expressions/common/expression_functions/specs/moving_average.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable } from '../../expression_types'; -import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; +import { buildResultColumns, getBucketIdentifier } from '../series_calculation_helpers'; export interface MovingAverageArgs { by?: string[]; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts new file mode 100644 index 0000000000000..e9c5918478510 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts @@ -0,0 +1,392 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { counterRate, CounterRateArgs } from '../counter_rate'; + +import { Datatable } from 'src/plugins/expressions/public'; +import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; + +describe('counter_rate', () => { + const fn = functionWrapper(counterRate); + const runFn = (input: Datatable, args: CounterRateArgs) => fn(input, args) as Datatable; + + it('calculates counter rate', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 2, 3, 2]); + }); + + 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 }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 2 - 1, + undefined, + undefined, + undefined, + 8 - 4, + ]); + }); + + 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 }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 2 - 1, + 0, + undefined, + undefined, + undefined, + undefined, + 8 - 0, + 0, + ]); + }); + + it('calculates counter rate 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' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + 3 - 2, + 4 - 1, + 5 - 4, + 6 - 5, + 7 - 3, + 8 - 7, + ]); + }); + + 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' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + 5 - 3, + 6 - 4, + 7 - 2, + 8 - 7, + ]); + }); + + 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: '' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + 5 - 3, + 6 - 4, + 7 - 5, + 8 - 2, + 9 - 7, + ]); + }); + + it('calculates counter rate 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' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split', 'split2'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + undefined, + undefined, + undefined, + 8 - 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' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([undefined, 2 - 1, undefined, 11 - 10]); + }); + + it('casts values to number before calculating counter rate', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: '3' }, { val: 2 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, 3, 2]); + }); + + it('casts values to number before calculating counter rate for NaN like values', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: {} }, { val: 2 }, { val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, NaN, 2, 5 - 2]); + }); + + 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 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + 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 }], + }, + { inputColumnId: 'val', outputColumnId: 'output', 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, { 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 }], + }, + { inputColumnId: 'val', outputColumnId: 'val' } + ) + ).toThrow(); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts new file mode 100644 index 0000000000000..89dedd846aaf9 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; +import { getBucketIdentifier, buildResultColumns } from '../../../../../../src/plugins/expressions'; + +export interface CounterRateArgs { + by?: string[]; + inputColumnId: string; + outputColumnId: string; + outputColumnName?: string; +} + +export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition< + 'counter_rate', + Datatable, + CounterRateArgs, + Datatable +>; + +// TODO +/** + * Calculates the counter rate 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 counter rate 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 counter rate of `inputColumnId` into `outputColumnId` + * * If provided will use `outputColumnName` as name for the newly created column. Otherwise falls back to `outputColumnId` + * * Counter rate always start with an undefined value for the first row of a series, a cell will contain its own value minus the + * value of the previous cell of the same series. + * + * 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 there is no previous row of the current series with a non `null` or `undefined` value, the output cell of the current row + * will be set to `undefined`. + * * If the row value contains `null` or `undefined`, it will be ignored and the output cell will be set to `undefined` + * * If the value of the previous row of the same series contains `null` or `undefined`, the output cell of the current row will be set to `undefined` as well + * * 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. + * Missing values (`null` and `undefined`) will be treated as empty strings. + */ +export const counterRate: ExpressionFunctionCounterRate = { + name: 'counter_rate', + type: 'datatable', + + inputTypes: ['datatable'], + + help: i18n.translate('expressions.functions.counterRate.help', { + defaultMessage: 'Calculates the counter rate of a column in a data table', + }), + + args: { + by: { + help: i18n.translate('expressions.functions.counterRate.args.byHelpText', { + defaultMessage: 'Column to split the counter rate calculation by', + }), + multi: true, + types: ['string'], + required: false, + }, + inputColumnId: { + help: i18n.translate('expressions.functions.counterRate.args.inputColumnIdHelpText', { + defaultMessage: 'Column to calculate the counter rate of', + }), + types: ['string'], + required: true, + }, + outputColumnId: { + help: i18n.translate('expressions.functions.counterRate.args.outputColumnIdHelpText', { + defaultMessage: 'Column to store the resulting counter rate in', + }), + types: ['string'], + required: true, + }, + outputColumnName: { + help: i18n.translate('expressions.functions.counterRate.args.outputColumnNameHelpText', { + defaultMessage: 'Name of the column to store the resulting counter rate in', + }), + types: ['string'], + required: false, + }, + }, + + fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) { + const previousValues: Partial> = {}; + return { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + const bucketIdentifier = getBucketIdentifier(row, by); + const previousValue = previousValues[bucketIdentifier]; + const currentValue = newRow[inputColumnId]; + if (currentValue != null && previousValue != null) { + if (Number(currentValue) >= previousValue) { + newRow[outputColumnId] = Number(currentValue) - previousValue; + } else { + newRow[outputColumnId] = Number(currentValue); + } + } else { + newRow[outputColumnId] = undefined; + } + + if (currentValue != null) { + previousValues[bucketIdentifier] = Number(currentValue); + } else { + previousValues[bucketIdentifier] = undefined; + } + + return newRow; + }), + }; + }, +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts index 0937f40eeb6d3..a48a629ba9122 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { search } from '../../../../../src/plugins/data/public'; +import { buildResultColumns } from '../../../../../src/plugins/expressions'; type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; @@ -69,17 +70,6 @@ export function getTimeScaleFunction(data: DataPublicPluginStart) { input, { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs ) { - if (input.columns.some((column) => column.id === outputColumnId)) { - throw new Error( - i18n.translate('xpack.lens.functions.timeScale.columnConflictMessage', { - defaultMessage: 'Specified outputColumnId {columnId} already exists.', - values: { - columnId: outputColumnId, - }, - }) - ); - } - const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); if (!dateColumnDefinition) { @@ -93,26 +83,17 @@ export function getTimeScaleFunction(data: DataPublicPluginStart) { ); } - const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId); + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName + ); - if (!inputColumnDefinition) { + if (!resultColumns) { return input; } - const outputColumnDefinition = { - ...inputColumnDefinition, - id: outputColumnId, - name: outputColumnName || outputColumnId, - }; - - const resultColumns = [...input.columns]; - // add output column after input column in the table - resultColumns.splice( - resultColumns.indexOf(inputColumnDefinition) + 1, - 0, - outputColumnDefinition - ); - const targetUnitInMs = unitInMs[targetUnit]; const timeInfo = await data.search.aggs.getDateMetaByDatatableColumn(dateColumnDefinition); const intervalDuration = timeInfo && search.aggs.parseInterval(timeInfo.interval); From bf5f53900f6b53a89d499fe448b7f457f491bcc5 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 9 Nov 2020 13:56:40 +0100 Subject: [PATCH 2/8] change names --- .../counter_rate/counter_rate.test.ts | 2 +- .../indexpattern_datasource/counter_rate/index.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts index e9c5918478510..a18b04cc8fa62 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts @@ -9,7 +9,7 @@ import { counterRate, CounterRateArgs } from '../counter_rate'; import { Datatable } from 'src/plugins/expressions/public'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; -describe('counter_rate', () => { +describe('lens_counter_rate', () => { const fn = functionWrapper(counterRate); const runFn = (input: Datatable, args: CounterRateArgs) => fn(input, args) as Datatable; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts index 89dedd846aaf9..d514922fde754 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts @@ -16,7 +16,7 @@ export interface CounterRateArgs { } export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition< - 'counter_rate', + 'lens_counter_rate', Datatable, CounterRateArgs, Datatable @@ -52,7 +52,7 @@ export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition< * Missing values (`null` and `undefined`) will be treated as empty strings. */ export const counterRate: ExpressionFunctionCounterRate = { - name: 'counter_rate', + name: 'lens_counter_rate', type: 'datatable', inputTypes: ['datatable'], @@ -94,6 +94,16 @@ export const counterRate: ExpressionFunctionCounterRate = { }, fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) { + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName + ); + + if (!resultColumns) { + return input; + } const previousValues: Partial> = {}; return { ...input, From e0b4b319da2671fcbc76be30df31763d6fd2f7e7 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 9 Nov 2020 14:14:01 +0100 Subject: [PATCH 3/8] docs --- .../public/indexpattern_datasource/counter_rate/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts index d514922fde754..7a30446fd551e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts @@ -22,7 +22,6 @@ export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition< Datatable >; -// TODO /** * Calculates the counter rate of a specified column in the data table. * @@ -35,8 +34,10 @@ export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition< * Behavior: * * Will write the counter rate of `inputColumnId` into `outputColumnId` * * If provided will use `outputColumnName` as name for the newly created column. Otherwise falls back to `outputColumnId` - * * Counter rate always start with an undefined value for the first row of a series, a cell will contain its own value minus the - * value of the previous cell of the same series. + * * Counter rate always start with an undefined value for the first row of a series. + * * If the value of the current cell is not smaller than the previous one, an output cell will contain + * * its own value minus the value of the previous cell of the same series. If the value is smaller, + * * an output cell will contain its own value * * Edge cases: * * Will return the input table if `inputColumnId` does not exist From 30b1eb79e6e5d99e70608d4f9e467d5bbb81bf9e Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 10 Nov 2020 09:02:52 +0100 Subject: [PATCH 4/8] correct imports --- .../public/indexpattern_datasource/counter_rate/index.ts | 5 ++++- .../lens/public/indexpattern_datasource/time_scale.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts index 7a30446fd551e..38b930e33e933 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts @@ -6,7 +6,10 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; -import { getBucketIdentifier, buildResultColumns } from '../../../../../../src/plugins/expressions'; +import { + getBucketIdentifier, + buildResultColumns, +} from '../../../../../../src/plugins/expressions/common'; export interface CounterRateArgs { by?: string[]; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts index a48a629ba9122..7a4e8f6bc0638 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { search } from '../../../../../src/plugins/data/public'; -import { buildResultColumns } from '../../../../../src/plugins/expressions'; +import { buildResultColumns } from '../../../../../src/plugins/expressions/common'; type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; From 31d06406f18703867566480f5e4bef643c813f4c Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 10 Nov 2020 09:20:58 +0100 Subject: [PATCH 5/8] i18n check --- .../indexpattern_datasource/counter_rate/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts index 38b930e33e933..88ea9fa80d1ea 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts @@ -61,13 +61,13 @@ export const counterRate: ExpressionFunctionCounterRate = { inputTypes: ['datatable'], - help: i18n.translate('expressions.functions.counterRate.help', { + help: i18n.translate('xpack.lens.functions.counterRate.help', { defaultMessage: 'Calculates the counter rate of a column in a data table', }), args: { by: { - help: i18n.translate('expressions.functions.counterRate.args.byHelpText', { + help: i18n.translate('xpack.lens.functions.counterRate.args.byHelpText', { defaultMessage: 'Column to split the counter rate calculation by', }), multi: true, @@ -75,21 +75,21 @@ export const counterRate: ExpressionFunctionCounterRate = { required: false, }, inputColumnId: { - help: i18n.translate('expressions.functions.counterRate.args.inputColumnIdHelpText', { + help: i18n.translate('xpack.lens.functions.counterRate.args.inputColumnIdHelpText', { defaultMessage: 'Column to calculate the counter rate of', }), types: ['string'], required: true, }, outputColumnId: { - help: i18n.translate('expressions.functions.counterRate.args.outputColumnIdHelpText', { + help: i18n.translate('xpack.lens.functions.counterRate.args.outputColumnIdHelpText', { defaultMessage: 'Column to store the resulting counter rate in', }), types: ['string'], required: true, }, outputColumnName: { - help: i18n.translate('expressions.functions.counterRate.args.outputColumnNameHelpText', { + help: i18n.translate('xpack.lens.functions.counterRate.args.outputColumnNameHelpText', { defaultMessage: 'Name of the column to store the resulting counter rate in', }), types: ['string'], From 068c5fdc4a4c3d95475bb16efa13893fc08cc8a1 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 10 Nov 2020 17:19:51 +0100 Subject: [PATCH 6/8] nitpick --- .../public/indexpattern_datasource/counter_rate/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts index 88ea9fa80d1ea..45a8265ffdc4c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts @@ -119,10 +119,11 @@ export const counterRate: ExpressionFunctionCounterRate = { const previousValue = previousValues[bucketIdentifier]; const currentValue = newRow[inputColumnId]; if (currentValue != null && previousValue != null) { - if (Number(currentValue) >= previousValue) { - newRow[outputColumnId] = Number(currentValue) - previousValue; + const currentValueAsNumber = Number(currentValue); + if (currentValueAsNumber >= previousValue) { + newRow[outputColumnId] = currentValueAsNumber - previousValue; } else { - newRow[outputColumnId] = Number(currentValue); + newRow[outputColumnId] = currentValueAsNumber; } } else { newRow[outputColumnId] = undefined; From c7cb0bcf8d862c6ce58aa9ab91b6ee6999380014 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 11 Nov 2020 09:43:58 +0100 Subject: [PATCH 7/8] adding decreasing values test --- .../counter_rate/counter_rate.test.ts | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts index a18b04cc8fa62..f216934d2041b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts @@ -18,7 +18,7 @@ describe('lens_counter_rate', () => { { type: 'datatable', columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], - rows: [{ val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }], + rows: [{ val: 5 }, { val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }], }, { inputColumnId: 'val', outputColumnId: 'output' } ); @@ -27,9 +27,43 @@ describe('lens_counter_rate', () => { name: 'output', meta: { type: 'number' }, }); - expect(result.rows.map((row) => row.output)).toEqual([undefined, 2, 3, 2]); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 0, 2, 3, 2]); }); + it('calculates counter rate with decreasing values in input', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 7 }, { val: 6 }, { val: 5 }, { val: 4 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 6, 5, 4]); + }); + + it('calculates counter rate with decreasing values in input', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 7 }, { val: 6 }, { val: 5 }, { val: 4 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 6, 5, 4]); + }); + it('skips null or undefined values until there is real data', () => { const result = runFn( { From a838633ff61db9ef7701f7560f2bb98e6b28e868 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 11 Nov 2020 10:45:46 +0100 Subject: [PATCH 8/8] copy --- .../counter_rate/counter_rate.test.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts index f216934d2041b..ccabb7147d2e4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts @@ -47,23 +47,6 @@ describe('lens_counter_rate', () => { expect(result.rows.map((row) => row.output)).toEqual([undefined, 6, 5, 4]); }); - it('calculates counter rate with decreasing values in input', () => { - const result = runFn( - { - type: 'datatable', - columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], - rows: [{ val: 7 }, { val: 6 }, { val: 5 }, { val: 4 }], - }, - { inputColumnId: 'val', outputColumnId: 'output' } - ); - expect(result.columns).toContainEqual({ - id: 'output', - name: 'output', - meta: { type: 'number' }, - }); - expect(result.rows.map((row) => row.output)).toEqual([undefined, 6, 5, 4]); - }); - it('skips null or undefined values until there is real data', () => { const result = runFn( {