From 2fc8e67b846e138dedc641380778f58a2d5aa159 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 5 Feb 2021 19:11:48 +0100 Subject: [PATCH 01/14] :truck: Move mapColumn to expressions plugin --- .../expression_functions/specs/index.ts | 3 + .../expression_functions/specs/map_column.ts | 124 +++++++++++ .../specs/tests/map_column.test.ts | 124 +++++++++++ .../expression_functions/specs/tests/utils.ts | 201 ++++++++++++++++++ .../functions/common/index.ts | 2 - .../functions/common/mapColumn.test.js | 69 ------ .../functions/common/mapColumn.ts | 79 ------- .../canvas/i18n/functions/dict/map_column.ts | 68 +++--- .../canvas/i18n/functions/function_help.ts | 2 - 9 files changed, 490 insertions(+), 182 deletions(-) create mode 100644 src/plugins/expressions/common/expression_functions/specs/map_column.ts create mode 100644 src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts delete mode 100644 x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js delete mode 100644 x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts index 938fce026027c..cf0ada2f7f5e9 100644 --- a/src/plugins/expressions/common/expression_functions/specs/index.ts +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -15,6 +15,7 @@ import { theme } from './theme'; import { cumulativeSum } from './cumulative_sum'; import { derivative } from './derivative'; import { movingAverage } from './moving_average'; +import { mapColumn } from './map_column'; export const functionSpecs: AnyExpressionFunctionDefinition[] = [ clog, @@ -25,6 +26,7 @@ export const functionSpecs: AnyExpressionFunctionDefinition[] = [ cumulativeSum, derivative, movingAverage, + mapColumn, ]; export * from './clog'; @@ -35,3 +37,4 @@ export * from './theme'; export * from './cumulative_sum'; export * from './derivative'; export * from './moving_average'; +export * from './map_column'; diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts new file mode 100644 index 0000000000000..a77bed99f9492 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -0,0 +1,124 @@ +/* + * 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 '../types'; +import { Datatable, getType } from '../../expression_types'; + +export interface MapColumnArguments { + id?: string | null; + name: string; + expression?: (datatable: Datatable) => Promise; + copyMetaFrom?: string | null; +} + +export const mapColumn: ExpressionFunctionDefinition< + 'mapColumn', + Datatable, + MapColumnArguments, + Promise +> = { + name: 'mapColumn', + aliases: ['mc'], // midnight commander. So many times I've launched midnight commander instead of moving a file. + type: 'datatable', + inputTypes: ['datatable'], + help: i18n.translate('expressions.functions.mapColumnHelpText', { + defaultMessage: + 'Adds a column calculated as the result of other columns. ' + + 'Changes are made only when you provide arguments.' + + 'See also {alterColumnFn} and {staticColumnFn}.', + values: { + alterColumnFn: '`alterColumn`', + staticColumnFn: '`staticColumn`', + }, + }), + args: { + id: { + types: ['string', 'null'], + help: i18n.translate('expressions.functions.mapColumn.args.idHelpText', { + defaultMessage: + 'An optional id of the resulting column. When `null` the name/column argument is used as id.', + }), + required: false, + default: null, + }, + name: { + types: ['string'], + aliases: ['_', 'column'], + help: i18n.translate('expressions.functions.mapColumn.args.nameHelpText', { + defaultMessage: 'The name of the resulting column.', + }), + required: true, + }, + expression: { + types: ['boolean', 'number', 'string', 'null'], + resolve: false, + aliases: ['exp', 'fn', 'function'], + help: i18n.translate('expressions.functions.mapColumn.args.expressionHelpText', { + defaultMessage: + 'A {CANVAS} expression that is passed to each row as a single row {DATATABLE}.', + values: { + CANVAS: 'canvas', + DATATABLE: '`datatable`', + }, + }), + required: true, + }, + copyMetaFrom: { + types: ['string', 'null'], + help: i18n.translate('expressions.functions.mapColumn.args.copyMetaFromHelpText', { + defaultMessage: + "if set, the meta object from the specified column id is copied over to the specified target column. Throws an exception of the column doesn't exist", + }), + required: false, + default: null, + }, + }, + fn: (input, args) => { + const expression = args.expression || (() => Promise.resolve(null)); + const columnId = args.id != null ? args.id : args.name; + + const columns = [...input.columns]; + const rowPromises = input.rows.map((row) => { + return expression({ + type: 'datatable', + columns, + rows: [row], + }).then((val) => ({ + ...row, + [columnId]: val, + })); + }); + + return Promise.all(rowPromises).then((rows) => { + const existingColumnIndex = columns.findIndex(({ name }) => name === args.name); + const type = rows.length ? getType(rows[0][args.name]) : 'null'; + const newColumn = { + id: columnId, + name: args.name, + meta: { type }, + }; + if (args.copyMetaFrom) { + const metaSourceFrom = columns.find(({ name }) => name === args.copyMetaFrom); + newColumn.meta = { ...newColumn.meta, ...(metaSourceFrom?.meta || {}) }; + } + + if (existingColumnIndex === -1) { + columns.push(newColumn); + } else { + columns[existingColumnIndex] = newColumn; + } + + return { + type: 'datatable', + columns, + rows, + } as Datatable; + }); + }, +}; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts new file mode 100644 index 0000000000000..588d1f34590f3 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -0,0 +1,124 @@ +/* + * 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 { Datatable } from '../../../expression_types'; +import { mapColumn, MapColumnArguments } from '../map_column'; +import { emptyTable, functionWrapper, testTable } from './utils'; + +const pricePlusTwo = (datatable: Datatable) => Promise.resolve(datatable.rows[0].price + 2); + +describe('mapColumn', () => { + const fn = functionWrapper(mapColumn); + const runFn = (input: Datatable, args: MapColumnArguments) => + fn(input, args) as Promise; + + it('returns a datatable with a new column with the values from mapping a function over each row in a datatable', () => { + return runFn(testTable, { + id: 'pricePlusTwo', + name: 'pricePlusTwo', + expression: pricePlusTwo, + }).then((result) => { + const arbitraryRowIndex = 2; + + expect(result.type).toBe('datatable'); + expect(result.columns).toEqual([ + ...testTable.columns, + { id: 'pricePlusTwo', name: 'pricePlusTwo', meta: { type: 'number' } }, + ]); + expect(result.columns[result.columns.length - 1]).toHaveProperty('name', 'pricePlusTwo'); + expect(result.rows[arbitraryRowIndex]).toHaveProperty('pricePlusTwo'); + }); + }); + + it('overwrites existing column with the new column if an existing column name is provided', () => { + return runFn(testTable, { name: 'name', expression: pricePlusTwo }).then((result) => { + const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); + const arbitraryRowIndex = 4; + + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(testTable.columns.length); + expect(result.columns[nameColumnIndex]).toHaveProperty('name', 'name'); + expect(result.columns[nameColumnIndex].meta).toHaveProperty('type', 'number'); + expect(result.rows[arbitraryRowIndex]).toHaveProperty('name', 202); + }); + }); + + it('adds a column to empty tables', () => { + return runFn(emptyTable, { name: 'name', expression: pricePlusTwo }).then((result) => { + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'name'); + expect(result.columns[0].meta).toHaveProperty('type', 'null'); + }); + }); + + it('should assign specific id, different from name, when id arg is passed for new columns', () => { + return runFn(emptyTable, { name: 'name', id: 'myid', expression: pricePlusTwo }).then( + (result) => { + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'name'); + expect(result.columns[0]).toHaveProperty('id', 'myid'); + expect(result.columns[0].meta).toHaveProperty('type', 'null'); + } + ); + }); + + it('should assign specific id, different from name, when id arg is passed for copied column', () => { + return runFn(testTable, { name: 'name', id: 'myid', expression: pricePlusTwo }).then( + (result) => { + const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); + expect(result.type).toBe('datatable'); + expect(result.columns[nameColumnIndex]).toEqual({ + id: 'myid', + name: 'name', + meta: { type: 'string' }, + }); + } + ); + }); + + it('should copy over the meta information from the specified column', () => { + return runFn(testTable, { name: 'name', copyMetaFrom: 'time', expression: pricePlusTwo }).then( + (result) => { + const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); + expect(result.type).toBe('datatable'); + expect(result.columns[nameColumnIndex]).toEqual({ + id: 'name', + name: 'name', + meta: { type: 'date' }, + }); + } + ); + }); + + it('should be resilient if the references column for meta information does not exists', () => { + return runFn(emptyTable, { name: 'name', copyMetaFrom: 'time', expression: pricePlusTwo }).then( + (result) => { + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'name'); + expect(result.columns[0]).toHaveProperty('id', 'name'); + expect(result.columns[0].meta).toHaveProperty('type', 'null'); + } + ); + }); + + describe('expression', () => { + it('maps null values to the new column', () => { + return runFn(testTable, { name: 'empty' }).then((result) => { + const emptyColumnIndex = result.columns.findIndex(({ name }) => name === 'empty'); + const arbitraryRowIndex = 8; + + expect(result.columns[emptyColumnIndex]).toHaveProperty('name', 'empty'); + expect(result.columns[emptyColumnIndex].meta).toHaveProperty('type', 'null'); + expect(result.rows[arbitraryRowIndex]).toHaveProperty('empty', null); + }); + }); + }); +}); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts index 9006de9067616..44ed87dc02360 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts @@ -9,6 +9,7 @@ import { mapValues } from 'lodash'; import { AnyExpressionFunctionDefinition } from '../../types'; import { ExecutionContext } from '../../../execution/types'; +import { Datatable } from '../../../expression_types'; /** * Takes a function spec and passes in default args, @@ -22,3 +23,203 @@ export const functionWrapper = (spec: AnyExpressionFunctionDefinition) => { handlers: ExecutionContext = {} as ExecutionContext ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); }; + +const emptyTable: Datatable = { + type: 'datatable', + columns: [], + rows: [], +}; + +const testTable: Datatable = { + type: 'datatable', + columns: [ + { + id: 'name', + name: 'name', + meta: { type: 'string' }, + }, + { + id: 'time', + name: 'time', + meta: { type: 'date' }, + }, + { + id: 'price', + name: 'price', + meta: { type: 'number' }, + }, + { + id: 'quantity', + name: 'quantity', + meta: { type: 'number' }, + }, + { + id: 'in_stock', + name: 'in_stock', + meta: { type: 'boolean' }, + }, + ], + rows: [ + { + name: 'product1', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 605, + quantity: 100, + in_stock: true, + }, + { + name: 'product1', + time: 1517929200950, // 06 Feb 2018 15:00:00 GMT + price: 583, + quantity: 200, + in_stock: true, + }, + { + name: 'product1', + time: 1518015600950, // 07 Feb 2018 15:00:00 GMT + price: 420, + quantity: 300, + in_stock: true, + }, + { + name: 'product2', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 216, + quantity: 350, + in_stock: false, + }, + { + name: 'product2', + time: 1517929200950, // 06 Feb 2018 15:00:00 GMT + price: 200, + quantity: 256, + in_stock: false, + }, + { + name: 'product2', + time: 1518015600950, // 07 Feb 2018 15:00:00 GMT + price: 190, + quantity: 231, + in_stock: false, + }, + { + name: 'product3', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 67, + quantity: 240, + in_stock: true, + }, + { + name: 'product4', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 311, + quantity: 447, + in_stock: false, + }, + { + name: 'product5', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 288, + quantity: 384, + in_stock: true, + }, + ], +}; + +const stringTable: Datatable = { + type: 'datatable', + columns: [ + { + id: 'name', + name: 'name', + meta: { type: 'string' }, + }, + { + id: 'time', + name: 'time', + meta: { type: 'string' }, + }, + { + id: 'price', + name: 'price', + meta: { type: 'string' }, + }, + { + id: 'quantity', + name: 'quantity', + meta: { type: 'string' }, + }, + { + id: 'in_stock', + name: 'in_stock', + meta: { type: 'string' }, + }, + ], + rows: [ + { + name: 'product1', + time: '2018-02-05T15:00:00.950Z', + price: '605', + quantity: '100', + in_stock: 'true', + }, + { + name: 'product1', + time: '2018-02-06T15:00:00.950Z', + price: '583', + quantity: '200', + in_stock: 'true', + }, + { + name: 'product1', + time: '2018-02-07T15:00:00.950Z', + price: '420', + quantity: '300', + in_stock: 'true', + }, + { + name: 'product2', + time: '2018-02-05T15:00:00.950Z', + price: '216', + quantity: '350', + in_stock: 'false', + }, + { + name: 'product2', + time: '2018-02-06T15:00:00.950Z', + price: '200', + quantity: '256', + in_stock: 'false', + }, + { + name: 'product2', + time: '2018-02-07T15:00:00.950Z', + price: '190', + quantity: '231', + in_stock: 'false', + }, + { + name: 'product3', + time: '2018-02-05T15:00:00.950Z', + price: '67', + quantity: '240', + in_stock: 'true', + }, + { + name: 'product4', + time: '2018-02-05T15:00:00.950Z', + price: '311', + quantity: '447', + in_stock: 'false', + }, + { + name: 'product5', + time: '2018-02-05T15:00:00.950Z', + price: '288', + quantity: '384', + in_stock: 'true', + }, + ], +}; + +export { emptyTable, testTable, stringTable }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts index 048bc3468f149..a00718fce0797 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts @@ -34,7 +34,6 @@ import { joinRows } from './join_rows'; import { lt } from './lt'; import { lte } from './lte'; import { mapCenter } from './map_center'; -import { mapColumn } from './mapColumn'; import { math } from './math'; import { metric } from './metric'; import { neq } from './neq'; @@ -89,7 +88,6 @@ export const functions = [ lte, joinRows, mapCenter, - mapColumn, math, metric, neq, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js deleted file mode 100644 index d511c7774122d..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.test.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { functionWrapper } from '../../../test_helpers/function_wrapper'; -import { testTable, emptyTable } from './__fixtures__/test_tables'; -import { mapColumn } from './mapColumn'; - -const pricePlusTwo = (datatable) => Promise.resolve(datatable.rows[0].price + 2); - -describe('mapColumn', () => { - const fn = functionWrapper(mapColumn); - - it('returns a datatable with a new column with the values from mapping a function over each row in a datatable', () => { - return fn(testTable, { - id: 'pricePlusTwo', - name: 'pricePlusTwo', - expression: pricePlusTwo, - }).then((result) => { - const arbitraryRowIndex = 2; - - expect(result.type).toBe('datatable'); - expect(result.columns).toEqual([ - ...testTable.columns, - { id: 'pricePlusTwo', name: 'pricePlusTwo', meta: { type: 'number' } }, - ]); - expect(result.columns[result.columns.length - 1]).toHaveProperty('name', 'pricePlusTwo'); - expect(result.rows[arbitraryRowIndex]).toHaveProperty('pricePlusTwo'); - }); - }); - - it('overwrites existing column with the new column if an existing column name is provided', () => { - return fn(testTable, { name: 'name', expression: pricePlusTwo }).then((result) => { - const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); - const arbitraryRowIndex = 4; - - expect(result.type).toBe('datatable'); - expect(result.columns).toHaveLength(testTable.columns.length); - expect(result.columns[nameColumnIndex]).toHaveProperty('name', 'name'); - expect(result.columns[nameColumnIndex].meta).toHaveProperty('type', 'number'); - expect(result.rows[arbitraryRowIndex]).toHaveProperty('name', 202); - }); - }); - - it('adds a column to empty tables', () => { - return fn(emptyTable, { name: 'name', expression: pricePlusTwo }).then((result) => { - expect(result.type).toBe('datatable'); - expect(result.columns).toHaveLength(1); - expect(result.columns[0]).toHaveProperty('name', 'name'); - expect(result.columns[0].meta).toHaveProperty('type', 'null'); - }); - }); - - describe('expression', () => { - it('maps null values to the new column', () => { - return fn(testTable, { name: 'empty' }).then((result) => { - const emptyColumnIndex = result.columns.findIndex(({ name }) => name === 'empty'); - const arbitraryRowIndex = 8; - - expect(result.columns[emptyColumnIndex]).toHaveProperty('name', 'empty'); - expect(result.columns[emptyColumnIndex].meta).toHaveProperty('type', 'null'); - expect(result.rows[arbitraryRowIndex]).toHaveProperty('empty', null); - }); - }); - }); -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts deleted file mode 100644 index 63cc0d6cbc687..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Datatable, ExpressionFunctionDefinition, getType } from '../../../types'; -import { getFunctionHelp } from '../../../i18n'; - -interface Arguments { - name: string; - expression: (datatable: Datatable) => Promise; -} - -export function mapColumn(): ExpressionFunctionDefinition< - 'mapColumn', - Datatable, - Arguments, - Promise -> { - const { help, args: argHelp } = getFunctionHelp().mapColumn; - - return { - name: 'mapColumn', - aliases: ['mc'], // midnight commander. So many times I've launched midnight commander instead of moving a file. - type: 'datatable', - inputTypes: ['datatable'], - help, - args: { - name: { - types: ['string'], - aliases: ['_', 'column'], - help: argHelp.name, - required: true, - }, - expression: { - types: ['boolean', 'number', 'string', 'null'], - resolve: false, - aliases: ['exp', 'fn', 'function'], - help: argHelp.expression, - required: true, - }, - }, - fn: (input, args) => { - const expression = args.expression || (() => Promise.resolve(null)); - - const columns = [...input.columns]; - const rowPromises = input.rows.map((row) => { - return expression({ - type: 'datatable', - columns, - rows: [row], - }).then((val) => ({ - ...row, - [args.name]: val, - })); - }); - - return Promise.all(rowPromises).then((rows) => { - const existingColumnIndex = columns.findIndex(({ name }) => name === args.name); - const type = rows.length ? getType(rows[0][args.name]) : 'null'; - const newColumn = { id: args.name, name: args.name, meta: { type } }; - - if (existingColumnIndex === -1) { - columns.push(newColumn); - } else { - columns[existingColumnIndex] = newColumn; - } - - return { - type: 'datatable', - columns, - rows, - } as Datatable; - }); - }, - }; -} diff --git a/x-pack/plugins/canvas/i18n/functions/dict/map_column.ts b/x-pack/plugins/canvas/i18n/functions/dict/map_column.ts index f8d0311d08961..2dbd3ff5d7657 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/map_column.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/map_column.ts @@ -5,34 +5,42 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; -import { mapColumn } from '../../../canvas_plugin_src/functions/common/mapColumn'; -import { FunctionHelp } from '../function_help'; -import { FunctionFactory } from '../../../types'; -import { CANVAS, DATATABLE } from '../../constants'; +// import { i18n } from '@kbn/i18n'; +// // import { mapColumn } from '../../../canvas_plugin_src/functions/common/mapColumn'; +// import { FunctionHelp } from '../function_help'; +// import { FunctionFactory } from '../../../types'; +// import { CANVAS, DATATABLE } from '../../constants'; -export const help: FunctionHelp> = { - help: i18n.translate('xpack.canvas.functions.mapColumnHelpText', { - defaultMessage: - 'Adds a column calculated as the result of other columns. ' + - 'Changes are made only when you provide arguments.' + - 'See also {alterColumnFn} and {staticColumnFn}.', - values: { - alterColumnFn: '`alterColumn`', - staticColumnFn: '`staticColumn`', - }, - }), - args: { - name: i18n.translate('xpack.canvas.functions.mapColumn.args.nameHelpText', { - defaultMessage: 'The name of the resulting column.', - }), - expression: i18n.translate('xpack.canvas.functions.mapColumn.args.expressionHelpText', { - defaultMessage: - 'A {CANVAS} expression that is passed to each row as a single row {DATATABLE}.', - values: { - CANVAS, - DATATABLE, - }, - }), - }, -}; +// export const help: FunctionHelp> = { +// help: i18n.translate('xpack.canvas.functions.mapColumnHelpText', { +// defaultMessage: +// 'Adds a column calculated as the result of other columns. ' + +// 'Changes are made only when you provide arguments.' + +// 'See also {alterColumnFn} and {staticColumnFn}.', +// values: { +// alterColumnFn: '`alterColumn`', +// staticColumnFn: '`staticColumn`', +// }, +// }), +// args: { +// id: i18n.translate('xpack.canvas.functions.mapColumn.args.idHelpText', { +// defaultMessage: +// 'An optional id of the resulting column. When `null` the name/column argument is used as id.', +// }), +// name: i18n.translate('xpack.canvas.functions.mapColumn.args.nameHelpText', { +// defaultMessage: 'The name of the resulting column.', +// }), +// expression: i18n.translate('xpack.canvas.functions.mapColumn.args.expressionHelpText', { +// defaultMessage: +// 'A {CANVAS} expression that is passed to each row as a single row {DATATABLE}.', +// values: { +// CANVAS, +// DATATABLE, +// }, +// }), +// copyMetaFrom: i18n.translate('xpack.canvas.functions.mapColumn.args.copyMetaFromHelpText', { +// defaultMessage: +// "if set, the meta object from the specified column id is copied over to the specified target column. Throws an exception of the column doesn't exist", +// }), +// }, +// }; diff --git a/x-pack/plugins/canvas/i18n/functions/function_help.ts b/x-pack/plugins/canvas/i18n/functions/function_help.ts index 245732e53cc89..87e6883e599ce 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_help.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_help.ts @@ -46,7 +46,6 @@ import { help as location } from './dict/location'; import { help as lt } from './dict/lt'; import { help as lte } from './dict/lte'; import { help as mapCenter } from './dict/map_center'; -import { help as mapColumn } from './dict/map_column'; import { help as markdown } from './dict/markdown'; import { help as math } from './dict/math'; import { help as metric } from './dict/metric'; @@ -209,7 +208,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({ lt, lte, mapCenter, - mapColumn, markdown, math, metric, From 96c3aae9ce2b0c9f615d186477a0ed8e26b74aaf Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 15 Feb 2021 14:51:05 +0100 Subject: [PATCH 02/14] :globe_with_meridians: Fix i18n for mapColumns --- .../canvas/i18n/functions/dict/map_column.ts | 46 ------------------- .../translations/translations/ja-JP.json | 3 -- .../translations/translations/zh-CN.json | 3 -- 3 files changed, 52 deletions(-) delete mode 100644 x-pack/plugins/canvas/i18n/functions/dict/map_column.ts diff --git a/x-pack/plugins/canvas/i18n/functions/dict/map_column.ts b/x-pack/plugins/canvas/i18n/functions/dict/map_column.ts deleted file mode 100644 index 2dbd3ff5d7657..0000000000000 --- a/x-pack/plugins/canvas/i18n/functions/dict/map_column.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// import { i18n } from '@kbn/i18n'; -// // import { mapColumn } from '../../../canvas_plugin_src/functions/common/mapColumn'; -// import { FunctionHelp } from '../function_help'; -// import { FunctionFactory } from '../../../types'; -// import { CANVAS, DATATABLE } from '../../constants'; - -// export const help: FunctionHelp> = { -// help: i18n.translate('xpack.canvas.functions.mapColumnHelpText', { -// defaultMessage: -// 'Adds a column calculated as the result of other columns. ' + -// 'Changes are made only when you provide arguments.' + -// 'See also {alterColumnFn} and {staticColumnFn}.', -// values: { -// alterColumnFn: '`alterColumn`', -// staticColumnFn: '`staticColumn`', -// }, -// }), -// args: { -// id: i18n.translate('xpack.canvas.functions.mapColumn.args.idHelpText', { -// defaultMessage: -// 'An optional id of the resulting column. When `null` the name/column argument is used as id.', -// }), -// name: i18n.translate('xpack.canvas.functions.mapColumn.args.nameHelpText', { -// defaultMessage: 'The name of the resulting column.', -// }), -// expression: i18n.translate('xpack.canvas.functions.mapColumn.args.expressionHelpText', { -// defaultMessage: -// 'A {CANVAS} expression that is passed to each row as a single row {DATATABLE}.', -// values: { -// CANVAS, -// DATATABLE, -// }, -// }), -// copyMetaFrom: i18n.translate('xpack.canvas.functions.mapColumn.args.copyMetaFromHelpText', { -// defaultMessage: -// "if set, the meta object from the specified column id is copied over to the specified target column. Throws an exception of the column doesn't exist", -// }), -// }, -// }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4d94539a514d1..32b2c4b3cdc04 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5980,9 +5980,6 @@ "xpack.canvas.functions.ltHelpText": "{CONTEXT} が引数よりも小さいかを戻します。", "xpack.canvas.functions.mapCenter.args.latHelpText": "マップの中央の緯度", "xpack.canvas.functions.mapCenterHelpText": "マップの中央座標とズームレベルのオブジェクトに戻ります。", - "xpack.canvas.functions.mapColumn.args.expressionHelpText": "単一行 {DATATABLE} として各行に渡される {CANVAS} 表現です。", - "xpack.canvas.functions.mapColumn.args.nameHelpText": "結果の列の名前です。", - "xpack.canvas.functions.mapColumnHelpText": "他の列の結果として計算された列を追加します。引数が指定された場合のみ変更が加えられます。{alterColumnFn}と{staticColumnFn}もご参照ください。", "xpack.canvas.functions.markdown.args.contentHelpText": "{MARKDOWN} を含むテキストの文字列です。連結させるには、{stringFn} 関数を複数回渡します。", "xpack.canvas.functions.markdown.args.fontHelpText": "コンテンツの {CSS} フォントプロパティです。たとえば、{fontFamily} または {fontWeight} です。", "xpack.canvas.functions.markdown.args.openLinkHelpText": "新しいタブでリンクを開くためのtrue/false値。デフォルト値は「false」です。「true」に設定するとすべてのリンクが新しいタブで開くようになります。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a35d4c67dde00..d9a1476d3fd02 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5991,9 +5991,6 @@ "xpack.canvas.functions.ltHelpText": "返回 {CONTEXT} 是否小于参数。", "xpack.canvas.functions.mapCenter.args.latHelpText": "地图中心的纬度", "xpack.canvas.functions.mapCenterHelpText": "返回包含地图中心坐标和缩放级别的对象。", - "xpack.canvas.functions.mapColumn.args.expressionHelpText": "作为单行 {DATATABLE} 传递到每一行的 {CANVAS} 表达式。", - "xpack.canvas.functions.mapColumn.args.nameHelpText": "结果列的名称。", - "xpack.canvas.functions.mapColumnHelpText": "添加计算为其他列的结果的列。只有提供参数时,才会执行更改。另请参见 {alterColumnFn} 和 {staticColumnFn}。", "xpack.canvas.functions.markdown.args.contentHelpText": "包含 {MARKDOWN} 的文本字符串。要进行串联,请多次传递 {stringFn} 函数。", "xpack.canvas.functions.markdown.args.fontHelpText": "内容的 {CSS} 字体属性。例如 {fontFamily} 或 {fontWeight}。", "xpack.canvas.functions.markdown.args.openLinkHelpText": "用于在新标签页中打开链接的 true 或 false 值。默认值为 `false`。设置为 `true` 时将在新标签页中打开所有链接。", From 241aa03ddf72d66595a97ae501e0a9d60d15a589 Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 15 Feb 2021 15:27:36 +0100 Subject: [PATCH 03/14] :truck: Move over the math function to shared expressions plugin --- .../expression_functions/specs/index.ts | 5 +- .../common/expression_functions/specs/math.ts | 136 ++++++++++++++++++ .../specs/tests/math.test.ts | 15 +- .../expression_functions/specs/tests/utils.ts | 6 +- .../functions/common/index.ts | 2 - .../functions/common/math.ts | 70 --------- .../canvas/i18n/functions/dict/math.ts | 69 --------- .../canvas/i18n/functions/function_errors.ts | 2 - .../canvas/i18n/functions/function_help.ts | 2 - 9 files changed, 150 insertions(+), 157 deletions(-) create mode 100644 src/plugins/expressions/common/expression_functions/specs/math.ts rename x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.test.js => src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts (89%) delete mode 100644 x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts delete mode 100644 x-pack/plugins/canvas/i18n/functions/dict/math.ts diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts index cf0ada2f7f5e9..9408b3a433712 100644 --- a/src/plugins/expressions/common/expression_functions/specs/index.ts +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -16,6 +16,7 @@ import { cumulativeSum } from './cumulative_sum'; import { derivative } from './derivative'; import { movingAverage } from './moving_average'; import { mapColumn } from './map_column'; +import { math } from './math'; export const functionSpecs: AnyExpressionFunctionDefinition[] = [ clog, @@ -27,6 +28,7 @@ export const functionSpecs: AnyExpressionFunctionDefinition[] = [ derivative, movingAverage, mapColumn, + math, ]; export * from './clog'; @@ -37,4 +39,5 @@ export * from './theme'; export * from './cumulative_sum'; export * from './derivative'; export * from './moving_average'; -export * from './map_column'; +export { mapColumn, MapColumnArguments } from './map_column'; +export { math, MathArguments, MathInput } from './math'; diff --git a/src/plugins/expressions/common/expression_functions/specs/math.ts b/src/plugins/expressions/common/expression_functions/specs/math.ts new file mode 100644 index 0000000000000..f963902c4b32f --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/math.ts @@ -0,0 +1,136 @@ +/* + * 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 { map, zipObject } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { evaluate } from '@kbn/tinymath'; +import { ExpressionFunctionDefinition } from '../types'; +import { Datatable, isDatatable } from '../../expression_types'; + +export interface MathArguments { + expression: string; +} + +export type MathInput = number | Datatable; + +const TINYMATH = '`TinyMath`'; +const TINYMATH_URL = + 'https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html'; + +const isString = (val: any): boolean => typeof val === 'string'; + +function pivotObjectArray< + RowType extends { [key: string]: any }, + ReturnColumns extends string | number | symbol = keyof RowType +>(rows: RowType[], columns?: string[]): Record { + const columnNames = columns || Object.keys(rows[0]); + if (!columnNames.every(isString)) { + throw new Error('Columns should be an array of strings'); + } + + const columnValues = map(columnNames, (name) => map(rows, name)); + return zipObject(columnNames, columnValues); +} + +export const errors = { + emptyExpression: () => + new Error( + i18n.translate('expressions.functions.math.emptyExpressionErrorMessage', { + defaultMessage: 'Empty expression', + }) + ), + tooManyResults: () => + new Error( + i18n.translate('expressions.functions.math.tooManyResultsErrorMessage', { + defaultMessage: + 'Expressions must return a single number. Try wrapping your expression in {mean} or {sum}', + values: { + mean: 'mean()', + sum: 'sum()', + }, + }) + ), + executionFailed: () => + new Error( + i18n.translate('expressions.functions.math.executionFailedErrorMessage', { + defaultMessage: 'Failed to execute math expression. Check your column names', + }) + ), + emptyDatatable: () => + new Error( + i18n.translate('expressions.functions.math.emptyDatatableErrorMessage', { + defaultMessage: 'Empty datatable', + }) + ), +}; + +export const math: ExpressionFunctionDefinition<'math', MathInput, MathArguments, number> = { + name: 'math', + type: 'number', + inputTypes: ['number', 'datatable'], + help: i18n.translate('expressions.functions.mathHelpText', { + defaultMessage: + 'Interprets a {TINYMATH} math expression using a {TYPE_NUMBER} or {DATATABLE} as {CONTEXT}. ' + + 'The {DATATABLE} columns are available by their column name. ' + + 'If the {CONTEXT} is a number it is available as {value}.', + values: { + TINYMATH, + CONTEXT: '_context_', + DATATABLE: '`datatable`', + value: '`value`', + TYPE_NUMBER: '`number`', + }, + }), + args: { + expression: { + aliases: ['_'], + types: ['string'], + help: i18n.translate('expressions.functions.math.args.expressionHelpText', { + defaultMessage: 'An evaluated {TINYMATH} expression. See {TINYMATH_URL}.', + values: { + TINYMATH, + TINYMATH_URL, + }, + }), + }, + }, + fn: (input, args) => { + const { expression } = args; + + if (!expression || expression.trim() === '') { + throw errors.emptyExpression(); + } + + const mathContext = isDatatable(input) + ? pivotObjectArray( + input.rows, + input.columns.map((col) => col.name) + ) + : { value: input }; + + try { + const result = evaluate(expression, mathContext); + if (Array.isArray(result)) { + if (result.length === 1) { + return result[0]; + } + throw errors.tooManyResults(); + } + if (isNaN(result)) { + throw errors.executionFailed(); + } + return result; + } catch (e) { + if (isDatatable(input) && input.rows.length === 0) { + throw errors.emptyDatatable(); + } else { + throw e; + } + } + }, +}; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.test.js b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts similarity index 89% rename from x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.test.js rename to src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts index f5b8123ab8568..5b0108720da03 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.test.js +++ b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts @@ -1,19 +1,16 @@ /* * 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; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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/function_wrapper'; -import { getFunctionErrors } from '../../../i18n'; -import { emptyTable, testTable } from './__fixtures__/test_tables'; -import { math } from './math'; - -const errors = getFunctionErrors().math; +import { errors, math } from '../math'; +import { emptyTable, functionWrapper, testTable } from './utils'; describe('math', () => { - const fn = functionWrapper(math); + const fn = functionWrapper(math); it('evaluates math expressions without reference to context', () => { expect(fn(null, { expression: '10.5345' })).toBe(10.5345); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts index 44ed87dc02360..7369570cf2c4b 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts @@ -15,10 +15,12 @@ import { Datatable } from '../../../expression_types'; * Takes a function spec and passes in default args, * overriding with any provided args. */ -export const functionWrapper = (spec: AnyExpressionFunctionDefinition) => { +export const functionWrapper = ( + spec: AnyExpressionFunctionDefinition +) => { const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default); return ( - context: object | null, + context: ContextType, args: Record = {}, handlers: ExecutionContext = {} as ExecutionContext ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts index a00718fce0797..5c4d1d55cff04 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts @@ -34,7 +34,6 @@ import { joinRows } from './join_rows'; import { lt } from './lt'; import { lte } from './lte'; import { mapCenter } from './map_center'; -import { math } from './math'; import { metric } from './metric'; import { neq } from './neq'; import { ply } from './ply'; @@ -88,7 +87,6 @@ export const functions = [ lte, joinRows, mapCenter, - math, metric, neq, ply, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts deleted file mode 100644 index af70fa729b7da..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { evaluate } from '@kbn/tinymath'; -import { pivotObjectArray } from '../../../common/lib/pivot_object_array'; -import { Datatable, isDatatable, ExpressionFunctionDefinition } from '../../../types'; -import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; - -interface Arguments { - expression: string; -} - -type Input = number | Datatable; - -export function math(): ExpressionFunctionDefinition<'math', Input, Arguments, number> { - const { help, args: argHelp } = getFunctionHelp().math; - const errors = getFunctionErrors().math; - - return { - name: 'math', - type: 'number', - inputTypes: ['number', 'datatable'], - help, - args: { - expression: { - aliases: ['_'], - types: ['string'], - help: argHelp.expression, - }, - }, - fn: (input, args) => { - const { expression } = args; - - if (!expression || expression.trim() === '') { - throw errors.emptyExpression(); - } - - const mathContext = isDatatable(input) - ? pivotObjectArray( - input.rows, - input.columns.map((col) => col.name) - ) - : { value: input }; - - try { - const result = evaluate(expression, mathContext); - if (Array.isArray(result)) { - if (result.length === 1) { - return result[0]; - } - throw errors.tooManyResults(); - } - if (isNaN(result)) { - throw errors.executionFailed(); - } - return result; - } catch (e) { - if (isDatatable(input) && input.rows.length === 0) { - throw errors.emptyDatatable(); - } else { - throw e; - } - } - }, - }; -} diff --git a/x-pack/plugins/canvas/i18n/functions/dict/math.ts b/x-pack/plugins/canvas/i18n/functions/dict/math.ts deleted file mode 100644 index 110136872e1ff..0000000000000 --- a/x-pack/plugins/canvas/i18n/functions/dict/math.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { math } from '../../../canvas_plugin_src/functions/common/math'; -import { FunctionHelp } from '../function_help'; -import { FunctionFactory } from '../../../types'; -import { DATATABLE, CONTEXT, TINYMATH, TINYMATH_URL, TYPE_NUMBER } from '../../constants'; - -export const help: FunctionHelp> = { - help: i18n.translate('xpack.canvas.functions.mathHelpText', { - defaultMessage: - 'Interprets a {TINYMATH} math expression using a {TYPE_NUMBER} or {DATATABLE} as {CONTEXT}. ' + - 'The {DATATABLE} columns are available by their column name. ' + - 'If the {CONTEXT} is a number it is available as {value}.', - values: { - TINYMATH, - CONTEXT, - DATATABLE, - value: '`value`', - TYPE_NUMBER, - }, - }), - args: { - expression: i18n.translate('xpack.canvas.functions.math.args.expressionHelpText', { - defaultMessage: 'An evaluated {TINYMATH} expression. See {TINYMATH_URL}.', - values: { - TINYMATH, - TINYMATH_URL, - }, - }), - }, -}; - -export const errors = { - emptyExpression: () => - new Error( - i18n.translate('xpack.canvas.functions.math.emptyExpressionErrorMessage', { - defaultMessage: 'Empty expression', - }) - ), - tooManyResults: () => - new Error( - i18n.translate('xpack.canvas.functions.math.tooManyResultsErrorMessage', { - defaultMessage: - 'Expressions must return a single number. Try wrapping your expression in {mean} or {sum}', - values: { - mean: 'mean()', - sum: 'sum()', - }, - }) - ), - executionFailed: () => - new Error( - i18n.translate('xpack.canvas.functions.math.executionFailedErrorMessage', { - defaultMessage: 'Failed to execute math expression. Check your column names', - }) - ), - emptyDatatable: () => - new Error( - i18n.translate('xpack.canvas.functions.math.emptyDatatableErrorMessage', { - defaultMessage: 'Empty datatable', - }) - ), -}; diff --git a/x-pack/plugins/canvas/i18n/functions/function_errors.ts b/x-pack/plugins/canvas/i18n/functions/function_errors.ts index ac86eb4c4d0e9..4a85018c1b4ac 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_errors.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_errors.ts @@ -16,7 +16,6 @@ import { errors as demodata } from './dict/demodata'; import { errors as getCell } from './dict/get_cell'; import { errors as image } from './dict/image'; import { errors as joinRows } from './dict/join_rows'; -import { errors as math } from './dict/math'; import { errors as ply } from './dict/ply'; import { errors as pointseries } from './dict/pointseries'; import { errors as progress } from './dict/progress'; @@ -36,7 +35,6 @@ export const getFunctionErrors = () => ({ getCell, image, joinRows, - math, ply, pointseries, progress, diff --git a/x-pack/plugins/canvas/i18n/functions/function_help.ts b/x-pack/plugins/canvas/i18n/functions/function_help.ts index 87e6883e599ce..512ebc4ff8c93 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_help.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_help.ts @@ -47,7 +47,6 @@ import { help as lt } from './dict/lt'; import { help as lte } from './dict/lte'; import { help as mapCenter } from './dict/map_center'; import { help as markdown } from './dict/markdown'; -import { help as math } from './dict/math'; import { help as metric } from './dict/metric'; import { help as neq } from './dict/neq'; import { help as pie } from './dict/pie'; @@ -209,7 +208,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({ lte, mapCenter, markdown, - math, metric, neq, pie, From b89934fad93e2c9ce74a2e1f1905bf9d684ef4b5 Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 15 Feb 2021 17:26:43 +0100 Subject: [PATCH 04/14] :sparkles: Add new onError argument --- .../common/expression_functions/specs/math.ts | 37 ++++++++++++++++--- .../specs/tests/math.test.ts | 20 ++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/math.ts b/src/plugins/expressions/common/expression_functions/specs/math.ts index f963902c4b32f..8f876f92eb072 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math.ts @@ -12,9 +12,10 @@ import { evaluate } from '@kbn/tinymath'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable, isDatatable } from '../../expression_types'; -export interface MathArguments { +export type MathArguments = { expression: string; -} + onError?: 'null' | 'zero' | 'false' | 'throw'; +}; export type MathInput = number | Datatable; @@ -69,9 +70,20 @@ export const errors = { ), }; -export const math: ExpressionFunctionDefinition<'math', MathInput, MathArguments, number> = { +const fallbackValue = { + null: null, + zero: 0, + false: false, +} as const; + +export const math: ExpressionFunctionDefinition< + 'math', + MathInput, + MathArguments, + boolean | number | null +> = { name: 'math', - type: 'number', + type: undefined, inputTypes: ['number', 'datatable'], help: i18n.translate('expressions.functions.mathHelpText', { defaultMessage: @@ -98,9 +110,20 @@ export const math: ExpressionFunctionDefinition<'math', MathInput, MathArguments }, }), }, + onError: { + types: ['string'], + help: i18n.translate('expressions.functions.math.args.onErrorHelpText', { + defaultMessage: + "In case the {TINYMATH} evaluation fails or returns NaN, the return value is specified by onError. If it's set to throw, it will throw an exception, terminating expression execution (default).", + values: { + TINYMATH, + }, + }), + }, }, fn: (input, args) => { - const { expression } = args; + const { expression, onError } = args; + const onErrorValue = onError ?? 'throw'; if (!expression || expression.trim() === '') { throw errors.emptyExpression(); @@ -122,6 +145,10 @@ export const math: ExpressionFunctionDefinition<'math', MathInput, MathArguments throw errors.tooManyResults(); } if (isNaN(result)) { + // make TS happy + if (onErrorValue !== 'throw' && onErrorValue in fallbackValue) { + return fallbackValue[onErrorValue]; + } throw errors.executionFailed(); } return result; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts index 5b0108720da03..ae5a2f1e6a008 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts @@ -45,6 +45,14 @@ describe('math', () => { expect(fn(testTable, { expression: 'count(name)' })).toBe(9); }); }); + + describe('onError', () => { + it('should return the desired fallback value, for invalid expressions', () => { + expect(fn(testTable, { expression: 'mean(name)', onError: 'zero' })).toBe(0); + expect(fn(testTable, { expression: 'mean(name)', onError: 'null' })).toBe(null); + expect(fn(testTable, { expression: 'mean(name)', onError: 'false' })).toBe(false); + }); + }); }); describe('invalid expressions', () => { @@ -85,5 +93,17 @@ describe('math', () => { new RegExp(errors.emptyDatatable().message) ); }); + + it('should not throw when requesting fallback values for invalid expression', () => { + expect(() => fn(testTable, { expression: 'mean(name)', onError: 'zero' })).not.toThrow(); + expect(() => fn(testTable, { expression: 'mean(name)', onError: 'false' })).not.toThrow(); + expect(() => fn(testTable, { expression: 'mean(name)', onError: 'null' })).not.toThrow(); + }); + + it('should throw when declared in the onError argument', () => { + expect(() => fn(testTable, { expression: 'mean(name)', onError: 'throw' })).toThrow( + new RegExp(errors.executionFailed().message) + ); + }); }); }); From 4c8fd8c8d2c04c192915eeeb1cedb4504c63ab5b Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 15 Feb 2021 17:28:17 +0100 Subject: [PATCH 05/14] :globe_with_meridians: Fix i18n for math --- x-pack/plugins/translations/translations/ja-JP.json | 6 ------ x-pack/plugins/translations/translations/zh-CN.json | 6 ------ 2 files changed, 12 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 32b2c4b3cdc04..dc79b22479d9d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5984,12 +5984,6 @@ "xpack.canvas.functions.markdown.args.fontHelpText": "コンテンツの {CSS} フォントプロパティです。たとえば、{fontFamily} または {fontWeight} です。", "xpack.canvas.functions.markdown.args.openLinkHelpText": "新しいタブでリンクを開くためのtrue/false値。デフォルト値は「false」です。「true」に設定するとすべてのリンクが新しいタブで開くようになります。", "xpack.canvas.functions.markdownHelpText": "{MARKDOWN} テキストをレンダリングするエレメントを追加します。ヒント:単一の数字、メトリック、テキストの段落には {markdownFn} 関数を使います。", - "xpack.canvas.functions.math.args.expressionHelpText": "評価された {TINYMATH} 表現です。{TINYMATH_URL} をご覧ください。", - "xpack.canvas.functions.math.emptyDatatableErrorMessage": "空のデータベース", - "xpack.canvas.functions.math.emptyExpressionErrorMessage": "空の表現", - "xpack.canvas.functions.math.executionFailedErrorMessage": "数式の実行に失敗しました。列名を確認してください", - "xpack.canvas.functions.math.tooManyResultsErrorMessage": "式は 1 つの数字を返す必要があります。表現を {mean} または {sum} で囲んでみてください", - "xpack.canvas.functions.mathHelpText": "{TYPE_NUMBER}または{DATATABLE}を{CONTEXT}として使用して、{TINYMATH}数式を解釈します。{DATATABLE}列は列名で表示されます。{CONTEXT}が数字の場合は、{value}と表示されます。", "xpack.canvas.functions.metric.args.labelFontHelpText": "ラベルの {CSS} フォントプロパティです。例: {FONT_FAMILY} または {FONT_WEIGHT}。", "xpack.canvas.functions.metric.args.labelHelpText": "メトリックを説明するテキストです。", "xpack.canvas.functions.metric.args.metricFontHelpText": "メトリックの {CSS} フォントプロパティです。例: {FONT_FAMILY} または {FONT_WEIGHT}。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d9a1476d3fd02..278fb49c4ceb5 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5995,12 +5995,6 @@ "xpack.canvas.functions.markdown.args.fontHelpText": "内容的 {CSS} 字体属性。例如 {fontFamily} 或 {fontWeight}。", "xpack.canvas.functions.markdown.args.openLinkHelpText": "用于在新标签页中打开链接的 true 或 false 值。默认值为 `false`。设置为 `true` 时将在新标签页中打开所有链接。", "xpack.canvas.functions.markdownHelpText": "添加呈现 {MARKDOWN} 文本的元素。提示:将 {markdownFn} 函数用于单个数字、指标和文本段落。", - "xpack.canvas.functions.math.args.expressionHelpText": "已计算的 {TINYMATH} 表达式。请参阅 {TINYMATH_URL}。", - "xpack.canvas.functions.math.emptyDatatableErrorMessage": "空数据表", - "xpack.canvas.functions.math.emptyExpressionErrorMessage": "空表达式", - "xpack.canvas.functions.math.executionFailedErrorMessage": "无法执行数学表达式。检查您的列名称", - "xpack.canvas.functions.math.tooManyResultsErrorMessage": "表达式必须返回单个数字。尝试将您的表达式包装在 {mean} 或 {sum} 中", - "xpack.canvas.functions.mathHelpText": "使用 {TYPE_NUMBER} 或 {DATATABLE} 作为 {CONTEXT} 来解释 {TINYMATH} 数学表达式。{DATATABLE} 列按列名使用。如果 {CONTEXT} 是数字,则作为 {value} 使用。", "xpack.canvas.functions.metric.args.labelFontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", "xpack.canvas.functions.metric.args.labelHelpText": "描述指标的文本。", "xpack.canvas.functions.metric.args.metricFontHelpText": "指标的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", From 75fecc8195e705244817d6867ec74d6ad710e89b Mon Sep 17 00:00:00 2001 From: dej611 Date: Tue, 16 Feb 2021 10:39:30 +0100 Subject: [PATCH 06/14] :memo: Update expressions doc with new args --- .../canvas/canvas-function-reference.asciidoc | 19 ++++++++++++++++++- .../expression_functions/specs/map_column.ts | 2 +- .../common/expression_functions/specs/math.ts | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index 6a6c840074f02..eaaf68eb06195 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -1697,6 +1697,16 @@ Aliases: `column`, `name` Aliases: `exp`, `fn`, `function` |`boolean`, `number`, `string`, `null` |A Canvas expression that is passed to each row as a single row `datatable`. + +|`id` + +|`string`, `null` +|An optional id of the resulting column. When not specified or `null` the name argument is used as id. + +|`copyMetaFrom` + +|`string`, `null` +|If set, the meta object from the specified column id is copied over to the specified target column. Throws an exception if the column doesn't exist |=== *Returns:* `datatable` @@ -1755,9 +1765,16 @@ Interprets a `TinyMath` math expression using a `number` or `datatable` as _cont Alias: `expression` |`string` |An evaluated `TinyMath` expression. See https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html. + +|`onError` + +|`string` +|In case the `TinyMath` evaluation fails or returns NaN, the return value is specified by onError. For example, `"null"`, `"zero"`, `"false"`, `"throw"`. When `"throw"`, it will throw an exception, terminating expression execution. + +Default: `"throw"` |=== -*Returns:* `number` +*Returns:* `number` | `boolean` | `null` [float] diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index a77bed99f9492..6c11b2d2df08d 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -73,7 +73,7 @@ export const mapColumn: ExpressionFunctionDefinition< types: ['string', 'null'], help: i18n.translate('expressions.functions.mapColumn.args.copyMetaFromHelpText', { defaultMessage: - "if set, the meta object from the specified column id is copied over to the specified target column. Throws an exception of the column doesn't exist", + "If set, the meta object from the specified column id is copied over to the specified target column. Throws an exception if the column doesn't exist", }), required: false, default: null, diff --git a/src/plugins/expressions/common/expression_functions/specs/math.ts b/src/plugins/expressions/common/expression_functions/specs/math.ts index 8f876f92eb072..764ef0cc80ff6 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math.ts @@ -114,7 +114,7 @@ export const math: ExpressionFunctionDefinition< types: ['string'], help: i18n.translate('expressions.functions.math.args.onErrorHelpText', { defaultMessage: - "In case the {TINYMATH} evaluation fails or returns NaN, the return value is specified by onError. If it's set to throw, it will throw an exception, terminating expression execution (default).", + "In case the {TINYMATH} evaluation fails or returns NaN, the return value is specified by onError. When `'throw'`, it will throw an exception, terminating expression execution (default).", values: { TINYMATH, }, From 7eb7ebd5b71629ac80741661380ebc233d9f5086 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 18 Feb 2021 15:23:21 +0100 Subject: [PATCH 07/14] :sparkles: Add the divide by zero handling --- .../common/expression_functions/specs/math.ts | 3 +++ .../expression_functions/specs/tests/math.test.ts | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/plugins/expressions/common/expression_functions/specs/math.ts b/src/plugins/expressions/common/expression_functions/specs/math.ts index 764ef0cc80ff6..c14b4ed5b7e4b 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math.ts @@ -153,6 +153,9 @@ export const math: ExpressionFunctionDefinition< } return result; } catch (e) { + if (onErrorValue !== 'throw' && onErrorValue in fallbackValue) { + return fallbackValue[onErrorValue]; + } if (isDatatable(input) && input.rows.length === 0) { throw errors.emptyDatatable(); } else { diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts index ae5a2f1e6a008..7541852cdbdaf 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts @@ -52,6 +52,11 @@ describe('math', () => { expect(fn(testTable, { expression: 'mean(name)', onError: 'null' })).toBe(null); expect(fn(testTable, { expression: 'mean(name)', onError: 'false' })).toBe(false); }); + it('should return the desired fallback value, for division by zero', () => { + expect(fn(testTable, { expression: '1/0', onError: 'zero' })).toBe(0); + expect(fn(testTable, { expression: '1/0', onError: 'null' })).toBe(null); + expect(fn(testTable, { expression: '1/0', onError: 'false' })).toBe(false); + }); }); }); @@ -105,5 +110,11 @@ describe('math', () => { new RegExp(errors.executionFailed().message) ); }); + + it('should throw when dividing by zero', () => { + expect(() => fn(testTable, { expression: '1/0', onError: 'throw' })).toThrow( + new RegExp('Cannot divide by 0') + ); + }); }); }); From 804507a8c8c2d8f050213b73dd4034f1faf31fae Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 18 Feb 2021 17:52:57 +0100 Subject: [PATCH 08/14] :sparkles: Add options for autocomplete --- .../expressions/common/expression_functions/specs/math.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/expressions/common/expression_functions/specs/math.ts b/src/plugins/expressions/common/expression_functions/specs/math.ts index c14b4ed5b7e4b..a70c032769b57 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math.ts @@ -112,6 +112,7 @@ export const math: ExpressionFunctionDefinition< }, onError: { types: ['string'], + options: ['throw', 'false', 'zero', 'null'], help: i18n.translate('expressions.functions.math.args.onErrorHelpText', { defaultMessage: "In case the {TINYMATH} evaluation fails or returns NaN, the return value is specified by onError. When `'throw'`, it will throw an exception, terminating expression execution (default).", From 0ac1f27f9a26f0aa5a7a9170b93256b4c422121e Mon Sep 17 00:00:00 2001 From: dej611 Date: Mon, 22 Feb 2021 12:55:01 +0100 Subject: [PATCH 09/14] :bug: Fix bug wth type picker --- .../expressions/common/expression_functions/specs/map_column.ts | 2 +- .../common/expression_functions/specs/tests/map_column.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index 6c11b2d2df08d..95e953fd6c528 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -97,7 +97,7 @@ export const mapColumn: ExpressionFunctionDefinition< return Promise.all(rowPromises).then((rows) => { const existingColumnIndex = columns.findIndex(({ name }) => name === args.name); - const type = rows.length ? getType(rows[0][args.name]) : 'null'; + const type = rows.length ? getType(rows[0][columnId]) : 'null'; const newColumn = { id: columnId, name: args.name, diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts index 588d1f34590f3..f83a22022066d 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -77,7 +77,7 @@ describe('mapColumn', () => { expect(result.columns[nameColumnIndex]).toEqual({ id: 'myid', name: 'name', - meta: { type: 'string' }, + meta: { type: 'number' }, }); } ); From a5c8bdfe02591232e8451274376feae116e51697 Mon Sep 17 00:00:00 2001 From: dej611 Date: Tue, 23 Feb 2021 18:20:50 +0100 Subject: [PATCH 10/14] :ok_hand: Integrated feedback --- .../expression_functions/specs/map_column.ts | 6 ++-- .../specs/tests/map_column.test.ts | 35 +++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index 95e953fd6c528..ad54ce4a754e1 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -61,7 +61,7 @@ export const mapColumn: ExpressionFunctionDefinition< aliases: ['exp', 'fn', 'function'], help: i18n.translate('expressions.functions.mapColumn.args.expressionHelpText', { defaultMessage: - 'A {CANVAS} expression that is passed to each row as a single row {DATATABLE}.', + 'An expression that is executed on every row, provided with a single-row {DATATABLE} context and returning the cell value.', values: { CANVAS: 'canvas', DATATABLE: '`datatable`', @@ -73,7 +73,7 @@ export const mapColumn: ExpressionFunctionDefinition< types: ['string', 'null'], help: i18n.translate('expressions.functions.mapColumn.args.copyMetaFromHelpText', { defaultMessage: - "If set, the meta object from the specified column id is copied over to the specified target column. Throws an exception if the column doesn't exist", + "If set, the meta object from the specified column id is copied over to the specified target column. If the column doesn't exist it silently fails.", }), required: false, default: null, @@ -104,7 +104,7 @@ export const mapColumn: ExpressionFunctionDefinition< meta: { type }, }; if (args.copyMetaFrom) { - const metaSourceFrom = columns.find(({ name }) => name === args.copyMetaFrom); + const metaSourceFrom = columns.find(({ id }) => id === args.copyMetaFrom); newColumn.meta = { ...newColumn.meta, ...(metaSourceFrom?.meta || {}) }; } diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts index f83a22022066d..284374dbefbf3 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -84,17 +84,30 @@ describe('mapColumn', () => { }); it('should copy over the meta information from the specified column', () => { - return runFn(testTable, { name: 'name', copyMetaFrom: 'time', expression: pricePlusTwo }).then( - (result) => { - const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); - expect(result.type).toBe('datatable'); - expect(result.columns[nameColumnIndex]).toEqual({ - id: 'name', - name: 'name', - meta: { type: 'date' }, - }); - } - ); + return runFn( + { + ...testTable, + columns: [ + ...testTable.columns, + // add a new entry + { + id: 'myId', + name: 'myName', + meta: { type: 'date' }, + }, + ], + rows: testTable.rows.map((row) => ({ ...row, myId: Date.now() })), + }, + { name: 'name', copyMetaFrom: 'myId', expression: pricePlusTwo } + ).then((result) => { + const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); + expect(result.type).toBe('datatable'); + expect(result.columns[nameColumnIndex]).toEqual({ + id: 'name', + name: 'name', + meta: { type: 'date' }, + }); + }); }); it('should be resilient if the references column for meta information does not exists', () => { From 6bfa8230c618774ff1f9d0b4e88aba759342d9c0 Mon Sep 17 00:00:00 2001 From: dej611 Date: Tue, 23 Feb 2021 19:22:23 +0100 Subject: [PATCH 11/14] :bug: Fix unused value --- .../expressions/common/expression_functions/specs/map_column.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index ad54ce4a754e1..e2605e5ddf38d 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -63,7 +63,6 @@ export const mapColumn: ExpressionFunctionDefinition< defaultMessage: 'An expression that is executed on every row, provided with a single-row {DATATABLE} context and returning the cell value.', values: { - CANVAS: 'canvas', DATATABLE: '`datatable`', }, }), From a6cb2d0c8d58485eccb129ba46c2a6be54e7340c Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 24 Feb 2021 10:06:29 +0100 Subject: [PATCH 12/14] :white_check_mark: Add more tests --- .../specs/tests/map_column.test.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts index 284374dbefbf3..08cbb74603836 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -93,7 +93,7 @@ describe('mapColumn', () => { { id: 'myId', name: 'myName', - meta: { type: 'date' }, + meta: { type: 'date', params: { type: 'date', digits: 2 } }, }, ], rows: testTable.rows.map((row) => ({ ...row, myId: Date.now() })), @@ -105,7 +105,7 @@ describe('mapColumn', () => { expect(result.columns[nameColumnIndex]).toEqual({ id: 'name', name: 'name', - meta: { type: 'date' }, + meta: { type: 'date', params: { type: 'date', digits: 2 } }, }); }); }); @@ -122,6 +122,19 @@ describe('mapColumn', () => { ); }); + it('should correctly infer the type fromt he first row if the references column for meta information does not exists', () => { + return runFn( + { ...emptyTable, rows: [...emptyTable.rows, { value: 5 }] }, + { name: 'value', copyMetaFrom: 'time', expression: pricePlusTwo } + ).then((result) => { + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'value'); + expect(result.columns[0]).toHaveProperty('id', 'value'); + expect(result.columns[0].meta).toHaveProperty('type', 'number'); + }); + }); + describe('expression', () => { it('maps null values to the new column', () => { return runFn(testTable, { name: 'empty' }).then((result) => { From 7cdf96f1434188e5f564348206eae8b7718958f3 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 24 Feb 2021 11:52:40 +0100 Subject: [PATCH 13/14] :bug: Fix type issue --- .../common/expression_functions/specs/tests/map_column.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts index 08cbb74603836..3432e2313495f 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -93,7 +93,7 @@ describe('mapColumn', () => { { id: 'myId', name: 'myName', - meta: { type: 'date', params: { type: 'date', digits: 2 } }, + meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } }, }, ], rows: testTable.rows.map((row) => ({ ...row, myId: Date.now() })), From 0f601c013da33772430f9bb41eba1fbd2590ccd5 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 24 Feb 2021 15:13:19 +0100 Subject: [PATCH 14/14] :white_check_mark: Fix unit test --- .../common/expression_functions/specs/tests/map_column.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts index 3432e2313495f..6b0dce4ff9a2a 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -105,7 +105,7 @@ describe('mapColumn', () => { expect(result.columns[nameColumnIndex]).toEqual({ id: 'name', name: 'name', - meta: { type: 'date', params: { type: 'date', digits: 2 } }, + meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } }, }); }); });