diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index efa4d263e2ba0..afe59c4af8882 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "license": "Apache-2.0", "scripts": { - "interpreter:peg": "pegjs common/lib/grammar.peg", + "interpreter:peg": "pegjs src/common/lib/grammar.peg", "build": "node scripts/build", "kbn:bootstrap": "node scripts/build --dev", "kbn:watch": "node scripts/build --dev --watch" diff --git a/packages/kbn-interpreter/src/common/lib/registry.js b/packages/kbn-interpreter/src/common/lib/registry.js index 9882f3abde723..3b22704b9e9c8 100644 --- a/packages/kbn-interpreter/src/common/lib/registry.js +++ b/packages/kbn-interpreter/src/common/lib/registry.js @@ -31,9 +31,7 @@ export class Registry { } register(fn) { - if (typeof fn !== 'function') throw new Error(`Register requires an function`); - - const obj = fn(); + const obj = typeof fn === 'function' ? fn() : fn; if (typeof obj !== 'object' || !obj[this._prop]) { throw new Error(`Registered functions must return an object with a ${this._prop} property`); diff --git a/packages/kbn-utility-types/README.md b/packages/kbn-utility-types/README.md index 9707ff5a1ed9c..aafae4d3a5134 100644 --- a/packages/kbn-utility-types/README.md +++ b/packages/kbn-utility-types/README.md @@ -24,3 +24,4 @@ type B = UnwrapPromise; // string - `ShallowPromise` — Same as `Promise` type, but it flat maps the wrapped type. - `UnwrapObservable` — Returns wrapped type of an observable. - `UnwrapPromise` — Returns wrapped type of a promise. +- `UnwrapPromiseOrReturn` — Returns wrapped type of a promise or the type itself, if it isn't a promise. diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 83a41a52aca38..ec81f7347b481 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -35,6 +35,11 @@ export type ShallowPromise = T extends Promise ? Promise : Promis */ export type UnwrapPromise> = PromiseType; +/** + * Returns wrapped type of a promise, or returns type as is, if it is not a promise. + */ +export type UnwrapPromiseOrReturn = T extends Promise ? U : T; + /** * Minimal interface for an object resembling an `Observable`. */ diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 43927337ce574..6a0748a33e724 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -24,7 +24,7 @@ import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { KibanaContext, KibanaDatatable, - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatableColumn, } from 'src/plugins/expressions/public'; import { @@ -66,7 +66,8 @@ export interface RequestHandlerParams { const name = 'esaggs'; -type Context = KibanaContext | null; +type Input = KibanaContext | null; +type Output = Promise; interface Arguments { index: string; @@ -76,8 +77,6 @@ interface Arguments { aggConfigs: string; } -type Return = Promise; - const handleCourierRequest = async ({ searchSource, aggs, @@ -221,12 +220,10 @@ const handleCourierRequest = async ({ return (searchSource as any).tabifiedResponse; }; -export const esaggs = (): ExpressionFunction => ({ +export const esaggs = (): ExpressionFunctionDefinition => ({ name, type: 'kibana_datatable', - context: { - types: ['kibana_context', 'null'], - }, + inputTypes: ['kibana_context', 'null'], help: i18n.translate('data.functions.esaggs.help', { defaultMessage: 'Run AggConfig aggregation', }), @@ -256,7 +253,7 @@ export const esaggs = (): ExpressionFunction { - const fn = functionWrapper(createInputControlVisFn); + const fn = functionWrapper(createInputControlVisFn()); const visConfig = { controls: [ { diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts index 0482c0d2cbff3..e779c6d344ab5 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts @@ -20,15 +20,11 @@ import { i18n } from '@kbn/i18n'; import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatable, Render, } from '../../../../plugins/expressions/public'; -const name = 'input_control_vis'; - -type Context = KibanaDatatable; - interface Arguments { visConfig: string; } @@ -40,19 +36,15 @@ interface RenderValue { visConfig: VisParams; } -type Return = Promise>; - -export const createInputControlVisFn = (): ExpressionFunction< - typeof name, - Context, +export const createInputControlVisFn = (): ExpressionFunctionDefinition< + 'input_control_vis', + KibanaDatatable, Arguments, - Return + Render > => ({ name: 'input_control_vis', type: 'render', - context: { - types: [], - }, + inputTypes: [], help: i18n.translate('inputControl.function.help', { defaultMessage: 'Input control visualization', }), @@ -63,7 +55,7 @@ export const createInputControlVisFn = (): ExpressionFunction< help: '', }, }, - async fn(context, args) { + fn(input, args) { const params = JSON.parse(args.visConfig); return { type: 'render', diff --git a/src/legacy/core_plugins/interpreter/README.md b/src/legacy/core_plugins/interpreter/README.md index 1a5cefbe0ed81..6d90ce2d5e2eb 100644 --- a/src/legacy/core_plugins/interpreter/README.md +++ b/src/legacy/core_plugins/interpreter/README.md @@ -1,22 +1,2 @@ Interpreter legacy plugin has been migrated to the New Platform. Use `expressions` New Platform plugin instead. - -In the New Platform: - -```ts -class MyPlugin { - setup(core, { expressions }) { - expressions.registerFunction(myFunction); - } - start(core, { expressions }) { - } -} -``` - -In the Legacy Platform: - -```ts -import { npSetup, npStart } from 'ui/new_platform'; - -npSetup.plugins.expressions.registerFunction(myFunction); -``` diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.ts b/src/legacy/core_plugins/interpreter/public/interpreter.ts index 71bce40ba8235..319a2779010c3 100644 --- a/src/legacy/core_plugins/interpreter/public/interpreter.ts +++ b/src/legacy/core_plugins/interpreter/public/interpreter.ts @@ -22,10 +22,7 @@ import 'uiExports/interpreter'; import { register, registryFactory } from '@kbn/interpreter/common'; import { npSetup } from 'ui/new_platform'; import { registries } from './registries'; -import { - ExpressionInterpretWithHandlers, - ExpressionExecutor, -} from '../../../../plugins/expressions/public'; +import { Executor, ExpressionExecutor } from '../../../../plugins/expressions/public'; // Expose kbnInterpreter.register(specs) and kbnInterpreter.registries() globally so that plugins // can register without a transpile step. @@ -46,7 +43,7 @@ export const getInterpreter = async () => { }; // TODO: This function will be left behind in the legacy platform. -export const interpretAst: ExpressionInterpretWithHandlers = async (ast, context, handlers) => { +export const interpretAst: Executor['run'] = async (ast, context, handlers) => { const { interpreter } = await getInterpreter(); return await interpreter.interpretAst(ast, context, handlers); }; diff --git a/src/legacy/core_plugins/region_map/public/region_map_fn.test.js b/src/legacy/core_plugins/region_map/public/region_map_fn.test.js index 4a788793736e8..07b4e33b85e27 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_fn.test.js +++ b/src/legacy/core_plugins/region_map/public/region_map_fn.test.js @@ -18,13 +18,13 @@ */ // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; +import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; import { createRegionMapFn } from './region_map_fn'; jest.mock('ui/new_platform'); describe('interpreter/functions#regionmap', () => { - const fn = functionWrapper(createRegionMapFn); + const fn = functionWrapper(createRegionMapFn()); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], diff --git a/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js b/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js index 4e11bb722bfe6..53553e6074d83 100644 --- a/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js +++ b/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js @@ -18,7 +18,7 @@ */ // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; +import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; import { createTileMapFn } from './tile_map_fn'; jest.mock('ui/new_platform'); @@ -41,7 +41,7 @@ jest.mock('ui/vis/map/convert_to_geojson', () => ({ import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; describe('interpreter/functions#tilemap', () => { - const fn = functionWrapper(createTileMapFn); + const fn = functionWrapper(createTileMapFn()); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.test.ts b/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.test.ts index 44e891ea1ac93..5f41840bac99b 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.test.ts @@ -18,11 +18,11 @@ */ // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; +import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; import { createMarkdownVisFn } from './markdown_fn'; describe('interpreter/functions#markdown', () => { - const fn = functionWrapper(createMarkdownVisFn); + const fn = functionWrapper(createMarkdownVisFn()); const args = { font: { spec: { fontSize: 12 } }, openLinksInNewTab: true, diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.ts b/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.ts index 91a0b2ce35604..bbf2b7844c73f 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_fn.ts @@ -18,31 +18,23 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, Render } from '../../../../plugins/expressions/public'; +import { ExpressionFunctionDefinition, Render } from '../../../../plugins/expressions/public'; import { Arguments, MarkdownVisParams } from './types'; -const name = 'markdownVis'; - -type Context = undefined; - interface RenderValue { visType: 'markdown'; visConfig: MarkdownVisParams; } -type Return = Promise>; - -export const createMarkdownVisFn = (): ExpressionFunction< - typeof name, - Context, +export const createMarkdownVisFn = (): ExpressionFunctionDefinition< + 'markdownVis', + unknown, Arguments, - Return + Render > => ({ - name, + name: 'markdownVis', type: 'render', - context: { - types: [], - }, + inputTypes: [], help: i18n.translate('visTypeMarkdown.function.help', { defaultMessage: 'Markdown visualization', }), @@ -70,7 +62,7 @@ export const createMarkdownVisFn = (): ExpressionFunction< }), }, }, - async fn(context, args) { + fn(input, args) { return { type: 'render', as: 'visualization', diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx index 64abee729f4e7..a93bb618da31f 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -19,13 +19,11 @@ import { last, findIndex, isNaN } from 'lodash'; import React, { Component } from 'react'; - import { isColorDark } from '@elastic/eui'; - import { getFormat } from '../legacy_imports'; import { MetricVisValue } from './metric_vis_value'; +import { Input } from '../metric_vis_fn'; import { FieldFormatsContentType, IFieldFormat } from '../../../../../plugins/data/public'; -import { Context } from '../metric_vis_fn'; import { KibanaDatatable } from '../../../../../plugins/expressions/public'; import { getHeatmapColors } from '../../../../../plugins/charts/public'; import { VisParams, MetricVisMetric } from '../types'; @@ -33,7 +31,7 @@ import { SchemaConfig, Vis } from '../../../visualizations/public'; export interface MetricVisComponentProps { visParams: VisParams; - visData: Context; + visData: Input; vis: Vis; renderComplete: () => void; } diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts index 389b0f53916d0..4094cd4eff060 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts @@ -19,12 +19,12 @@ import { createMetricVisFn } from './metric_vis_fn'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; +import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; jest.mock('ui/new_platform'); describe('interpreter/functions#metric', () => { - const fn = functionWrapper(createMetricVisFn); + const fn = functionWrapper(createMetricVisFn()); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index 644de88021c1f..03b412c6fff15 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatable, Range, Render, @@ -30,9 +30,7 @@ import { ColorModes } from '../../vis_type_vislib/public'; import { visType, DimensionsVisParam, VisParams } from './types'; import { ColorSchemas, vislibColorMaps } from '../../../../plugins/charts/public'; -export type Context = KibanaDatatable; - -const name = 'metricVis'; +export type Input = KibanaDatatable; interface Arguments { percentageMode: boolean; @@ -51,24 +49,20 @@ interface Arguments { interface RenderValue { visType: typeof visType; - visData: Context; + visData: Input; visConfig: Pick; params: any; } -type Return = Render; - -export const createMetricVisFn = (): ExpressionFunction< - typeof name, - Context, +export const createMetricVisFn = (): ExpressionFunctionDefinition< + 'metricVis', + Input, Arguments, - Return + Render > => ({ - name, + name: 'metricVis', type: 'render', - context: { - types: ['kibana_datatable'], - }, + inputTypes: ['kibana_datatable'], help: i18n.translate('visTypeMetric.function.help', { defaultMessage: 'Metric visualization', }), @@ -165,7 +159,7 @@ export const createMetricVisFn = (): ExpressionFunction< }), }, }, - fn(context: Context, args: Arguments) { + fn(input, args) { const dimensions: DimensionsVisParam = { metrics: args.metric, }; @@ -184,7 +178,7 @@ export const createMetricVisFn = (): ExpressionFunction< type: 'render', as: 'visualization', value: { - visData: context, + visData: input, visType, visConfig: { metric: { diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts index c8a4cade0efcb..36392c10f93f3 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.test.ts @@ -21,7 +21,7 @@ import { createTableVisFn } from './table_vis_fn'; import { tableVisResponseHandler } from './table_vis_response_handler'; // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; +import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; jest.mock('./table_vis_response_handler', () => ({ tableVisResponseHandler: jest.fn().mockReturnValue({ @@ -30,7 +30,7 @@ jest.mock('./table_vis_response_handler', () => ({ })); describe('interpreter/functions#table', () => { - const fn = functionWrapper(createTableVisFn); + const fn = functionWrapper(createTableVisFn()); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.ts index 67dd3b7c90335..a97e596e89754 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_fn.ts @@ -19,16 +19,13 @@ import { i18n } from '@kbn/i18n'; import { tableVisResponseHandler, TableContext } from './table_vis_response_handler'; - import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatable, Render, } from '../../../../plugins/expressions/public'; -const name = 'kibana_table'; - -export type Context = KibanaDatatable; +export type Input = KibanaDatatable; interface Arguments { visConfig: string | null; @@ -45,19 +42,15 @@ interface RenderValue { }; } -type Return = Render; - -export const createTableVisFn = (): ExpressionFunction< - typeof name, - Context, +export const createTableVisFn = (): ExpressionFunctionDefinition< + 'kibana_table', + Input, Arguments, - Return + Render > => ({ - name, + name: 'kibana_table', type: 'render', - context: { - types: ['kibana_datatable'], - }, + inputTypes: ['kibana_datatable'], help: i18n.translate('visTypeTable.function.help', { defaultMessage: 'Table visualization', }), @@ -68,9 +61,9 @@ export const createTableVisFn = (): ExpressionFunction< help: '', }, }, - fn(context, args) { + fn(input, args) { const visConfig = args.visConfig && JSON.parse(args.visConfig); - const convertedData = tableVisResponseHandler(context, visConfig.dimensions); + const convertedData = tableVisResponseHandler(input, visConfig.dimensions); return { type: 'render', diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_response_handler.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_response_handler.ts index c835d5361fc14..426480fa5b52d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_response_handler.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_response_handler.ts @@ -20,7 +20,7 @@ import { Required } from '@kbn/utility-types'; import { getFormat } from './legacy_imports'; -import { Context } from './table_vis_fn'; +import { Input } from './table_vis_fn'; export interface TableContext { tables: Array; @@ -29,7 +29,7 @@ export interface TableContext { export interface TableGroup { $parent: TableContext; - table: Context; + table: Input; tables: Table[]; title: string; name: string; @@ -40,11 +40,11 @@ export interface TableGroup { export interface Table { $parent?: TableGroup; - columns: Context['columns']; - rows: Context['rows']; + columns: Input['columns']; + rows: Input['rows']; } -export function tableVisResponseHandler(table: Context, dimensions: any): TableContext { +export function tableVisResponseHandler(table: Input, dimensions: any): TableContext { const converted: TableContext = { tables: [], }; @@ -63,8 +63,7 @@ export function tableVisResponseHandler(table: Context, dimensions: any): TableC const splitValue: any = row[splitColumn.id]; if (!splitMap.hasOwnProperty(splitValue as any)) { - // @ts-ignore - splitMap[splitValue] = splitIndex++; + (splitMap as any)[splitValue] = splitIndex++; const tableGroup: Required = { $parent: converted, title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`, @@ -85,10 +84,8 @@ export function tableVisResponseHandler(table: Context, dimensions: any): TableC converted.tables.push(tableGroup); } - // @ts-ignore - const tableIndex = splitMap[splitValue]; - // @ts-ignore - converted.tables[tableIndex].tables[0].rows.push(row); + const tableIndex = (splitMap as any)[splitValue]; + (converted.tables[tableIndex] as any).tables[0].rows.push(row); }); } else { converted.tables.push({ diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts index 16982a76412e9..65c54766133d1 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts @@ -20,10 +20,10 @@ import { createTagCloudFn } from './tag_cloud_fn'; // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; +import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; describe('interpreter/functions#tagcloud', () => { - const fn = functionWrapper(createTagCloudFn); + const fn = functionWrapper(createTagCloudFn()); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.ts index 90f952fde3447..31c7fd118cefd 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_fn.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatable, Render, } from '../../../../plugins/expressions/public'; @@ -28,8 +28,6 @@ import { TagCloudVisParams } from './types'; const name = 'tagcloud'; -type Context = KibanaDatatable; - interface Arguments extends TagCloudVisParams { metric: any; // these aren't typed yet bucket: any; // these aren't typed yet @@ -37,24 +35,20 @@ interface Arguments extends TagCloudVisParams { interface RenderValue { visType: typeof name; - visData: Context; + visData: KibanaDatatable; visConfig: Arguments; params: any; } -type Return = Render; - -export const createTagCloudFn = (): ExpressionFunction< +export const createTagCloudFn = (): ExpressionFunctionDefinition< typeof name, - Context, + KibanaDatatable, Arguments, - Return + Render > => ({ name, type: 'render', - context: { - types: ['kibana_datatable'], - }, + inputTypes: ['kibana_datatable'], help: i18n.translate('visTypeTagCloud.function.help', { defaultMessage: 'Tagcloud visualization', }), @@ -104,7 +98,7 @@ export const createTagCloudFn = (): ExpressionFunction< }), }, }, - fn(context, args) { + fn(input, args) { const visConfig = { scale: args.scale, orientation: args.orientation, @@ -122,7 +116,7 @@ export const createTagCloudFn = (): ExpressionFunction< type: 'render', as: 'visualization', value: { - visData: context, + visData: input, visType: name, visConfig, params: { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index 8a517b6cecbc7..c02f43818af9c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -19,36 +19,36 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; +import { + ExpressionFunctionDefinition, + KibanaContext, + Render, +} from 'src/plugins/expressions/public'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; import { TIMELION_VIS_NAME } from './timelion_vis_type'; import { TimelionVisDependencies } from './plugin'; -const name = 'timelion_vis'; - +type Input = KibanaContext | null; +type Output = Promise>; interface Arguments { expression: string; interval: string; } interface RenderValue { - visData: Context; + visData: Input; visType: 'timelion'; visParams: VisParams; } -type Context = KibanaContext | null; export type VisParams = Arguments; -type Return = Promise>; export const getTimelionVisualizationConfig = ( dependencies: TimelionVisDependencies -): ExpressionFunction => ({ - name, +): ExpressionFunctionDefinition<'timelion_vis', Input, Arguments, Output> => ({ + name: 'timelion_vis', type: 'render', - context: { - types: ['kibana_context', 'null'], - }, + inputTypes: ['kibana_context', 'null'], help: i18n.translate('timelion.function.help', { defaultMessage: 'Timelion visualization', }), @@ -65,15 +65,15 @@ export const getTimelionVisualizationConfig = ( help: '', }, }, - async fn(context, args) { + async fn(input, args) { const timelionRequestHandler = getTimelionRequestHandler(dependencies); const visParams = { expression: args.expression, interval: args.interval }; const response = await timelionRequestHandler({ - timeRange: get(context, 'timeRange'), - query: get(context, 'query'), - filters: get(context, 'filters'), + timeRange: get(input, 'timeRange'), + query: get(input, 'query'), + filters: get(input, 'filters'), visParams, forceFetch: true, }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts index 5786399fc7830..576723bad1e43 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts @@ -19,14 +19,18 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext, Render } from '../../../../plugins/expressions/public'; +import { + ExpressionFunctionDefinition, + KibanaContext, + Render, +} from '../../../../plugins/expressions/public'; // @ts-ignore import { metricsRequestHandler } from './request_handler'; import { PersistedState } from './legacy_imports'; -const name = 'tsvb'; -type Context = KibanaContext | null; +type Input = KibanaContext | null; +type Output = Promise>; interface Arguments { params: string; @@ -38,19 +42,20 @@ type VisParams = Required; interface RenderValue { visType: 'metrics'; - visData: Context; + visData: Input; visConfig: VisParams; uiState: any; } -type Return = Promise>; - -export const createMetricsFn = (): ExpressionFunction => ({ - name, +export const createMetricsFn = (): ExpressionFunctionDefinition< + 'tsvb', + Input, + Arguments, + Output +> => ({ + name: 'tsvb', type: 'render', - context: { - types: ['kibana_context', 'null'], - }, + inputTypes: ['kibana_context', 'null'], help: i18n.translate('visTypeTimeseries.function.help', { defaultMessage: 'TSVB visualization', }), @@ -71,16 +76,16 @@ export const createMetricsFn = (): ExpressionFunction>; interface Arguments { spec: string; @@ -34,21 +37,17 @@ interface Arguments { export type VisParams = Required; interface RenderValue { - visData: Context; - visType: typeof name; + visData: Input; + visType: 'vega'; visConfig: VisParams; } -type Return = Promise>; - export const createVegaFn = ( dependencies: VegaVisualizationDependencies -): ExpressionFunction => ({ - name, +): ExpressionFunctionDefinition<'vega', Input, Arguments, Output> => ({ + name: 'vega', type: 'render', - context: { - types: ['kibana_context', 'null'], - }, + inputTypes: ['kibana_context', 'null'], help: i18n.translate('visTypeVega.function.help', { defaultMessage: 'Vega visualization', }), @@ -59,13 +58,13 @@ export const createVegaFn = ( help: '', }, }, - async fn(context, args) { + async fn(input, args) { const vegaRequestHandler = createVegaRequestHandler(dependencies); const response = await vegaRequestHandler({ - timeRange: get(context, 'timeRange'), - query: get(context, 'query'), - filters: get(context, 'filters'), + timeRange: get(input, 'timeRange'), + query: get(input, 'query'), + filters: get(input, 'filters'), visParams: { spec: args.spec }, }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts index 54bd9e93292e2..15c80e4719487 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts @@ -18,7 +18,7 @@ */ // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; +import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; import { createPieVisFn } from './pie_fn'; // @ts-ignore import { vislibSlicesResponseHandler } from './vislib/response_handler'; @@ -42,7 +42,7 @@ jest.mock('./vislib/response_handler', () => ({ })); describe('interpreter/functions#pie', () => { - const fn = functionWrapper(createPieVisFn); + const fn = functionWrapper(createPieVisFn()); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], diff --git a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts index 5e80e28b7cc6b..452e0be0df3e4 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts @@ -18,19 +18,14 @@ */ import { i18n } from '@kbn/i18n'; - import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatable, Render, } from '../../../../plugins/expressions/public'; // @ts-ignore import { vislibSlicesResponseHandler } from './vislib/response_handler'; -const name = 'kibana_pie'; - -type Context = KibanaDatatable; - interface Arguments { visConfig: string; } @@ -41,14 +36,15 @@ interface RenderValue { visConfig: VisParams; } -type Return = Render; - -export const createPieVisFn = (): ExpressionFunction => ({ +export const createPieVisFn = (): ExpressionFunctionDefinition< + 'kibana_pie', + KibanaDatatable, + Arguments, + Render +> => ({ name: 'kibana_pie', type: 'render', - context: { - types: ['kibana_datatable'], - }, + inputTypes: ['kibana_datatable'], help: i18n.translate('visTypeVislib.functions.pie.help', { defaultMessage: 'Pie visualization', }), @@ -59,9 +55,9 @@ export const createPieVisFn = (): ExpressionFunction, void> { createGaugeVisTypeDefinition, createGoalVisTypeDefinition, ]; - const vislibFns = [createVisTypeVislibVisFn, createPieVisFn]; + const vislibFns = [createVisTypeVislibVisFn(), createPieVisFn()]; const visTypeXy = core.injectedMetadata.getInjectedVar('visTypeXy') as | VisTypeXyConfigSchema['visTypeXy'] diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index 5e948496ff08a..854b70b04e58a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -18,19 +18,14 @@ */ import { i18n } from '@kbn/i18n'; - import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatable, Render, } from '../../../../plugins/expressions/public'; // @ts-ignore import { vislibSeriesResponseHandler } from './vislib/response_handler'; -const name = 'vislib'; - -type Context = KibanaDatatable; - interface Arguments { type: string; visConfig: string; @@ -43,19 +38,15 @@ interface RenderValue { visConfig: VisParams; } -type Return = Render; - -export const createVisTypeVislibVisFn = (): ExpressionFunction< - typeof name, - Context, +export const createVisTypeVislibVisFn = (): ExpressionFunctionDefinition< + 'vislib', + KibanaDatatable, Arguments, - Return + Render > => ({ name: 'vislib', type: 'render', - context: { - types: ['kibana_datatable'], - }, + inputTypes: ['kibana_datatable'], help: i18n.translate('visTypeVislib.functions.vislib.help', { defaultMessage: 'Vislib visualization', }), diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts index d3badcc6bdc3f..049dec792ff4d 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -350,7 +350,6 @@ export class VisualizeEmbeddable extends Embeddable ({ help: 'User interface state', }, }, - async fn(context, args, handlers) { + async fn(input, args, { inspectorAdapters }) { const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {}; const schemas = args.schemas ? JSON.parse(args.schemas) : {}; const visType = getTypes().get(args.type || 'histogram') as any; @@ -96,25 +96,25 @@ export const visualization = (): ExpressionFunctionVisualization => ({ const uiState = new PersistedState(uiStateParams); if (typeof visType.requestHandler === 'function') { - context = await visType.requestHandler({ + input = await visType.requestHandler({ partialRows: args.partialRows, metricsAtAllLevels: args.metricsAtAllLevels, index: indexPattern, visParams: visConfigParams, - timeRange: get(context, 'timeRange', null), - query: get(context, 'query', null), - filters: get(context, 'filters', null), + timeRange: get(input, 'timeRange', null), + query: get(input, 'query', null), + filters: get(input, 'filters', null), uiState, - inspectorAdapters: handlers.inspectorAdapters, + inspectorAdapters, queryFilter: getFilterManager(), forceFetch: true, }); } if (typeof visType.responseHandler === 'function') { - if (context.columns) { + if (input.columns) { // assign schemas to aggConfigs - context.columns.forEach((column: any) => { + input.columns.forEach((column: any) => { if (column.aggConfig) { column.aggConfig.aggConfigs.schemas = visType.schemas.all; } @@ -122,21 +122,21 @@ export const visualization = (): ExpressionFunctionVisualization => ({ Object.keys(schemas).forEach(key => { schemas[key].forEach((i: any) => { - if (context.columns[i] && context.columns[i].aggConfig) { - context.columns[i].aggConfig.schema = key; + if (input.columns[i] && input.columns[i].aggConfig) { + input.columns[i].aggConfig.schema = key; } }); }); } - context = await visType.responseHandler(context, visConfigParams.dimensions); + input = await visType.responseHandler(input, visConfigParams.dimensions); } return { type: 'render', as: 'visualization', value: { - visData: context, + visData: input, visType: args.type || '', visConfig: visConfigParams, }, diff --git a/src/plugins/expressions/README.md b/src/plugins/expressions/README.md new file mode 100644 index 0000000000000..c1f032ace37c9 --- /dev/null +++ b/src/plugins/expressions/README.md @@ -0,0 +1,35 @@ +# `expressions` plugin + +This plugin provides methods which will parse & execute an *expression pipeline* +string for you, as well as a series of registries for advanced users who might +want to incorporate their own functions, types, and renderers into the service +for use in their own application. + +Expression pipeline is a chain of functions that *pipe* its output to the +input of the next function. Functions can be configured using arguments provided +by the user. The final output of the expression pipeline can be rendered using +one of the *renderers* registered in `expressions` plugin. + +Expressions power visualizations in Dashboard and Lens, as well as, every +*element* in Canvas is backed by an expression. + +Below is an example of one Canvas element that fetches data using `essql` function, +pipes it further to `math` and `metric` functions, and final `render` function +renders the result. + +``` +filters +| essql + query="SELECT COUNT(timestamp) as total_errors + FROM kibana_sample_data_logs + WHERE tags LIKE '%warning%' OR tags LIKE '%error%'" +| math "total_errors" +| metric "TOTAL ISSUES" + metricFont={font family="'Open Sans', Helvetica, Arial, sans-serif" size=48 align="left" color="#FFFFFF" weight="normal" underline=false italic=false} + labelFont={font family="'Open Sans', Helvetica, Arial, sans-serif" size=30 align="left" color="#FFFFFF" weight="lighter" underline=false italic=false} +| render +``` + +![image](https://user-images.githubusercontent.com/9773803/74162514-3250a880-4c21-11ea-9e68-86f66862a183.png) + +[See Canvas documentation about expressions](https://www.elastic.co/guide/en/kibana/current/canvas-function-arguments.html). diff --git a/src/plugins/expressions/common/ast/format.test.ts b/src/plugins/expressions/common/ast/format.test.ts new file mode 100644 index 0000000000000..d680ab2e30ce4 --- /dev/null +++ b/src/plugins/expressions/common/ast/format.test.ts @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { formatExpression } from './format'; + +describe('formatExpression()', () => { + test('converts expression AST to string', () => { + const str = formatExpression({ + type: 'expression', + chain: [ + { + type: 'function', + arguments: { + bar: ['baz'], + }, + function: 'foo', + }, + ], + }); + + expect(str).toMatchInlineSnapshot(`"foo bar=\\"baz\\""`); + }); +}); diff --git a/src/plugins/expressions/common/ast/format.ts b/src/plugins/expressions/common/ast/format.ts new file mode 100644 index 0000000000000..985f07008b33d --- /dev/null +++ b/src/plugins/expressions/common/ast/format.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionAstExpression, ExpressionAstArgument } from './types'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { toExpression } = require('@kbn/interpreter/common'); + +export function format( + ast: ExpressionAstExpression | ExpressionAstArgument, + type: 'expression' | 'argument' +): string { + return toExpression(ast, type); +} + +export function formatExpression(ast: ExpressionAstExpression): string { + return format(ast, 'expression'); +} diff --git a/src/plugins/expressions/common/ast/index.ts b/src/plugins/expressions/common/ast/index.ts new file mode 100644 index 0000000000000..398718e8092b3 --- /dev/null +++ b/src/plugins/expressions/common/ast/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './types'; +export * from './parse'; +export * from './parse_expression'; +export * from './format'; diff --git a/src/plugins/expressions/common/ast/parse.test.ts b/src/plugins/expressions/common/ast/parse.test.ts new file mode 100644 index 0000000000000..967091a52082f --- /dev/null +++ b/src/plugins/expressions/common/ast/parse.test.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { parse } from './parse'; + +describe('parse()', () => { + test('parses an expression', () => { + const ast = parse('foo bar="baz"', 'expression'); + + expect(ast).toMatchObject({ + type: 'expression', + chain: [ + { + type: 'function', + arguments: { + bar: ['baz'], + }, + function: 'foo', + }, + ], + }); + }); + + test('parses an argument', () => { + const arg = parse('foo', 'argument'); + expect(arg).toBe('foo'); + }); +}); diff --git a/src/plugins/expressions/common/ast/parse.ts b/src/plugins/expressions/common/ast/parse.ts new file mode 100644 index 0000000000000..0204694d1926d --- /dev/null +++ b/src/plugins/expressions/common/ast/parse.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionAstExpression, ExpressionAstArgument } from './types'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { parse: parseRaw } = require('@kbn/interpreter/common'); + +export function parse( + expression: string, + startRule: 'expression' | 'argument' +): ExpressionAstExpression | ExpressionAstArgument { + try { + return parseRaw(String(expression), { startRule }); + } catch (e) { + throw new Error(`Unable to parse expression: ${e.message}`); + } +} diff --git a/src/plugins/expressions/common/ast/parse_expression.test.ts b/src/plugins/expressions/common/ast/parse_expression.test.ts new file mode 100644 index 0000000000000..c387e58d9b787 --- /dev/null +++ b/src/plugins/expressions/common/ast/parse_expression.test.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { parseExpression } from './parse_expression'; + +describe('parseExpression()', () => { + test('parses an expression', () => { + const ast = parseExpression('foo bar="baz"'); + + expect(ast).toMatchObject({ + type: 'expression', + chain: [ + { + type: 'function', + arguments: { + bar: ['baz'], + }, + function: 'foo', + }, + ], + }); + }); + + test('parses an expression with sub-expression', () => { + const ast = parseExpression('foo bar="baz" quux={quix}'); + + expect(ast).toMatchObject({ + type: 'expression', + chain: [ + { + type: 'function', + arguments: { + bar: ['baz'], + quux: [ + { + type: 'expression', + chain: [ + { + type: 'function', + function: 'quix', + arguments: {}, + }, + ], + }, + ], + }, + function: 'foo', + }, + ], + }); + }); +}); diff --git a/src/plugins/expressions/common/ast/parse_expression.ts b/src/plugins/expressions/common/ast/parse_expression.ts new file mode 100644 index 0000000000000..ae4d80bd1fb5b --- /dev/null +++ b/src/plugins/expressions/common/ast/parse_expression.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionAstExpression } from './types'; +import { parse } from './parse'; + +/** + * Given expression pipeline string, returns parsed AST. + * + * @param expression Expression pipeline string. + */ +export function parseExpression(expression: string): ExpressionAstExpression { + return parse(expression, 'expression') as ExpressionAstExpression; +} diff --git a/src/plugins/expressions/common/ast/types.ts b/src/plugins/expressions/common/ast/types.ts new file mode 100644 index 0000000000000..82a7578dd4b89 --- /dev/null +++ b/src/plugins/expressions/common/ast/types.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type ExpressionAstNode = + | ExpressionAstExpression + | ExpressionAstFunction + | ExpressionAstArgument; + +export interface ExpressionAstExpression { + type: 'expression'; + chain: ExpressionAstFunction[]; +} + +export interface ExpressionAstFunction { + type: 'function'; + function: string; + arguments: Record; +} + +export type ExpressionAstArgument = string | boolean | number | ExpressionAstExpression; diff --git a/src/plugins/expressions/common/execution/container.ts b/src/plugins/expressions/common/execution/container.ts new file mode 100644 index 0000000000000..d6271869134d1 --- /dev/null +++ b/src/plugins/expressions/common/execution/container.ts @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + StateContainer, + createStateContainer, +} from '../../../kibana_utils/common/state_containers'; +import { ExecutorState, defaultState as executorDefaultState } from '../executor'; +import { ExpressionAstExpression } from '../ast'; +import { ExpressionValue } from '../expression_types'; + +export interface ExecutionState extends ExecutorState { + ast: ExpressionAstExpression; + + /** + * Tracks state of execution. + * + * - `not-started` - before .start() method was called. + * - `pending` - immediately after .start() method is called. + * - `result` - when expression execution completed. + * - `error` - when execution failed with error. + */ + state: 'not-started' | 'pending' | 'result' | 'error'; + + /** + * Result of the expression execution. + */ + result?: Output; + + /** + * Error happened during the execution. + */ + error?: Error; +} + +const executionDefaultState: ExecutionState = { + ...executorDefaultState, + state: 'not-started', + ast: { + type: 'expression', + chain: [], + }, +}; + +// eslint-disable-next-line +export interface ExecutionPureTransitions { + start: (state: ExecutionState) => () => ExecutionState; + setResult: (state: ExecutionState) => (result: Output) => ExecutionState; + setError: (state: ExecutionState) => (error: Error) => ExecutionState; +} + +export const executionPureTransitions: ExecutionPureTransitions = { + start: state => () => ({ + ...state, + state: 'pending', + }), + setResult: state => result => ({ + ...state, + state: 'result', + result, + }), + setError: state => error => ({ + ...state, + state: 'error', + error, + }), +}; + +export type ExecutionContainer = StateContainer< + ExecutionState, + ExecutionPureTransitions +>; + +const freeze = (state: T): T => state; + +export const createExecutionContainer = ( + state: ExecutionState = executionDefaultState +): ExecutionContainer => { + const container = createStateContainer< + ExecutionState, + ExecutionPureTransitions, + object + >( + state, + executionPureTransitions, + {}, + { + freeze, + } + ); + return container; +}; diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts new file mode 100644 index 0000000000000..3937bd309327d --- /dev/null +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -0,0 +1,372 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Execution } from './execution'; +import { parseExpression } from '../ast'; +import { createUnitTestExecutor } from '../test_helpers'; +import { ExpressionFunctionDefinition } from '../../public'; + +const createExecution = ( + expression: string = 'foo bar=123', + context: Record = {} +) => { + const executor = createUnitTestExecutor(); + const execution = new Execution({ + executor, + ast: parseExpression(expression), + context, + }); + return execution; +}; + +const run = async ( + expression: string = 'foo bar=123', + context?: Record, + input: any = null +) => { + const execution = createExecution(expression, context); + execution.start(input); + return await execution.result; +}; + +describe('Execution', () => { + test('can instantiate', () => { + const execution = createExecution('foo bar=123'); + expect(execution.params.ast.chain[0].arguments.bar).toEqual([123]); + }); + + test('initial input is null at creation', () => { + const execution = createExecution(); + expect(execution.input).toBe(null); + }); + + test('creates default ExecutionContext', () => { + const execution = createExecution(); + expect(execution.context).toMatchObject({ + getInitialInput: expect.any(Function), + variables: expect.any(Object), + types: expect.any(Object), + }); + }); + + test('executes a single clog function in expression pipeline', async () => { + const execution = createExecution('clog'); + /* eslint-disable no-console */ + const console$log = console.log; + const spy = (console.log = jest.fn()); + /* eslint-enable no-console */ + + execution.start(123); + const result = await execution.result; + + expect(result).toBe(123); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(123); + + /* eslint-disable no-console */ + console.log = console$log; + /* eslint-enable no-console */ + }); + + test('executes a chain of multiple "add" functions', async () => { + const execution = createExecution('add val=1 | add val=2 | add val=3'); + execution.start({ + type: 'num', + value: -1, + }); + + const result = await execution.result; + + expect(result).toEqual({ + type: 'num', + value: 5, + }); + }); + + test('executes a chain of "add" and "mult" functions', async () => { + const execution = createExecution('add val=5 | mult val=-1 | add val=-10 | mult val=2'); + execution.start({ + type: 'num', + value: 0, + }); + + const result = await execution.result; + + expect(result).toEqual({ + type: 'num', + value: -30, + }); + }); + + test('casts input to correct type', async () => { + const execution = createExecution('add val=1'); + + // Below 1 is cast to { type: 'num', value: 1 }. + execution.start(1); + const result = await execution.result; + + expect(result).toEqual({ + type: 'num', + value: 2, + }); + }); + + describe('execution context', () => { + test('context.variables is an object', async () => { + const { result } = (await run('introspectContext key="variables"')) as any; + expect(typeof result).toBe('object'); + }); + + test('context.types is an object', async () => { + const { result } = (await run('introspectContext key="types"')) as any; + expect(typeof result).toBe('object'); + }); + + test('context.abortSignal is an object', async () => { + const { result } = (await run('introspectContext key="abortSignal"')) as any; + expect(typeof result).toBe('object'); + }); + + test('context.inspectorAdapters is an object', async () => { + const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; + expect(typeof result).toBe('object'); + }); + + test('unknown context key is undefined', async () => { + const { result } = (await run('introspectContext key="foo"')) as any; + expect(typeof result).toBe('undefined'); + }); + + test('can set context variables', async () => { + const variables = { foo: 'bar' }; + const result = await run('var name="foo"', { variables }); + expect(result).toBe('bar'); + }); + }); + + describe('inspector adapters', () => { + test('by default, "data" and "requests" inspector adapters are available', async () => { + const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; + expect(result).toMatchObject({ + data: expect.any(Object), + requests: expect.any(Object), + }); + }); + + test('can set custom inspector adapters', async () => { + const inspectorAdapters = {}; + const { result } = (await run('introspectContext key="inspectorAdapters"', { + inspectorAdapters, + })) as any; + expect(result).toBe(inspectorAdapters); + }); + + test('can access custom inspector adapters on Execution object', async () => { + const inspectorAdapters = {}; + const execution = createExecution('introspectContext key="inspectorAdapters"', { + inspectorAdapters, + }); + expect(execution.inspectorAdapters).toBe(inspectorAdapters); + }); + }); + + describe('expression abortion', () => { + test('context has abortSignal object', async () => { + const { result } = (await run('introspectContext key="abortSignal"')) as any; + + expect(typeof result).toBe('object'); + expect((result as AbortSignal).aborted).toBe(false); + }); + }); + + describe('expression execution', () => { + test('supports default argument alias _', async () => { + const execution = createExecution('add val=1 | add 2'); + execution.start({ + type: 'num', + value: 0, + }); + + const result = await execution.result; + + expect(result).toEqual({ + type: 'num', + value: 3, + }); + }); + + test('can execute async functions', async () => { + const res = await run('sleep 10 | sleep 10'); + expect(res).toBe(null); + }); + + test('result is undefined until execution completes', async () => { + const execution = createExecution('sleep 10'); + expect(execution.state.get().result).toBe(undefined); + execution.start(null); + expect(execution.state.get().result).toBe(undefined); + await new Promise(r => setTimeout(r, 1)); + expect(execution.state.get().result).toBe(undefined); + await new Promise(r => setTimeout(r, 11)); + expect(execution.state.get().result).toBe(null); + }); + }); + + describe('when function throws', () => { + test('error is reported in output object', async () => { + const result = await run('error "foobar"'); + + expect(result).toMatchObject({ + type: 'error', + }); + }); + + test('error message is prefixed with function name', async () => { + const result = await run('error "foobar"'); + + expect(result).toMatchObject({ + error: { + message: `[error] > foobar`, + }, + }); + }); + + test('returns error of the first function that throws', async () => { + const result = await run('error "foo" | error "bar"'); + + expect(result).toMatchObject({ + error: { + message: `[error] > foo`, + }, + }); + }); + + test('when function throws, execution still succeeds', async () => { + const execution = await createExecution('error "foo"'); + execution.start(null); + + const result = await execution.result; + + expect(result).toMatchObject({ + type: 'error', + }); + expect(execution.state.get().state).toBe('result'); + expect(execution.state.get().result).toMatchObject({ + type: 'error', + }); + }); + + test('does not execute remaining functions in pipeline', async () => { + const spy: ExpressionFunctionDefinition<'spy', any, {}, any> = { + name: 'spy', + args: {}, + help: '', + fn: jest.fn(), + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(spy); + + await executor.run('error "..." | spy', null); + + expect(spy.fn).toHaveBeenCalledTimes(0); + }); + }); + + describe('state', () => { + test('execution state is "not-started" before .start() is called', async () => { + const execution = createExecution('var foo'); + expect(execution.state.get().state).toBe('not-started'); + }); + + test('execution state is "pending" after .start() was called', async () => { + const execution = createExecution('var foo'); + execution.start(null); + expect(execution.state.get().state).toBe('pending'); + }); + + test('execution state is "pending" while execution is in progress', async () => { + const execution = createExecution('sleep 20'); + execution.start(null); + await new Promise(r => setTimeout(r, 5)); + expect(execution.state.get().state).toBe('pending'); + }); + + test('execution state is "result" when execution successfully completes', async () => { + const execution = createExecution('sleep 1'); + execution.start(null); + await new Promise(r => setTimeout(r, 30)); + expect(execution.state.get().state).toBe('result'); + }); + + test('execution state is "result" when execution successfully completes - 2', async () => { + const execution = createExecution('var foo'); + execution.start(null); + await execution.result; + expect(execution.state.get().state).toBe('result'); + }); + }); + + describe('sub-expressions', () => { + test('executes sub-expressions', async () => { + const result = await run('add val={add 5 | access "value"}', {}, null); + + expect(result).toMatchObject({ + type: 'num', + value: 5, + }); + }); + }); + + describe('when arguments are missing', () => { + test('when required argument is missing and has not alias, returns error', async () => { + const requiredArg: ExpressionFunctionDefinition<'requiredArg', any, { arg: any }, any> = { + name: 'requiredArg', + args: { + arg: { + help: '', + required: true, + }, + }, + help: '', + fn: jest.fn(), + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(requiredArg); + const result = await executor.run('requiredArg', null, {}); + + expect(result).toMatchObject({ + type: 'error', + error: { + message: '[requiredArg] > requiredArg requires an argument', + }, + }); + }); + + test('when required argument is missing and has alias, returns error', async () => { + const result = await run('var_set', {}); + + expect(result).toMatchObject({ + type: 'error', + error: { + message: '[var_set] > var_set requires an "name" argument', + }, + }); + }); + }); +}); diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts new file mode 100644 index 0000000000000..7f4efafc13de8 --- /dev/null +++ b/src/plugins/expressions/common/execution/execution.ts @@ -0,0 +1,330 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { keys, last, mapValues, reduce, zipObject } from 'lodash'; +import { Executor } from '../executor'; +import { createExecutionContainer, ExecutionContainer } from './container'; +import { createError } from '../util'; +import { Defer } from '../../../kibana_utils/common'; +import { RequestAdapter, DataAdapter } from '../../../inspector/common'; +import { isExpressionValueError } from '../expression_types/specs/error'; +import { ExpressionAstExpression, ExpressionAstFunction, parse } from '../ast'; +import { ExecutionContext, DefaultInspectorAdapters } from './types'; +import { getType } from '../expression_types'; +import { ArgumentType, ExpressionFunction } from '../expression_functions'; +import { getByAlias } from '../util/get_by_alias'; + +export interface ExecutionParams< + ExtraContext extends Record = Record +> { + executor: Executor; + ast: ExpressionAstExpression; + context?: ExtraContext; +} + +const createDefaultInspectorAdapters = (): DefaultInspectorAdapters => ({ + requests: new RequestAdapter(), + data: new DataAdapter(), +}); + +export class Execution< + ExtraContext extends Record = Record, + Input = unknown, + Output = unknown, + InspectorAdapters = ExtraContext['inspectorAdapters'] extends object + ? ExtraContext['inspectorAdapters'] + : DefaultInspectorAdapters +> { + /** + * Dynamic state of the execution. + */ + public readonly state: ExecutionContainer; + + /** + * Initial input of the execution. + * + * N.B. It is initialized to `null` rather than `undefined` for legacy reasons, + * because in legacy interpreter it was set to `null` by default. + */ + public input: Input = null as any; + + /** + * Execution context - object that allows to do side-effects. Context is passed + * to every function. + */ + public readonly context: ExecutionContext & ExtraContext; + + /** + * AbortController to cancel this Execution. + */ + private readonly abortController = new AbortController(); + + /** + * Whether .start() method has been called. + */ + private hasStarted: boolean = false; + + /** + * Future that tracks result or error of this execution. + */ + private readonly firstResultFuture = new Defer(); + + public get result(): Promise { + return this.firstResultFuture.promise; + } + + public get inspectorAdapters(): InspectorAdapters { + return this.context.inspectorAdapters; + } + + constructor(public readonly params: ExecutionParams) { + const { executor, ast } = params; + this.state = createExecutionContainer({ + ...executor.state.get(), + state: 'not-started', + ast, + }); + + this.context = { + getInitialInput: () => this.input, + variables: {}, + types: executor.getTypes(), + abortSignal: this.abortController.signal, + ...(params.context || ({} as ExtraContext)), + inspectorAdapters: (params.context && params.context.inspectorAdapters + ? params.context.inspectorAdapters + : createDefaultInspectorAdapters()) as InspectorAdapters, + }; + } + + /** + * Stop execution of expression. + */ + cancel() { + this.abortController.abort(); + } + + /** + * Call this method to start execution. + * + * N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons, + * because in legacy interpreter it was set to `null` by default. + */ + public start(input: Input = null as any) { + if (this.hasStarted) throw new Error('Execution already started.'); + this.hasStarted = true; + + this.input = input; + this.state.transitions.start(); + + const { resolve, reject } = this.firstResultFuture; + this.invokeChain(this.state.get().ast.chain, input).then(resolve, reject); + + this.firstResultFuture.promise.then( + result => { + this.state.transitions.setResult(result); + }, + error => { + this.state.transitions.setError(error); + } + ); + } + + async invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise { + if (!chainArr.length) return input; + + for (const link of chainArr) { + // if execution was aborted return error + if (this.context.abortSignal && this.context.abortSignal.aborted) { + return createError({ + message: 'The expression was aborted.', + name: 'AbortError', + }); + } + + const { function: fnName, arguments: fnArgs } = link; + const fnDef = getByAlias(this.state.get().functions, fnName); + + if (!fnDef) { + return createError({ message: `Function ${fnName} could not be found.` }); + } + + try { + // Resolve arguments before passing to function + // resolveArgs returns an object because the arguments themselves might + // actually have a 'then' function which would be treated as a promise + const { resolvedArgs } = await this.resolveArgs(fnDef, input, fnArgs); + const output = await this.invokeFunction(fnDef, input, resolvedArgs); + if (getType(output) === 'error') return output; + input = output; + } catch (e) { + e.message = `[${fnName}] > ${e.message}`; + return createError(e); + } + } + + return input; + } + + async invokeFunction( + fn: ExpressionFunction, + input: unknown, + args: Record + ): Promise { + const normalizedInput = this.cast(input, fn.inputTypes); + const output = await fn.fn(normalizedInput, args, this.context); + + // Validate that the function returned the type it said it would. + // This isn't required, but it keeps function developers honest. + const returnType = getType(output); + const expectedType = fn.type; + if (expectedType && returnType !== expectedType) { + throw new Error( + `Function '${fn.name}' should return '${expectedType}',` + + ` actually returned '${returnType}'` + ); + } + + // Validate the function output against the type definition's validate function. + const type = this.context.types[fn.type]; + if (type && type.validate) { + try { + type.validate(output); + } catch (e) { + throw new Error(`Output of '${fn.name}' is not a valid type '${fn.type}': ${e}`); + } + } + + return output; + } + + public cast(value: any, toTypeNames?: string[]) { + // If you don't give us anything to cast to, you'll get your input back + if (!toTypeNames || toTypeNames.length === 0) return value; + + // No need to cast if node is already one of the valid types + const fromTypeName = getType(value); + if (toTypeNames.includes(fromTypeName)) return value; + + const { types } = this.state.get(); + const fromTypeDef = types[fromTypeName]; + + for (const toTypeName of toTypeNames) { + // First check if the current type can cast to this type + if (fromTypeDef && fromTypeDef.castsTo(toTypeName)) { + return fromTypeDef.to(value, toTypeName, types); + } + + // If that isn't possible, check if this type can cast from the current type + const toTypeDef = types[toTypeName]; + if (toTypeDef && toTypeDef.castsFrom(fromTypeName)) return toTypeDef.from(value, types); + } + + throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`); + } + + // Processes the multi-valued AST argument values into arguments that can be passed to the function + async resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Promise { + const argDefs = fnDef.args; + + // Use the non-alias name from the argument definition + const dealiasedArgAsts = reduce( + argAsts, + (acc, argAst, argName) => { + const argDef = getByAlias(argDefs, argName); + if (!argDef) { + throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`); + } + acc[argDef.name] = (acc[argDef.name] || []).concat(argAst); + return acc; + }, + {} as any + ); + + // Check for missing required arguments. + for (const argDef of Object.values(argDefs)) { + const { aliases, default: argDefault, name: argName, required } = argDef as ArgumentType< + any + > & { name: string }; + if ( + typeof argDefault !== 'undefined' || + !required || + typeof dealiasedArgAsts[argName] !== 'undefined' + ) + continue; + + if (!aliases || aliases.length === 0) { + throw new Error(`${fnDef.name} requires an argument`); + } + + // use an alias if _ is the missing arg + const errorArg = argName === '_' ? aliases[0] : argName; + throw new Error(`${fnDef.name} requires an "${errorArg}" argument`); + } + + // Fill in default values from argument definition + const argAstsWithDefaults = reduce( + argDefs, + (acc: any, argDef: any, argName: any) => { + if (typeof acc[argName] === 'undefined' && typeof argDef.default !== 'undefined') { + acc[argName] = [parse(argDef.default, 'argument')]; + } + + return acc; + }, + dealiasedArgAsts + ); + + // Create the functions to resolve the argument ASTs into values + // These are what are passed to the actual functions if you opt out of resolving + const resolveArgFns = mapValues(argAstsWithDefaults, (asts, argName) => { + return asts.map((item: ExpressionAstExpression) => { + return async (subInput = input) => { + const output = await this.params.executor.interpret(item, subInput); + if (isExpressionValueError(output)) throw output.error; + const casted = this.cast(output, argDefs[argName as any].types); + return casted; + }; + }); + }); + + const argNames = keys(resolveArgFns); + + // Actually resolve unless the argument definition says not to + const resolvedArgValues = await Promise.all( + argNames.map(argName => { + const interpretFns = resolveArgFns[argName]; + if (!argDefs[argName].resolve) return interpretFns; + return Promise.all(interpretFns.map((fn: any) => fn())); + }) + ); + + const resolvedMultiArgs = zipObject(argNames, resolvedArgValues); + + // Just return the last unless the argument definition allows multiple + const resolvedArgs = mapValues(resolvedMultiArgs, (argValues, argName) => { + if (argDefs[argName as any].multi) return argValues; + return last(argValues as any); + }); + + // Return an object here because the arguments themselves might actually have a 'then' + // function which would be treated as a promise + return { resolvedArgs }; + } +} diff --git a/src/plugins/expressions/public/registries/index.ts b/src/plugins/expressions/common/execution/index.ts similarity index 88% rename from src/plugins/expressions/public/registries/index.ts rename to src/plugins/expressions/common/execution/index.ts index 16c8d8fc4c93a..2452b0999d23e 100644 --- a/src/plugins/expressions/public/registries/index.ts +++ b/src/plugins/expressions/common/execution/index.ts @@ -17,6 +17,6 @@ * under the License. */ -export * from './type_registry'; -export * from './function_registry'; -export * from './render_registry'; +export * from './types'; +export * from './container'; +export * from './execution'; diff --git a/src/plugins/expressions/common/execution/types.ts b/src/plugins/expressions/common/execution/types.ts new file mode 100644 index 0000000000000..e05eb7cc94486 --- /dev/null +++ b/src/plugins/expressions/common/execution/types.ts @@ -0,0 +1,72 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionType } from '../expression_types'; +import { DataAdapter, RequestAdapter } from '../../../inspector/common'; +import { TimeRange, Query, esFilters } from '../../../data/common'; + +/** + * `ExecutionContext` is an object available to all functions during a single execution; + * it provides various methods to perform side-effects. + */ +export interface ExecutionContext { + /** + * Get initial input with which execution started. + */ + getInitialInput: () => Input; + + /** + * Context variables that can be consumed using `var` and `var_set` functions. + */ + variables: Record; + + /** + * A map of available expression types. + */ + types: Record; + + /** + * Adds ability to abort current execution. + */ + abortSignal: AbortSignal; + + /** + * Adapters for `inspector` plugin. + */ + inspectorAdapters: InspectorAdapters; + + /** + * Search context in which expression should operate. + */ + search?: ExecutionContextSearch; +} + +/** + * Default inspector adapters created if inspector adapters are not set explicitly. + */ +export interface DefaultInspectorAdapters { + requests: RequestAdapter; + data: DataAdapter; +} + +export interface ExecutionContextSearch { + filters?: esFilters.Filter[]; + query?: Query | Query[]; + timeRange?: TimeRange; +} diff --git a/src/plugins/expressions/common/executor/container.ts b/src/plugins/expressions/common/executor/container.ts new file mode 100644 index 0000000000000..c9c1ab34e7ac3 --- /dev/null +++ b/src/plugins/expressions/common/executor/container.ts @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + StateContainer, + createStateContainer, +} from '../../../kibana_utils/common/state_containers'; +import { ExpressionFunction } from '../expression_functions'; +import { ExpressionType } from '../expression_types'; + +export interface ExecutorState = Record> { + functions: Record; + types: Record; + context: Context; +} + +export const defaultState: ExecutorState = { + functions: {}, + types: {}, + context: {}, +}; + +export interface ExecutorPureTransitions { + addFunction: (state: ExecutorState) => (fn: ExpressionFunction) => ExecutorState; + addType: (state: ExecutorState) => (type: ExpressionType) => ExecutorState; + extendContext: (state: ExecutorState) => (extraContext: Record) => ExecutorState; +} + +export const pureTransitions: ExecutorPureTransitions = { + addFunction: state => fn => ({ ...state, functions: { ...state.functions, [fn.name]: fn } }), + addType: state => type => ({ ...state, types: { ...state.types, [type.name]: type } }), + extendContext: state => extraContext => ({ + ...state, + context: { ...state.context, ...extraContext }, + }), +}; + +export interface ExecutorPureSelectors { + getFunction: (state: ExecutorState) => (id: string) => ExpressionFunction | null; + getType: (state: ExecutorState) => (id: string) => ExpressionType | null; + getContext: (state: ExecutorState) => () => ExecutorState['context']; +} + +export const pureSelectors: ExecutorPureSelectors = { + getFunction: state => id => state.functions[id] || null, + getType: state => id => state.types[id] || null, + getContext: ({ context }) => () => context, +}; + +export type ExecutorContainer< + Context extends Record = Record +> = StateContainer, ExecutorPureTransitions, ExecutorPureSelectors>; + +export const createExecutorContainer = < + Context extends Record = Record +>( + state: ExecutorState = defaultState +): ExecutorContainer => { + const container = createStateContainer< + ExecutorState, + ExecutorPureTransitions, + ExecutorPureSelectors + >(state, pureTransitions, pureSelectors); + return container; +}; diff --git a/src/plugins/expressions/common/executor/executor.test.ts b/src/plugins/expressions/common/executor/executor.test.ts new file mode 100644 index 0000000000000..502728bb66403 --- /dev/null +++ b/src/plugins/expressions/common/executor/executor.test.ts @@ -0,0 +1,155 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Executor } from './executor'; +import * as expressionTypes from '../expression_types'; +import * as expressionFunctions from '../expression_functions'; +import { Execution } from '../execution'; +import { parseExpression } from '../ast'; + +describe('Executor', () => { + test('can instantiate', () => { + new Executor(); + }); + + describe('type registry', () => { + test('can register a type', () => { + const executor = new Executor(); + executor.registerType(expressionTypes.datatable); + }); + + test('can register all types', () => { + const executor = new Executor(); + for (const type of expressionTypes.typeSpecs) executor.registerType(type); + }); + + test('can retrieve all types', () => { + const executor = new Executor(); + executor.registerType(expressionTypes.datatable); + const types = executor.getTypes(); + expect(Object.keys(types)).toEqual(['datatable']); + }); + + test('can retrieve all types - 2', () => { + const executor = new Executor(); + for (const type of expressionTypes.typeSpecs) executor.registerType(type); + const types = executor.getTypes(); + expect(Object.keys(types).sort()).toEqual( + expressionTypes.typeSpecs.map(spec => spec.name).sort() + ); + }); + }); + + describe('function registry', () => { + test('can register a function', () => { + const executor = new Executor(); + executor.registerFunction(expressionFunctions.clog); + }); + + test('can register all functions', () => { + const executor = new Executor(); + for (const functionDefinition of expressionFunctions.functionSpecs) + executor.registerFunction(functionDefinition); + }); + + test('can retrieve all functions', () => { + const executor = new Executor(); + executor.registerFunction(expressionFunctions.clog); + const functions = executor.getFunctions(); + expect(Object.keys(functions)).toEqual(['clog']); + }); + + test('can retrieve all functions - 2', () => { + const executor = new Executor(); + for (const functionDefinition of expressionFunctions.functionSpecs) + executor.registerFunction(functionDefinition); + const functions = executor.getFunctions(); + expect(Object.keys(functions).sort()).toEqual( + expressionFunctions.functionSpecs.map(spec => spec.name).sort() + ); + }); + }); + + describe('context', () => { + test('context is empty by default', () => { + const executor = new Executor(); + expect(executor.context).toEqual({}); + }); + + test('can extend context', () => { + const executor = new Executor(); + executor.extendContext({ + foo: 'bar', + }); + expect(executor.context).toEqual({ + foo: 'bar', + }); + }); + + test('can extend context multiple times with multiple keys', () => { + const executor = new Executor(); + const abortSignal = {}; + const env = {}; + + executor.extendContext({ + foo: 'bar', + }); + executor.extendContext({ + abortSignal, + env, + }); + + expect(executor.context).toEqual({ + foo: 'bar', + abortSignal, + env, + }); + }); + }); + + describe('execution', () => { + describe('createExecution()', () => { + test('returns Execution object from string', () => { + const executor = new Executor(); + const execution = executor.createExecution('foo bar="baz"'); + + expect(execution).toBeInstanceOf(Execution); + expect(execution.params.ast.chain[0].function).toBe('foo'); + }); + + test('returns Execution object from AST', () => { + const executor = new Executor(); + const ast = parseExpression('foo bar="baz"'); + const execution = executor.createExecution(ast); + + expect(execution).toBeInstanceOf(Execution); + expect(execution.params.ast.chain[0].function).toBe('foo'); + }); + + test('Execution inherits context from Executor', () => { + const executor = new Executor(); + const foo = {}; + executor.extendContext({ foo }); + const execution = executor.createExecution('foo bar="baz"'); + + expect((execution.context as any).foo).toBe(foo); + }); + }); + }); +}); diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts new file mode 100644 index 0000000000000..5c27201b43fc0 --- /dev/null +++ b/src/plugins/expressions/common/executor/executor.ts @@ -0,0 +1,204 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* eslint-disable max-classes-per-file */ + +import { ExecutorState, ExecutorContainer } from './container'; +import { createExecutorContainer } from './container'; +import { AnyExpressionFunctionDefinition, ExpressionFunction } from '../expression_functions'; +import { Execution } from '../execution/execution'; +import { IRegistry } from '../types'; +import { ExpressionType } from '../expression_types/expression_type'; +import { AnyExpressionTypeDefinition } from '../expression_types/types'; +import { getType } from '../expression_types'; +import { ExpressionAstExpression, ExpressionAstNode, parseExpression } from '../ast'; +import { typeSpecs } from '../expression_types/specs'; +import { functionSpecs } from '../expression_functions/specs'; + +export class TypesRegistry implements IRegistry { + constructor(private readonly executor: Executor) {} + + public register( + typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition) + ) { + this.executor.registerType(typeDefinition); + } + + public get(id: string): ExpressionType | null { + return this.executor.state.selectors.getType(id); + } + + public toJS(): Record { + return this.executor.getTypes(); + } + + public toArray(): ExpressionType[] { + return Object.values(this.toJS()); + } +} + +export class FunctionsRegistry implements IRegistry { + constructor(private readonly executor: Executor) {} + + public register( + functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition) + ) { + this.executor.registerFunction(functionDefinition); + } + + public get(id: string): ExpressionFunction | null { + return this.executor.state.selectors.getFunction(id); + } + + public toJS(): Record { + return this.executor.getFunctions(); + } + + public toArray(): ExpressionFunction[] { + return Object.values(this.toJS()); + } +} + +export class Executor = Record> { + static createWithDefaults = Record>( + state?: ExecutorState + ): Executor { + const executor = new Executor(state); + for (const type of typeSpecs) executor.registerType(type); + for (const func of functionSpecs) executor.registerFunction(func); + return executor; + } + + public readonly state: ExecutorContainer; + + /** + * @deprecated + */ + public readonly functions: FunctionsRegistry; + + /** + * @deprecated + */ + public readonly types: TypesRegistry; + + constructor(state?: ExecutorState) { + this.state = createExecutorContainer(state); + this.functions = new FunctionsRegistry(this); + this.types = new TypesRegistry(this); + } + + public registerFunction( + functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition) + ) { + const fn = new ExpressionFunction( + typeof functionDefinition === 'object' ? functionDefinition : functionDefinition() + ); + this.state.transitions.addFunction(fn); + } + + public getFunction(name: string): ExpressionFunction | undefined { + return this.state.get().functions[name]; + } + + public getFunctions(): Record { + return { ...this.state.get().functions }; + } + + public registerType( + typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition) + ) { + const type = new ExpressionType( + typeof typeDefinition === 'object' ? typeDefinition : typeDefinition() + ); + this.state.transitions.addType(type); + } + + public getType(name: string): ExpressionType | undefined { + return this.state.get().types[name]; + } + + public getTypes(): Record { + return { ...this.state.get().types }; + } + + public extendContext(extraContext: Record) { + this.state.transitions.extendContext(extraContext); + } + + public get context(): Record { + return this.state.selectors.getContext(); + } + + public async interpret(ast: ExpressionAstNode, input: T): Promise { + switch (getType(ast)) { + case 'expression': + return await this.interpretExpression(ast as ExpressionAstExpression, input); + case 'string': + case 'number': + case 'null': + case 'boolean': + return ast; + default: + throw new Error(`Unknown AST object: ${JSON.stringify(ast)}`); + } + } + + public async interpretExpression( + ast: string | ExpressionAstExpression, + input: T + ): Promise { + const execution = this.createExecution(ast); + execution.start(input); + return await execution.result; + } + + /** + * Execute expression and return result. + * + * @param ast Expression AST or a string representing expression. + * @param input Initial input to the first expression function. + * @param context Extra global context object that will be merged into the + * expression global context object that is provided to each function to allow side-effects. + */ + public async run< + Input, + Output, + ExtraContext extends Record = Record + >(ast: string | ExpressionAstExpression, input: Input, context?: ExtraContext) { + const execution = this.createExecution(ast, context); + execution.start(input); + return (await execution.result) as Output; + } + + public createExecution = Record>( + ast: string | ExpressionAstExpression, + context: ExtraContext = {} as ExtraContext + ): Execution { + if (typeof ast === 'string') ast = parseExpression(ast); + const execution = new Execution({ + ast, + executor: this, + context: { + ...this.context, + ...context, + } as Context & ExtraContext, + }); + return execution; + } +} diff --git a/src/plugins/expressions/public/create_handlers.ts b/src/plugins/expressions/common/executor/index.ts similarity index 90% rename from src/plugins/expressions/public/create_handlers.ts rename to src/plugins/expressions/common/executor/index.ts index 46e85411c5895..ea49dfc85c1f5 100644 --- a/src/plugins/expressions/public/create_handlers.ts +++ b/src/plugins/expressions/common/executor/index.ts @@ -17,8 +17,5 @@ * under the License. */ -export function createHandlers() { - return { - environment: 'client', - }; -} +export * from './container'; +export * from './executor'; diff --git a/src/plugins/expressions/common/types/arguments.ts b/src/plugins/expressions/common/expression_functions/arguments.ts similarity index 99% rename from src/plugins/expressions/common/types/arguments.ts rename to src/plugins/expressions/common/expression_functions/arguments.ts index 20bec9359a593..38cee64aca521 100644 --- a/src/plugins/expressions/common/types/arguments.ts +++ b/src/plugins/expressions/common/expression_functions/arguments.ts @@ -17,7 +17,7 @@ * under the License. */ -import { KnownTypeToString, TypeString, UnmappedTypeStrings } from './common'; +import { KnownTypeToString, TypeString, UnmappedTypeStrings } from '../types/common'; /** * This type represents all of the possible combinations of properties of an diff --git a/src/plugins/expressions/common/expression_functions/expression_function.ts b/src/plugins/expressions/common/expression_functions/expression_function.ts new file mode 100644 index 0000000000000..71f0d91510136 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/expression_function.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AnyExpressionFunctionDefinition } from './types'; +import { ExpressionFunctionParameter } from './expression_function_parameter'; +import { ExpressionValue } from '../expression_types/types'; +import { ExecutionContext } from '../execution'; + +export class ExpressionFunction { + /** + * Name of function + */ + name: string; + + /** + * Aliases that can be used instead of `name`. + */ + aliases: string[]; + + /** + * Return type of function. This SHOULD be supplied. We use it for UI + * and autocomplete hinting. We may also use it for optimizations in + * the future. + */ + type: string; + + /** + * Function to run function (context, args) + */ + fn: (input: ExpressionValue, params: Record, handlers: object) => ExpressionValue; + + /** + * A short help text. + */ + help: string; + + /** + * Specification of expression function parameters. + */ + args: Record = {}; + + /** + * Type of inputs that this function supports. + */ + inputTypes: string[] | undefined; + + constructor(functionDefinition: AnyExpressionFunctionDefinition) { + const { name, type, aliases, fn, help, args, inputTypes, context } = functionDefinition; + + this.name = name; + this.type = type; + this.aliases = aliases || []; + this.fn = (input, params, handlers) => + Promise.resolve(fn(input, params, handlers as ExecutionContext)); + this.help = help || ''; + this.inputTypes = inputTypes || context?.types; + + for (const [key, arg] of Object.entries(args || {})) { + this.args[key] = new ExpressionFunctionParameter(key, arg); + } + } + + accepts = (type: string): boolean => { + // If you don't tell us input types, we'll assume you don't care what you get. + if (!this.inputTypes) return true; + return this.inputTypes.indexOf(type) > -1; + }; +} diff --git a/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts b/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts new file mode 100644 index 0000000000000..e94c0fa8a5b50 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ArgumentType } from './arguments'; + +export class ExpressionFunctionParameter { + name: string; + required: boolean; + help: string; + types: string[]; + default: any; + aliases: string[]; + multi: boolean; + resolve: boolean; + options: any[]; + + constructor(name: string, arg: ArgumentType) { + const { required, help, types, aliases, multi, resolve, options } = arg; + + if (name === '_') { + throw Error('Arg names must not be _. Use it in aliases instead.'); + } + + this.name = name; + this.required = !!required; + this.help = help || ''; + this.types = types || []; + this.default = arg.default; + this.aliases = aliases || []; + this.multi = !!multi; + this.resolve = resolve == null ? true : resolve; + this.options = options || []; + } + + accepts(type: string) { + if (!this.types.length) return true; + return this.types.indexOf(type) > -1; + } +} diff --git a/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts b/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts new file mode 100644 index 0000000000000..e52f7ec090282 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionFunctionParameter } from './expression_function_parameter'; + +describe('ExpressionFunctionParameter', () => { + test('can instantiate', () => { + const param = new ExpressionFunctionParameter('foo', { + help: 'bar', + }); + + expect(param.name).toBe('foo'); + }); + + test('checks supported types', () => { + const param = new ExpressionFunctionParameter('foo', { + help: 'bar', + types: ['baz', 'quux'], + }); + + expect(param.accepts('baz')).toBe(true); + expect(param.accepts('quux')).toBe(true); + expect(param.accepts('quix')).toBe(false); + }); + + test('if no types are provided, then accepts any type', () => { + const param = new ExpressionFunctionParameter('foo', { + help: 'bar', + }); + + expect(param.accepts('baz')).toBe(true); + expect(param.accepts('quux')).toBe(true); + expect(param.accepts('quix')).toBe(true); + }); +}); diff --git a/src/plugins/expressions/common/expression_functions/index.ts b/src/plugins/expressions/common/expression_functions/index.ts new file mode 100644 index 0000000000000..b29e6b78b8f4d --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './types'; +export * from './arguments'; +export * from './expression_function_parameter'; +export * from './expression_function'; +export * from './specs'; diff --git a/src/plugins/expressions/public/functions/clog.ts b/src/plugins/expressions/common/expression_functions/specs/clog.ts similarity index 71% rename from src/plugins/expressions/public/functions/clog.ts rename to src/plugins/expressions/common/expression_functions/specs/clog.ts index 2931b3b00d345..7839f1fc7998d 100644 --- a/src/plugins/expressions/public/functions/clog.ts +++ b/src/plugins/expressions/common/expression_functions/specs/clog.ts @@ -17,19 +17,15 @@ * under the License. */ -import { ExpressionFunction } from '../../common/types'; +import { ExpressionFunctionDefinition } from '../types'; -const name = 'clog'; - -type Context = any; -type ClogExpressionFunction = ExpressionFunction; - -export const clog = (): ClogExpressionFunction => ({ - name, +export const clog: ExpressionFunctionDefinition<'clog', unknown, {}, unknown> = { + name: 'clog', args: {}, help: 'Outputs the context to the console', - fn: context => { - console.log(context); // eslint-disable-line no-console - return context; + fn: (input: unknown) => { + // eslint-disable-next-line no-console + console.log(input); + return input; }, -}); +}; diff --git a/src/plugins/expressions/common/expression_functions/specs/font.ts b/src/plugins/expressions/common/expression_functions/specs/font.ts new file mode 100644 index 0000000000000..3e305998a0157 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/font.ts @@ -0,0 +1,182 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../types'; +import { openSans, FontLabel as FontFamily } from '../../fonts'; +import { CSSStyle, FontStyle, FontWeight, Style, TextAlignment, TextDecoration } from '../../types'; + +const dashify = (str: string) => { + return str + .trim() + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/\W/g, m => (/[À-ž]/.test(m) ? m : '-')) + .replace(/^-+|-+$/g, '') + .toLowerCase(); +}; + +const inlineStyle = (obj: Record) => { + if (!obj) return ''; + const styles = Object.keys(obj).map(key => { + const prop = dashify(key); + const line = prop.concat(':').concat(String(obj[key])); + return line; + }); + return styles.join(';'); +}; + +interface Arguments { + align?: TextAlignment; + color?: string; + family?: FontFamily; + italic?: boolean; + lHeight?: number | null; + size?: number; + underline?: boolean; + weight?: FontWeight; +} + +export const font: ExpressionFunctionDefinition<'font', null, Arguments, Style> = { + name: 'font', + aliases: [], + type: 'style', + help: i18n.translate('expressions.functions.fontHelpText', { + defaultMessage: 'Create a font style.', + }), + inputTypes: ['null'], + args: { + align: { + default: 'left', + help: i18n.translate('expressions.functions.font.args.alignHelpText', { + defaultMessage: 'The horizontal text alignment.', + }), + options: Object.values(TextAlignment), + types: ['string'], + }, + color: { + help: i18n.translate('expressions.functions.font.args.colorHelpText', { + defaultMessage: 'The text color.', + }), + types: ['string'], + }, + family: { + default: `"${openSans.value}"`, + help: i18n.translate('expressions.functions.font.args.familyHelpText', { + defaultMessage: 'An acceptable {css} web font string', + values: { + css: 'CSS', + }, + }), + types: ['string'], + }, + italic: { + default: false, + help: i18n.translate('expressions.functions.font.args.italicHelpText', { + defaultMessage: 'Italicize the text?', + }), + options: [true, false], + types: ['boolean'], + }, + lHeight: { + default: null, + aliases: ['lineHeight'], + help: i18n.translate('expressions.functions.font.args.lHeightHelpText', { + defaultMessage: 'The line height in pixels', + }), + types: ['number', 'null'], + }, + size: { + default: 14, + help: i18n.translate('expressions.functions.font.args.sizeHelpText', { + defaultMessage: 'The font size in pixels', + }), + types: ['number'], + }, + underline: { + default: false, + help: i18n.translate('expressions.functions.font.args.underlineHelpText', { + defaultMessage: 'Underline the text?', + }), + options: [true, false], + types: ['boolean'], + }, + weight: { + default: 'normal', + help: i18n.translate('expressions.functions.font.args.weightHelpText', { + defaultMessage: 'The font weight. For example, {list}, or {end}.', + values: { + list: Object.values(FontWeight) + .slice(0, -1) + .map(weight => `\`"${weight}"\``) + .join(', '), + end: `\`"${Object.values(FontWeight).slice(-1)[0]}"\``, + }, + }), + options: Object.values(FontWeight), + types: ['string'], + }, + }, + fn: (input, args) => { + if (!Object.values(FontWeight).includes(args.weight!)) { + throw new Error( + i18n.translate('expressions.functions.font.invalidFontWeightErrorMessage', { + defaultMessage: "Invalid font weight: '{weight}'", + values: { + weight: args.weight, + }, + }) + ); + } + if (!Object.values(TextAlignment).includes(args.align!)) { + throw new Error( + i18n.translate('expressions.functions.font.invalidTextAlignmentErrorMessage', { + defaultMessage: "Invalid text alignment: '{align}'", + values: { + align: args.align, + }, + }) + ); + } + + // the line height shouldn't ever be lower than the size, and apply as a + // pixel setting + const lineHeight = args.lHeight != null ? `${args.lHeight}px` : '1'; + + const spec: CSSStyle = { + fontFamily: args.family, + fontWeight: args.weight, + fontStyle: args.italic ? FontStyle.ITALIC : FontStyle.NORMAL, + textDecoration: args.underline ? TextDecoration.UNDERLINE : TextDecoration.NONE, + textAlign: args.align, + fontSize: `${args.size}px`, // apply font size as a pixel setting + lineHeight, // apply line height as a pixel setting + }; + + // conditionally apply styles based on input + if (args.color) { + spec.color = args.color; + } + + return { + type: 'style', + spec, + css: inlineStyle(spec as Record), + }; + }, +}; diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts new file mode 100644 index 0000000000000..514068da8f10c --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { clog } from './clog'; +import { font } from './font'; +import { kibana } from './kibana'; +import { variableSet } from './var_set'; +import { variable } from './var'; +import { AnyExpressionFunctionDefinition } from '../types'; + +export const functionSpecs: AnyExpressionFunctionDefinition[] = [ + clog, + font, + kibana, + variableSet, + variable, +]; + +export * from './clog'; +export * from './font'; +export * from './kibana'; +export * from './var_set'; +export * from './var'; diff --git a/src/plugins/expressions/public/functions/kibana.ts b/src/plugins/expressions/common/expression_functions/specs/kibana.ts similarity index 52% rename from src/plugins/expressions/public/functions/kibana.ts rename to src/plugins/expressions/common/expression_functions/specs/kibana.ts index 81d0eec5f7896..2144a8aba2d19 100644 --- a/src/plugins/expressions/public/functions/kibana.ts +++ b/src/plugins/expressions/common/expression_functions/specs/kibana.ts @@ -18,47 +18,43 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction } from '../../common/types'; -import { KibanaContext } from '../../common/expression_types'; +import { ExpressionFunctionDefinition } from '../types'; +import { ExpressionValueSearchContext } from '../../expression_types'; -export type ExpressionFunctionKibana = ExpressionFunction< +const toArray = (query: undefined | T | T[]): T[] => + !query ? [] : Array.isArray(query) ? query : [query]; + +export type ExpressionFunctionKibana = ExpressionFunctionDefinition< 'kibana', - KibanaContext | null, + // TODO: Get rid of the `null` type below. + ExpressionValueSearchContext | null, object, - KibanaContext + ExpressionValueSearchContext >; -export const kibana = (): ExpressionFunctionKibana => ({ +export const kibana: ExpressionFunctionKibana = { name: 'kibana', type: 'kibana_context', - context: { - types: ['kibana_context', 'null'], - }, + inputTypes: ['kibana_context', 'null'], help: i18n.translate('expressions.functions.kibana.help', { defaultMessage: 'Gets kibana global context', }), - args: {}, - fn(context, args, handlers) { - const initialContext = handlers.getInitialContext ? handlers.getInitialContext() : {}; - - if (context && context.query) { - initialContext.query = initialContext.query.concat(context.query); - } - if (context && context.filters) { - initialContext.filters = initialContext.filters.concat(context.filters); - } - - const timeRange = initialContext.timeRange || (context ? context.timeRange : undefined); + args: {}, - return { - ...context, + fn(input, _, { search = {} }) { + const output: ExpressionValueSearchContext = { + // TODO: This spread is left here for legacy reasons, possibly Lens uses it. + // TODO: But it shouldn't be need. + ...input, type: 'kibana_context', - query: initialContext.query, - filters: initialContext.filters, - timeRange, + query: [...toArray(search.query), ...toArray((input || {}).query)], + filters: [...(search.filters || []), ...((input || {}).filters || [])], + timeRange: search.timeRange || (input ? input.timeRange : undefined), }; + + return output; }, -}); +}; diff --git a/src/plugins/expressions/public/functions/tests/__snapshots__/kibana.test.ts.snap b/src/plugins/expressions/common/expression_functions/specs/tests/__snapshots__/kibana.test.ts.snap similarity index 81% rename from src/plugins/expressions/public/functions/tests/__snapshots__/kibana.test.ts.snap rename to src/plugins/expressions/common/expression_functions/specs/tests/__snapshots__/kibana.test.ts.snap index 5a3810d8ddd93..2400f7a1f67d6 100644 --- a/src/plugins/expressions/public/functions/tests/__snapshots__/kibana.test.ts.snap +++ b/src/plugins/expressions/common/expression_functions/specs/tests/__snapshots__/kibana.test.ts.snap @@ -14,10 +14,12 @@ Object { }, }, ], - "query": Object { - "language": "lucene", - "query": "geo.src:US", - }, + "query": Array [ + Object { + "language": "lucene", + "query": "geo.src:US", + }, + ], "timeRange": Object { "from": "2", "to": "3", diff --git a/src/plugins/expressions/public/functions/tests/font.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts similarity index 99% rename from src/plugins/expressions/public/functions/tests/font.test.ts rename to src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts index 6430801182277..42b75b47a5aea 100644 --- a/src/plugins/expressions/public/functions/tests/font.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { openSans } from '../../../common/fonts'; +import { openSans } from '../../../fonts'; import { font } from '../font'; import { functionWrapper } from './utils'; diff --git a/src/plugins/expressions/public/functions/tests/kibana.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/kibana.test.ts similarity index 65% rename from src/plugins/expressions/public/functions/tests/kibana.test.ts rename to src/plugins/expressions/common/expression_functions/specs/tests/kibana.test.ts index b9fec590d823f..e5bd53f63c91d 100644 --- a/src/plugins/expressions/public/functions/tests/kibana.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/kibana.test.ts @@ -19,18 +19,18 @@ import { functionWrapper } from './utils'; import { kibana } from '../kibana'; -import { FunctionHandlers } from '../../../common/types'; -import { KibanaContext } from '../../../common/expression_types/kibana_context'; +import { ExecutionContext } from '../../../execution/types'; +import { KibanaContext, ExpressionValueSearchContext } from '../../../expression_types'; describe('interpreter/functions#kibana', () => { const fn = functionWrapper(kibana); - let context: Partial; - let initialContext: KibanaContext; - let handlers: FunctionHandlers; + let input: Partial; + let search: ExpressionValueSearchContext; + let context: ExecutionContext; beforeEach(() => { - context = { timeRange: { from: '0', to: '1' } }; - initialContext = { + input = { timeRange: { from: '0', to: '1' } }; + search = { type: 'kibana_context', query: { language: 'lucene', query: 'geo.src:US' }, filters: [ @@ -45,31 +45,29 @@ describe('interpreter/functions#kibana', () => { ], timeRange: { from: '2', to: '3' }, }; - handlers = { - getInitialContext: () => initialContext, + context = { + search, + getInitialInput: () => input, + types: {}, + variables: {}, + abortSignal: {} as any, + inspectorAdapters: {} as any, }; }); it('returns an object with the correct structure', () => { - const actual = fn(context, {}, handlers); + const actual = fn(input, {}, context); expect(actual).toMatchSnapshot(); }); - it('uses timeRange from context if not provided in initialContext', () => { - initialContext.timeRange = undefined; - const actual = fn(context, {}, handlers); + it('uses timeRange from input if not provided in search context', () => { + search.timeRange = undefined; + const actual = fn(input, {}, context); expect(actual.timeRange).toEqual({ from: '0', to: '1' }); }); - it.skip('combines query from context with initialContext', () => { - context.query = { language: 'kuery', query: 'geo.dest:CN' }; - // TODO: currently this fails & likely requires a fix in run_pipeline - const actual = fn(context, {}, handlers); - expect(actual.query).toEqual('TBD'); - }); - - it('combines filters from context with initialContext', () => { - context.filters = [ + it('combines filters from input with search context', () => { + input.filters = [ { meta: { disabled: true, @@ -79,7 +77,7 @@ describe('interpreter/functions#kibana', () => { query: { match: {} }, }, ]; - const actual = fn(context, {}, handlers); + const actual = fn(input, {}, context); expect(actual.filters).toEqual([ { meta: { diff --git a/src/plugins/expressions/public/functions/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts similarity index 74% rename from src/plugins/expressions/public/functions/tests/utils.ts rename to src/plugins/expressions/common/expression_functions/specs/tests/utils.ts index 749b45ef0319b..bc721a772d50f 100644 --- a/src/plugins/expressions/public/functions/tests/utils.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts @@ -18,16 +18,18 @@ */ import { mapValues } from 'lodash'; -import { AnyExpressionFunction, FunctionHandlers } from '../../../common/types'; +import { AnyExpressionFunctionDefinition } from '../../types'; +import { ExecutionContext } from '../../../execution/types'; -// Takes a function spec and passes in default args, -// overriding with any provided args. -export const functionWrapper = (fnSpec: () => T) => { - const spec = fnSpec(); +/** + * Takes a function spec and passes in default args, + * overriding with any provided args. + */ +export const functionWrapper = (spec: AnyExpressionFunctionDefinition) => { const defaultArgs = mapValues(spec.args, argSpec => argSpec.default); return ( context: object | null, args: Record = {}, - handlers: FunctionHandlers = {} + handlers: ExecutionContext = {} as ExecutionContext ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); }; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts new file mode 100644 index 0000000000000..ccf49ec918d3d --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { functionWrapper } from './utils'; +import { variable } from '../var'; +import { ExecutionContext } from '../../../execution/types'; +import { KibanaContext } from '../../../expression_types'; + +describe('expression_functions', () => { + describe('var', () => { + const fn = functionWrapper(variable); + let input: Partial; + let context: ExecutionContext; + + beforeEach(() => { + input = { timeRange: { from: '0', to: '1' } }; + context = { + getInitialInput: () => input, + types: {}, + variables: { test: 1 }, + abortSignal: {} as any, + inspectorAdapters: {} as any, + }; + }); + + it('returns the selected variable', () => { + const actual = fn(input, { name: 'test' }, context); + expect(actual).toEqual(1); + }); + + it('returns undefined if variable does not exist', () => { + const actual = fn(input, { name: 'unknown' }, context); + expect(actual).toEqual(undefined); + }); + }); +}); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts new file mode 100644 index 0000000000000..b1ae44e6f899e --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { functionWrapper } from './utils'; +import { variableSet } from '../var_set'; +import { ExecutionContext } from '../../../execution/types'; +import { KibanaContext } from '../../../expression_types'; + +describe('expression_functions', () => { + describe('var_set', () => { + const fn = functionWrapper(variableSet); + let input: Partial; + let context: ExecutionContext; + let variables: Record; + + beforeEach(() => { + input = { timeRange: { from: '0', to: '1' } }; + context = { + getInitialInput: () => input, + types: {}, + variables: { test: 1 }, + abortSignal: {} as any, + inspectorAdapters: {} as any, + }; + + variables = context.variables; + }); + + it('updates a variable', () => { + const actual = fn(input, { name: 'test', value: 2 }, context); + expect(variables.test).toEqual(2); + expect(actual).toEqual(input); + }); + + it('sets a new variable', () => { + const actual = fn(input, { name: 'new', value: 3 }, context); + expect(variables.new).toEqual(3); + expect(actual).toEqual(input); + }); + + it('stores context if value is not set', () => { + const actual = fn(input, { name: 'test' }, context); + expect(variables.test).toEqual(input); + expect(actual).toEqual(input); + }); + }); +}); diff --git a/src/plugins/expressions/public/functions/var.ts b/src/plugins/expressions/common/expression_functions/specs/var.ts similarity index 80% rename from src/plugins/expressions/public/functions/var.ts rename to src/plugins/expressions/common/expression_functions/specs/var.ts index 9410149060216..e90a21101c557 100644 --- a/src/plugins/expressions/public/functions/var.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var.ts @@ -18,16 +18,15 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction } from '../../common/types'; +import { ExpressionFunctionDefinition } from '../types'; interface Arguments { name: string; } -type Context = any; -type ExpressionFunctionVar = ExpressionFunction<'var', Context, Arguments, any>; +type ExpressionFunctionVar = ExpressionFunctionDefinition<'var', unknown, Arguments, unknown>; -export const variable = (): ExpressionFunctionVar => ({ +export const variable: ExpressionFunctionVar = { name: 'var', help: i18n.translate('expressions.functions.var.help', { defaultMessage: 'Updates kibana global context', @@ -42,8 +41,8 @@ export const variable = (): ExpressionFunctionVar => ({ }), }, }, - fn(context, args, handlers) { - const variables: Record = handlers.variables; + fn(input, args, context) { + const variables: Record = context.variables; return variables[args.name]; }, -}); +}; diff --git a/src/plugins/expressions/public/functions/var_set.ts b/src/plugins/expressions/common/expression_functions/specs/var_set.ts similarity index 77% rename from src/plugins/expressions/public/functions/var_set.ts rename to src/plugins/expressions/common/expression_functions/specs/var_set.ts index a10ee7a00814f..0bf89f5470b3d 100644 --- a/src/plugins/expressions/public/functions/var_set.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var_set.ts @@ -18,17 +18,14 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction } from '../../common/types'; +import { ExpressionFunctionDefinition } from '../types'; interface Arguments { name: string; value?: any; } -type Context = any; -type ExpressionFunctionVarSet = ExpressionFunction<'var_set', Context, Arguments, Context>; - -export const variableSet = (): ExpressionFunctionVarSet => ({ +export const variableSet: ExpressionFunctionDefinition<'var_set', unknown, Arguments, unknown> = { name: 'var_set', help: i18n.translate('expressions.functions.varset.help', { defaultMessage: 'Updates kibana global context', @@ -50,9 +47,9 @@ export const variableSet = (): ExpressionFunctionVarSet => ({ }), }, }, - fn(context, args, handlers) { - const variables: Record = handlers.variables; - variables[args.name] = args.value === undefined ? context : args.value; - return context; + fn(input, args, context) { + const variables: Record = context.variables; + variables[args.name] = args.value === undefined ? input : args.value; + return input; }, -}); +}; diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts new file mode 100644 index 0000000000000..b91deea36aee8 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/types.ts @@ -0,0 +1,96 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; +import { ArgumentType } from './arguments'; +import { TypeToString } from '../types/common'; +import { ExecutionContext } from '../execution/types'; + +/** + * `ExpressionFunctionDefinition` is the interface plugins have to implement to + * register a function in `expressions` plugin. + */ +export interface ExpressionFunctionDefinition< + Name extends string, + Input, + Arguments, + Output, + Context extends ExecutionContext = ExecutionContext +> { + /** + * The name of the function, as will be used in expression. + */ + name: Name; + + /** + * Name of type of value this function outputs. + */ + type?: TypeToString>; + + /** + * List of allowed type names for input value of this function. If this + * property is set the input of function will be cast to the first possible + * type in this list. If this property is missing the input will be provided + * to the function as-is. + */ + inputTypes?: Array>; + + /** + * Specification of arguments that function supports. This list will also be + * used for autocomplete functionality when your function is being edited. + */ + args: { [key in keyof Arguments]: ArgumentType }; + + /** + * @todo What is this? + */ + aliases?: string[]; + + /** + * Help text displayed in the Expression editor. This text should be + * internationalized. + */ + help: string; + + /** + * The actual implementation of the function. + * + * @param input Output of the previous function, or initial input. + * @param args Parameters set for this function in expression. + * @param context Object with functions to perform side effects. This object + * is created for the duration of the execution of expression and is the + * same for all functions in expression chain. + */ + fn(input: Input, args: Arguments, context: Context): Output; + + /** + * @deprecated Use `inputTypes` instead. + */ + context?: { + /** + * @deprecated This is alias for `inputTypes`, use `inputTypes` instead. + */ + types: AnyExpressionFunctionDefinition['inputTypes']; + }; +} + +/** + * Type to capture every possible expression function definition. + */ +export type AnyExpressionFunctionDefinition = ExpressionFunctionDefinition; diff --git a/src/plugins/expressions/common/expression_renderers/expression_renderer.ts b/src/plugins/expressions/common/expression_renderers/expression_renderer.ts new file mode 100644 index 0000000000000..c25534c440f32 --- /dev/null +++ b/src/plugins/expressions/common/expression_renderers/expression_renderer.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionRenderDefinition } from './types'; + +export class ExpressionRenderer { + public readonly name: string; + public readonly displayName: string; + public readonly help: string; + public readonly validate: () => void | Error; + public readonly reuseDomNode: boolean; + public readonly render: ExpressionRenderDefinition['render']; + + constructor(config: ExpressionRenderDefinition) { + const { name, displayName, help, validate, reuseDomNode, render } = config; + + this.name = name; + this.displayName = displayName || name; + this.help = help || ''; + this.validate = validate || (() => {}); + this.reuseDomNode = Boolean(reuseDomNode); + this.render = render; + } +} diff --git a/src/plugins/expressions/common/expression_renderers/expression_renderer_registry.ts b/src/plugins/expressions/common/expression_renderers/expression_renderer_registry.ts new file mode 100644 index 0000000000000..69c0f3fad701b --- /dev/null +++ b/src/plugins/expressions/common/expression_renderers/expression_renderer_registry.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRegistry } from '../types'; +import { ExpressionRenderer } from './expression_renderer'; +import { AnyExpressionRenderDefinition } from './types'; + +export class ExpressionRendererRegistry implements IRegistry { + private readonly renderers: Map = new Map< + string, + ExpressionRenderer + >(); + + register(definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)) { + if (typeof definition === 'function') definition = definition(); + const renderer = new ExpressionRenderer(definition); + this.renderers.set(renderer.name, renderer); + } + + public get(id: string): ExpressionRenderer | null { + return this.renderers.get(id) || null; + } + + public toJS(): Record { + return this.toArray().reduce( + (acc, renderer) => ({ + ...acc, + [renderer.name]: renderer, + }), + {} as Record + ); + } + + public toArray(): ExpressionRenderer[] { + return [...this.renderers.values()]; + } +} diff --git a/src/plugins/expressions/common/expression_renderers/index.ts b/src/plugins/expressions/common/expression_renderers/index.ts new file mode 100644 index 0000000000000..915e0944e9c44 --- /dev/null +++ b/src/plugins/expressions/common/expression_renderers/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './types'; +export * from './expression_renderer'; +export * from './expression_renderer_registry'; diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts new file mode 100644 index 0000000000000..7b3e812eafedd --- /dev/null +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface ExpressionRenderDefinition { + /** + * Technical name of the renderer, used as ID to identify renderer in + * expression renderer registry. This must match the name of the expression + * function that is used to create the `type: render` object. + */ + name: string; + + /** + * A user friendly name of the renderer as will be displayed to user in UI. + */ + displayName: string; + + /** + * Help text as will be displayed to user. A sentence or few about what this + * element does. + */ + help?: string; + + /** + * Used to validate the data before calling the render function. + */ + validate?: () => undefined | Error; + + /** + * Tell the renderer if the dom node should be reused, it's recreated each + * time by default. + */ + reuseDomNode: boolean; + + /** + * The function called to render the output data of an expression. + */ + render: ( + domNode: HTMLElement, + config: Config, + handlers: IInterpreterRenderHandlers + ) => void | Promise; +} + +export type AnyExpressionRenderDefinition = ExpressionRenderDefinition; + +export interface IInterpreterRenderHandlers { + /** + * Done increments the number of rendering successes + */ + done: () => void; + onDestroy: (fn: () => void) => void; + reload: () => void; + update: (params: any) => void; + event: (event: any) => void; +} diff --git a/src/plugins/expressions/common/expression_types/expression_type.test.ts b/src/plugins/expressions/common/expression_types/expression_type.test.ts new file mode 100644 index 0000000000000..a692ec9501cc5 --- /dev/null +++ b/src/plugins/expressions/common/expression_types/expression_type.test.ts @@ -0,0 +1,145 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionType } from './expression_type'; +import { ExpressionTypeDefinition } from './types'; +import { ExpressionValueRender } from './specs'; + +export const boolean: ExpressionTypeDefinition<'boolean', boolean> = { + name: 'boolean', + from: { + null: () => false, + number: n => Boolean(n), + string: s => Boolean(s), + }, + to: { + render: (value): ExpressionValueRender<{ text: string }> => { + const text = `${value}`; + return { + type: 'render', + as: 'text', + value: { text }, + }; + }, + }, +}; + +export const render: ExpressionTypeDefinition<'render', ExpressionValueRender> = { + name: 'render', + from: { + '*': (v: T): ExpressionValueRender => ({ + type: name, + as: 'debug', + value: v, + }), + }, +}; + +const emptyDatatableValue = { + type: 'datatable', + columns: [], + rows: [], +}; + +describe('ExpressionType', () => { + test('can create a boolean type', () => { + new ExpressionType(boolean); + }); + + describe('castsFrom()', () => { + describe('when "from" definition specifies "*" as one of its from types', () => { + test('returns true for any value', () => { + const type = new ExpressionType(render); + expect(type.castsFrom(123)).toBe(true); + expect(type.castsFrom('foo')).toBe(true); + expect(type.castsFrom(true)).toBe(true); + expect( + type.castsFrom({ + type: 'datatable', + columns: [], + rows: [], + }) + ).toBe(true); + }); + }); + }); + + describe('castsTo()', () => { + describe('when "to" definition is not specified', () => { + test('returns false for any value', () => { + const type = new ExpressionType(render); + expect(type.castsTo(123)).toBe(false); + expect(type.castsTo('foo')).toBe(false); + expect(type.castsTo(true)).toBe(false); + expect(type.castsTo(emptyDatatableValue)).toBe(false); + }); + }); + }); + + describe('from()', () => { + test('can cast from any type specified in definition', () => { + const type = new ExpressionType(boolean); + expect(type.from(1, {})).toBe(true); + expect(type.from(0, {})).toBe(false); + expect(type.from('foo', {})).toBe(true); + expect(type.from('', {})).toBe(false); + expect(type.from(null, {})).toBe(false); + + // undefined is used like null in legacy interpreter + expect(type.from(undefined, {})).toBe(false); + }); + + test('throws when casting from type that is not supported', async () => { + const type = new ExpressionType(boolean); + expect(() => type.from(emptyDatatableValue, {})).toThrowError(); + expect(() => type.from(emptyDatatableValue, {})).toThrowErrorMatchingInlineSnapshot( + `"Can not cast 'boolean' from datatable"` + ); + }); + }); + + describe('to()', () => { + test('can cast to type specified in definition', () => { + const type = new ExpressionType(boolean); + + expect(type.to(true, 'render', {})).toMatchObject({ + as: 'text', + type: 'render', + value: { + text: 'true', + }, + }); + expect(type.to(false, 'render', {})).toMatchObject({ + as: 'text', + type: 'render', + value: { + text: 'false', + }, + }); + }); + + test('throws when casting to type that is not supported', async () => { + const type = new ExpressionType(boolean); + expect(() => type.to(emptyDatatableValue, 'number', {})).toThrowError(); + expect(() => type.to(emptyDatatableValue, 'number', {})).toThrowErrorMatchingInlineSnapshot( + `"Can not cast object of type 'datatable' using 'boolean'"` + ); + }); + }); +}); diff --git a/src/plugins/expressions/common/type.ts b/src/plugins/expressions/common/expression_types/expression_type.ts similarity index 50% rename from src/plugins/expressions/common/type.ts rename to src/plugins/expressions/common/expression_types/expression_type.ts index c9daed9b6785a..71fa842f4dde7 100644 --- a/src/plugins/expressions/common/type.ts +++ b/src/plugins/expressions/common/expression_types/expression_type.ts @@ -17,35 +17,10 @@ * under the License. */ -import { get, identity } from 'lodash'; -import { AnyExpressionType, ExpressionValue } from './types'; - -export function getType(node: any) { - if (node == null) return 'null'; - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; - } - return typeof node; -} - -export function serializeProvider(types: any) { - function provider(key: any) { - return (context: any) => { - const type = getType(context); - const typeDef = types[type]; - const fn: any = get(typeDef, key) || identity; - return fn(context); - }; - } - - return { - serialize: provider('serialize'), - deserialize: provider('deserialize'), - }; -} +import { AnyExpressionTypeDefinition, ExpressionValue, ExpressionValueConverter } from './types'; +import { getType } from './get_type'; -export class Type { +export class ExpressionType { name: string; /** @@ -66,41 +41,53 @@ export class Type { serialize?: (value: ExpressionValue) => any; deserialize?: (serialized: any) => ExpressionValue; - constructor(private readonly config: AnyExpressionType) { - const { name, help, deserialize, serialize, validate } = config; + constructor(private readonly definition: AnyExpressionTypeDefinition) { + const { name, help, deserialize, serialize, validate } = definition; this.name = name; this.help = help || ''; this.validate = validate || (() => {}); // Optional - this.create = (config as any).create; + this.create = (definition as any).create; this.serialize = serialize; this.deserialize = deserialize; } - getToFn = (value: any) => get(this.config, ['to', value]) || get(this.config, ['to', '*']); - getFromFn = (value: any) => get(this.config, ['from', value]) || get(this.config, ['from', '*']); + getToFn = ( + typeName: string + ): undefined | ExpressionValueConverter => + !this.definition.to ? undefined : this.definition.to[typeName] || this.definition.to['*']; + + getFromFn = ( + typeName: string + ): undefined | ExpressionValueConverter => + !this.definition.from ? undefined : this.definition.from[typeName] || this.definition.from['*']; + + castsTo = (value: ExpressionValue) => typeof this.getToFn(value) === 'function'; - castsTo = (value: any) => typeof this.getToFn(value) === 'function'; - castsFrom = (value: any) => typeof this.getFromFn(value) === 'function'; + castsFrom = (value: ExpressionValue) => typeof this.getFromFn(value) === 'function'; + + to = (value: ExpressionValue, toTypeName: string, types: Record) => { + const typeName = getType(value); - to = (node: any, toTypeName: any, types: any) => { - const typeName = getType(node); if (typeName !== this.name) { throw new Error(`Can not cast object of type '${typeName}' using '${this.name}'`); } else if (!this.castsTo(toTypeName)) { throw new Error(`Can not cast '${typeName}' to '${toTypeName}'`); } - return (this.getToFn(toTypeName) as any)(node, types); + return this.getToFn(toTypeName)!(value, types); }; - from = (node: any, types: any) => { - const typeName = getType(node); - if (!this.castsFrom(typeName)) throw new Error(`Can not cast '${this.name}' from ${typeName}`); + from = (value: ExpressionValue, types: Record) => { + const typeName = getType(value); + + if (!this.castsFrom(typeName)) { + throw new Error(`Can not cast '${this.name}' from ${typeName}`); + } - return (this.getFromFn(typeName) as any)(node, types); + return this.getFromFn(typeName)!(value, types); }; } diff --git a/src/plugins/expressions/common/type.test.ts b/src/plugins/expressions/common/expression_types/get_type.test.ts similarity index 97% rename from src/plugins/expressions/common/type.test.ts rename to src/plugins/expressions/common/expression_types/get_type.test.ts index 94979febd623c..ba4fad5e96c49 100644 --- a/src/plugins/expressions/common/type.test.ts +++ b/src/plugins/expressions/common/expression_types/get_type.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { getType } from './type'; +import { getType } from './get_type'; describe('getType()', () => { test('returns "null" string for null or undefined', () => { diff --git a/src/plugins/expressions/public/registries/type_registry.ts b/src/plugins/expressions/common/expression_types/get_type.ts similarity index 67% rename from src/plugins/expressions/public/registries/type_registry.ts rename to src/plugins/expressions/common/expression_types/get_type.ts index 6dfb71f1006ce..9e80ffeada678 100644 --- a/src/plugins/expressions/public/registries/type_registry.ts +++ b/src/plugins/expressions/common/expression_types/get_type.ts @@ -17,13 +17,11 @@ * under the License. */ -import { Registry } from './registry'; -import { Type } from '../../common/type'; -import { AnyExpressionType } from '../../common/types'; - -export class TypesRegistry extends Registry { - register(typeDefinition: AnyExpressionType | (() => AnyExpressionType)) { - const type = new Type(typeof typeDefinition === 'object' ? typeDefinition : typeDefinition()); - this.set(type.name, type); +export function getType(node: any) { + if (node == null) return 'null'; + if (typeof node === 'object') { + if (!node.type) throw new Error('Objects must have a type property'); + return node.type; } + return typeof node; } diff --git a/src/plugins/expressions/common/expression_types/index.ts b/src/plugins/expressions/common/expression_types/index.ts index a5d182fee75ed..5ec9a2e83583e 100644 --- a/src/plugins/expressions/common/expression_types/index.ts +++ b/src/plugins/expressions/common/expression_types/index.ts @@ -17,52 +17,8 @@ * under the License. */ -import { boolean } from './boolean'; -import { datatable } from './datatable'; -import { error } from './error'; -import { filter } from './filter'; -import { image } from './image'; -import { kibanaContext } from './kibana_context'; -import { kibanaDatatable } from './kibana_datatable'; -import { nullType } from './null'; -import { number } from './number'; -import { pointseries } from './pointseries'; -import { range } from './range'; -import { render } from './render'; -import { shape } from './shape'; -import { string } from './string'; -import { style } from './style'; - -export const typeSpecs = [ - boolean, - datatable, - error, - filter, - image, - kibanaContext, - kibanaDatatable, - nullType, - number, - pointseries, - range, - render, - shape, - string, - style, -]; - -export * from './boolean'; -export * from './datatable'; -export * from './error'; -export * from './filter'; -export * from './image'; -export * from './kibana_context'; -export * from './kibana_datatable'; -export * from './null'; -export * from './number'; -export * from './pointseries'; -export * from './range'; -export * from './render'; -export * from './shape'; -export * from './string'; -export * from './style'; +export * from './types'; +export * from './get_type'; +export * from './serialize_provider'; +export * from './expression_type'; +export * from './specs'; diff --git a/src/plugins/expressions/common/expression_types/serialize_provider.ts b/src/plugins/expressions/common/expression_types/serialize_provider.ts new file mode 100644 index 0000000000000..1cd6a24bca31b --- /dev/null +++ b/src/plugins/expressions/common/expression_types/serialize_provider.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionType } from './expression_type'; +import { ExpressionValue } from './types'; +import { getType } from './get_type'; + +const identity = (x: T) => x; + +export const serializeProvider = (types: Record) => ({ + serialize: (value: ExpressionValue) => (types[getType(value)].serialize || identity)(value), + deserialize: (value: ExpressionValue) => (types[getType(value)].deserialize || identity)(value), +}); diff --git a/src/plugins/expressions/common/expression_types/boolean.ts b/src/plugins/expressions/common/expression_types/specs/boolean.ts similarity index 83% rename from src/plugins/expressions/common/expression_types/boolean.ts rename to src/plugins/expressions/common/expression_types/specs/boolean.ts index 0ad2c14f87756..fee4608418406 100644 --- a/src/plugins/expressions/common/expression_types/boolean.ts +++ b/src/plugins/expressions/common/expression_types/specs/boolean.ts @@ -17,13 +17,13 @@ * under the License. */ -import { ExpressionType } from '../types'; +import { ExpressionTypeDefinition } from '../types'; import { Datatable } from './datatable'; -import { Render } from './render'; +import { ExpressionValueRender } from './render'; const name = 'boolean'; -export const boolean = (): ExpressionType<'boolean', boolean> => ({ +export const boolean: ExpressionTypeDefinition<'boolean', boolean> = { name, from: { null: () => false, @@ -31,7 +31,7 @@ export const boolean = (): ExpressionType<'boolean', boolean> => ({ string: s => Boolean(s), }, to: { - render: (value): Render<{ text: string }> => { + render: (value): ExpressionValueRender<{ text: string }> => { const text = `${value}`; return { type: 'render', @@ -45,4 +45,4 @@ export const boolean = (): ExpressionType<'boolean', boolean> => ({ rows: [{ value }], }), }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts similarity index 93% rename from src/plugins/expressions/common/expression_types/datatable.ts rename to src/plugins/expressions/common/expression_types/specs/datatable.ts index d58a709349c50..92254a3d02438 100644 --- a/src/plugins/expressions/common/expression_types/datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts @@ -19,9 +19,9 @@ import { map, pick, zipObject } from 'lodash'; -import { ExpressionType } from '../types'; +import { ExpressionTypeDefinition } from '../types'; import { PointSeries } from './pointseries'; -import { Render } from './render'; +import { ExpressionValueRender } from './render'; const name = 'datatable'; @@ -70,7 +70,7 @@ interface RenderedDatatable { showHeader: boolean; } -export const datatable = (): ExpressionType => ({ +export const datatable: ExpressionTypeDefinition = { name, validate: table => { // TODO: Check columns types. Only string, boolean, number, date, allowed for now. @@ -115,7 +115,7 @@ export const datatable = (): ExpressionType => ({ + render: (table): ExpressionValueRender => ({ type: 'render', as: 'table', value: { @@ -143,4 +143,4 @@ export const datatable = (): ExpressionType; +export const isExpressionValueError = (value: any): value is ExpressionValueError => + getType(value) === 'error'; + /** * @deprecated * @@ -38,10 +45,10 @@ export type ExpressionValueError = ExpressionValueBoxed< */ export type InterpreterErrorType = ExpressionValueError; -export const error = (): ExpressionType<'error', ExpressionValueError> => ({ +export const error: ExpressionTypeDefinition<'error', ExpressionValueError> = { name, to: { - render: (input): Render> => { + render: (input): ExpressionValueRender> => { return { type: 'render', as: name, @@ -52,4 +59,4 @@ export const error = (): ExpressionType<'error', ExpressionValueError> => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/filter.ts b/src/plugins/expressions/common/expression_types/specs/filter.ts similarity index 90% rename from src/plugins/expressions/common/expression_types/filter.ts rename to src/plugins/expressions/common/expression_types/specs/filter.ts index 2608da6854b18..01d6b8a603db6 100644 --- a/src/plugins/expressions/common/expression_types/filter.ts +++ b/src/plugins/expressions/common/expression_types/specs/filter.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ExpressionType } from '../types'; +import { ExpressionTypeDefinition } from '../types'; const name = 'filter'; @@ -34,7 +34,7 @@ export interface Filter { query?: string | null; } -export const filter = (): ExpressionType => ({ +export const filter: ExpressionTypeDefinition = { name, from: { null: () => { @@ -47,4 +47,4 @@ export const filter = (): ExpressionType => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/image.ts b/src/plugins/expressions/common/expression_types/specs/image.ts similarity index 78% rename from src/plugins/expressions/common/expression_types/image.ts rename to src/plugins/expressions/common/expression_types/specs/image.ts index b4b6b27bbc8bc..8d89959cddb01 100644 --- a/src/plugins/expressions/common/expression_types/image.ts +++ b/src/plugins/expressions/common/expression_types/specs/image.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ExpressionType } from '../types'; -import { Render } from './render'; +import { ExpressionTypeDefinition } from '../types'; +import { ExpressionValueRender } from './render'; const name = 'image'; @@ -28,10 +28,10 @@ export interface ExpressionImage { dataurl: string; } -export const image = (): ExpressionType => ({ +export const image: ExpressionTypeDefinition = { name, to: { - render: (input): Render> => { + render: (input): ExpressionValueRender> => { return { type: 'render', as: 'image', @@ -39,4 +39,4 @@ export const image = (): ExpressionType => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/specs/index.ts b/src/plugins/expressions/common/expression_types/specs/index.ts new file mode 100644 index 0000000000000..31210b11f6b7a --- /dev/null +++ b/src/plugins/expressions/common/expression_types/specs/index.ts @@ -0,0 +1,72 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { boolean } from './boolean'; +import { datatable } from './datatable'; +import { error } from './error'; +import { filter } from './filter'; +import { image } from './image'; +import { kibanaContext } from './kibana_context'; +import { kibanaDatatable } from './kibana_datatable'; +import { nullType } from './null'; +import { num } from './num'; +import { number } from './number'; +import { pointseries } from './pointseries'; +import { range } from './range'; +import { render } from './render'; +import { shape } from './shape'; +import { string } from './string'; +import { style } from './style'; +import { AnyExpressionTypeDefinition } from '../types'; + +export const typeSpecs: AnyExpressionTypeDefinition[] = [ + boolean, + datatable, + error, + filter, + image, + kibanaContext, + kibanaDatatable, + nullType, + num, + number, + pointseries, + range, + render, + shape, + string, + style, +]; + +export * from './boolean'; +export * from './datatable'; +export * from './error'; +export * from './filter'; +export * from './image'; +export * from './kibana_context'; +export * from './kibana_datatable'; +export * from './null'; +export * from './num'; +export * from './number'; +export * from './pointseries'; +export * from './range'; +export * from './render'; +export * from './shape'; +export * from './string'; +export * from './style'; diff --git a/src/plugins/expressions/common/expression_types/kibana_context.ts b/src/plugins/expressions/common/expression_types/specs/kibana_context.ts similarity index 68% rename from src/plugins/expressions/common/expression_types/kibana_context.ts rename to src/plugins/expressions/common/expression_types/specs/kibana_context.ts index bcf8e2853dec8..3af7b990429c0 100644 --- a/src/plugins/expressions/common/expression_types/kibana_context.ts +++ b/src/plugins/expressions/common/expression_types/specs/kibana_context.ts @@ -17,24 +17,24 @@ * under the License. */ -import { TimeRange, Query, esFilters } from 'src/plugins/data/public'; +import { ExpressionValueBoxed } from '../types'; +import { ExecutionContextSearch } from '../../execution/types'; -const name = 'kibana_context'; -export type KIBANA_CONTEXT_NAME = 'kibana_context'; +export type ExpressionValueSearchContext = ExpressionValueBoxed< + 'kibana_context', + ExecutionContextSearch +>; -export interface KibanaContext { - type: typeof name; - query?: Query | Query[]; - filters?: esFilters.Filter[]; - timeRange?: TimeRange; -} +// TODO: These two are exported for legacy reasons - remove them eventually. +export type KIBANA_CONTEXT_NAME = 'kibana_context'; +export type KibanaContext = ExpressionValueSearchContext; -export const kibanaContext = () => ({ - name, +export const kibanaContext = { + name: 'kibana_context', from: { null: () => { return { - type: name, + type: 'kibana_context', }; }, }, @@ -45,4 +45,4 @@ export const kibanaContext = () => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/kibana_datatable.ts b/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts similarity index 95% rename from src/plugins/expressions/common/expression_types/kibana_datatable.ts rename to src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts index 38227d2ed6207..7742594d751de 100644 --- a/src/plugins/expressions/common/expression_types/kibana_datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/kibana_datatable.ts @@ -18,7 +18,7 @@ */ import { map } from 'lodash'; -import { SerializedFieldFormat } from '../types/common'; +import { SerializedFieldFormat } from '../../types/common'; import { Datatable, PointSeries } from '.'; const name = 'kibana_datatable'; @@ -46,7 +46,7 @@ export interface KibanaDatatable { rows: KibanaDatatableRow[]; } -export const kibanaDatatable = () => ({ +export const kibanaDatatable = { name, from: { datatable: (context: Datatable) => { @@ -72,4 +72,4 @@ export const kibanaDatatable = () => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/null.ts b/src/plugins/expressions/common/expression_types/specs/null.ts similarity index 87% rename from src/plugins/expressions/common/expression_types/null.ts rename to src/plugins/expressions/common/expression_types/specs/null.ts index 63039507870fc..60ded1dbca02f 100644 --- a/src/plugins/expressions/common/expression_types/null.ts +++ b/src/plugins/expressions/common/expression_types/specs/null.ts @@ -17,13 +17,13 @@ * under the License. */ -import { ExpressionType } from '../types'; +import { ExpressionTypeDefinition } from '../types'; const name = 'null'; -export const nullType = (): ExpressionType => ({ +export const nullType: ExpressionTypeDefinition = { name, from: { '*': () => null, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/specs/num.ts b/src/plugins/expressions/common/expression_types/specs/num.ts new file mode 100644 index 0000000000000..99b3bc3419173 --- /dev/null +++ b/src/plugins/expressions/common/expression_types/specs/num.ts @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; +import { Datatable } from './datatable'; +import { ExpressionValueRender } from './render'; + +export type ExpressionValueNum = ExpressionValueBoxed< + 'num', + { + value: number; + } +>; + +export const num: ExpressionTypeDefinition<'num', ExpressionValueNum> = { + name: 'num', + from: { + null: () => ({ + type: 'num', + value: 0, + }), + boolean: b => ({ + type: 'num', + value: Number(b), + }), + string: n => { + const value = Number(n); + if (Number.isNaN(value)) { + throw new Error( + i18n.translate('expressions.types.number.fromStringConversionErrorMessage', { + defaultMessage: 'Can\'t typecast "{string}" string to number', + values: { + string: n, + }, + }) + ); + } + return { + type: 'num', + value, + }; + }, + '*': value => ({ + type: 'num', + value: Number(value), + }), + }, + to: { + render: ({ value }): ExpressionValueRender<{ text: string }> => { + const text = `${value}`; + return { + type: 'render', + as: 'text', + value: { text }, + }; + }, + datatable: ({ value }): Datatable => ({ + type: 'datatable', + columns: [{ name: 'value', type: 'number' }], + rows: [{ value }], + }), + }, +}; diff --git a/src/plugins/expressions/common/expression_types/number.ts b/src/plugins/expressions/common/expression_types/specs/number.ts similarity index 86% rename from src/plugins/expressions/common/expression_types/number.ts rename to src/plugins/expressions/common/expression_types/specs/number.ts index b168391c7a65d..f346ae837adb4 100644 --- a/src/plugins/expressions/common/expression_types/number.ts +++ b/src/plugins/expressions/common/expression_types/specs/number.ts @@ -18,13 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionType } from '../../common/types'; +import { ExpressionTypeDefinition } from '../types'; import { Datatable } from './datatable'; -import { Render } from './render'; +import { ExpressionValueRender } from './render'; const name = 'number'; -export const number = (): ExpressionType => ({ +export const number: ExpressionTypeDefinition = { name, from: { null: () => 0, @@ -45,7 +45,7 @@ export const number = (): ExpressionType => ({ }, }, to: { - render: (value: number): Render<{ text: string }> => { + render: (value: number): ExpressionValueRender<{ text: string }> => { const text = `${value}`; return { type: 'render', @@ -59,4 +59,4 @@ export const number = (): ExpressionType => ({ rows: [{ value }], }), }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/pointseries.ts b/src/plugins/expressions/common/expression_types/specs/pointseries.ts similarity index 87% rename from src/plugins/expressions/common/expression_types/pointseries.ts rename to src/plugins/expressions/common/expression_types/specs/pointseries.ts index adf2bfc67f160..9058c003b41bd 100644 --- a/src/plugins/expressions/common/expression_types/pointseries.ts +++ b/src/plugins/expressions/common/expression_types/specs/pointseries.ts @@ -17,10 +17,9 @@ * under the License. */ -import { ExpressionType } from '../types'; +import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; import { Datatable } from './datatable'; -import { Render } from './render'; -import { ExpressionValueBoxed } from '../types/types'; +import { ExpressionValueRender } from './render'; const name = 'pointseries'; @@ -56,7 +55,7 @@ export type PointSeries = ExpressionValueBoxed< } >; -export const pointseries = (): ExpressionType<'pointseries', PointSeries> => ({ +export const pointseries: ExpressionTypeDefinition<'pointseries', PointSeries> = { name, from: { null: () => { @@ -71,7 +70,7 @@ export const pointseries = (): ExpressionType<'pointseries', PointSeries> => ({ render: ( pseries: PointSeries, types - ): Render<{ datatable: Datatable; showHeader: boolean }> => { + ): ExpressionValueRender<{ datatable: Datatable; showHeader: boolean }> => { const datatable: Datatable = types.datatable.from(pseries, types); return { type: 'render', @@ -83,4 +82,4 @@ export const pointseries = (): ExpressionType<'pointseries', PointSeries> => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/range.ts b/src/plugins/expressions/common/expression_types/specs/range.ts similarity index 83% rename from src/plugins/expressions/common/expression_types/range.ts rename to src/plugins/expressions/common/expression_types/specs/range.ts index 082056c909988..3d7170cf715d7 100644 --- a/src/plugins/expressions/common/expression_types/range.ts +++ b/src/plugins/expressions/common/expression_types/specs/range.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ExpressionType } from '../types'; -import { Render } from '.'; +import { ExpressionTypeDefinition } from '../types'; +import { ExpressionValueRender } from '.'; const name = 'range'; @@ -28,7 +28,7 @@ export interface Range { to: number; } -export const range = (): ExpressionType => ({ +export const range: ExpressionTypeDefinition = { name, from: { null: (): Range => { @@ -40,7 +40,7 @@ export const range = (): ExpressionType => ({ }, }, to: { - render: (value: Range): Render<{ text: string }> => { + render: (value: Range): ExpressionValueRender<{ text: string }> => { const text = `from ${value.from} to ${value.to}`; return { type: 'render', @@ -49,4 +49,4 @@ export const range = (): ExpressionType => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/render.ts b/src/plugins/expressions/common/expression_types/specs/render.ts similarity index 71% rename from src/plugins/expressions/common/expression_types/render.ts rename to src/plugins/expressions/common/expression_types/specs/render.ts index 3d6852b897508..d0af59ba6d718 100644 --- a/src/plugins/expressions/common/expression_types/render.ts +++ b/src/plugins/expressions/common/expression_types/specs/render.ts @@ -17,15 +17,14 @@ * under the License. */ -import { ExpressionType } from '../types'; -import { ExpressionValueBoxed } from '../types/types'; +import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; const name = 'render'; /** * Represents an object that is intended to be rendered. */ -export type Render = ExpressionValueBoxed< +export type ExpressionValueRender = ExpressionValueBoxed< typeof name, { as: string; @@ -33,13 +32,20 @@ export type Render = ExpressionValueBoxed< } >; -export const render = (): ExpressionType> => ({ +/** + * @deprecated + * + * Use `ExpressionValueRender` instead. + */ +export type Render = ExpressionValueRender; + +export const render: ExpressionTypeDefinition> = { name, from: { - '*': (v: T): Render => ({ + '*': (v: T): ExpressionValueRender => ({ type: name, as: 'debug', value: v, }), }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/shape.ts b/src/plugins/expressions/common/expression_types/specs/shape.ts similarity index 83% rename from src/plugins/expressions/common/expression_types/shape.ts rename to src/plugins/expressions/common/expression_types/specs/shape.ts index fd176e188a47b..315838043cb49 100644 --- a/src/plugins/expressions/common/expression_types/shape.ts +++ b/src/plugins/expressions/common/expression_types/specs/shape.ts @@ -17,12 +17,12 @@ * under the License. */ -import { ExpressionType } from '../types'; -import { Render } from './render'; +import { ExpressionTypeDefinition } from '../types'; +import { ExpressionValueRender } from './render'; const name = 'shape'; -export const shape = (): ExpressionType> => ({ +export const shape: ExpressionTypeDefinition> = { name: 'shape', to: { render: input => { @@ -33,4 +33,4 @@ export const shape = (): ExpressionType> => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/string.ts b/src/plugins/expressions/common/expression_types/specs/string.ts similarity index 83% rename from src/plugins/expressions/common/expression_types/string.ts rename to src/plugins/expressions/common/expression_types/specs/string.ts index 52b7c35189612..d46f0e5f6b7c2 100644 --- a/src/plugins/expressions/common/expression_types/string.ts +++ b/src/plugins/expressions/common/expression_types/specs/string.ts @@ -17,13 +17,13 @@ * under the License. */ -import { ExpressionType } from '../types'; +import { ExpressionTypeDefinition } from '../types'; import { Datatable } from './datatable'; -import { Render } from './render'; +import { ExpressionValueRender } from './render'; const name = 'string'; -export const string = (): ExpressionType => ({ +export const string: ExpressionTypeDefinition = { name, from: { null: () => '', @@ -31,7 +31,7 @@ export const string = (): ExpressionType => ({ number: n => String(n), }, to: { - render: (text: T): Render<{ text: T }> => { + render: (text: T): ExpressionValueRender<{ text: T }> => { return { type: 'render', as: 'text', @@ -44,4 +44,4 @@ export const string = (): ExpressionType => ({ rows: [{ value }], }), }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/style.ts b/src/plugins/expressions/common/expression_types/specs/style.ts similarity index 82% rename from src/plugins/expressions/common/expression_types/style.ts rename to src/plugins/expressions/common/expression_types/specs/style.ts index d93893d25c11c..57c12e2829fa0 100644 --- a/src/plugins/expressions/common/expression_types/style.ts +++ b/src/plugins/expressions/common/expression_types/specs/style.ts @@ -17,11 +17,12 @@ * under the License. */ -import { ExpressionType, ExpressionTypeStyle } from '../types'; +import { ExpressionTypeDefinition } from '../types'; +import { ExpressionTypeStyle } from '../../types/style'; const name = 'style'; -export const style = (): ExpressionType => ({ +export const style: ExpressionTypeDefinition = { name, from: { null: () => { @@ -32,4 +33,4 @@ export const style = (): ExpressionType => ({ }; }, }, -}); +}; diff --git a/src/plugins/expressions/common/expression_types/tests/number.test.ts b/src/plugins/expressions/common/expression_types/specs/tests/number.test.ts similarity index 91% rename from src/plugins/expressions/common/expression_types/tests/number.test.ts rename to src/plugins/expressions/common/expression_types/specs/tests/number.test.ts index 3336a1384ea79..c643ae849c034 100644 --- a/src/plugins/expressions/common/expression_types/tests/number.test.ts +++ b/src/plugins/expressions/common/expression_types/specs/tests/number.test.ts @@ -21,7 +21,7 @@ import { number } from '../number'; describe('number', () => { it('should fail when typecasting not numeric string to number', () => { - expect(() => number().from!.string('123test', {})).toThrowErrorMatchingInlineSnapshot( + expect(() => number.from!.string('123test', {})).toThrowErrorMatchingInlineSnapshot( `"Can't typecast \\"123test\\" string to number"` ); }); diff --git a/src/plugins/expressions/common/types/types.ts b/src/plugins/expressions/common/expression_types/types.ts similarity index 93% rename from src/plugins/expressions/common/types/types.ts rename to src/plugins/expressions/common/expression_types/types.ts index e7b30d24fa6eb..3817530c27029 100644 --- a/src/plugins/expressions/common/types/types.ts +++ b/src/plugins/expressions/common/expression_types/types.ts @@ -34,7 +34,7 @@ export type ExpressionValueConverter; +export type AnyExpressionTypeDefinition = ExpressionTypeDefinition; diff --git a/src/plugins/expressions/common/index.ts b/src/plugins/expressions/common/index.ts index f4bd448c19772..f03fdcbda7ff1 100644 --- a/src/plugins/expressions/common/index.ts +++ b/src/plugins/expressions/common/index.ts @@ -17,6 +17,13 @@ * under the License. */ -export * from './type'; export * from './types'; +export * from './ast'; +export * from './fonts'; export * from './expression_types'; +export * from './expression_functions'; +export * from './expression_renderers'; +export * from './executor'; +export * from './execution'; +export * from './service'; +export * from './util'; diff --git a/src/plugins/expressions/common/mocks.ts b/src/plugins/expressions/common/mocks.ts new file mode 100644 index 0000000000000..502d88ac955ae --- /dev/null +++ b/src/plugins/expressions/common/mocks.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExecutionContext } from './execution/types'; + +export const createMockExecutionContext = ( + extraContext: ExtraContext = {} as ExtraContext +): ExecutionContext & ExtraContext => { + const executionContext: ExecutionContext = { + getInitialInput: jest.fn(), + variables: {}, + types: {}, + abortSignal: { + aborted: false, + addEventListener: jest.fn(), + dispatchEvent: jest.fn(), + onabort: jest.fn(), + removeEventListener: jest.fn(), + }, + inspectorAdapters: { + requests: {} as any, + data: {} as any, + }, + search: {}, + }; + + return { + ...executionContext, + ...extraContext, + }; +}; diff --git a/src/plugins/expressions/common/service/expressions_services.test.ts b/src/plugins/expressions/common/service/expressions_services.test.ts new file mode 100644 index 0000000000000..c9687192481c6 --- /dev/null +++ b/src/plugins/expressions/common/service/expressions_services.test.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionsService } from './expressions_services'; + +describe('ExpressionsService', () => { + test('can instantiate', () => { + new ExpressionsService(); + }); + + test('returns expected setup contract', () => { + const expressions = new ExpressionsService(); + + expect(expressions.setup()).toMatchObject({ + getFunctions: expect.any(Function), + registerFunction: expect.any(Function), + registerType: expect.any(Function), + registerRenderer: expect.any(Function), + run: expect.any(Function), + }); + }); + + test('returns expected start contract', () => { + const expressions = new ExpressionsService(); + expressions.setup(); + + expect(expressions.start()).toMatchObject({ + getFunctions: expect.any(Function), + run: expect.any(Function), + }); + }); + + test('has pre-installed default functions', () => { + const expressions = new ExpressionsService(); + + expect(typeof expressions.setup().getFunctions().var_set).toBe('object'); + }); +}); diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts new file mode 100644 index 0000000000000..8543fbe0fced2 --- /dev/null +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -0,0 +1,168 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Executor } from '../executor'; +import { ExpressionRendererRegistry } from '../expression_renderers'; +import { ExpressionAstExpression } from '../ast'; + +export type ExpressionsServiceSetup = ReturnType; +export type ExpressionsServiceStart = ReturnType; + +/** + * `ExpressionsService` class is used for multiple purposes: + * + * 1. It implements the same Expressions service that can be used on both: + * (1) server-side and (2) browser-side. + * 2. It implements the same Expressions service that users can fork/clone, + * thus have their own instance of the Expressions plugin. + * 3. `ExpressionsService` defines the public contracts of *setup* and *start* + * Kibana Platform life-cycles for ease-of-use on server-side and browser-side. + * 4. `ExpressionsService` creates a bound version of all exported contract functions. + * 5. Functions are bound the way there are: + * + * ```ts + * registerFunction = (...args: Parameters + * ): ReturnType => this.executor.registerFunction(...args); + * ``` + * + * so that JSDoc appears in developers IDE when they use those `plugins.expressions.registerFunction(`. + */ +export class ExpressionsService { + public readonly executor = Executor.createWithDefaults(); + public readonly renderers = new ExpressionRendererRegistry(); + + /** + * Register an expression function, which will be possible to execute as + * part of the expression pipeline. + * + * Below we register a function which simply sleeps for given number of + * milliseconds to delay the execution and outputs its input as-is. + * + * ```ts + * expressions.registerFunction({ + * name: 'sleep', + * args: { + * time: { + * aliases: ['_'], + * help: 'Time in milliseconds for how long to sleep', + * types: ['number'], + * }, + * }, + * help: '', + * fn: async (input, args, context) => { + * await new Promise(r => setTimeout(r, args.time)); + * return input; + * }, + * } + * ``` + * + * The actual function is defined in the `fn` key. The function can be *async*. + * It receives three arguments: (1) `input` is the output of the previous function + * or the initial input of the expression if the function is first in chain; + * (2) `args` are function arguments as defined in expression string, that can + * be edited by user (e.g in case of Canvas); (3) `context` is a shared object + * passed to all functions that can be used for side-effects. + */ + public readonly registerFunction = ( + ...args: Parameters + ): ReturnType => this.executor.registerFunction(...args); + + /** + * Executes expression string or a parsed expression AST and immediately + * returns the result. + * + * Below example will execute `sleep 100 | clog` expression with `123` initial + * input to the first function. + * + * ```ts + * expressions.run('sleep 100 | clog', 123); + * ``` + * + * - `sleep 100` will delay execution by 100 milliseconds and pass the `123` input as + * its output. + * - `clog` will print to console `123` and pass it as its output. + * - The final result of the execution will be `123`. + * + * Optionally, you can pass an object as the third argument which will be used + * to extend the `ExecutionContext`—an object passed to each function + * as the third argument, that allows functions to perform side-effects. + * + * ```ts + * expressions.run('...', null, { elasticsearchClient }); + * ``` + */ + public readonly run = < + Input, + Output, + ExtraContext extends Record = Record + >( + ast: string | ExpressionAstExpression, + input: Input, + context?: ExtraContext + ): Promise => this.executor.run(ast, input, context); + + public setup() { + const { executor, renderers, registerFunction, run } = this; + + const getFunction = executor.getFunction.bind(executor); + const getFunctions = executor.getFunctions.bind(executor); + const getRenderer = renderers.get.bind(renderers); + const getRenderers = renderers.toJS.bind(renderers); + const getType = executor.getType.bind(executor); + const getTypes = executor.getTypes.bind(executor); + const registerRenderer = renderers.register.bind(renderers); + const registerType = executor.registerType.bind(executor); + + return { + getFunction, + getFunctions, + getRenderer, + getRenderers, + getType, + getTypes, + registerFunction, + registerRenderer, + registerType, + run, + }; + } + + public start() { + const { executor, renderers, run } = this; + + const getFunction = executor.getFunction.bind(executor); + const getFunctions = executor.getFunctions.bind(executor); + const getRenderer = renderers.get.bind(renderers); + const getRenderers = renderers.toJS.bind(renderers); + const getType = executor.getType.bind(executor); + const getTypes = executor.getTypes.bind(executor); + + return { + getFunction, + getFunctions, + getRenderer, + getRenderers, + getType, + getTypes, + run, + }; + } + + public stop() {} +} diff --git a/src/plugins/expressions/common/service/index.ts b/src/plugins/expressions/common/service/index.ts new file mode 100644 index 0000000000000..219da048251f7 --- /dev/null +++ b/src/plugins/expressions/common/service/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './expressions_services'; diff --git a/src/plugins/expressions/public/registries/registry.ts b/src/plugins/expressions/common/test_helpers/create_unit_test_executor.ts similarity index 69% rename from src/plugins/expressions/public/registries/registry.ts rename to src/plugins/expressions/common/test_helpers/create_unit_test_executor.ts index fe149116fbf14..1414db4f50b27 100644 --- a/src/plugins/expressions/public/registries/registry.ts +++ b/src/plugins/expressions/common/test_helpers/create_unit_test_executor.ts @@ -17,26 +17,15 @@ * under the License. */ -export class Registry { - private data: Record = {}; +import { Executor } from '../executor'; +import { functionTestSpecs } from './expression_functions'; - set(id: string, item: T) { - this.data[id] = item; - } - - get(id: string): T | null { - return this.data[id] || null; - } +export const createUnitTestExecutor = () => { + const executor = Executor.createWithDefaults(); - toJS(): Record { - return { ...this.data }; + for (const func of functionTestSpecs) { + executor.registerFunction(func); } - toArray(): T[] { - return Object.values(this.data); - } - - reset() { - this.data = {}; - } -} + return executor; +}; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/access.ts b/src/plugins/expressions/common/test_helpers/expression_functions/access.ts new file mode 100644 index 0000000000000..72adf95745f7d --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/expression_functions/access.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionFunctionDefinition } from '../../expression_functions'; + +export const access: ExpressionFunctionDefinition<'access', any, { key: string }, any> = { + name: 'access', + help: 'Access key on input object or return the input, if it is not an object', + args: { + key: { + aliases: ['_'], + help: 'Key on input object', + types: ['string'], + }, + }, + fn: (input, { key }, context) => { + return !input ? input : typeof input === 'object' ? input[key] : input; + }, +}; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/add.ts b/src/plugins/expressions/common/test_helpers/expression_functions/add.ts new file mode 100644 index 0000000000000..5c031a64e4cc5 --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/expression_functions/add.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionFunctionDefinition } from '../../expression_functions'; +import { ExpressionValueNum } from '../../expression_types'; + +export const add: ExpressionFunctionDefinition< + 'add', + ExpressionValueNum, + { val: number | null | string }, + ExpressionValueNum +> = { + name: 'add', + help: 'This function adds a number to input', + inputTypes: ['num'], + args: { + val: { + default: 0, + aliases: ['_'], + help: 'Number to add to input', + types: ['null', 'number', 'string'], + }, + }, + fn: ({ value: value1 }, { val: input2 }, context) => { + const value2 = !input2 + ? 0 + : typeof input2 === 'object' + ? (input2 as any).value + : Number(input2); + + return { + type: 'num', + value: value1 + value2, + }; + }, +}; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/error.ts b/src/plugins/expressions/common/test_helpers/expression_functions/error.ts new file mode 100644 index 0000000000000..e672bccad4720 --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/expression_functions/error.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionFunctionDefinition } from '../../expression_functions'; +import { ExpressionValueNum } from '../../expression_types'; + +export const error: ExpressionFunctionDefinition< + 'error', + ExpressionValueNum, + { message: string }, + ExpressionValueNum +> = { + name: 'error', + help: 'This function always throws an error', + args: { + message: { + default: 'Unknown', + aliases: ['_'], + help: 'Number to add to input', + types: ['string'], + }, + }, + fn: (input, args, context) => { + throw new Error(args.message); + }, +}; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/index.ts b/src/plugins/expressions/common/test_helpers/expression_functions/index.ts new file mode 100644 index 0000000000000..5b141983b7bec --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/expression_functions/index.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { access } from './access'; +import { add } from './add'; +import { error } from './error'; +import { introspectContext } from './introspect_context'; +import { mult } from './mult'; +import { sleep } from './sleep'; +import { AnyExpressionFunctionDefinition } from '../../expression_functions'; + +export const functionTestSpecs: AnyExpressionFunctionDefinition[] = [ + access, + add, + error, + introspectContext, + mult, + sleep, +]; diff --git a/src/plugins/expressions/public/serialize_provider.ts b/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts similarity index 64% rename from src/plugins/expressions/public/serialize_provider.ts rename to src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts index f5a69ed52ed52..0e2b356b5c5a9 100644 --- a/src/plugins/expressions/public/serialize_provider.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts @@ -17,21 +17,26 @@ * under the License. */ -import { get, identity } from 'lodash'; -import { getType } from '../common/type'; +import { ExpressionFunctionDefinition } from '../../expression_functions'; -export function serializeProvider(types: any) { - return { - serialize: provider('serialize'), - deserialize: provider('deserialize'), - }; - - function provider(key: any) { - return (context: any) => { - const type = getType(context); - const typeDef = types[type]; - const fn: any = get(typeDef, key) || identity; - return fn(context); +export const introspectContext: ExpressionFunctionDefinition< + 'introspectContext', + any, + { key: string }, + any +> = { + name: 'introspectContext', + args: { + key: { + help: 'Context key to introspect', + types: ['string'], + }, + }, + help: '', + fn: (input, args, context) => { + return { + type: 'any', + result: (context as any)[args.key], }; - } -} + }, +}; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/mult.ts b/src/plugins/expressions/common/test_helpers/expression_functions/mult.ts new file mode 100644 index 0000000000000..7a220188c6cea --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/expression_functions/mult.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionFunctionDefinition } from '../../expression_functions'; +import { ExpressionValueNum } from '../../expression_types'; + +export const mult: ExpressionFunctionDefinition< + 'mult', + ExpressionValueNum, + { val: number }, + ExpressionValueNum +> = { + name: 'mult', + help: 'This function multiplies input by a number', + args: { + val: { + default: 0, + help: 'Number to multiply input by', + types: ['number'], + }, + }, + fn: ({ value }, args, context) => { + return { + type: 'num', + value: value * args.val, + }; + }, +}; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts new file mode 100644 index 0000000000000..e9ff6e0698560 --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionFunctionDefinition } from '../../expression_functions'; + +export const sleep: ExpressionFunctionDefinition<'sleep', any, { time: number }, any> = { + name: 'sleep', + args: { + time: { + aliases: ['_'], + help: 'Time in milliseconds for how long to sleep', + types: ['number'], + }, + }, + help: '', + fn: async (input, args, context) => { + await new Promise(r => setTimeout(r, args.time)); + return input; + }, +}; diff --git a/src/plugins/expressions/common/test_helpers/index.ts b/src/plugins/expressions/common/test_helpers/index.ts new file mode 100644 index 0000000000000..c1e68496140e7 --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './create_unit_test_executor'; diff --git a/src/plugins/expressions/common/types/common.ts b/src/plugins/expressions/common/types/common.ts index 68df29ee69846..f532f9708940e 100644 --- a/src/plugins/expressions/common/types/common.ts +++ b/src/plugins/expressions/common/types/common.ts @@ -17,6 +17,8 @@ * under the License. */ +import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; + /** * This can convert a type into a known Expression string representation of * that type. For example, `TypeToString` will resolve to `'datatable'`. @@ -45,7 +47,7 @@ export type KnownTypeToString = * * `someArgument: Promise` results in `types: ['boolean', 'string']` */ -export type TypeString = KnownTypeToString>; +export type TypeString = KnownTypeToString>; /** * Types used in Expressions that don't map to a primitive cleanly: @@ -54,11 +56,6 @@ export type TypeString = KnownTypeToString>; */ export type UnmappedTypeStrings = 'date' | 'filter'; -/** - * Utility type: extracts returned type from a Promise. - */ -export type UnwrapPromise = T extends Promise ? P : T; - /** * JSON representation of a field formatter configuration. * Is used to carry information about how to format data in diff --git a/src/plugins/expressions/common/types/functions.ts b/src/plugins/expressions/common/types/functions.ts deleted file mode 100644 index 5ead129398e42..0000000000000 --- a/src/plugins/expressions/common/types/functions.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ArgumentType } from './arguments'; -import { TypeToString, UnwrapPromise } from './common'; - -/** - * A generic type which represents an Expression Function definition. - */ -export interface ExpressionFunction { - /** Arguments for the Function */ - args: { [key in keyof Arguments]: ArgumentType }; - aliases?: string[]; - context?: { - types: Array>; - }; - /** Help text displayed in the Expression editor */ - help: string; - /** The name of the Function */ - name: Name; - /** The type of the Function */ - type?: TypeToString>; - /** The implementation of the Function */ - fn(context: Context, args: Arguments, handlers: FunctionHandlers): Return; -} - -// TODO: Handlers can be passed to the `fn` property of the Function. At the moment, these Functions -// are not strongly defined. -export interface FunctionHandlers { - [key: string]: (...args: any) => any; -} - -export type AnyExpressionFunction = ExpressionFunction; diff --git a/src/plugins/expressions/common/types/index.ts b/src/plugins/expressions/common/types/index.ts index d3be079604dee..4313ea934d038 100644 --- a/src/plugins/expressions/common/types/index.ts +++ b/src/plugins/expressions/common/types/index.ts @@ -17,34 +17,13 @@ * under the License. */ -export * from './types'; - export { TypeToString, KnownTypeToString, TypeString, UnmappedTypeStrings, - UnwrapPromise, SerializedFieldFormat, } from './common'; export * from './style'; - -export { ArgumentType } from './arguments'; - -export { ExpressionFunction, AnyExpressionFunction, FunctionHandlers } from './functions'; - -export type ExpressionArgAST = string | boolean | number | ExpressionAST; - -export interface ExpressionFunctionAST { - type: 'function'; - function: string; - arguments: { - [key: string]: ExpressionArgAST[]; - }; -} - -export interface ExpressionAST { - type: 'expression'; - chain: ExpressionFunctionAST[]; -} +export * from './registry'; diff --git a/src/plugins/expressions/common/types/registry.ts b/src/plugins/expressions/common/types/registry.ts new file mode 100644 index 0000000000000..ba4bff3b8f1bb --- /dev/null +++ b/src/plugins/expressions/common/types/registry.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface IRegistry { + get(id: string): T | null; + + toJS(): Record; + + toArray(): T[]; +} diff --git a/src/plugins/expressions/public/create_error.ts b/src/plugins/expressions/common/util/create_error.ts similarity index 100% rename from src/plugins/expressions/public/create_error.ts rename to src/plugins/expressions/common/util/create_error.ts diff --git a/src/plugins/expressions/common/util/get_by_alias.ts b/src/plugins/expressions/common/util/get_by_alias.ts new file mode 100644 index 0000000000000..6868abb5da923 --- /dev/null +++ b/src/plugins/expressions/common/util/get_by_alias.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This is used for looking up function/argument definitions. It looks through + * the given object/array for a case-insensitive match, which could be either the + * `name` itself, or something under the `aliases` property. + */ +export function getByAlias( + node: T[] | Record, + nodeName: string +): T | undefined { + const lowerCaseName = nodeName.toLowerCase(); + return Object.values(node).find(({ name, aliases }) => { + if (!name) return false; + if (name.toLowerCase() === lowerCaseName) return true; + return (aliases || []).some(alias => { + return alias.toLowerCase() === lowerCaseName; + }); + }); +} diff --git a/src/plugins/expressions/common/util/index.ts b/src/plugins/expressions/common/util/index.ts new file mode 100644 index 0000000000000..ee677d54ce968 --- /dev/null +++ b/src/plugins/expressions/common/util/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './create_error'; +export * from './get_by_alias'; diff --git a/src/plugins/expressions/index.ts b/src/plugins/expressions/index.ts new file mode 100644 index 0000000000000..a9794d9e4647a --- /dev/null +++ b/src/plugins/expressions/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './common'; diff --git a/src/plugins/expressions/public/batched_fetch.test.ts b/src/plugins/expressions/public/batched_fetch.test.ts deleted file mode 100644 index 7273be872a725..0000000000000 --- a/src/plugins/expressions/public/batched_fetch.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { batchedFetch, Request } from './batched_fetch'; -import { defer } from '../../kibana_utils/public'; -import { Subject } from 'rxjs'; - -const serialize = (o: any) => JSON.stringify(o); - -const fetchStreaming = jest.fn(({ body }) => { - const { functions } = JSON.parse(body); - const { promise, resolve } = defer(); - const stream = new Subject(); - - setTimeout(() => { - functions.map(({ id, functionName, context, args }: Request) => - stream.next( - JSON.stringify({ - id, - statusCode: context, - result: Number(context) >= 400 ? { err: {} } : `${functionName}${context}${args}`, - }) + '\n' - ) - ); - resolve(); - }, 1); - - return { promise, stream }; -}) as any; - -describe('batchedFetch', () => { - it('resolves the correct promise', async () => { - const ajax = batchedFetch({ fetchStreaming, serialize, ms: 1 }); - - const result = await Promise.all([ - ajax({ functionName: 'a', context: 1, args: 'aaa' }), - ajax({ functionName: 'b', context: 2, args: 'bbb' }), - ]); - - expect(result).toEqual(['a1aaa', 'b2bbb']); - }); - - it('dedupes duplicate calls', async () => { - const ajax = batchedFetch({ fetchStreaming, serialize, ms: 1 }); - - const result = await Promise.all([ - ajax({ functionName: 'a', context: 1, args: 'aaa' }), - ajax({ functionName: 'b', context: 2, args: 'bbb' }), - ajax({ functionName: 'a', context: 1, args: 'aaa' }), - ajax({ functionName: 'a', context: 1, args: 'aaa' }), - ]); - - expect(result).toEqual(['a1aaa', 'b2bbb', 'a1aaa', 'a1aaa']); - expect(fetchStreaming).toHaveBeenCalledTimes(2); - }); - - it('rejects responses whose statusCode is >= 300', async () => { - const ajax = batchedFetch({ fetchStreaming, serialize, ms: 1 }); - - const result = await Promise.all([ - ajax({ functionName: 'a', context: 500, args: 'aaa' }).catch(() => 'fail'), - ajax({ functionName: 'b', context: 400, args: 'bbb' }).catch(() => 'fail'), - ajax({ functionName: 'c', context: 200, args: 'ccc' }), - ]); - - expect(result).toEqual(['fail', 'fail', 'c200ccc']); - }); -}); diff --git a/src/plugins/expressions/public/batched_fetch.ts b/src/plugins/expressions/public/batched_fetch.ts deleted file mode 100644 index 6a155b7d42b72..0000000000000 --- a/src/plugins/expressions/public/batched_fetch.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { filter, map } from 'rxjs/operators'; -// eslint-disable-next-line -import { split, BfetchPublicContract } from '../../bfetch/public'; -import { defer } from '../../kibana_utils/public'; - -export interface Options { - fetchStreaming: BfetchPublicContract['fetchStreaming']; - serialize: any; - ms?: number; -} - -export type Batch = Record; - -export interface BatchEntry { - future: any; - request: Request; -} - -export interface Request { - id?: number; - functionName: string; - args: any; - context: string; -} - -/** - * Create a function which executes an Expression function on the - * server as part of a larger batch of executions. - */ -export function batchedFetch({ fetchStreaming, serialize, ms = 10 }: Options) { - // Uniquely identifies each function call in a batch operation - // so that the appropriate promise can be resolved / rejected later. - let id = 0; - - // A map like { id: { future, request } }, which is used to - // track all of the function calls in a batch operation. - let batch: Batch = {}; - let timeout: any; - - const nextId = () => ++id; - - const reset = () => { - id = 0; - batch = {}; - timeout = undefined; - }; - - const runBatch = () => { - processBatch(fetchStreaming, batch); - reset(); - }; - - return ({ functionName, context, args }: any) => { - if (!timeout) { - timeout = setTimeout(runBatch, ms); - } - - const request: Request = { - functionName, - args, - context: serialize(context), - }; - - // Check to see if this is a duplicate server function. - const duplicate: any = Object.values(batch).find((batchedRequest: any) => - _.isMatch(batchedRequest.request, request) - ); - - // If it is, just return the promise of the duplicated request. - if (duplicate) { - return duplicate.future.promise; - } - - // If not, create a new promise, id, and add it to the batched collection. - const future = defer(); - const newId = nextId(); - request.id = newId; - - batch[newId] = { - future, - request, - }; - - return future.promise; - }; -} - -/** - * Runs the specified batch of functions on the server, then resolves - * the related promises. - */ -async function processBatch(fetchStreaming: BfetchPublicContract['fetchStreaming'], batch: Batch) { - const { stream } = fetchStreaming({ - url: `/api/interpreter/fns`, - body: JSON.stringify({ - functions: Object.values(batch).map(({ request }) => request), - }), - }); - - stream - .pipe( - split('\n'), - filter(Boolean), - map((json: string) => JSON.parse(json)) - ) - .subscribe((message: any) => { - const { id, statusCode, result } = message; - const { future } = batch[id]; - - if (statusCode >= 400) { - future.reject(result); - } else { - future.resolve(result); - } - }); - - try { - await stream.toPromise(); - } catch (error) { - Object.values(batch).forEach(({ future }) => { - future.reject(error); - }); - } -} diff --git a/src/plugins/expressions/public/execute.test.ts b/src/plugins/expressions/public/execute.test.ts index 9e2bb322ad5ff..a3469f05e6736 100644 --- a/src/plugins/expressions/public/execute.test.ts +++ b/src/plugins/expressions/public/execute.test.ts @@ -17,14 +17,13 @@ * under the License. */ -import { fromExpression } from '@kbn/interpreter/common'; import { execute, ExpressionDataHandler } from './execute'; -import { ExpressionAST } from '../common/types'; +import { ExpressionAstExpression, parseExpression } from '../common'; jest.mock('./services', () => ({ getInterpreter: () => { return { - interpretAst: async (expression: ExpressionAST) => { + interpretAst: async (expression: ExpressionAstExpression) => { return {}; }, }; @@ -55,7 +54,7 @@ describe('ExpressionDataHandler', () => { }); it('accepts expression AST', () => { - const expressionAST = fromExpression(expressionString) as ExpressionAST; + const expressionAST = parseExpression(expressionString) as ExpressionAstExpression; const expressionDataHandler = new ExpressionDataHandler(expressionAST, {}); expect(expressionDataHandler.getExpression()).toEqual(expressionString); expect(expressionDataHandler.getAst()).toEqual(expressionAST); @@ -70,7 +69,7 @@ describe('ExpressionDataHandler', () => { it('allows passing in search context', () => { const expressionDataHandler = new ExpressionDataHandler(expressionString, { - searchContext: { type: 'kibana_context', filters: [] }, + searchContext: { filters: [] }, }); expect(expressionDataHandler.getExpression()).toEqual(expressionString); }); diff --git a/src/plugins/expressions/public/execute.ts b/src/plugins/expressions/public/execute.ts index 9df4c1c4d79b6..c07fb9ad0549c 100644 --- a/src/plugins/expressions/public/execute.ts +++ b/src/plugins/expressions/public/execute.ts @@ -17,11 +17,15 @@ * under the License. */ -import { fromExpression, toExpression } from '@kbn/interpreter/common'; import { DataAdapter, RequestAdapter, Adapters } from '../../inspector/public'; import { getInterpreter } from './services'; -import { IExpressionLoaderParams, IInterpreterResult } from './types'; -import { ExpressionAST } from '../common/types'; +import { IExpressionLoaderParams } from './types'; +import { + ExpressionAstExpression, + parseExpression, + formatExpression, + ExpressionValue, +} from '../common'; /** * The search context describes a specific context (filters, time range and query) @@ -34,48 +38,42 @@ import { ExpressionAST } from '../common/types'; export class ExpressionDataHandler { private abortController: AbortController; private expression: string; - private ast: ExpressionAST; + private ast: ExpressionAstExpression; private inspectorAdapters: Adapters; - private promise: Promise; + private promise: Promise; public isPending: boolean = true; - constructor(expression: string | ExpressionAST, params: IExpressionLoaderParams) { + constructor(expression: string | ExpressionAstExpression, params: IExpressionLoaderParams) { if (typeof expression === 'string') { this.expression = expression; - this.ast = fromExpression(expression) as ExpressionAST; + this.ast = parseExpression(expression); } else { this.ast = expression; - this.expression = toExpression(this.ast); + this.expression = formatExpression(this.ast); } this.abortController = new AbortController(); this.inspectorAdapters = params.inspectorAdapters || this.getActiveInspectorAdapters(); - const getInitialContext = () => ({ - type: 'kibana_context', - ...params.searchContext, - }); - - const defaultContext = { type: 'null' }; - + const defaultInput = { type: 'null' }; const interpreter = getInterpreter(); this.promise = interpreter - .interpretAst(this.ast, params.context || defaultContext, { - getInitialContext, + .interpretAst(this.ast, params.context || defaultInput, { + search: params.searchContext, inspectorAdapters: this.inspectorAdapters, abortSignal: this.abortController.signal, variables: params.variables, }) .then( - (v: IInterpreterResult) => { + (v: ExpressionValue) => { this.isPending = false; return v; }, () => { this.isPending = false; } - ); + ) as Promise; } cancel = () => { @@ -133,7 +131,7 @@ export class ExpressionDataHandler { } export function execute( - expression: string | ExpressionAST, + expression: string | ExpressionAstExpression, params: IExpressionLoaderParams = {} ): ExpressionDataHandler { return new ExpressionDataHandler(expression, params); diff --git a/src/plugins/expressions/public/functions/kibana_context.ts b/src/plugins/expressions/public/expression_functions/kibana_context.ts similarity index 88% rename from src/plugins/expressions/public/functions/kibana_context.ts rename to src/plugins/expressions/public/expression_functions/kibana_context.ts index 1c873573aff2d..f997972c33839 100644 --- a/src/plugins/expressions/public/functions/kibana_context.ts +++ b/src/plugins/expressions/public/expression_functions/kibana_context.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction } from '../../common/types'; +import { ExpressionFunctionDefinition } from '../../common'; import { KibanaContext } from '../../common/expression_types'; import { savedObjects } from '../services'; @@ -29,7 +29,7 @@ interface Arguments { savedSearchId?: string | null; } -export type ExpressionFunctionKibanaContext = ExpressionFunction< +export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition< 'kibana_context', KibanaContext | null, Arguments, @@ -39,9 +39,7 @@ export type ExpressionFunctionKibanaContext = ExpressionFunction< export const kibanaContext = (): ExpressionFunctionKibanaContext => ({ name: 'kibana_context', type: 'kibana_context', - context: { - types: ['kibana_context', 'null'], - }, + inputTypes: ['kibana_context', 'null'], help: i18n.translate('expressions.functions.kibana_context.help', { defaultMessage: 'Updates kibana global context', }), @@ -76,7 +74,7 @@ export const kibanaContext = (): ExpressionFunctionKibanaContext => ({ }), }, }, - async fn(context, args, handlers) { + async fn(input, args) { const queryArg = args.q ? JSON.parse(args.q) : []; let queries = Array.isArray(queryArg) ? queryArg : [queryArg]; let filters = args.filters ? JSON.parse(args.filters) : []; @@ -89,18 +87,18 @@ export const kibanaContext = (): ExpressionFunctionKibanaContext => ({ filters = filters.concat(data.filter); } - if (context && context.query) { - queries = queries.concat(context.query); + if (input && input.query) { + queries = queries.concat(input.query); } - if (context && context.filters) { - filters = filters.concat(context.filters).filter((f: any) => !f.meta.disabled); + if (input && input.filters) { + filters = filters.concat(input.filters).filter((f: any) => !f.meta.disabled); } const timeRange = args.timeRange ? JSON.parse(args.timeRange) - : context - ? context.timeRange + : input + ? input.timeRange : undefined; return { diff --git a/src/plugins/expressions/public/fonts.ts b/src/plugins/expressions/public/fonts.ts deleted file mode 100644 index cdf3d4c16f3b5..0000000000000 --- a/src/plugins/expressions/public/fonts.ts +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * This type contains a unions of all supported font labels, or the the name of - * the font the user would see in a UI. - */ -export type FontLabel = typeof fonts[number]['label']; - -/** - * This type contains a union of all supported font values, equivalent to the CSS - * `font-value` property. - */ -export type FontValue = typeof fonts[number]['value']; - -/** - * An interface representing a font in Canvas, with a textual label and the CSS - * `font-value`. - */ -export interface Font { - label: FontLabel; - value: FontValue; -} - -// This function allows one to create a strongly-typed font for inclusion in -// the font collection. As a result, the values and labels are known to the -// type system, preventing one from specifying a non-existent font at build -// time. -function createFont< - RawFont extends { value: RawFontValue; label: RawFontLabel }, - RawFontValue extends string, - RawFontLabel extends string ->(font: RawFont) { - return font; -} - -export const americanTypewriter = createFont({ - label: 'American Typewriter', - value: "'American Typewriter', 'Courier New', Courier, Monaco, mono", -}); - -export const arial = createFont({ label: 'Arial', value: 'Arial, sans-serif' }); - -export const baskerville = createFont({ - label: 'Baskerville', - value: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const bookAntiqua = createFont({ - label: 'Book Antiqua', - value: "'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const brushScript = createFont({ - label: 'Brush Script', - value: "'Brush Script MT', 'Comic Sans', sans-serif", -}); - -export const chalkboard = createFont({ - label: 'Chalkboard', - value: "Chalkboard, 'Comic Sans', sans-serif", -}); - -export const didot = createFont({ - label: 'Didot', - value: "Didot, Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const futura = createFont({ - label: 'Futura', - value: 'Futura, Impact, Helvetica, Arial, sans-serif', -}); - -export const gillSans = createFont({ - label: 'Gill Sans', - value: - "'Gill Sans', 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", -}); - -export const helveticaNeue = createFont({ - label: 'Helvetica Neue', - value: "'Helvetica Neue', Helvetica, Arial, sans-serif", -}); - -export const hoeflerText = createFont({ - label: 'Hoefler Text', - value: "'Hoefler Text', Garamond, Georgia, 'Times New Roman', Times, serif", -}); - -export const lucidaGrande = createFont({ - label: 'Lucida Grande', - value: "'Lucida Grande', 'Lucida Sans Unicode', Lucida, Verdana, Helvetica, Arial, sans-serif", -}); - -export const myriad = createFont({ - label: 'Myriad', - value: 'Myriad, Helvetica, Arial, sans-serif', -}); - -export const openSans = createFont({ - label: 'Open Sans', - value: "'Open Sans', Helvetica, Arial, sans-serif", -}); - -export const optima = createFont({ - label: 'Optima', - value: "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", -}); - -export const palatino = createFont({ - label: 'Palatino', - value: "Palatino, 'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -/** - * A collection of supported fonts. - */ -export const fonts = [ - americanTypewriter, - arial, - baskerville, - bookAntiqua, - brushScript, - chalkboard, - didot, - futura, - gillSans, - helveticaNeue, - hoeflerText, - lucidaGrande, - myriad, - openSans, - optima, - palatino, -]; diff --git a/src/plugins/expressions/public/functions/font.ts b/src/plugins/expressions/public/functions/font.ts deleted file mode 100644 index 096f0ef196be3..0000000000000 --- a/src/plugins/expressions/public/functions/font.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import { openSans, FontLabel as FontFamily } from '../fonts'; -import { - ExpressionFunction, - CSSStyle, - FontStyle, - FontWeight, - Style, - TextAlignment, - TextDecoration, -} from '../../common/types'; - -const dashify = (str: string) => { - return str - .trim() - .replace(/([a-z])([A-Z])/g, '$1-$2') - .replace(/\W/g, m => (/[À-ž]/.test(m) ? m : '-')) - .replace(/^-+|-+$/g, '') - .toLowerCase(); -}; - -const inlineStyle = (obj: Record) => { - if (!obj) return ''; - const styles = Object.keys(obj).map(key => { - const prop = dashify(key); - const line = prop.concat(':').concat(String(obj[key])); - return line; - }); - return styles.join(';'); -}; - -interface Arguments { - align?: TextAlignment; - color?: string; - family?: FontFamily; - italic?: boolean; - lHeight?: number | null; - size?: number; - underline?: boolean; - weight?: FontWeight; -} - -export function font(): ExpressionFunction<'font', null, Arguments, Style> { - return { - name: 'font', - aliases: [], - type: 'style', - help: i18n.translate('expressions.functions.fontHelpText', { - defaultMessage: 'Create a font style.', - }), - context: { - types: ['null'], - }, - args: { - align: { - default: 'left', - help: i18n.translate('expressions.functions.font.args.alignHelpText', { - defaultMessage: 'The horizontal text alignment.', - }), - options: Object.values(TextAlignment), - types: ['string'], - }, - color: { - help: i18n.translate('expressions.functions.font.args.colorHelpText', { - defaultMessage: 'The text color.', - }), - types: ['string'], - }, - family: { - default: `"${openSans.value}"`, - help: i18n.translate('expressions.functions.font.args.familyHelpText', { - defaultMessage: 'An acceptable {css} web font string', - values: { - css: 'CSS', - }, - }), - types: ['string'], - }, - italic: { - default: false, - help: i18n.translate('expressions.functions.font.args.italicHelpText', { - defaultMessage: 'Italicize the text?', - }), - options: [true, false], - types: ['boolean'], - }, - lHeight: { - default: null, - aliases: ['lineHeight'], - help: i18n.translate('expressions.functions.font.args.lHeightHelpText', { - defaultMessage: 'The line height in pixels', - }), - types: ['number', 'null'], - }, - size: { - default: 14, - help: i18n.translate('expressions.functions.font.args.sizeHelpText', { - defaultMessage: 'The font size in pixels', - }), - types: ['number'], - }, - underline: { - default: false, - help: i18n.translate('expressions.functions.font.args.underlineHelpText', { - defaultMessage: 'Underline the text?', - }), - options: [true, false], - types: ['boolean'], - }, - weight: { - default: 'normal', - help: i18n.translate('expressions.functions.font.args.weightHelpText', { - defaultMessage: 'The font weight. For example, {list}, or {end}.', - values: { - list: Object.values(FontWeight) - .slice(0, -1) - .map(weight => `\`"${weight}"\``) - .join(', '), - end: `\`"${Object.values(FontWeight).slice(-1)[0]}"\``, - }, - }), - options: Object.values(FontWeight), - types: ['string'], - }, - }, - fn: (_context, args) => { - if (!Object.values(FontWeight).includes(args.weight!)) { - throw new Error( - i18n.translate('expressions.functions.font.invalidFontWeightErrorMessage', { - defaultMessage: "Invalid font weight: '{weight}'", - values: { - weight: args.weight, - }, - }) - ); - } - if (!Object.values(TextAlignment).includes(args.align!)) { - throw new Error( - i18n.translate('expressions.functions.font.invalidTextAlignmentErrorMessage', { - defaultMessage: "Invalid text alignment: '{align}'", - values: { - align: args.align, - }, - }) - ); - } - - // the line height shouldn't ever be lower than the size, and apply as a - // pixel setting - const lineHeight = args.lHeight != null ? `${args.lHeight}px` : '1'; - - const spec: CSSStyle = { - fontFamily: args.family, - fontWeight: args.weight, - fontStyle: args.italic ? FontStyle.ITALIC : FontStyle.NORMAL, - textDecoration: args.underline ? TextDecoration.UNDERLINE : TextDecoration.NONE, - textAlign: args.align, - fontSize: `${args.size}px`, // apply font size as a pixel setting - lineHeight, // apply line height as a pixel setting - }; - - // conditionally apply styles based on input - if (args.color) { - spec.color = args.color; - } - - return { - type: 'style', - spec, - css: inlineStyle(spec as Record), - }; - }, - }; -} diff --git a/src/plugins/expressions/public/functions/tests/var.test.ts b/src/plugins/expressions/public/functions/tests/var.test.ts deleted file mode 100644 index fe5963ec8c509..0000000000000 --- a/src/plugins/expressions/public/functions/tests/var.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { functionWrapper } from './utils'; -import { variable } from '../var'; -import { FunctionHandlers } from '../../../common/types'; -import { KibanaContext } from '../../../common/expression_types/kibana_context'; - -describe('interpreter/functions#var', () => { - const fn = functionWrapper(variable); - let context: Partial; - let initialContext: KibanaContext; - let handlers: FunctionHandlers; - - beforeEach(() => { - context = { timeRange: { from: '0', to: '1' } }; - initialContext = { - type: 'kibana_context', - query: { language: 'lucene', query: 'geo.src:US' }, - filters: [ - { - meta: { - disabled: false, - negate: false, - alias: null, - }, - query: { match: {} }, - }, - ], - timeRange: { from: '2', to: '3' }, - }; - handlers = { - getInitialContext: () => initialContext, - variables: { test: 1 } as any, - }; - }); - - it('returns the selected variable', () => { - const actual = fn(context, { name: 'test' }, handlers); - expect(actual).toEqual(1); - }); - - it('returns undefined if variable does not exist', () => { - const actual = fn(context, { name: 'unknown' }, handlers); - expect(actual).toEqual(undefined); - }); -}); diff --git a/src/plugins/expressions/public/functions/tests/var_set.test.ts b/src/plugins/expressions/public/functions/tests/var_set.test.ts deleted file mode 100644 index 7efa8ebc0dd3f..0000000000000 --- a/src/plugins/expressions/public/functions/tests/var_set.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { functionWrapper } from './utils'; -import { variableSet } from '../var_set'; -import { FunctionHandlers } from '../../../common/types'; -import { KibanaContext } from '../../../common/expression_types/kibana_context'; - -describe('interpreter/functions#varset', () => { - const fn = functionWrapper(variableSet); - let context: Partial; - let initialContext: KibanaContext; - let handlers: FunctionHandlers; - let variables: Record; - - beforeEach(() => { - context = { timeRange: { from: '0', to: '1' } }; - initialContext = { - type: 'kibana_context', - query: { language: 'lucene', query: 'geo.src:US' }, - filters: [ - { - meta: { - disabled: false, - negate: false, - alias: null, - }, - query: { match: {} }, - }, - ], - timeRange: { from: '2', to: '3' }, - }; - handlers = { - getInitialContext: () => initialContext, - variables: { test: 1 } as any, - }; - - variables = handlers.variables; - }); - - it('updates a variable', () => { - const actual = fn(context, { name: 'test', value: 2 }, handlers); - expect(variables.test).toEqual(2); - expect(actual).toEqual(context); - }); - - it('sets a new variable', () => { - const actual = fn(context, { name: 'new', value: 3 }, handlers); - expect(variables.new).toEqual(3); - expect(actual).toEqual(context); - }); - - it('stores context if value is not set', () => { - const actual = fn(context, { name: 'test' }, handlers); - expect(variables.test).toEqual(context); - expect(actual).toEqual(context); - }); -}); diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 951d643c9df68..59d529dc9caff 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -20,17 +20,97 @@ import { PluginInitializerContext } from '../../../core/public'; import { ExpressionsPublicPlugin } from './plugin'; +// Kibana Platform. export { ExpressionsPublicPlugin as Plugin }; - export * from './plugin'; -export * from './types'; -export * from '../common'; -export { interpreterProvider, ExpressionInterpret } from './interpreter_provider'; -export { ExpressionRenderer, ExpressionRendererProps } from './expression_renderer'; -export { ExpressionDataHandler } from './execute'; - -export { ExpressionRenderHandler } from './render'; - export function plugin(initializerContext: PluginInitializerContext) { return new ExpressionsPublicPlugin(initializerContext); } + +// Static exports. +export { ExpressionExecutor, IExpressionLoaderParams } from './types'; +export { + ExpressionRendererComponent, + ReactExpressionRenderer, + ReactExpressionRendererProps, + ReactExpressionRendererType, +} from './react_expression_renderer'; +export { ExpressionDataHandler } from './execute'; +export { ExpressionRenderHandler } from './render'; +export { + AnyExpressionFunctionDefinition, + AnyExpressionTypeDefinition, + ArgumentType, + Datatable, + DatatableColumn, + DatatableColumnType, + DatatableRow, + Execution, + ExecutionContainer, + ExecutionContext, + ExecutionParams, + ExecutionState, + Executor, + ExecutorContainer, + ExecutorState, + ExpressionAstArgument, + ExpressionAstExpression, + ExpressionAstFunction, + ExpressionAstNode, + ExpressionFunction, + ExpressionFunctionDefinition, + ExpressionFunctionKibana, + ExpressionFunctionParameter, + ExpressionImage, + ExpressionRenderDefinition, + ExpressionRenderer, + ExpressionRendererRegistry, + ExpressionType, + ExpressionTypeDefinition, + ExpressionTypeStyle, + ExpressionValue, + ExpressionValueBoxed, + ExpressionValueConverter, + ExpressionValueError, + ExpressionValueNum, + ExpressionValueRender, + ExpressionValueSearchContext, + ExpressionValueUnboxed, + Filter, + Font, + FontLabel, + FontStyle, + FontValue, + FontWeight, + format, + formatExpression, + FunctionsRegistry, + IInterpreterRenderHandlers, + InterpreterErrorType, + IRegistry, + KIBANA_CONTEXT_NAME, + KibanaContext, + KibanaDatatable, + KibanaDatatableColumn, + KibanaDatatableRow, + KnownTypeToString, + Overflow, + parse, + parseExpression, + PointSeries, + PointSeriesColumn, + PointSeriesColumnName, + PointSeriesColumns, + PointSeriesRow, + Range, + SerializedDatatable, + SerializedFieldFormat, + Style, + TextAlignment, + TextDecoration, + TypesRegistry, + TypeString, + TypeToString, + UnmappedTypeStrings, + ExpressionValueRender as Render, +} from '../common'; diff --git a/src/plugins/expressions/public/interpreter_provider.ts b/src/plugins/expressions/public/interpreter_provider.ts deleted file mode 100644 index f4b65c630089a..0000000000000 --- a/src/plugins/expressions/public/interpreter_provider.ts +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-ignore -import { fromExpression, getByAlias } from '@kbn/interpreter/common'; - -import { clone, each, keys, last, mapValues, reduce, zipObject } from 'lodash'; -import { createError } from './create_error'; -import { - ExpressionAST, - ExpressionFunctionAST, - AnyExpressionFunction, - ArgumentType, -} from '../common/types'; -import { getType } from '../common/type'; -import { FunctionsRegistry } from './registries'; - -export { createError }; - -export interface InterpreterConfig { - functions: FunctionsRegistry; - types: any; - handlers: any; -} - -export type ExpressionInterpret = (ast: ExpressionAST, context?: any) => any; - -export function interpreterProvider(config: InterpreterConfig): ExpressionInterpret { - const { functions, types } = config; - const handlers = { ...config.handlers, types }; - - function cast(node: any, toTypeNames: any) { - // If you don't give us anything to cast to, you'll get your input back - if (!toTypeNames || toTypeNames.length === 0) return node; - - // No need to cast if node is already one of the valid types - const fromTypeName = getType(node); - if (toTypeNames.includes(fromTypeName)) return node; - - const fromTypeDef = types[fromTypeName]; - - for (let i = 0; i < toTypeNames.length; i++) { - // First check if the current type can cast to this type - if (fromTypeDef && fromTypeDef.castsTo(toTypeNames[i])) { - return fromTypeDef.to(node, toTypeNames[i], types); - } - - // If that isn't possible, check if this type can cast from the current type - const toTypeDef = types[toTypeNames[i]]; - if (toTypeDef && toTypeDef.castsFrom(fromTypeName)) return toTypeDef.from(node, types); - } - - throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`); - } - - async function invokeChain(chainArr: ExpressionFunctionAST[], context: any): Promise { - if (!chainArr.length) return context; - // if execution was aborted return error - if (handlers.abortSignal && handlers.abortSignal.aborted) { - return createError({ - message: 'The expression was aborted.', - name: 'AbortError', - }); - } - const chain = clone(chainArr); - const link = chain.shift(); // Every thing in the chain will always be a function right? - if (!link) throw Error('Function chain is empty.'); - const { function: fnName, arguments: fnArgs } = link; - const fnDef = getByAlias(functions.toJS(), fnName); - - if (!fnDef) { - return createError({ message: `Function ${fnName} could not be found.` }); - } - - try { - // Resolve arguments before passing to function - // resolveArgs returns an object because the arguments themselves might - // actually have a 'then' function which would be treated as a promise - const { resolvedArgs } = await resolveArgs(fnDef, context, fnArgs); - const newContext = await invokeFunction(fnDef, context, resolvedArgs); - - // if something failed, just return the failure - if (getType(newContext) === 'error') return newContext; - - // Continue re-invoking chain until it's empty - return invokeChain(chain, newContext); - } catch (e) { - // Everything that throws from a function will hit this - // The interpreter should *never* fail. It should always return a `{type: error}` on failure - e.message = `[${fnName}] > ${e.message}`; - return createError(e); - } - } - - async function invokeFunction( - fnDef: AnyExpressionFunction, - context: any, - args: Record - ): Promise { - // Check function input. - const acceptableContext = cast(context, fnDef.context ? fnDef.context.types : undefined); - const fnOutput = await fnDef.fn(acceptableContext, args, handlers); - - // Validate that the function returned the type it said it would. - // This isn't really required, but it keeps function developers honest. - const returnType = getType(fnOutput); - const expectedType = fnDef.type; - if (expectedType && returnType !== expectedType) { - throw new Error( - `Function '${fnDef.name}' should return '${expectedType}',` + - ` actually returned '${returnType}'` - ); - } - - // Validate the function output against the type definition's validate function - const type = handlers.types[fnDef.type]; - if (type && type.validate) { - try { - type.validate(fnOutput); - } catch (e) { - throw new Error(`Output of '${fnDef.name}' is not a valid type '${fnDef.type}': ${e}`); - } - } - - return fnOutput; - } - - // Processes the multi-valued AST argument values into arguments that can be passed to the function - async function resolveArgs( - fnDef: AnyExpressionFunction, - context: any, - argAsts: any - ): Promise { - const argDefs = fnDef.args; - - // Use the non-alias name from the argument definition - const dealiasedArgAsts = reduce( - argAsts, - (acc, argAst, argName) => { - const argDef = getByAlias(argDefs, argName); - // TODO: Implement a system to allow for undeclared arguments - if (!argDef) { - throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`); - } - - acc[argDef.name] = (acc[argDef.name] || []).concat(argAst); - return acc; - }, - {} as any - ); - - // Check for missing required arguments - each(argDefs, argDef => { - const { aliases, default: argDefault, name: argName, required } = argDef as ArgumentType< - any - > & { name: string }; - if ( - typeof argDefault === 'undefined' && - required && - typeof dealiasedArgAsts[argName] === 'undefined' - ) { - if (!aliases || aliases.length === 0) { - throw new Error(`${fnDef.name} requires an argument`); - } else { - const errorArg = argName === '_' ? aliases[0] : argName; // use an alias if _ is the missing arg - throw new Error(`${fnDef.name} requires an "${errorArg}" argument`); - } - } - }); - - // Fill in default values from argument definition - const argAstsWithDefaults = reduce( - argDefs, - (acc: any, argDef: any, argName: any) => { - if (typeof acc[argName] === 'undefined' && typeof argDef.default !== 'undefined') { - acc[argName] = [(fromExpression as any)(argDef.default, 'argument')]; - } - - return acc; - }, - dealiasedArgAsts - ); - - // Create the functions to resolve the argument ASTs into values - // These are what are passed to the actual functions if you opt out of resolving - const resolveArgFns = mapValues(argAstsWithDefaults, (asts, argName) => { - return asts.map((item: any) => { - return async (ctx = context) => { - const newContext = await interpret(item, ctx); - // This is why when any sub-expression errors, the entire thing errors - if (getType(newContext) === 'error') throw newContext.error; - return cast(newContext, argDefs[argName as any].types); - }; - }); - }); - - const argNames = keys(resolveArgFns); - - // Actually resolve unless the argument definition says not to - const resolvedArgValues = await Promise.all( - argNames.map(argName => { - const interpretFns = resolveArgFns[argName]; - if (!argDefs[argName].resolve) return interpretFns; - return Promise.all(interpretFns.map((fn: any) => fn())); - }) - ); - - const resolvedMultiArgs = zipObject(argNames, resolvedArgValues); - - // Just return the last unless the argument definition allows multiple - const resolvedArgs = mapValues(resolvedMultiArgs, (argValues, argName) => { - if (argDefs[argName as any].multi) return argValues; - return last(argValues as any); - }); - - // Return an object here because the arguments themselves might actually have a 'then' - // function which would be treated as a promise - return { resolvedArgs }; - } - - const interpret: ExpressionInterpret = async function interpret(ast, context = null) { - const type = getType(ast); - switch (type) { - case 'expression': - return invokeChain(ast.chain, context); - case 'string': - case 'number': - case 'null': - case 'boolean': - return ast; - default: - throw new Error(`Unknown AST object: ${JSON.stringify(ast)}`); - } - }; - - return interpret; -} diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts index 5a7c4fc9ab60a..4139c2af2b366 100644 --- a/src/plugins/expressions/public/loader.test.ts +++ b/src/plugins/expressions/public/loader.test.ts @@ -19,11 +19,9 @@ import { Observable } from 'rxjs'; import { first, skip, toArray } from 'rxjs/operators'; -import { fromExpression } from '@kbn/interpreter/common'; import { loader, ExpressionLoader } from './loader'; import { ExpressionDataHandler } from './execute'; -import { IInterpreterRenderHandlers } from './types'; -import { ExpressionAST } from '../common/types'; +import { ExpressionAstExpression, parseExpression, IInterpreterRenderHandlers } from '../common'; const element: HTMLElement = null as any; @@ -38,7 +36,7 @@ jest.mock('./services', () => { return { getInterpreter: () => { return { - interpretAst: async (expression: ExpressionAST) => { + interpretAst: async (expression: ExpressionAstExpression) => { return { type: 'render', as: 'test' }; }, }; @@ -83,7 +81,7 @@ describe('ExpressionLoader', () => { }); it('accepts expression AST', () => { - const expressionAST = fromExpression(expressionString) as ExpressionAST; + const expressionAST = parseExpression(expressionString); const expressionLoader = new ExpressionLoader(element, expressionAST, {}); expect(expressionLoader.getExpression()).toEqual(expressionString); expect(expressionLoader.getAst()).toEqual(expressionAST); diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index d714282360f71..320a8469fe9e3 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -22,10 +22,12 @@ import { filter, map } from 'rxjs/operators'; import { Adapters, InspectorSession } from '../../inspector/public'; import { ExpressionDataHandler } from './execute'; import { ExpressionRenderHandler } from './render'; -import { Data, IExpressionLoaderParams } from './types'; -import { ExpressionAST } from '../common/types'; +import { IExpressionLoaderParams } from './types'; +import { ExpressionAstExpression } from '../common'; import { getInspector } from './services'; +type Data = any; + export class ExpressionLoader { data$: Observable; update$: ExpressionRenderHandler['update$']; @@ -42,7 +44,7 @@ export class ExpressionLoader { constructor( element: HTMLElement, - expression?: string | ExpressionAST, + expression?: string | ExpressionAstExpression, params?: IExpressionLoaderParams ) { this.dataSubject = new Subject(); @@ -64,8 +66,11 @@ export class ExpressionLoader { this.update$ = this.renderHandler.update$; this.events$ = this.renderHandler.events$; - this.update$.subscribe(({ newExpression, newParams }) => { - this.update(newExpression, newParams); + this.update$.subscribe(value => { + if (value) { + const { newExpression, newParams } = value; + this.update(newExpression, newParams); + } }); this.data$.subscribe(data => { @@ -105,7 +110,7 @@ export class ExpressionLoader { } } - getAst(): ExpressionAST | undefined { + getAst(): ExpressionAstExpression | undefined { if (this.dataHandler) { return this.dataHandler.getAst(); } @@ -130,7 +135,7 @@ export class ExpressionLoader { } } - update(expression?: string | ExpressionAST, params?: IExpressionLoaderParams): void { + update(expression?: string | ExpressionAstExpression, params?: IExpressionLoaderParams): void { this.setParams(params); this.loadingSubject.next(true); @@ -142,7 +147,7 @@ export class ExpressionLoader { } private loadData = async ( - expression: string | ExpressionAST, + expression: string | ExpressionAstExpression, params: IExpressionLoaderParams ): Promise => { if (this.dataHandler && this.dataHandler.isPending) { @@ -186,7 +191,7 @@ export class ExpressionLoader { export type IExpressionLoader = ( element: HTMLElement, - expression: string | ExpressionAST, + expression: string | ExpressionAstExpression, params: IExpressionLoaderParams ) => ExpressionLoader; diff --git a/src/plugins/expressions/public/mocks.tsx b/src/plugins/expressions/public/mocks.tsx index a3476a24dd7ed..70760ada83955 100644 --- a/src/plugins/expressions/public/mocks.tsx +++ b/src/plugins/expressions/public/mocks.tsx @@ -31,9 +31,16 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { + getFunction: jest.fn(), + getFunctions: jest.fn(), + getRenderer: jest.fn(), + getRenderers: jest.fn(), + getType: jest.fn(), + getTypes: jest.fn(), registerFunction: jest.fn(), registerRenderer: jest.fn(), registerType: jest.fn(), + run: jest.fn(), __LEGACY: { functions: { register: () => {}, @@ -46,7 +53,7 @@ const createSetupContract = (): Setup => { } as any, getExecutor: () => ({ interpreter: { - interpretAst: () => {}, + interpretAst: (() => {}) as any, }, }), loadLegacyServerFunctionWrappers: () => Promise.resolve(), @@ -60,10 +67,17 @@ const createStartContract = (): Start => { execute: jest.fn(), ExpressionDataHandler: jest.fn(), ExpressionLoader: jest.fn(), - ExpressionRenderer: jest.fn(props => <>), ExpressionRenderHandler: jest.fn(), + getFunction: jest.fn(), + getFunctions: jest.fn(), + getRenderer: jest.fn(), + getRenderers: jest.fn(), + getType: jest.fn(), + getTypes: jest.fn(), loader: jest.fn(), + ReactExpressionRenderer: jest.fn(props => <>), render: jest.fn(), + run: jest.fn(), }; }; diff --git a/src/plugins/expressions/public/plugin.test.ts b/src/plugins/expressions/public/plugin.test.ts new file mode 100644 index 0000000000000..5437a7d21f338 --- /dev/null +++ b/src/plugins/expressions/public/plugin.test.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { expressionsPluginMock } from './mocks'; +import { add } from '../common/test_helpers/expression_functions/add'; + +describe('ExpressionsPublicPlugin', () => { + test('can instantiate from mocks', async () => { + const { setup } = await expressionsPluginMock.createPlugin(); + expect(typeof setup.registerFunction).toBe('function'); + }); + + describe('setup contract', () => { + describe('.registerFunction()', () => { + test('can register a function', async () => { + const { setup } = await expressionsPluginMock.createPlugin(); + expect(setup.getFunctions().add).toBe(undefined); + setup.registerFunction(add); + expect(setup.getFunctions().add.name).toBe('add'); + }); + }); + + describe('.run()', () => { + test('can execute simple expression', async () => { + const { setup } = await expressionsPluginMock.createPlugin(); + const bar = await setup.run('var_set name="foo" value="bar" | var name="foo"', null); + expect(bar).toBe('bar'); + }); + }); + }); + + describe('start contract', () => { + describe('.execute()', () => { + test('can parse a single function expression', async () => { + const { doStart } = await expressionsPluginMock.createPlugin(); + const start = await doStart(); + + const handler = start.execute('clog'); + expect(handler.getAst()).toMatchInlineSnapshot(` + Object { + "chain": Array [ + Object { + "arguments": Object {}, + "function": "clog", + "type": "function", + }, + ], + "type": "expression", + } + `); + }); + }); + }); +}); diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index 2ba10be76cd92..6799b1590f252 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -18,10 +18,18 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; -import { ExpressionInterpretWithHandlers, ExpressionExecutor } from './types'; -import { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './registries'; -import { BfetchPublicSetup, BfetchPublicStart } from '../../bfetch/public'; +import { ExpressionExecutor } from './types'; +import { + ExpressionRendererRegistry, + FunctionsRegistry, + serializeProvider, + TypesRegistry, + ExpressionsService, + ExpressionsServiceSetup, + ExpressionsServiceStart, +} from '../common'; import { Setup as InspectorSetup, Start as InspectorStart } from '../../inspector/public'; +import { BfetchPublicSetup, BfetchPublicStart } from '../../bfetch/public'; import { setCoreStart, setInspector, @@ -29,37 +37,11 @@ import { setRenderersRegistry, setNotifications, } from './services'; -import { clog as clogFunction } from './functions/clog'; -import { font as fontFunction } from './functions/font'; -import { kibana as kibanaFunction } from './functions/kibana'; -import { kibanaContext as kibanaContextFunction } from './functions/kibana_context'; -import { variable } from './functions/var'; -import { variableSet } from './functions/var_set'; -import { - boolean as booleanType, - datatable as datatableType, - error as errorType, - filter as filterType, - image as imageType, - nullType, - number as numberType, - pointseries, - range as rangeType, - render as renderType, - shape as shapeType, - string as stringType, - style as styleType, - kibanaContext as kibanaContextType, - kibanaDatatable as kibanaDatatableType, -} from '../common/expression_types'; -import { interpreterProvider } from './interpreter_provider'; -import { createHandlers } from './create_handlers'; -import { ExpressionRendererImplementation } from './expression_renderer'; +import { kibanaContext as kibanaContextFunction } from './expression_functions/kibana_context'; +import { ReactExpressionRenderer } from './react_expression_renderer'; import { ExpressionLoader, loader } from './loader'; import { ExpressionDataHandler, execute } from './execute'; import { render, ExpressionRenderHandler } from './render'; -import { AnyExpressionFunction, AnyExpressionType } from '../common/types'; -import { serializeProvider } from '../common'; export interface ExpressionsSetupDeps { bfetch: BfetchPublicSetup; @@ -71,82 +53,77 @@ export interface ExpressionsStartDeps { inspector: InspectorStart; } -export interface ExpressionsSetup { - registerFunction: (fn: AnyExpressionFunction | (() => AnyExpressionFunction)) => void; - registerRenderer: (renderer: any) => void; - registerType: (type: () => AnyExpressionType) => void; +export interface ExpressionsSetup extends ExpressionsServiceSetup { + /** + * @todo Get rid of these `__LEGACY` APIs. + * + * `__LEGACY` APIs are used by Canvas. It should be possible to stop + * using all of them (except `loadLegacyServerFunctionWrappers`) and use + * Kibana Platform plugin contracts instead. + */ __LEGACY: { - functions: FunctionsRegistry; - renderers: RenderFunctionsRegistry; + /** + * Use `registerType` and `getTypes` instead. + */ types: TypesRegistry; + + /** + * Use `registerFunction` and `getFunctions` instead. + */ + functions: FunctionsRegistry; + + /** + * Use `registerRenderer` and `getRenderers`, and `getRenderer` instead. + */ + renderers: ExpressionRendererRegistry; + + /** + * Use `run` function instead. + */ getExecutor: () => ExpressionExecutor; + + /** + * This function is used by Canvas to load server-side function and create + * browser-side "wrapper" for each one. This function can be removed once + * we enable expressions on server-side: https://github.com/elastic/kibana/issues/46906 + */ loadLegacyServerFunctionWrappers: () => Promise; }; } -export interface ExpressionsStart { +export interface ExpressionsStart extends ExpressionsServiceStart { execute: typeof execute; ExpressionDataHandler: typeof ExpressionDataHandler; ExpressionLoader: typeof ExpressionLoader; - ExpressionRenderer: typeof ExpressionRendererImplementation; ExpressionRenderHandler: typeof ExpressionRenderHandler; loader: typeof loader; + ReactExpressionRenderer: typeof ReactExpressionRenderer; render: typeof render; } export class ExpressionsPublicPlugin implements Plugin { - private readonly functions = new FunctionsRegistry(); - private readonly renderers = new RenderFunctionsRegistry(); - private readonly types = new TypesRegistry(); + private readonly expressions: ExpressionsService = new ExpressionsService(); constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, { inspector, bfetch }: ExpressionsSetupDeps): ExpressionsSetup { - const { functions, renderers, types } = this; + const { expressions } = this; + const { executor, renderers } = expressions; + + executor.extendContext({ + environment: 'client', + }); + executor.registerFunction(kibanaContextFunction()); setRenderersRegistry(renderers); - const registerFunction: ExpressionsSetup['registerFunction'] = fn => { - functions.register(fn); - }; + const expressionsSetup = expressions.setup(); - registerFunction(clogFunction); - registerFunction(fontFunction); - registerFunction(kibanaFunction); - registerFunction(kibanaContextFunction); - registerFunction(variable); - registerFunction(variableSet); - - types.register(booleanType); - types.register(datatableType); - types.register(errorType); - types.register(filterType); - types.register(imageType); - types.register(nullType); - types.register(numberType); - types.register(pointseries); - types.register(rangeType); - types.register(renderType); - types.register(shapeType); - types.register(stringType); - types.register(styleType); - types.register(kibanaContextType); - types.register(kibanaDatatableType); - - // TODO: Refactor this function. - const getExecutor = () => { - const interpretAst: ExpressionInterpretWithHandlers = (ast, context, handlers) => { - const interpret = interpreterProvider({ - types: types.toJS(), - handlers: { ...handlers, ...createHandlers() }, - functions, - }); - return interpret(ast, context); - }; - const executor: ExpressionExecutor = { interpreter: { interpretAst } }; - return executor; + // This is legacy. Should go away when we get rid of __LEGACY. + const getExecutor = (): ExpressionExecutor => { + return { interpreter: { interpretAst: expressionsSetup.run } }; }; setInterpreter(getExecutor().interpreter); @@ -157,19 +134,22 @@ export class ExpressionsPublicPlugin cached = (async () => { const serverFunctionList = await core.http.get(`/api/interpreter/fns`); const batchedFunction = bfetch.batchedFunction({ url: `/api/interpreter/fns` }); - const { serialize } = serializeProvider(types.toJS()); + const { serialize } = serializeProvider(executor.getTypes()); // For every sever-side function, register a client-side // function that matches its definition, but which simply // calls the server-side function endpoint. Object.keys(serverFunctionList).forEach(functionName => { + if (expressionsSetup.getFunction(functionName)) { + return; + } const fn = () => ({ ...serverFunctionList[functionName], - fn: (context: any, args: any) => { - return batchedFunction({ functionName, args, context: serialize(context) }); + fn: (input: any, args: any) => { + return batchedFunction({ functionName, args, context: serialize(input) }); }, }); - registerFunction(fn); + expressionsSetup.registerFunction(fn); }); })(); } @@ -177,17 +157,11 @@ export class ExpressionsPublicPlugin }; const setup: ExpressionsSetup = { - registerFunction, - registerRenderer: (renderer: any) => { - renderers.register(renderer); - }, - registerType: type => { - types.register(type); - }, + ...expressionsSetup, __LEGACY: { - functions, + types: executor.types, + functions: executor.functions, renderers, - types, getExecutor, loadLegacyServerFunctionWrappers, }, @@ -196,18 +170,22 @@ export class ExpressionsPublicPlugin return setup; } - public start(core: CoreStart, { inspector }: ExpressionsStartDeps): ExpressionsStart { + public start(core: CoreStart, { inspector, bfetch }: ExpressionsStartDeps): ExpressionsStart { setCoreStart(core); setInspector(inspector); setNotifications(core.notifications); + const { expressions } = this; + const expressionsStart = expressions.start(); + return { + ...expressionsStart, execute, ExpressionDataHandler, ExpressionLoader, - ExpressionRenderer: ExpressionRendererImplementation, ExpressionRenderHandler, loader, + ReactExpressionRenderer, render, }; } diff --git a/src/plugins/expressions/public/expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx similarity index 95% rename from src/plugins/expressions/public/expression_renderer.test.tsx rename to src/plugins/expressions/public/react_expression_renderer.test.tsx index 217618bc3a177..65cc5fc1569cb 100644 --- a/src/plugins/expressions/public/expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { Subject } from 'rxjs'; import { share } from 'rxjs/operators'; -import { ExpressionRendererImplementation } from './expression_renderer'; +import { ReactExpressionRenderer } from './react_expression_renderer'; import { ExpressionLoader } from './loader'; import { mount } from 'enzyme'; import { EuiProgress } from '@elastic/eui'; @@ -54,7 +54,7 @@ describe('ExpressionRenderer', () => { }; }); - const instance = mount(); + const instance = mount(); act(() => { loadingSubject.next(); @@ -108,7 +108,7 @@ describe('ExpressionRenderer', () => { }); const instance = mount( -
{message}
} /> diff --git a/src/plugins/expressions/public/expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx similarity index 91% rename from src/plugins/expressions/public/expression_renderer.tsx rename to src/plugins/expressions/public/react_expression_renderer.tsx index 5c04d8405479f..242a49c6d6639 100644 --- a/src/plugins/expressions/public/expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -25,27 +25,29 @@ import { filter } from 'rxjs/operators'; import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { IExpressionLoaderParams, IInterpreterRenderHandlers, RenderError } from './types'; -import { ExpressionAST } from '../common/types'; +import { IExpressionLoaderParams, RenderError } from './types'; +import { ExpressionAstExpression, IInterpreterRenderHandlers } from '../common'; import { ExpressionLoader } from './loader'; // Accept all options of the runner as props except for the // dom element which is provided by the component itself -export interface ExpressionRendererProps extends IExpressionLoaderParams { +export interface ReactExpressionRendererProps extends IExpressionLoaderParams { className?: string; dataAttrs?: string[]; - expression: string | ExpressionAST; + expression: string | ExpressionAstExpression; renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; } +export type ReactExpressionRendererType = React.ComponentType; + interface State { isEmpty: boolean; isLoading: boolean; error: null | RenderError; } -export type ExpressionRenderer = React.FC; +export type ExpressionRendererComponent = React.FC; const defaultState: State = { isEmpty: true, @@ -53,14 +55,14 @@ const defaultState: State = { error: null, }; -export const ExpressionRendererImplementation = ({ +export const ReactExpressionRenderer = ({ className, dataAttrs, padding, renderError, expression, ...expressionLoaderOptions -}: ExpressionRendererProps) => { +}: ReactExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); const [state, setState] = useState({ ...defaultState }); const hasCustomRenderErrorHandler = !!renderError; diff --git a/src/plugins/expressions/public/registries/function_registry.ts b/src/plugins/expressions/public/registries/function_registry.ts deleted file mode 100644 index 43d6086274fc0..0000000000000 --- a/src/plugins/expressions/public/registries/function_registry.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable max-classes-per-file */ - -import { - ArgumentType, - ExpressionValue, - AnyExpressionFunction, - FunctionHandlers, -} from '../../common/types'; -import { Registry } from './registry'; - -export class FunctionParameter { - name: string; - required: boolean; - help: string; - types: string[]; - default: any; - aliases: string[]; - multi: boolean; - resolve: boolean; - options: any[]; - - constructor(name: string, arg: ArgumentType) { - const { required, help, types, aliases, multi, resolve, options } = arg; - - if (name === '_') { - throw Error('Arg names must not be _. Use it in aliases instead.'); - } - - this.name = name; - this.required = !!required; - this.help = help || ''; - this.types = types || []; - this.default = arg.default; - this.aliases = aliases || []; - this.multi = !!multi; - this.resolve = resolve == null ? true : resolve; - this.options = options || []; - } - - accepts(type: string) { - if (!this.types.length) return true; - return this.types.indexOf(type) > -1; - } -} - -export class Function { - /** - * Name of function - */ - name: string; - - /** - * Aliases that can be used instead of `name`. - */ - aliases: string[]; - - /** - * Return type of function. This SHOULD be supplied. We use it for UI - * and autocomplete hinting. We may also use it for optimizations in - * the future. - */ - type: string; - - /** - * Function to run function (context, args) - */ - fn: ( - input: ExpressionValue, - params: Record, - handlers: FunctionHandlers - ) => ExpressionValue; - - /** - * A short help text. - */ - help: string; - - args: Record = {}; - - context: { types?: string[] }; - - constructor(functionDefinition: AnyExpressionFunction) { - const { name, type, aliases, fn, help, args, context } = functionDefinition; - - this.name = name; - this.type = type; - this.aliases = aliases || []; - this.fn = (input, params, handlers) => Promise.resolve(fn(input, params, handlers)); - this.help = help || ''; - this.context = context || {}; - - for (const [key, arg] of Object.entries(args || {})) { - this.args[key] = new FunctionParameter(key, arg); - } - } - - accepts = (type: string): boolean => { - // If you don't tell us about context, we'll assume you don't care what you get. - if (!this.context.types) return true; - return this.context.types.indexOf(type) > -1; - }; -} - -export class FunctionsRegistry extends Registry { - register(functionDefinition: AnyExpressionFunction | (() => AnyExpressionFunction)) { - const fn = new Function( - typeof functionDefinition === 'object' ? functionDefinition : functionDefinition() - ); - this.set(fn.name, fn); - } -} diff --git a/src/plugins/expressions/public/registries/render_registry.ts b/src/plugins/expressions/public/registries/render_registry.ts deleted file mode 100644 index 6fd48f5f0c6af..0000000000000 --- a/src/plugins/expressions/public/registries/render_registry.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable max-classes-per-file */ - -import { Registry } from './registry'; - -export interface ExpressionRenderDefinition { - name: string; - displayName: string; - help?: string; - validate?: () => void | Error; - reuseDomNode: boolean; - render: (domNode: HTMLElement, config: Config, handlers: any) => Promise; -} - -class ExpressionRenderFunction { - /** - * This must match the name of the function that is used to create the `type: render` object. - */ - name: string; - - /** - * Use this to set a more friendly name. - */ - displayName: string; - - /** - * A sentence or few about what this element does. - */ - help: string; - - /** - * Used to validate the data before calling the render function. - */ - validate: () => void | Error; - - /** - * Tell the renderer if the dom node should be reused, it's recreated each time by default. - */ - reuseDomNode: boolean; - - /** - * The function called to render the data. - */ - render: (domNode: HTMLElement, config: any, handlers: any) => Promise; - - constructor(config: ExpressionRenderDefinition) { - const { name, displayName, help, validate, reuseDomNode, render } = config; - - this.name = name; - this.displayName = displayName || name; - this.help = help || ''; - this.validate = validate || (() => {}); - this.reuseDomNode = Boolean(reuseDomNode); - this.render = render; - } -} - -export class RenderFunctionsRegistry extends Registry { - register(definition: ExpressionRenderDefinition | (() => ExpressionRenderDefinition)) { - const renderFunction = new ExpressionRenderFunction( - typeof definition === 'object' ? definition : definition() - ); - this.set(renderFunction.name, renderFunction); - } -} diff --git a/src/plugins/expressions/public/render.test.ts b/src/plugins/expressions/public/render.test.ts index 56eb43a9bd133..b9601f6d1e920 100644 --- a/src/plugins/expressions/public/render.test.ts +++ b/src/plugins/expressions/public/render.test.ts @@ -19,9 +19,10 @@ import { ExpressionRenderHandler, render } from './render'; import { Observable } from 'rxjs'; -import { IInterpreterRenderHandlers, RenderError } from './types'; +import { RenderError } from './types'; import { getRenderersRegistry } from './services'; import { first, take, toArray } from 'rxjs/operators'; +import { IInterpreterRenderHandlers } from '../common'; const element: HTMLElement = {} as HTMLElement; const mockNotificationService = { diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index 62bde12490fbe..86e360f8135e7 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -20,16 +20,10 @@ import * as Rx from 'rxjs'; import { Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; -import { - Data, - event, - IInterpreterRenderHandlers, - RenderError, - RenderErrorHandlerFnType, - RenderId, -} from './types'; +import { RenderError, RenderErrorHandlerFnType, IExpressionLoaderParams } from './types'; import { getRenderersRegistry } from './services'; import { renderErrorHandler as defaultRenderErrorHandler } from './render_error_handler'; +import { IInterpreterRenderHandlers, ExpressionAstExpression } from '../common'; export type IExpressionRendererExtraHandlers = Record; @@ -37,17 +31,27 @@ export interface ExpressionRenderHandlerParams { onRenderError: RenderErrorHandlerFnType; } +interface Event { + name: string; + data: any; +} + +interface UpdateValue { + newExpression?: string | ExpressionAstExpression; + newParams: IExpressionLoaderParams; +} + export class ExpressionRenderHandler { - render$: Observable; - update$: Observable; - events$: Observable; + render$: Observable; + update$: Observable; + events$: Observable; private element: HTMLElement; private destroyFn?: any; private renderCount: number = 0; - private renderSubject: Rx.BehaviorSubject; + private renderSubject: Rx.BehaviorSubject; private eventsSubject: Rx.Subject; - private updateSubject: Rx.Subject; + private updateSubject: Rx.Subject; private handlers: IInterpreterRenderHandlers; private onRenderError: RenderErrorHandlerFnType; @@ -58,13 +62,13 @@ export class ExpressionRenderHandler { this.element = element; this.eventsSubject = new Rx.Subject(); - this.events$ = this.eventsSubject.asObservable(); + this.events$ = this.eventsSubject.asObservable() as Observable; this.onRenderError = onRenderError || defaultRenderErrorHandler; - this.renderSubject = new Rx.BehaviorSubject(null as RenderId | null); + this.renderSubject = new Rx.BehaviorSubject(null as any | null); this.render$ = this.renderSubject.asObservable().pipe(filter(_ => _ !== null)) as Observable< - RenderId + any >; this.updateSubject = new Rx.Subject(); @@ -90,7 +94,7 @@ export class ExpressionRenderHandler { }; } - render = async (data: Data, extraHandlers: IExpressionRendererExtraHandlers = {}) => { + render = async (data: any, extraHandlers: IExpressionRendererExtraHandlers = {}) => { if (!data || typeof data !== 'object') { return this.handleRenderError(new Error('invalid data provided to the expression renderer')); } @@ -113,7 +117,10 @@ export class ExpressionRenderHandler { // Rendering is asynchronous, completed by handlers.done() await getRenderersRegistry() .get(data.as)! - .render(this.element, data.value, { ...this.handlers, ...extraHandlers }); + .render(this.element, data.value, { + ...this.handlers, + ...extraHandlers, + } as any); } catch (e) { return this.handleRenderError(e); } @@ -139,7 +146,7 @@ export class ExpressionRenderHandler { export function render( element: HTMLElement, - data: Data, + data: any, options?: Partial ): ExpressionRenderHandler { const handler = new ExpressionRenderHandler(element, options); diff --git a/src/plugins/expressions/public/render_error_handler.ts b/src/plugins/expressions/public/render_error_handler.ts index 4d6bee1e375e0..432ef3ed96536 100644 --- a/src/plugins/expressions/public/render_error_handler.ts +++ b/src/plugins/expressions/public/render_error_handler.ts @@ -18,8 +18,9 @@ */ import { i18n } from '@kbn/i18n'; -import { RenderErrorHandlerFnType, IInterpreterRenderHandlers, RenderError } from './types'; +import { RenderErrorHandlerFnType, RenderError } from './types'; import { getNotifications } from './services'; +import { IInterpreterRenderHandlers } from '../common'; export const renderErrorHandler: RenderErrorHandlerFnType = ( element: HTMLElement, diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index e094e5e91d006..c77698d3661c2 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -17,42 +17,33 @@ * under the License. */ -import { ExpressionInterpret } from '../interpreter_provider'; -import { TimeRange, Query, esFilters } from '../../../data/public'; import { Adapters } from '../../../inspector/public'; -import { ExpressionRenderDefinition } from '../registries'; - -export type ExpressionInterpretWithHandlers = ( - ast: Parameters[0], - context: Parameters[1], - handlers: IInterpreterHandlers -) => ReturnType; - -export interface ExpressionInterpreter { - interpretAst: ExpressionInterpretWithHandlers; -} - +import { + IInterpreterRenderHandlers, + ExpressionValue, + ExecutionContextSearch, + ExpressionsService, +} from '../../common'; + +/** + * @deprecated + * + * This type if remainder from legacy platform, will be deleted going further. + */ export interface ExpressionExecutor { interpreter: ExpressionInterpreter; } -export type RenderId = number; -export type Data = any; -export type event = any; -export type Context = object; - -export interface SearchContext { - type: 'kibana_context'; - filters?: esFilters.Filter[]; - query?: Query; - timeRange?: TimeRange; +/** + * @deprecated + */ +export interface ExpressionInterpreter { + interpretAst: ExpressionsService['run']; } -export type IGetInitialContext = () => SearchContext | Context; - export interface IExpressionLoaderParams { - searchContext?: SearchContext; - context?: Context; + searchContext?: ExecutionContextSearch; + context?: ExpressionValue; variables?: Record; disableCaching?: boolean; customFunctions?: []; @@ -62,49 +53,6 @@ export interface IExpressionLoaderParams { onRenderError?: RenderErrorHandlerFnType; } -export interface IInterpreterHandlers { - getInitialContext: IGetInitialContext; - inspectorAdapters?: Adapters; - variables?: Record; - abortSignal?: AbortSignal; -} - -export interface IInterpreterRenderHandlers { - /** - * Done increments the number of rendering successes - */ - done: () => void; - onDestroy: (fn: () => void) => void; - reload: () => void; - update: (params: any) => void; - event: (event: event) => void; -} - -export interface IInterpreterRenderFunction { - name: string; - displayName: string; - help: string; - validate: () => void; - reuseDomNode: boolean; - render: (domNode: Element, data: T, handlers: IInterpreterRenderHandlers) => void | Promise; -} - -export interface IInterpreterErrorResult { - type: 'error'; - error: { message: string; name: string; stack: string }; -} - -export interface IInterpreterSuccessResult { - type: string; - as?: string; - value?: unknown; - error?: unknown; -} - -export type IInterpreterResult = IInterpreterSuccessResult & IInterpreterErrorResult; - -export { ExpressionRenderDefinition }; - export interface RenderError extends Error { type?: string; } diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts index 54e2a5a387342..17aa1c66a6835 100644 --- a/src/plugins/expressions/server/legacy.ts +++ b/src/plugins/expressions/server/legacy.ts @@ -28,12 +28,12 @@ import Boom from 'boom'; import { schema } from '@kbn/config-schema'; import { CoreSetup, Logger } from 'src/core/server'; import { ExpressionsServerSetupDependencies } from './plugin'; -import { typeSpecs as types, Type } from '../common'; +import { typeSpecs, ExpressionType } from '../common'; import { serializeProvider } from '../common'; export class TypesRegistry extends Registry { wrapper(obj: any) { - return new (Type as any)(obj); + return new (ExpressionType as any)(obj); } } @@ -57,7 +57,7 @@ export const createLegacyServerInterpreterApi = (): LegacyInterpreterServerApi = const api = registryFactory(registries); register(registries, { - types, + types: typeSpecs, }); return api; diff --git a/src/plugins/expressions/server/plugin.ts b/src/plugins/expressions/server/plugin.ts index 84c780b5ca226..49229b6868062 100644 --- a/src/plugins/expressions/server/plugin.ts +++ b/src/plugins/expressions/server/plugin.ts @@ -24,6 +24,7 @@ import { createLegacyServerInterpreterApi, createLegacyServerEndpoints, } from './legacy'; +import { ExpressionsService } from '../common'; // eslint-disable-next-line export interface ExpressionsServerSetupDependencies { @@ -50,6 +51,8 @@ export class ExpressionsServerPlugin ExpressionsServerSetupDependencies, ExpressionsServerStartDependencies > { + readonly expressions: ExpressionsService = new ExpressionsService(); + constructor(private readonly initializerContext: PluginInitializerContext) {} public setup( @@ -57,6 +60,12 @@ export class ExpressionsServerPlugin plugins: ExpressionsServerSetupDependencies ): ExpressionsServerSetup { const logger = this.initializerContext.logger.get(); + const { expressions } = this; + const { executor } = expressions; + + executor.extendContext({ + environment: 'server', + }); const legacyApi = createLegacyServerInterpreterApi(); createLegacyServerEndpoints(legacyApi, logger, core, plugins); diff --git a/src/plugins/inspector/public/adapters/data/data_adapter.ts b/src/plugins/inspector/common/adapters/data/data_adapter.ts similarity index 100% rename from src/plugins/inspector/public/adapters/data/data_adapter.ts rename to src/plugins/inspector/common/adapters/data/data_adapter.ts diff --git a/src/plugins/inspector/public/adapters/data/data_adapters.test.ts b/src/plugins/inspector/common/adapters/data/data_adapters.test.ts similarity index 100% rename from src/plugins/inspector/public/adapters/data/data_adapters.test.ts rename to src/plugins/inspector/common/adapters/data/data_adapters.test.ts diff --git a/src/plugins/inspector/public/adapters/data/formatted_data.ts b/src/plugins/inspector/common/adapters/data/formatted_data.ts similarity index 100% rename from src/plugins/inspector/public/adapters/data/formatted_data.ts rename to src/plugins/inspector/common/adapters/data/formatted_data.ts diff --git a/src/plugins/inspector/public/adapters/data/index.ts b/src/plugins/inspector/common/adapters/data/index.ts similarity index 100% rename from src/plugins/inspector/public/adapters/data/index.ts rename to src/plugins/inspector/common/adapters/data/index.ts diff --git a/src/plugins/inspector/public/adapters/data/types.ts b/src/plugins/inspector/common/adapters/data/types.ts similarity index 100% rename from src/plugins/inspector/public/adapters/data/types.ts rename to src/plugins/inspector/common/adapters/data/types.ts diff --git a/src/plugins/inspector/public/adapters/index.ts b/src/plugins/inspector/common/adapters/index.ts similarity index 100% rename from src/plugins/inspector/public/adapters/index.ts rename to src/plugins/inspector/common/adapters/index.ts diff --git a/src/plugins/inspector/public/adapters/request/index.ts b/src/plugins/inspector/common/adapters/request/index.ts similarity index 100% rename from src/plugins/inspector/public/adapters/request/index.ts rename to src/plugins/inspector/common/adapters/request/index.ts diff --git a/src/plugins/inspector/public/adapters/request/request_adapter.test.ts b/src/plugins/inspector/common/adapters/request/request_adapter.test.ts similarity index 100% rename from src/plugins/inspector/public/adapters/request/request_adapter.test.ts rename to src/plugins/inspector/common/adapters/request/request_adapter.test.ts diff --git a/src/plugins/inspector/public/adapters/request/request_adapter.ts b/src/plugins/inspector/common/adapters/request/request_adapter.ts similarity index 100% rename from src/plugins/inspector/public/adapters/request/request_adapter.ts rename to src/plugins/inspector/common/adapters/request/request_adapter.ts diff --git a/src/plugins/inspector/public/adapters/request/request_responder.ts b/src/plugins/inspector/common/adapters/request/request_responder.ts similarity index 100% rename from src/plugins/inspector/public/adapters/request/request_responder.ts rename to src/plugins/inspector/common/adapters/request/request_responder.ts diff --git a/src/plugins/inspector/public/adapters/request/types.ts b/src/plugins/inspector/common/adapters/request/types.ts similarity index 100% rename from src/plugins/inspector/public/adapters/request/types.ts rename to src/plugins/inspector/common/adapters/request/types.ts diff --git a/src/plugins/inspector/common/index.ts b/src/plugins/inspector/common/index.ts new file mode 100644 index 0000000000000..06ab36a577d98 --- /dev/null +++ b/src/plugins/inspector/common/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './adapters'; diff --git a/src/plugins/inspector/index.ts b/src/plugins/inspector/index.ts new file mode 100644 index 0000000000000..a9794d9e4647a --- /dev/null +++ b/src/plugins/inspector/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './common'; diff --git a/src/plugins/inspector/public/index.ts b/src/plugins/inspector/public/index.ts index ea3985563118b..e90e05aa2830a 100644 --- a/src/plugins/inspector/public/index.ts +++ b/src/plugins/inspector/public/index.ts @@ -26,4 +26,4 @@ export function plugin(initializerContext: PluginInitializerContext) { export { InspectorPublicPlugin as Plugin, Setup, Start } from './plugin'; export * from './types'; -export * from './adapters'; +export * from '../common/adapters'; diff --git a/src/plugins/inspector/public/test/is_available.test.ts b/src/plugins/inspector/public/test/is_available.test.ts index 1aeffd68a9f3d..0604129a0734a 100644 --- a/src/plugins/inspector/public/test/is_available.test.ts +++ b/src/plugins/inspector/public/test/is_available.test.ts @@ -18,8 +18,8 @@ */ import { inspectorPluginMock } from '../mocks'; -import { DataAdapter } from '../adapters/data/data_adapter'; -import { RequestAdapter } from '../adapters/request/request_adapter'; +import { DataAdapter } from '../../common/adapters/data/data_adapter'; +import { RequestAdapter } from '../../common/adapters/request/request_adapter'; const adapter1 = new DataAdapter(); const adapter2 = new RequestAdapter(); diff --git a/src/plugins/inspector/public/views/data/components/data_table.tsx b/src/plugins/inspector/public/views/data/components/data_table.tsx index b78a3920804d2..69be069272f79 100644 --- a/src/plugins/inspector/public/views/data/components/data_table.tsx +++ b/src/plugins/inspector/public/views/data/components/data_table.tsx @@ -35,7 +35,7 @@ import { i18n } from '@kbn/i18n'; import { DataDownloadOptions } from './download_options'; import { DataViewRow, DataViewColumn } from '../types'; -import { TabularData } from '../../../adapters/data/types'; +import { TabularData } from '../../../../common/adapters/data/types'; import { IUiSettingsClient } from '../../../../../../core/public'; interface DataTableFormatState { diff --git a/src/plugins/inspector/public/views/data/components/data_view.test.tsx b/src/plugins/inspector/public/views/data/components/data_view.test.tsx index 55322bf5ec91a..2772069d36877 100644 --- a/src/plugins/inspector/public/views/data/components/data_view.test.tsx +++ b/src/plugins/inspector/public/views/data/components/data_view.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { getDataViewDescription } from '../index'; -import { DataAdapter } from '../../../adapters/data'; +import { DataAdapter } from '../../../../common/adapters/data'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { IUiSettingsClient } from '../../../../../../core/public'; diff --git a/src/plugins/inspector/public/views/data/components/data_view.tsx b/src/plugins/inspector/public/views/data/components/data_view.tsx index 91f42a54f64d0..e03c165d96a27 100644 --- a/src/plugins/inspector/public/views/data/components/data_view.tsx +++ b/src/plugins/inspector/public/views/data/components/data_view.tsx @@ -31,7 +31,11 @@ import { import { DataTableFormat } from './data_table'; import { InspectorViewProps, Adapters } from '../../../types'; -import { TabularLoaderOptions, TabularData, TabularCallback } from '../../../adapters/data/types'; +import { + TabularLoaderOptions, + TabularData, + TabularCallback, +} from '../../../../common/adapters/data/types'; import { IUiSettingsClient } from '../../../../../../core/public'; interface DataViewComponentState { diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx index 24412e860f73c..d7cb8f5745613 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_details_request.tsx @@ -20,7 +20,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { EuiCodeBlock } from '@elastic/eui'; -import { Request } from '../../../../adapters/request/types'; +import { Request } from '../../../../../common/adapters/request/types'; import { RequestDetailsProps } from '../types'; export class RequestDetailsRequest extends Component { diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx index f72cde24854a2..933495ff47396 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_details_response.tsx @@ -20,7 +20,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { EuiCodeBlock } from '@elastic/eui'; -import { Request } from '../../../../adapters/request/types'; +import { Request } from '../../../../../common/adapters/request/types'; import { RequestDetailsProps } from '../types'; export class RequestDetailsResponse extends Component { diff --git a/src/plugins/inspector/public/views/requests/components/details/req_details_stats.tsx b/src/plugins/inspector/public/views/requests/components/details/req_details_stats.tsx index c58795d09946c..767f1c2c5ebcf 100644 --- a/src/plugins/inspector/public/views/requests/components/details/req_details_stats.tsx +++ b/src/plugins/inspector/public/views/requests/components/details/req_details_stats.tsx @@ -28,7 +28,7 @@ import { EuiTableRowCell, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Request, RequestStatistic } from '../../../../adapters/request/types'; +import { Request, RequestStatistic } from '../../../../../common/adapters/request/types'; import { RequestDetailsProps } from '../types'; // TODO: Replace by property once available diff --git a/src/plugins/inspector/public/views/requests/components/request_selector.tsx b/src/plugins/inspector/public/views/requests/components/request_selector.tsx index 535ce8ef4b7fc..7971f44be6ebd 100644 --- a/src/plugins/inspector/public/views/requests/components/request_selector.tsx +++ b/src/plugins/inspector/public/views/requests/components/request_selector.tsx @@ -35,8 +35,8 @@ import { EuiToolTip, } from '@elastic/eui'; -import { RequestStatus } from '../../../adapters'; -import { Request } from '../../../adapters/request/types'; +import { RequestStatus } from '../../../../common/adapters'; +import { Request } from '../../../../common/adapters/request/types'; interface RequestSelectorState { isPopoverOpen: boolean; diff --git a/src/plugins/inspector/public/views/requests/components/requests_view.tsx b/src/plugins/inspector/public/views/requests/components/requests_view.tsx index 01ae5e739c93b..a433ea70dc35c 100644 --- a/src/plugins/inspector/public/views/requests/components/requests_view.tsx +++ b/src/plugins/inspector/public/views/requests/components/requests_view.tsx @@ -22,8 +22,8 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiEmptyPrompt, EuiSpacer, EuiText, EuiTextColor } from '@elastic/eui'; -import { RequestStatus } from '../../../adapters'; -import { Request } from '../../../adapters/request/types'; +import { RequestStatus } from '../../../../common/adapters'; +import { Request } from '../../../../common/adapters/request/types'; import { InspectorViewProps } from '../../../types'; import { RequestSelector } from './request_selector'; diff --git a/src/plugins/inspector/public/views/requests/components/types.ts b/src/plugins/inspector/public/views/requests/components/types.ts index ebc3b41e41019..54ba8f0636c1e 100644 --- a/src/plugins/inspector/public/views/requests/components/types.ts +++ b/src/plugins/inspector/public/views/requests/components/types.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Request } from '../../../adapters/request/types'; +import { Request } from '../../../../common/adapters/request/types'; export interface RequestDetailsProps { request: Request; diff --git a/src/plugins/kibana_utils/common/state_containers/create_state_container.ts b/src/plugins/kibana_utils/common/state_containers/create_state_container.ts index 78bfc0c3e9090..c6e1f53145312 100644 --- a/src/plugins/kibana_utils/common/state_containers/create_state_container.ts +++ b/src/plugins/kibana_utils/common/state_containers/create_state_container.ts @@ -36,7 +36,7 @@ const isProduction = ? process.env.NODE_ENV === 'production' : !process.env.NODE_ENV || process.env.NODE_ENV === 'production'; -const freeze: (value: T) => T = isProduction +const defaultFreeze: (value: T) => T = isProduction ? (value: T) => value as T : (value: T): T => { const isFreezable = value !== null && typeof value === 'object'; @@ -44,6 +44,22 @@ const freeze: (value: T) => T = isProduction return value as T; }; +export interface CreateStateContainerOptions { + /** + * Function to use when freezing state. Supply identity function + * + * ```ts + * { + * freeze: state => state, + * } + * ``` + * + * if you expect that your state will be mutated externally an you cannot + * prevent that. + */ + freeze?: (state: T) => T; +} + export function createStateContainer( defaultState: State ): ReduxLikeStateContainer; @@ -58,7 +74,8 @@ export function createStateContainer< >( defaultState: State, pureTransitions: PureTransitions, - pureSelectors: PureSelectors + pureSelectors: PureSelectors, + options?: CreateStateContainerOptions ): ReduxLikeStateContainer; export function createStateContainer< State extends BaseState, @@ -67,8 +84,10 @@ export function createStateContainer< >( defaultState: State, pureTransitions: PureTransitions = {} as PureTransitions, - pureSelectors: PureSelectors = {} as PureSelectors + pureSelectors: PureSelectors = {} as PureSelectors, + options: CreateStateContainerOptions = {} ): ReduxLikeStateContainer { + const { freeze = defaultFreeze } = options; const data$ = new BehaviorSubject(freeze(defaultState)); const state$ = data$.pipe(skip(1)); const get = () => data$.getValue(); diff --git a/src/plugins/visualizations/public/expression_functions/range.ts b/src/plugins/visualizations/public/expression_functions/range.ts index 27c3654e2182a..42eb6aa781970 100644 --- a/src/plugins/visualizations/public/expression_functions/range.ts +++ b/src/plugins/visualizations/public/expression_functions/range.ts @@ -18,19 +18,20 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaDatatable, Range } from '../../../expressions/public'; - -const name = 'range'; - -type Context = KibanaDatatable | null; +import { ExpressionFunctionDefinition, KibanaDatatable, Range } from '../../../expressions/public'; interface Arguments { from: number; to: number; } -export const range = (): ExpressionFunction => ({ - name, +export const range = (): ExpressionFunctionDefinition< + 'range', + KibanaDatatable | null, + Arguments, + Range +> => ({ + name: 'range', help: i18n.translate('visualizations.function.range.help', { defaultMessage: 'Generates range object', }), diff --git a/src/plugins/visualizations/public/expression_functions/vis_dimension.ts b/src/plugins/visualizations/public/expression_functions/vis_dimension.ts index 4ad73ef504874..b9d1a23b1c503 100644 --- a/src/plugins/visualizations/public/expression_functions/vis_dimension.ts +++ b/src/plugins/visualizations/public/expression_functions/vis_dimension.ts @@ -18,11 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaDatatable } from '../../../expressions/public'; - -const name = 'visdimension'; - -type Context = KibanaDatatable | null; +import { + ExpressionFunctionDefinition, + ExpressionValueBoxed, + KibanaDatatable, + KibanaDatatableColumn, +} from '../../../expressions/public'; interface Arguments { accessor: string | number; @@ -30,17 +31,29 @@ interface Arguments { formatParams?: string; } -type Return = any; +type ExpressionValueVisDimension = ExpressionValueBoxed< + 'vis_dimension', + { + accessor: number | KibanaDatatableColumn; + format: { + id?: string; + params: unknown; + }; + } +>; -export const visDimension = (): ExpressionFunction => ({ +export const visDimension = (): ExpressionFunctionDefinition< + 'visdimension', + KibanaDatatable, + Arguments, + ExpressionValueVisDimension +> => ({ name: 'visdimension', help: i18n.translate('visualizations.function.visDimension.help', { defaultMessage: 'Generates visConfig dimension object', }), type: 'vis_dimension', - context: { - types: ['kibana_datatable'], - }, + inputTypes: ['kibana_datatable'], args: { accessor: { types: ['string', 'number'], @@ -64,11 +77,12 @@ export const visDimension = (): ExpressionFunction { + fn: (input, args) => { const accessor = typeof args.accessor === 'number' ? args.accessor - : context!.columns.find(c => c.id === args.accessor); + : input.columns.find(c => c.id === args.accessor); + if (accessor === undefined) { throw new Error( i18n.translate('visualizations.function.visDimension.error.accessor', { diff --git a/test/interpreter_functional/README.md b/test/interpreter_functional/README.md index 73df0ce4c9f04..928792ff8d484 100644 --- a/test/interpreter_functional/README.md +++ b/test/interpreter_functional/README.md @@ -22,6 +22,7 @@ node scripts/functional_test_runner.js --config test/interpreter_functional/conf Look into test_suites/run_pipeline/basic.ts for examples to update baseline screenshots and snapshots run with: + ``` node scripts/functional_test_runner.js --config test/interpreter_functional/config.ts --updateBaselines -``` \ No newline at end of file +``` diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx index daa19f22a7023..41e466fddd11e 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx @@ -20,27 +20,19 @@ import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@elastic/eui'; import { first } from 'rxjs/operators'; -import { - RequestAdapter, - DataAdapter, -} from '../../../../../../../../src/plugins/inspector/public/adapters'; -import { - Adapters, - Context, - ExpressionRenderHandler, - ExpressionDataHandler, - RenderId, -} from '../../types'; +import { IInterpreterRenderHandlers, ExpressionValue } from 'src/plugins/expressions'; +import { RequestAdapter, DataAdapter } from '../../../../../../../../src/plugins/inspector'; +import { Adapters, ExpressionRenderHandler, ExpressionDataHandler } from '../../types'; import { getExpressions } from '../../services'; declare global { interface Window { runPipeline: ( expressions: string, - context?: Context, - initialContext?: Context + context?: ExpressionValue, + initialContext?: ExpressionValue ) => ReturnType; - renderPipelineResponse: (context?: Context) => Promise; + renderPipelineResponse: (context?: ExpressionValue) => Promise; } } @@ -60,8 +52,8 @@ class Main extends React.Component<{}, State> { window.runPipeline = async ( expression: string, - context: Context = {}, - initialContext: Context = {} + context: ExpressionValue = {}, + initialContext: ExpressionValue = {} ) => { this.setState({ expression }); const adapters: Adapters = { @@ -86,7 +78,7 @@ class Main extends React.Component<{}, State> { } lastRenderHandler = getExpressions().render(this.chartRef.current!, context, { - onRenderError: (el, error, handler) => { + onRenderError: (el: HTMLElement, error: unknown, handler: IInterpreterRenderHandlers) => { this.setState({ expression: 'Render error!\n\n' + JSON.stringify(error), }); diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts index cc4190bd099fa..6e0a93e4a3cb1 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts @@ -19,19 +19,10 @@ import { ExpressionsStart, - Context, ExpressionRenderHandler, ExpressionDataHandler, - RenderId, } from 'src/plugins/expressions/public'; import { Adapters } from 'src/plugins/inspector/public'; -export { - ExpressionsStart, - Context, - ExpressionRenderHandler, - ExpressionDataHandler, - RenderId, - Adapters, -}; +export { ExpressionsStart, ExpressionRenderHandler, ExpressionDataHandler, Adapters }; diff --git a/test/interpreter_functional/snapshots/baseline/combined_test0.json b/test/interpreter_functional/snapshots/baseline/combined_test0.json index 2af0407f0d521..8f00d72df8ab3 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test0.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test0.json @@ -1 +1 @@ -{"filters":null,"query":null,"timeRange":null,"type":"kibana_context"} \ No newline at end of file +{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test0.json b/test/interpreter_functional/snapshots/baseline/step_output_test0.json index 2af0407f0d521..8f00d72df8ab3 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test0.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test0.json @@ -1 +1 @@ -{"filters":null,"query":null,"timeRange":null,"type":"kibana_context"} \ No newline at end of file +{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test0.json b/test/interpreter_functional/snapshots/session/combined_test0.json index 2af0407f0d521..8f00d72df8ab3 100644 --- a/test/interpreter_functional/snapshots/session/combined_test0.json +++ b/test/interpreter_functional/snapshots/session/combined_test0.json @@ -1 +1 @@ -{"filters":null,"query":null,"timeRange":null,"type":"kibana_context"} \ No newline at end of file +{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test0.json b/test/interpreter_functional/snapshots/session/step_output_test0.json index 2af0407f0d521..8f00d72df8ab3 100644 --- a/test/interpreter_functional/snapshots/session/step_output_test0.json +++ b/test/interpreter_functional/snapshots/session/step_output_test0.json @@ -1 +1 @@ -{"filters":null,"query":null,"timeRange":null,"type":"kibana_context"} \ No newline at end of file +{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/test_suites/run_pipeline/helpers.ts b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts index 7fedf1723908a..015c311c30aef 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/helpers.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts @@ -18,12 +18,9 @@ */ import expect from '@kbn/expect'; +import { ExpressionValue } from 'src/plugins/expressions'; import { FtrProviderContext } from '../../../functional/ftr_provider_context'; -import { - ExpressionDataHandler, - Context, - RenderId, -} from '../../plugins/kbn_tp_run_pipeline/public/np_ready/types'; +import { ExpressionDataHandler } from '../../plugins/kbn_tp_run_pipeline/public/np_ready/types'; type UnWrapPromise = T extends Promise ? U : T; export type ExpressionResult = UnWrapPromise>; @@ -31,14 +28,14 @@ export type ExpressionResult = UnWrapPromise ExpectExpressionHandler; export interface ExpectExpressionHandler { toReturn: (expectedResult: ExpressionResult) => Promise; getResponse: () => Promise; - runExpression: (step?: string, stepContext?: Context) => Promise; + runExpression: (step?: string, stepContext?: ExpressionValue) => Promise; steps: { toMatchSnapshot: () => Promise; }; @@ -68,8 +65,8 @@ export function expectExpressionProvider({ return ( name: string, expression: string, - context: Context = {}, - initialContext: Context = {} + context: ExpressionValue = {}, + initialContext: ExpressionValue = {} ): ExpectExpressionHandler => { log.debug(`executing expression ${expression}`); const steps = expression.split('|'); // todo: we should actually use interpreter parser and get the ast @@ -101,14 +98,14 @@ export function expectExpressionProvider({ */ runExpression: async ( step: string = expression, - stepContext: Context = context + stepContext: ExpressionValue = context ): Promise => { log.debug(`running expression ${step || expression}`); return browser.executeAsync( ( _expression: string, - _currentContext: Context & { type: string }, - _initialContext: Context, + _currentContext: ExpressionValue & { type: string }, + _initialContext: ExpressionValue, done: (expressionResult: ExpressionResult) => void ) => { if (!_currentContext) _currentContext = { type: 'null' }; @@ -168,8 +165,8 @@ export function expectExpressionProvider({ toMatchScreenshot: async () => { const pipelineResponse = await handler.getResponse(); log.debug('starting to render'); - const result = await browser.executeAsync( - (_context: ExpressionResult, done: (renderResult: RenderId) => void) => + const result = await browser.executeAsync( + (_context: ExpressionResult, done: (renderResult: any) => void) => window.renderPipelineResponse(_context).then(renderResult => { done(renderResult); return renderResult; diff --git a/x-pack/legacy/plugins/canvas/__tests__/fixtures/function_specs.ts b/x-pack/legacy/plugins/canvas/__tests__/fixtures/function_specs.ts index 8cb0b26565ef3..3ed08268222d0 100644 --- a/x-pack/legacy/plugins/canvas/__tests__/fixtures/function_specs.ts +++ b/x-pack/legacy/plugins/canvas/__tests__/fixtures/function_specs.ts @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore Untyped Library -import { Fn } from '@kbn/interpreter/common'; -import { uniq } from 'lodash'; import { functions as browserFns } from '../../canvas_plugin_src/functions/browser'; -import { functions as commonFns } from '../../canvas_plugin_src/functions/common'; -import { functions as serverFns } from '../../canvas_plugin_src/functions/server'; +import { ExpressionFunction } from '../../../../../../src/plugins/expressions'; -export const functionSpecs = uniq([...browserFns, ...commonFns, ...serverFns], 'name').map(fn => new Fn(fn())); +export const functionSpecs = browserFns.map(fn => new ExpressionFunction(fn())); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts index e728ea25f5504..fbe7825c3b2c8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionType } from 'src/plugins/expressions/public'; +import { ExpressionTypeDefinition } from '../../../../../../src/plugins/expressions'; import { EmbeddableInput } from '../../../../../../src/plugins/embeddable/public'; import { EmbeddableTypes } from './embeddable_types'; @@ -17,7 +17,7 @@ export interface EmbeddableExpression { embeddableType: string; } -export const embeddableType = (): ExpressionType< +export const embeddableType = (): ExpressionTypeDefinition< typeof EmbeddableExpressionType, EmbeddableExpression > => ({ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts index af4d0a4ffda92..1e13ebdee3e4b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/location.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Datatable, ExpressionFunction } from '../../../types'; +import { Datatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; const noop = () => {}; @@ -14,15 +14,13 @@ interface Return extends Datatable { rows: [{ latitude: number; longitude: number }]; } -export function location(): ExpressionFunction<'location', null, {}, Promise> { +export function location(): ExpressionFunctionDefinition<'location', null, {}, Promise> { const { help } = getFunctionHelp().location; return { name: 'location', type: 'datatable', - context: { - types: ['null'], - }, + inputTypes: ['null'], args: {}, help, fn: () => { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts index 364dd2eb426fa..95859feeed5f3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Datatable, Render, Style, ExpressionFunction } from 'src/plugins/expressions/common'; +import { + Datatable, + Render, + Style, + ExpressionFunctionDefinition, +} from 'src/plugins/expressions/common'; // @ts-ignore untyped local import { Handlebars } from '../../../common/lib/handlebars'; import { getFunctionHelp } from '../../../i18n'; @@ -21,7 +26,12 @@ interface Return { font: Style; } -export function markdown(): ExpressionFunction<'markdown', Context, Arguments, Render> { +export function markdown(): ExpressionFunctionDefinition< + 'markdown', + Context, + Arguments, + Render +> { const { help, args: argHelp } = getFunctionHelp().markdown; return { @@ -29,9 +39,7 @@ export function markdown(): ExpressionFunction<'markdown', Context, Arguments, R aliases: [], type: 'render', help, - context: { - types: ['datatable', 'null'], - }, + inputTypes: ['datatable', 'null'], args: { content: { aliases: ['_', 'expression'], @@ -46,7 +54,7 @@ export function markdown(): ExpressionFunction<'markdown', Context, Arguments, R default: '{font}', }, }, - fn: (context, args) => { + fn: (input, args) => { const compileFunctions = args.content.map(str => Handlebars.compile(String(str), { knownHelpersOnly: true }) ); @@ -54,7 +62,7 @@ export function markdown(): ExpressionFunction<'markdown', Context, Arguments, R columns: [], rows: [], type: null, - ...context, + ...input, }; return { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/urlparam.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/urlparam.ts index c7109adffd481..0fcde6cbcf309 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/urlparam.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/browser/urlparam.ts @@ -5,7 +5,7 @@ */ import { parse } from 'url'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -13,7 +13,12 @@ interface Arguments { default: string; } -export function urlparam(): ExpressionFunction<'urlparam', null, Arguments, string | string[]> { +export function urlparam(): ExpressionFunctionDefinition< + 'urlparam', + null, + Arguments, + string | string[] +> { const { help, args: argHelp } = getFunctionHelp().urlparam; return { @@ -21,9 +26,7 @@ export function urlparam(): ExpressionFunction<'urlparam', null, Arguments, stri aliases: [], type: 'string', help, - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { param: { types: ['string'], @@ -38,7 +41,7 @@ export function urlparam(): ExpressionFunction<'urlparam', null, Arguments, stri help: argHelp.default, }, }, - fn: (_context, args) => { + fn: (input, args) => { const query = parse(window.location.href, true).query; return query[args.param] || args.default; }, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/all.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/all.ts index 821ab520d8897..812341db0198f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/all.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/all.ts @@ -4,23 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { condition: boolean[]; } -export function all(): ExpressionFunction<'all', null, Arguments, boolean> { +export function all(): ExpressionFunctionDefinition<'all', null, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().all; return { name: 'all', type: 'boolean', help, - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { condition: { aliases: ['_'], @@ -30,7 +28,7 @@ export function all(): ExpressionFunction<'all', null, Arguments, boolean> { multi: true, }, }, - fn: (_context, args) => { + fn: (input, args) => { const conditions = args.condition || []; return conditions.every(Boolean); }, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts index c87d136007b9b..e6739a71b1608 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/alterColumn.ts @@ -6,7 +6,7 @@ import { omit } from 'lodash'; import { Datatable } from 'src/plugins/expressions/common'; -import { DatatableColumn, DatatableColumnType, ExpressionFunction } from '../../../types'; +import { DatatableColumn, DatatableColumnType, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; interface Arguments { @@ -15,17 +15,20 @@ interface Arguments { name: string; } -export function alterColumn(): ExpressionFunction<'alterColumn', Datatable, Arguments, Datatable> { +export function alterColumn(): ExpressionFunctionDefinition< + 'alterColumn', + Datatable, + Arguments, + Datatable +> { const { help, args: argHelp } = getFunctionHelp().alterColumn; const errors = getFunctionErrors().alterColumn; return { name: 'alterColumn', type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { column: { aliases: ['_'], @@ -43,12 +46,12 @@ export function alterColumn(): ExpressionFunction<'alterColumn', Datatable, Argu options: ['null', 'boolean', 'number', 'string', 'date'], }, }, - fn: (context, args) => { + fn: (input, args) => { if (!args.column || (!args.type && !args.name)) { - return context; + return input; } - const column = context.columns.find(col => col.name === args.column); + const column = input.columns.find(col => col.name === args.column); if (!column) { throw errors.columnNotFound(args.column); } @@ -56,7 +59,7 @@ export function alterColumn(): ExpressionFunction<'alterColumn', Datatable, Argu const name = args.name || column.name; const type = args.type || column.type; - const columns = context.columns.reduce((all: DatatableColumn[], col) => { + const columns = input.columns.reduce((all: DatatableColumn[], col) => { if (col.name !== args.name) { if (col.name !== column.name) { all.push(col); @@ -91,7 +94,7 @@ export function alterColumn(): ExpressionFunction<'alterColumn', Datatable, Argu })(); } - const rows = context.rows.map(row => ({ + const rows = input.rows.map(row => ({ ...omit(row, column.name), [name]: handler(row[column.name]), })); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/any.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/any.ts index 8f86351dcad82..4b8097d36cf5d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/any.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/any.ts @@ -4,22 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { condition: boolean[]; } -export function any(): ExpressionFunction<'any', null, Arguments, boolean> { +export function any(): ExpressionFunctionDefinition<'any', null, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().any; return { name: 'any', type: 'boolean', - context: { - types: ['null'], - }, + inputTypes: ['null'], help, args: { condition: { @@ -30,7 +28,7 @@ export function any(): ExpressionFunction<'any', null, Arguments, boolean> { help: argHelp.condition, }, }, - fn: (_context, args) => { + fn: (input, args) => { const conditions = args.condition || []; return conditions.some(Boolean); }, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/as.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/as.ts index ffb493f76e739..9c10e85227398 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/as.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/as.ts @@ -4,24 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Datatable, ExpressionFunction, getType } from '../../../types'; +import { Datatable, ExpressionFunctionDefinition, getType } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { name: string; } -type Context = string | boolean | number | null; +type Input = string | boolean | number | null; -export function asFn(): ExpressionFunction<'as', Context, Arguments, Datatable> { +export function asFn(): ExpressionFunctionDefinition<'as', Input, Arguments, Datatable> { const { help, args: argHelp } = getFunctionHelp().as; return { name: 'as', type: 'datatable', - context: { - types: ['string', 'boolean', 'number', 'null'], - }, + inputTypes: ['string', 'boolean', 'number', 'null'], help, args: { name: { @@ -31,18 +29,18 @@ export function asFn(): ExpressionFunction<'as', Context, Arguments, Datatable> default: 'value', }, }, - fn: (context, args) => { + fn: (input, args) => { return { type: 'datatable', columns: [ { name: args.name, - type: getType(context), + type: getType(input), }, ], rows: [ { - [args.name]: context, + [args.name]: input, }, ], }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.ts index 76e69eb7caf72..47da6f0560302 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/axisConfig.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Position } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; @@ -21,7 +21,12 @@ interface AxisConfig extends Arguments { type: 'axisConfig'; } -export function axisConfig(): ExpressionFunction<'axisConfig', null, Arguments, AxisConfig> { +export function axisConfig(): ExpressionFunctionDefinition< + 'axisConfig', + null, + Arguments, + AxisConfig +> { const { help, args: argHelp } = getFunctionHelp().axisConfig; const errors = getFunctionErrors().axisConfig; @@ -29,10 +34,8 @@ export function axisConfig(): ExpressionFunction<'axisConfig', null, Arguments, name: 'axisConfig', aliases: [], type: 'axisConfig', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: { max: { types: ['number', 'string', 'null'], @@ -58,7 +61,7 @@ export function axisConfig(): ExpressionFunction<'axisConfig', null, Arguments, help: argHelp.tickSize, }, }, - fn: (_context, args) => { + fn: (input, args) => { const { position, min, max, ...rest } = args; if (!Object.values(Position).includes(position)) { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/case.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/case.ts index e059910a948b8..dd573b1283915 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/case.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/case.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -18,7 +18,7 @@ interface Case { result: any; } -export function caseFn(): ExpressionFunction<'case', any, Arguments, Promise> { +export function caseFn(): ExpressionFunctionDefinition<'case', any, Arguments, Promise> { const { help, args: argHelp } = getFunctionHelp().case; return { @@ -41,9 +41,9 @@ export function caseFn(): ExpressionFunction<'case', any, Arguments, Promise { - const matches = await doesMatch(context, args); - const result = matches ? await getResult(context, args) : null; + fn: async (input, args) => { + const matches = await doesMatch(input, args); + const result = matches ? await getResult(input, args) : null; return { type: 'case', matches, result }; }, }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/clear.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/clear.ts index 51bcb9552e3dd..fe074190d2450 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/clear.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/clear.ts @@ -3,19 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; -export function clear(): ExpressionFunction<'clear', any, {}, null> { +export function clear(): ExpressionFunctionDefinition<'clear', any, {}, null> { const { help } = getFunctionHelp().clear; return { name: 'clear', type: 'null', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: {}, fn: () => null, }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/columns.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/columns.ts index 8c1be7df1f208..71c5376428a79 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/columns.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/columns.ts @@ -5,7 +5,7 @@ */ import { omit, pick, find } from 'lodash'; -import { Datatable, DatatableColumn, ExpressionFunction } from '../../../types'; +import { Datatable, DatatableColumn, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -13,16 +13,19 @@ interface Arguments { exclude: string; } -export function columns(): ExpressionFunction<'columns', Datatable, Arguments, Datatable> { +export function columns(): ExpressionFunctionDefinition< + 'columns', + Datatable, + Arguments, + Datatable +> { const { help, args: argHelp } = getFunctionHelp().columns; return { name: 'columns', type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { include: { aliases: ['_'], @@ -34,10 +37,10 @@ export function columns(): ExpressionFunction<'columns', Datatable, Arguments, D help: argHelp.exclude, }, }, - fn: (context, args) => { + fn: (input, args) => { const { include, exclude } = args; - const { columns: contextColumns, rows: contextRows, ...rest } = context; - let result = { ...context }; + const { columns: contextColumns, rows: contextRows, ...rest } = input; + let result = { ...input }; if (exclude) { const fields = exclude.split(',').map(field => field.trim()); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/compare.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/compare.ts index 3e17fe9b89dab..e952faca1d5eb 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/compare.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/compare.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; export enum Operation { @@ -23,7 +23,7 @@ interface Arguments { type Context = boolean | number | string | null; -export function compare(): ExpressionFunction<'compare', Context, Arguments, boolean> { +export function compare(): ExpressionFunctionDefinition<'compare', Context, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().compare; const errors = getFunctionErrors().compare; @@ -32,9 +32,7 @@ export function compare(): ExpressionFunction<'compare', Context, Arguments, boo help, aliases: ['condition'], type: 'boolean', - context: { - types: ['string', 'number', 'boolean', 'null'], - }, + inputTypes: ['string', 'number', 'boolean', 'null'], args: { op: { aliases: ['_'], @@ -48,8 +46,8 @@ export function compare(): ExpressionFunction<'compare', Context, Arguments, boo help: argHelp.to, }, }, - fn: (context, args) => { - const a = context; + fn: (input, args) => { + const a = input; const { to: b, op } = args; const typesMatch = typeof a === typeof b; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts index fe399ce5970ed..b841fde284ab6 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts @@ -3,21 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { ContainerStyle, Overflow, BackgroundRepeat, BackgroundSize } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; // @ts-ignore untyped local import { isValidUrl } from '../../../common/lib/url'; -interface Return extends ContainerStyle { +interface Output extends ContainerStyle { type: 'containerStyle'; } -export function containerStyle(): ExpressionFunction< +export function containerStyle(): ExpressionFunctionDefinition< 'containerStyle', null, ContainerStyle, - Return + Output > { const { help, args: argHelp } = getFunctionHelp().containerStyle; const errors = getFunctionErrors().containerStyle; @@ -26,10 +26,8 @@ export function containerStyle(): ExpressionFunction< name: 'containerStyle', aliases: [], type: 'containerStyle', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: { backgroundColor: { types: ['string'], @@ -74,12 +72,12 @@ export function containerStyle(): ExpressionFunction< help: argHelp.padding, }, }, - fn: (_context, args) => { + fn: (input, args) => { const { backgroundImage, backgroundSize, backgroundRepeat, ...remainingArgs } = args; const style = { type: 'containerStyle', ...remainingArgs, - } as Return; + } as Output; if (backgroundImage) { if (!isValidUrl(backgroundImage)) { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/context.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/context.ts index 021c6d529672c..d1302a1e579a1 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/context.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/context.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; -export function context(): ExpressionFunction<'context', any, {}, any> { +export function context(): ExpressionFunctionDefinition<'context', unknown, {}, unknown> { const { help } = getFunctionHelp().context; return { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/csv.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/csv.ts index 753ab84f13207..705639baffc98 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/csv.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/csv.ts @@ -5,7 +5,7 @@ */ import Papa from 'papaparse'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Datatable } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; @@ -15,17 +15,15 @@ interface Arguments { newline: string; } -export function csv(): ExpressionFunction<'csv', null, Arguments, Datatable> { +export function csv(): ExpressionFunctionDefinition<'csv', null, Arguments, Datatable> { const { help, args: argHelp } = getFunctionHelp().csv; const errorMessages = getFunctionErrors().csv; return { name: 'csv', type: 'datatable', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: { data: { aliases: ['_'], @@ -42,7 +40,7 @@ export function csv(): ExpressionFunction<'csv', null, Arguments, Datatable> { help: argHelp.newline, }, }, - fn(_context, args) { + fn(input, args) { const { data: csvString, delimiter, newline } = args; const config: Papa.ParseConfig = { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/date.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/date.ts index 67a557259709e..573ea8a855607 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/date.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/date.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; interface Arguments { @@ -13,7 +13,7 @@ interface Arguments { format: string; } -export function date(): ExpressionFunction<'date', null, Arguments, number> { +export function date(): ExpressionFunctionDefinition<'date', null, Arguments, number> { const { help, args: argHelp } = getFunctionHelp().date; const errors = getFunctionErrors().date; @@ -21,9 +21,7 @@ export function date(): ExpressionFunction<'date', null, Arguments, number> { name: 'date', type: 'number', help, - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { value: { aliases: ['_'], @@ -35,7 +33,7 @@ export function date(): ExpressionFunction<'date', null, Arguments, number> { help: argHelp.format, }, }, - fn: (_context, args) => { + fn: (input, args) => { const { value: argDate, format } = args; const outputDate = diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/do.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/do.ts index 5fafedaf58c80..5f0c848d76708 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/do.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/do.ts @@ -3,14 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { fn: any[]; } -export function doFn(): ExpressionFunction<'do', any, Arguments, any> { +export function doFn(): ExpressionFunctionDefinition<'do', unknown, Arguments, unknown> { const { help, args: argHelp } = getFunctionHelp().do; return { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts index a4bef4e5e40b2..29a277283494a 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/dropdownControl.ts @@ -5,7 +5,7 @@ */ import { uniq } from 'lodash'; -import { Datatable, Render, ExpressionFunction } from '../../../types'; +import { Datatable, Render, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -19,7 +19,7 @@ interface Return { choices: any; } -export function dropdownControl(): ExpressionFunction< +export function dropdownControl(): ExpressionFunctionDefinition< 'dropdownControl', Datatable, Arguments, @@ -31,9 +31,7 @@ export function dropdownControl(): ExpressionFunction< name: 'dropdownControl', aliases: [], type: 'render', - context: { - types: ['datatable'], - }, + inputTypes: ['datatable'], help, args: { filterColumn: { @@ -51,11 +49,11 @@ export function dropdownControl(): ExpressionFunction< help: argHelp.filterGroup, }, }, - fn: (context, { valueColumn, filterColumn, filterGroup }) => { + fn: (input, { valueColumn, filterColumn, filterGroup }) => { let choices = []; - if (context.rows[0][valueColumn]) { - choices = uniq(context.rows.map(row => row[valueColumn])).sort(); + if (input.rows[0][valueColumn]) { + choices = uniq(input.rows.map(row => row[valueColumn])).sort(); } const column = filterColumn || valueColumn; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/eq.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/eq.ts index 1df74c9d0b689..9cb28dea42607 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/eq.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/eq.ts @@ -3,25 +3,23 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { - value: Context; + value: Input; } -type Context = boolean | number | string | null; +type Input = boolean | number | string | null; -export function eq(): ExpressionFunction<'eq', Context, Arguments, boolean> { +export function eq(): ExpressionFunctionDefinition<'eq', Input, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().eq; return { name: 'eq', type: 'boolean', + inputTypes: ['boolean', 'number', 'string', 'null'], help, - context: { - types: ['boolean', 'number', 'string', 'null'], - }, args: { value: { aliases: ['_'], @@ -30,8 +28,8 @@ export function eq(): ExpressionFunction<'eq', Context, Arguments, boolean> { help: argHelp.value, }, }, - fn: (context, args) => { - return context === args.value; + fn: (input, args) => { + return input === args.value; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/exactly.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/exactly.ts index 5e1775940c86a..88a24186d6044 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/exactly.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/exactly.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Filter, ExpressionFunction } from '../../../types'; +import { Filter, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -13,7 +13,7 @@ interface Arguments { filterGroup: string; } -export function exactly(): ExpressionFunction<'exactly', Filter, Arguments, Filter> { +export function exactly(): ExpressionFunctionDefinition<'exactly', Filter, Arguments, Filter> { const { help, args: argHelp } = getFunctionHelp().exactly; return { @@ -21,9 +21,7 @@ export function exactly(): ExpressionFunction<'exactly', Filter, Arguments, Filt aliases: [], type: 'filter', help, - context: { - types: ['filter'], - }, + inputTypes: ['filter'], args: { column: { types: ['string'], @@ -42,7 +40,7 @@ export function exactly(): ExpressionFunction<'exactly', Filter, Arguments, Filt help: argHelp.filterGroup, }, }, - fn: (context, args) => { + fn: (input, args) => { const { value, column } = args; const filter = { @@ -52,7 +50,7 @@ export function exactly(): ExpressionFunction<'exactly', Filter, Arguments, Filt and: [], }; - return { ...context, and: [...context.and, filter] }; + return { ...input, and: [...input.and, filter] }; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts index 5c9502cd51dbf..17d5211588238 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Datatable, ExpressionFunction } from '../../../types'; +import { Datatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { fn: (datatable: Datatable) => Promise; } -export function filterrows(): ExpressionFunction< +export function filterrows(): ExpressionFunctionDefinition< 'filterrows', Datatable, Arguments, @@ -23,10 +23,8 @@ export function filterrows(): ExpressionFunction< name: 'filterrows', aliases: [], type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { fn: { resolve: false, @@ -36,20 +34,20 @@ export function filterrows(): ExpressionFunction< help: argHelp.fn, }, }, - fn(context, { fn }) { - const checks = context.rows.map(row => + fn(input, { fn }) { + const checks = input.rows.map(row => fn({ - ...context, + ...input, rows: [row], }) ); return Promise.all(checks) - .then(results => context.rows.filter((row, i) => results[i])) + .then(results => input.rows.filter((row, i) => results[i])) .then( rows => ({ - ...context, + ...input, rows, } as Datatable) ); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatdate.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatdate.ts index 921f14f1e1634..ba892ef3dae44 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatdate.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatdate.ts @@ -5,23 +5,26 @@ */ import moment from 'moment'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; export interface Arguments { format: string; } -export function formatdate(): ExpressionFunction<'formatdate', number | string, Arguments, string> { +export function formatdate(): ExpressionFunctionDefinition< + 'formatdate', + number | string, + Arguments, + string +> { const { help, args: argHelp } = getFunctionHelp().formatdate; return { name: 'formatdate', type: 'string', + inputTypes: ['number', 'string'], help, - context: { - types: ['number', 'string'], - }, args: { format: { aliases: ['_'], @@ -30,11 +33,11 @@ export function formatdate(): ExpressionFunction<'formatdate', number | string, help: argHelp.format, }, }, - fn: (context, args) => { + fn: (input, args) => { if (!args.format) { - return moment.utc(new Date(context)).toISOString(); + return moment.utc(new Date(input)).toISOString(); } - return moment.utc(new Date(context)).format(args.format); + return moment.utc(new Date(input)).format(args.format); }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.ts index 38040513a47d1..0584b31b7c8a4 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/formatnumber.ts @@ -5,23 +5,26 @@ */ import numeral from '@elastic/numeral'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; export interface Arguments { format: string; } -export function formatnumber(): ExpressionFunction<'formatnumber', number, Arguments, string> { +export function formatnumber(): ExpressionFunctionDefinition< + 'formatnumber', + number, + Arguments, + string +> { const { help, args: argHelp } = getFunctionHelp().formatnumber; return { name: 'formatnumber', type: 'string', help, - context: { - types: ['number'], - }, + inputTypes: ['number'], args: { format: { aliases: ['_'], @@ -30,11 +33,11 @@ export function formatnumber(): ExpressionFunction<'formatnumber', number, Argum required: true, }, }, - fn: (context, args) => { + fn: (input, args) => { if (!args.format) { - return String(context); + return String(input); } - return numeral(context).format(args.format); + return numeral(input).format(args.format); }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/getCell.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/getCell.ts index 98e8cc86f29e8..bb435629a578e 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/getCell.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/getCell.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Datatable } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; @@ -12,16 +12,14 @@ interface Arguments { row: number; } -export function getCell(): ExpressionFunction<'getCell', Datatable, Arguments, any> { +export function getCell(): ExpressionFunctionDefinition<'getCell', Datatable, Arguments, any> { const { help, args: argHelp } = getFunctionHelp().getCell; const errors = getFunctionErrors().getCell; return { name: 'getCell', help, - context: { - types: ['datatable'], - }, + inputTypes: ['datatable'], args: { column: { types: ['string'], @@ -35,13 +33,13 @@ export function getCell(): ExpressionFunction<'getCell', Datatable, Arguments, a default: 0, }, }, - fn: (context, args) => { - const row = context.rows[args.row]; + fn: (input, args) => { + const row = input.rows[args.row]; if (!row) { throw errors.rowNotFound(args.row); } - const { column = context.columns[0].name } = args; + const { column = input.columns[0].name } = args; const value = row[column]; if (typeof value === 'undefined') { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gt.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gt.ts index 88ff04161222d..b4c6bce5bd31c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gt.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gt.ts @@ -3,24 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; -type Context = number | string; +type Input = number | string; interface Arguments { - value: Context; + value: Input; } -export function gt(): ExpressionFunction<'gt', Context, Arguments, boolean> { +export function gt(): ExpressionFunctionDefinition<'gt', Input, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().gt; return { name: 'gt', type: 'boolean', - context: { - types: ['number', 'string'], - }, + inputTypes: ['number', 'string'], help, args: { value: { @@ -30,14 +28,14 @@ export function gt(): ExpressionFunction<'gt', Context, Arguments, boolean> { help: argHelp.value, }, }, - fn: (context, args) => { + fn: (input, args) => { const { value } = args; - if (typeof context !== typeof value) { + if (typeof input !== typeof value) { return false; } - return context > value; + return input > value; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gte.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gte.ts index c2c9fe2f476fc..3ddab57b5429b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gte.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/gte.ts @@ -3,24 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; -type Context = number | string; +type Input = number | string; interface Arguments { - value: Context; + value: Input; } -export function gte(): ExpressionFunction<'gte', Context, Arguments, boolean> { +export function gte(): ExpressionFunctionDefinition<'gte', Input, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().gte; return { name: 'gte', type: 'boolean', - context: { - types: ['number', 'string'], - }, + inputTypes: ['number', 'string'], help, args: { value: { @@ -30,14 +28,14 @@ export function gte(): ExpressionFunction<'gte', Context, Arguments, boolean> { help: argHelp.value, }, }, - fn: (context, args) => { + fn: (input, args) => { const { value } = args; - if (typeof context !== typeof value) { + if (typeof input !== typeof value) { return false; } - return context >= value; + return input >= value; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/head.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/head.ts index b16e383de6467..b91db30c2535b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/head.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/head.ts @@ -5,24 +5,22 @@ */ import { take } from 'lodash'; -import { Datatable, ExpressionFunction } from '../../../types'; +import { Datatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { count: number; } -export function head(): ExpressionFunction<'head', Datatable, Arguments, Datatable> { +export function head(): ExpressionFunctionDefinition<'head', Datatable, Arguments, Datatable> { const { help, args: argHelp } = getFunctionHelp().head; return { name: 'head', aliases: [], type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { count: { aliases: ['_'], @@ -31,9 +29,9 @@ export function head(): ExpressionFunction<'head', Datatable, Arguments, Datatab default: 1, }, }, - fn: (context, args) => ({ - ...context, - rows: take(context.rows, args.count), + fn: (input, args) => ({ + ...input, + rows: take(input.rows, args.count), }), }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/if.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/if.ts index 1be8777a98555..6b9464843fca4 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/if.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/if.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -12,7 +12,7 @@ interface Arguments { else: () => Promise; } -export function ifFn(): ExpressionFunction<'if', any, Arguments, any> { +export function ifFn(): ExpressionFunctionDefinition<'if', unknown, Arguments, unknown> { const { help, args: argHelp } = getFunctionHelp().if; return { @@ -33,15 +33,15 @@ export function ifFn(): ExpressionFunction<'if', any, Arguments, any> { help: argHelp.else, }, }, - fn: async (context, args) => { + fn: async (input, args) => { if (args.condition) { if (typeof args.then === 'undefined') { - return context; + return input; } return await args.then(); } else { if (typeof args.else === 'undefined') { - return context; + return input; } return await args.else(); } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/image.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/image.ts index d21e0bb360ab0..c43ff6373ea0f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/image.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/image.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; // @ts-ignore untyped local @@ -28,7 +28,7 @@ interface Return { dataurl: string; } -export function image(): ExpressionFunction<'image', null, Arguments, Return> { +export function image(): ExpressionFunctionDefinition<'image', null, Arguments, Return> { const { help, args: argHelp } = getFunctionHelp().image; const errors = getFunctionErrors().image; @@ -36,10 +36,8 @@ export function image(): ExpressionFunction<'image', null, Arguments, Return> { name: 'image', aliases: [], type: 'image', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: { dataurl: { // This was accepting dataurl, but there was no facility in fn for checking type and handling a dataurl type. @@ -55,7 +53,7 @@ export function image(): ExpressionFunction<'image', null, Arguments, Return> { options: Object.values(ImageMode), }, }, - fn: (_context, { dataurl, mode }) => { + fn: (input, { dataurl, mode }) => { if (!mode || !Object.values(ImageMode).includes(mode)) { throw errors.invalidImageMode(); } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts index 687b95188a98c..7f8a7b525180c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/join_rows.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Datatable, ExpressionFunction } from '../../../types'; +import { Datatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; interface Arguments { @@ -23,16 +23,14 @@ const escapeString = (data: string, quotechar: string): string => { } }; -export function joinRows(): ExpressionFunction<'joinRows', Datatable, Arguments, string> { +export function joinRows(): ExpressionFunctionDefinition<'joinRows', Datatable, Arguments, string> { const { help, args: argHelp } = getFunctionHelp().joinRows; const errors = getFunctionErrors().joinRows; return { name: 'joinRows', type: 'string', help, - context: { - types: ['datatable'], - }, + inputTypes: ['datatable'], args: { column: { aliases: ['_'], @@ -57,14 +55,14 @@ export function joinRows(): ExpressionFunction<'joinRows', Datatable, Arguments, default: ',', }, }, - fn: (context, { column, separator, quote, distinct }) => { - const columnMatch = context.columns.find(col => col.name === column); + fn: (input, { column, separator, quote, distinct }) => { + const columnMatch = input.columns.find(col => col.name === column); if (!columnMatch) { throw errors.columnNotFound(column); } - return context.rows + return input.rows .reduce((acc, row) => { const value = row[column]; if (distinct && acc.includes(value)) return acc; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lt.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lt.ts index c6ca30e7e5e91..6c51ea9705669 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lt.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lt.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; type Context = number | string; @@ -12,15 +12,13 @@ interface Arguments { value: Context; } -export function lt(): ExpressionFunction<'lt', Context, Arguments, boolean> { +export function lt(): ExpressionFunctionDefinition<'lt', Context, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().lt; return { name: 'lt', type: 'boolean', - context: { - types: ['number', 'string'], - }, + inputTypes: ['number', 'string'], help, args: { value: { @@ -30,14 +28,14 @@ export function lt(): ExpressionFunction<'lt', Context, Arguments, boolean> { help: argHelp.value, }, }, - fn: (context, args) => { + fn: (input, args) => { const { value } = args; - if (typeof context !== typeof value) { + if (typeof input !== typeof value) { return false; } - return context < value; + return input < value; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lte.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lte.ts index b976600aaab94..470e4f5f08cf8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lte.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/lte.ts @@ -3,24 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; import { getFunctionHelp } from '../../../i18n'; -type Context = number | string; +type Input = number | string; interface Arguments { - value: Context; + value: Input; } -export function lte(): ExpressionFunction<'lte', Context, Arguments, boolean> { +export function lte(): ExpressionFunctionDefinition<'lte', Input, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().lte; return { name: 'lte', type: 'boolean', - context: { - types: ['number', 'string'], - }, + inputTypes: ['number', 'string'], help, args: { value: { @@ -30,14 +28,14 @@ export function lte(): ExpressionFunction<'lte', Context, Arguments, boolean> { help: argHelp.value, }, }, - fn: (context, args) => { + fn: (input, args) => { const { value } = args; - if (typeof context !== typeof value) { + if (typeof input !== typeof value) { return false; } - return context <= value; + return input <= value; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts index 701322066f100..d8b15a65252e6 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Datatable, ExpressionFunction, getType } from '../../../types'; +import { Datatable, ExpressionFunctionDefinition, getType } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -12,7 +12,7 @@ interface Arguments { expression: (datatable: Datatable) => Promise; } -export function mapColumn(): ExpressionFunction< +export function mapColumn(): ExpressionFunctionDefinition< 'mapColumn', Datatable, Arguments, @@ -24,10 +24,8 @@ export function mapColumn(): ExpressionFunction< name: 'mapColumn', aliases: ['mc'], // midnight commander. So many times I've launched midnight commander instead of moving a file. type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { name: { types: ['string'], @@ -43,11 +41,11 @@ export function mapColumn(): ExpressionFunction< required: true, }, }, - fn: (context, args) => { + fn: (input, args) => { const expression = args.expression || (() => Promise.resolve(null)); - const columns = [...context.columns]; - const rowPromises = context.rows.map(row => { + const columns = [...input.columns]; + const rowPromises = input.rows.map(row => { return expression({ type: 'datatable', columns, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/map_center.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/map_center.ts index 21f9e9fe3148d..8ec2b7d7d3dc3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/map_center.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/map_center.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n/functions'; import { MapCenter } from '../../../types'; @@ -14,15 +14,13 @@ interface Args { zoom: number; } -export function mapCenter(): ExpressionFunction<'mapCenter', null, Args, MapCenter> { +export function mapCenter(): ExpressionFunctionDefinition<'mapCenter', null, Args, MapCenter> { const { help, args: argHelp } = getFunctionHelp().mapCenter; return { name: 'mapCenter', help, type: 'mapCenter', - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { lat: { types: ['number'], @@ -40,7 +38,7 @@ export function mapCenter(): ExpressionFunction<'mapCenter', null, Args, MapCent help: argHelp.zoom, }, }, - fn: (context, args) => { + fn: (input, args) => { return { type: 'mapCenter', ...args, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts index 8fcdf00a7f8d6..dfbb37be0797c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/math.ts @@ -8,26 +8,24 @@ import { evaluate } from 'tinymath'; // @ts-ignore untyped local import { pivotObjectArray } from '../../../common/lib/pivot_object_array'; -import { Datatable, isDatatable, ExpressionFunction } from '../../../types'; +import { Datatable, isDatatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; interface Arguments { expression: string; } -type Context = number | Datatable; +type Input = number | Datatable; -export function math(): ExpressionFunction<'math', Context, Arguments, number> { +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, - context: { - types: ['number', 'datatable'], - }, args: { expression: { aliases: ['_'], @@ -35,19 +33,19 @@ export function math(): ExpressionFunction<'math', Context, Arguments, number> { help: argHelp.expression, }, }, - fn: (context, args) => { + fn: (input, args) => { const { expression } = args; if (!expression || expression.trim() === '') { throw errors.emptyExpression(); } - const mathContext = isDatatable(context) + const mathContext = isDatatable(input) ? pivotObjectArray( - context.rows, - context.columns.map(col => col.name) + input.rows, + input.columns.map(col => col.name) ) - : { value: context }; + : { value: input }; try { const result = evaluate(expression, mathContext); @@ -62,7 +60,7 @@ export function math(): ExpressionFunction<'math', Context, Arguments, number> { } return result; } catch (e) { - if (isDatatable(context) && context.rows.length === 0) { + if (isDatatable(input) && input.rows.length === 0) { throw errors.emptyDatatable(); } else { throw e; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/metric.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/metric.ts index 597e8dd731515..6aab1a7dfb99b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/metric.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/metric.ts @@ -5,10 +5,10 @@ */ import { openSans } from '../../../common/lib/fonts'; -import { Render, Style, ExpressionFunction } from '../../../types'; +import { Render, Style, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; -type Context = number | string | null; +type Input = number | string | null; interface Arguments { label: string; @@ -17,17 +17,20 @@ interface Arguments { labelFont: Style; } -export function metric(): ExpressionFunction<'metric', Context, Arguments, Render> { +export function metric(): ExpressionFunctionDefinition< + 'metric', + Input, + Arguments, + Render +> { const { help, args: argHelp } = getFunctionHelp().metric; return { name: 'metric', aliases: [], type: 'render', + inputTypes: ['number', 'string', 'null'], help, - context: { - types: ['number', 'string', 'null'], - }, args: { label: { types: ['string'], @@ -51,12 +54,12 @@ export function metric(): ExpressionFunction<'metric', Context, Arguments, Rende help: argHelp.metricFormat, }, }, - fn: (context, { label, labelFont, metricFont, metricFormat }) => { + fn: (input, { label, labelFont, metricFont, metricFormat }) => { return { type: 'render', as: 'metric', value: { - metric: context === null ? '?' : context, + metric: input === null ? '?' : input, label, labelFont, metricFont, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/neq.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/neq.ts index f9026453d340b..4066a35ea41f2 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/neq.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/neq.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; -type Context = boolean | number | string | null; +type Input = boolean | number | string | null; interface Arguments { - value: Context; + value: Input; } -export function neq(): ExpressionFunction<'neq', Context, Arguments, boolean> { +export function neq(): ExpressionFunctionDefinition<'neq', Input, Arguments, boolean> { const { help, args: argHelp } = getFunctionHelp().neq; return { @@ -28,8 +28,8 @@ export function neq(): ExpressionFunction<'neq', Context, Arguments, boolean> { help: argHelp.value, }, }, - fn: (context, args) => { - return context !== args.value; + fn: (input, args) => { + return input !== args.value; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/palette.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/palette.ts index 441dce286cac3..63cd663d2ac4c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/palette.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/palette.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; // @ts-ignore untyped local import { palettes } from '../../../common/lib/palettes'; import { getFunctionHelp } from '../../../i18n'; @@ -15,23 +15,21 @@ interface Arguments { reverse: boolean; } -interface Return { +interface Output { type: 'palette'; colors: string[]; gradient: boolean; } -export function palette(): ExpressionFunction<'palette', null, Arguments, Return> { +export function palette(): ExpressionFunctionDefinition<'palette', null, Arguments, Output> { const { help, args: argHelp } = getFunctionHelp().palette; return { name: 'palette', aliases: [], type: 'palette', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: { color: { aliases: ['_'], @@ -52,7 +50,7 @@ export function palette(): ExpressionFunction<'palette', null, Arguments, Return options: [true, false], }, }, - fn: (_context, args) => { + fn: (input, args) => { const { color, reverse, gradient } = args; const colors = ([] as string[]).concat(color || palettes.paul_tor_14.colors); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/pie.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/pie.ts index a8250cfebfaeb..36f1bf85b97e7 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/pie.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/pie.ts @@ -19,7 +19,7 @@ import { Render, SeriesStyle, Style, - ExpressionFunction, + ExpressionFunctionDefinition, } from '../../../types'; interface PieSeriesOptions { @@ -77,17 +77,15 @@ interface Arguments { tilt: number; } -export function pie(): ExpressionFunction<'pie', PointSeries, Arguments, Render> { +export function pie(): ExpressionFunctionDefinition<'pie', PointSeries, Arguments, Render> { const { help, args: argHelp } = getFunctionHelp().pie; return { name: 'pie', aliases: [], type: 'render', + inputTypes: ['pointseries'], help, - context: { - types: ['pointseries'], - }, args: { font: { types: ['style'], @@ -136,11 +134,11 @@ export function pie(): ExpressionFunction<'pie', PointSeries, Arguments, Render< help: argHelp.tilt, }, }, - fn: (context, args) => { + fn: (input, args) => { const { tilt, radius, labelRadius, labels, hole, legend, palette, font, seriesStyle } = args; const seriesStyles = keyBy(seriesStyle || [], 'label') || {}; - const data: PieData[] = map(groupBy(context.rows, 'color'), (series, label = '') => { + const data: PieData[] = map(groupBy(input.rows, 'color'), (series, label = '') => { const item: PieData = { label, data: series.map(point => point.size || 1), diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts index 98eab84643da6..34e5d9f600d8d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts @@ -7,7 +7,7 @@ // @ts-ignore no @typed def import keyBy from 'lodash.keyby'; import { groupBy, get, set, map, sortBy } from 'lodash'; -import { ExpressionFunction, Style } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition, Style } from 'src/plugins/expressions'; // @ts-ignore untyped local import { getColorsFromPalette } from '../../../../common/lib/get_colors_from_palette'; // @ts-ignore untyped local @@ -29,17 +29,15 @@ interface Arguments { yaxis: AxisConfig | boolean; } -export function plot(): ExpressionFunction<'plot', PointSeries, Arguments, Render> { +export function plot(): ExpressionFunctionDefinition<'plot', PointSeries, Arguments, Render> { const { help, args: argHelp } = getFunctionHelp().plot; return { name: 'plot', aliases: [], type: 'render', + inputTypes: ['pointseries'], help, - context: { - types: ['pointseries'], - }, args: { defaultStyle: { multi: false, @@ -79,12 +77,12 @@ export function plot(): ExpressionFunction<'plot', PointSeries, Arguments, Rende default: true, }, }, - fn: (context, args) => { + fn: (input, args) => { const seriesStyles: { [key: string]: SeriesStyle } = keyBy(args.seriesStyle || [], 'label') || {}; - const sortedRows = sortBy(context.rows, ['x', 'y', 'color', 'size', 'text']); - const ticks = getTickHash(context.columns, sortedRows); + const sortedRows = sortBy(input.rows, ['x', 'y', 'color', 'size', 'text']); + const ticks = getTickHash(input.columns, sortedRows); const font = args.font ? getFontSpec(args.font) : {}; const data = map(groupBy(sortedRows, 'color'), (series, label) => { @@ -104,8 +102,8 @@ export function plot(): ExpressionFunction<'plot', PointSeries, Arguments, Rende text?: string; } = {}; - const x = get(context.columns, 'x.type') === 'string' ? ticks.x.hash[point.x] : point.x; - const y = get(context.columns, 'y.type') === 'string' ? ticks.y.hash[point.y] : point.y; + const x = get(input.columns, 'x.type') === 'string' ? ticks.x.hash[point.x] : point.x; + const y = get(input.columns, 'y.type') === 'string' ? ticks.y.hash[point.y] : point.y; if (point.size != null) { attrs.size = point.size; @@ -136,7 +134,7 @@ export function plot(): ExpressionFunction<'plot', PointSeries, Arguments, Rende }, }; - const result = { + const output = { type: 'render', as: 'plot', value: { @@ -148,12 +146,12 @@ export function plot(): ExpressionFunction<'plot', PointSeries, Arguments, Rende legend: getLegendConfig(args.legend, data.length), grid: gridConfig, xaxis: getFlotAxisConfig('x', args.xaxis, { - columns: context.columns, + columns: input.columns, ticks, font, }), yaxis: getFlotAxisConfig('y', args.yaxis, { - columns: context.columns, + columns: input.columns, ticks, font, }), @@ -169,7 +167,7 @@ export function plot(): ExpressionFunction<'plot', PointSeries, Arguments, Rende // TODO: holy hell, why does this work?! the working theory is that some values become undefined // and serializing the result here causes them to be dropped off, and this makes flot react differently. // It's also possible that something else ends up mutating this object, but that seems less likely. - return JSON.parse(JSON.stringify(result)); + return JSON.parse(JSON.stringify(output)); }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/ply.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/ply.ts index 24fe16bd8d24d..391ff20461fb4 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/ply.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/ply.ts @@ -5,7 +5,7 @@ */ import { groupBy, flatten, pick, map } from 'lodash'; -import { Datatable, DatatableColumn, ExpressionFunction } from '../../../types'; +import { Datatable, DatatableColumn, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; interface Arguments { @@ -13,19 +13,17 @@ interface Arguments { expression: Array<(datatable: Datatable) => Promise>; } -type Return = Datatable | Promise; +type Output = Datatable | Promise; -export function ply(): ExpressionFunction<'ply', Datatable, Arguments, Return> { +export function ply(): ExpressionFunctionDefinition<'ply', Datatable, Arguments, Output> { const { help, args: argHelp } = getFunctionHelp().ply; const errors = getFunctionErrors().ply; return { name: 'ply', type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { by: { types: ['string'], @@ -40,9 +38,9 @@ export function ply(): ExpressionFunction<'ply', Datatable, Arguments, Return> { help: argHelp.expression, }, }, - fn: (context, args) => { + fn: (input, args) => { if (!args) { - return context; + return input; } let byColumns: DatatableColumn[]; @@ -50,7 +48,7 @@ export function ply(): ExpressionFunction<'ply', Datatable, Arguments, Return> { if (args.by) { byColumns = args.by.map(by => { - const column = context.columns.find(col => col.name === by); + const column = input.columns.find(col => col.name === by); if (!column) { throw errors.columnNotFound(by); @@ -59,14 +57,14 @@ export function ply(): ExpressionFunction<'ply', Datatable, Arguments, Return> { return column; }); - const keyedDatatables = groupBy(context.rows, row => JSON.stringify(pick(row, args.by))); + const keyedDatatables = groupBy(input.rows, row => JSON.stringify(pick(row, args.by))); originalDatatables = Object.values(keyedDatatables).map(rows => ({ - ...context, + ...input, rows, })); } else { - originalDatatables = [context]; + originalDatatables = [input]; } const datatablePromises = originalDatatables.map(originalDatatable => { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/progress.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/progress.ts index 399c0acf249d1..6fc1e509cd5e6 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/progress.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/progress.ts @@ -6,7 +6,7 @@ import { get } from 'lodash'; import { openSans } from '../../../common/lib/fonts'; -import { Render, Style, ExpressionFunction } from '../../../types'; +import { Render, Style, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; export enum Shape { @@ -31,7 +31,12 @@ interface Arguments { valueWeight: number; } -export function progress(): ExpressionFunction<'progress', number, Arguments, Render> { +export function progress(): ExpressionFunctionDefinition< + 'progress', + number, + Arguments, + Render +> { const { help, args: argHelp } = getFunctionHelp().progress; const errors = getFunctionErrors().progress; @@ -39,10 +44,8 @@ export function progress(): ExpressionFunction<'progress', number, Arguments, Re name: 'progress', aliases: [], type: 'render', + inputTypes: ['number'], help, - context: { - types: ['number'], - }, args: { shape: { aliases: ['_'], diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/render.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/render.ts index f181f4ed3e513..da50195480c68 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/render.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/render.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Render, ContainerStyle } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; // @ts-ignore unconverted local file @@ -19,17 +19,20 @@ interface Arguments { css: string; containerStyle: ContainerStyleArgument; } -export function render(): ExpressionFunction<'render', Render, Arguments, Render> { +export function render(): ExpressionFunctionDefinition< + 'render', + Render, + Arguments, + Render +> { const { help, args: argHelp } = getFunctionHelp().render; return { name: 'render', aliases: [], type: 'render', + inputTypes: ['render'], help, - context: { - types: ['render'], - }, args: { as: { types: ['string'], @@ -64,10 +67,10 @@ export function render(): ExpressionFunction<'render', Render, Arguments, R default: '{containerStyle}', }, }, - fn: (context, args) => { + fn: (input, args) => { return { - ...context, - as: args.as || context.as, + ...input, + as: args.as || input.as, css: args.css || DEFAULT_ELEMENT_CSS, containerStyle: args.containerStyle, }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts index f52dc140f1c8c..f91fd3dfc5522 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; // @ts-ignore untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; // @ts-ignore .png file @@ -19,7 +19,7 @@ interface Arguments { emptyImage: string | null; } -export function repeatImage(): ExpressionFunction< +export function repeatImage(): ExpressionFunctionDefinition< 'repeatImage', number, Arguments, @@ -31,10 +31,8 @@ export function repeatImage(): ExpressionFunction< name: 'repeatImage', aliases: [], type: 'render', + inputTypes: ['number'], help, - context: { - types: ['number'], - }, args: { emptyImage: { types: ['string', 'null'], diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/replace.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/replace.ts index 3cb6d17b7cd4f..70497f39de9a7 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/replace.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/replace.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -12,16 +12,14 @@ interface Arguments { flags: string; replacement: string; } -export function replace(): ExpressionFunction<'replace', string, Arguments, string> { +export function replace(): ExpressionFunctionDefinition<'replace', string, Arguments, string> { const { help, args: argHelp } = getFunctionHelp().replace; return { name: 'replace', type: 'string', help, - context: { - types: ['string'], - }, + inputTypes: ['string'], args: { pattern: { aliases: ['_', 'regex'], @@ -40,6 +38,6 @@ export function replace(): ExpressionFunction<'replace', string, Arguments, stri default: '""', }, }, - fn: (context, args) => context.replace(new RegExp(args.pattern, args.flags), args.replacement), + fn: (input, args) => input.replace(new RegExp(args.pattern, args.flags), args.replacement), }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts index 4b327ab91af41..d961227a302b8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions'; // @ts-ignore untyped local import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; // @ts-ignore .png file import { elasticOutline } from '../../lib/elastic_outline'; -import { Render } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; export enum Origin { @@ -25,11 +24,11 @@ interface Arguments { origin: Origin; } -export function revealImage(): ExpressionFunction< +export function revealImage(): ExpressionFunctionDefinition< 'revealImage', number, Arguments, - Render + ExpressionValueRender > { const { help, args: argHelp } = getFunctionHelp().revealImage; const errors = getFunctionErrors().revealImage; @@ -38,10 +37,8 @@ export function revealImage(): ExpressionFunction< name: 'revealImage', aliases: [], type: 'render', + inputTypes: ['number'], help, - context: { - types: ['number'], - }, args: { image: { types: ['string', 'null'], diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rounddate.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rounddate.ts index 275484458384e..a215f545fd531 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rounddate.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rounddate.ts @@ -5,23 +5,21 @@ */ import moment from 'moment'; -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; export interface Arguments { format: string; } -export function rounddate(): ExpressionFunction<'rounddate', number, Arguments, number> { +export function rounddate(): ExpressionFunctionDefinition<'rounddate', number, Arguments, number> { const { help, args: argHelp } = getFunctionHelp().rounddate; return { name: 'rounddate', type: 'number', help, - context: { - types: ['number'], - }, + inputTypes: ['number'], args: { format: { aliases: ['_'], @@ -29,11 +27,11 @@ export function rounddate(): ExpressionFunction<'rounddate', number, Arguments, help: argHelp.format, }, }, - fn: (context, args) => { + fn: (input, args) => { if (!args.format) { - return context; + return input; } - return moment.utc(moment.utc(context).format(args.format), args.format).valueOf(); + return moment.utc(moment.utc(input).format(args.format), args.format).valueOf(); }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts index 9104343d7afe8..d1027f784c9a9 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/rowCount.ts @@ -4,22 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Datatable } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; -export function rowCount(): ExpressionFunction<'rowCount', Datatable, {}, number> { +export function rowCount(): ExpressionFunctionDefinition<'rowCount', Datatable, {}, number> { const { help } = getFunctionHelp().rowCount; return { name: 'rowCount', aliases: [], type: 'number', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: {}, - fn: context => context.rows.length, + fn: input => input.rows.length, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.test.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.test.ts index 5b95886faa13d..cf0c76be4580d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.test.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.test.ts @@ -31,13 +31,13 @@ describe('savedMap', () => { }; it('accepts null context', () => { - const expression = fn(null, args, {}); + const expression = fn(null, args, {} as any); expect(expression.input.filters).toEqual([]); }); it('accepts filter context', () => { - const expression = fn(filterContext, args, {}); + const expression = fn(filterContext, args, {} as any); const embeddableFilters = getQueryFilters(filterContext.and); expect(expression.input.filters).toEqual(embeddableFilters); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts index 39d8dbe116f3a..0d4616635d5c7 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { TimeRange } from 'src/plugins/data/public'; import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { getQueryFilters } from '../../../server/lib/build_embeddable_filters'; @@ -50,10 +50,14 @@ const defaultTimeRange = { to: 'now', }; -type Return = EmbeddableExpression; +type Output = EmbeddableExpression; -export function savedMap(): ExpressionFunction<'savedMap', Filter | null, Arguments, Return> { - // @ts-ignore elastic/kibana#44822 Disabling pending filters work +export function savedMap(): ExpressionFunctionDefinition< + 'savedMap', + Filter | null, + Arguments, + Output +> { const { help, args: argHelp } = getFunctionHelp().savedMap; return { name: 'savedMap', @@ -87,8 +91,8 @@ export function savedMap(): ExpressionFunction<'savedMap', Filter | null, Argume }, }, type: EmbeddableExpressionType, - fn: (context, args) => { - const filters = context ? context.and : []; + fn: (input, args) => { + const filters = input ? input.and : []; const center = args.center ? { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.test.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.test.ts index 9e5d4b2dd31a1..294d6124c7e33 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.test.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.test.ts @@ -27,14 +27,14 @@ describe('savedSearch', () => { }; it('accepts null context', () => { - const expression = fn(null, args, {}); + const expression = fn(null, args, {} as any); expect(expression.input.filters).toEqual([]); expect(expression.input.timeRange).toBeUndefined(); }); it('accepts filter context', () => { - const expression = fn(filterContext, args, {}); + const expression = fn(filterContext, args, {} as any); const embeddableFilters = buildEmbeddableFilters(filterContext.and); expect(expression.input.filters).toEqual(embeddableFilters.filters); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts index c1f89eeaa232d..a351bcb46cdd3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { SearchInput } from 'src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable'; import { EmbeddableTypes, @@ -20,10 +20,14 @@ interface Arguments { id: string; } -type Return = EmbeddableExpression & { id: SearchInput['id'] }>; +type Output = EmbeddableExpression & { id: SearchInput['id'] }>; -export function savedSearch(): ExpressionFunction<'savedSearch', Filter | null, Arguments, Return> { - // @ts-ignore elastic/kibana#44822 Disabling pending filters work +export function savedSearch(): ExpressionFunctionDefinition< + 'savedSearch', + Filter | null, + Arguments, + Output +> { const { help, args: argHelp } = getFunctionHelp().savedSearch; return { name: 'savedSearch', @@ -36,8 +40,8 @@ export function savedSearch(): ExpressionFunction<'savedSearch', Filter | null, }, }, type: EmbeddableExpressionType, - fn: (context, { id }) => { - const filters = context ? context.and : []; + fn: (input, { id }) => { + const filters = input ? input.and : []; return { type: EmbeddableExpressionType, input: { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.test.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.test.ts index 965491272cef8..49b4b77de763b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.test.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.test.ts @@ -27,14 +27,14 @@ describe('savedVisualization', () => { }; it('accepts null context', () => { - const expression = fn(null, args, {}); + const expression = fn(null, args, {} as any); expect(expression.input.filters).toEqual([]); expect(expression.input.timeRange).toBeUndefined(); }); it('accepts filter context', () => { - const expression = fn(filterContext, args, {}); + const expression = fn(filterContext, args, {} as any); const embeddableFilters = buildEmbeddableFilters(filterContext.and); expect(expression.input.filters).toEqual(embeddableFilters.filters); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts index 817cb1573e66b..6097e48eb8937 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; import { VisualizeInput } from 'src/legacy/core_plugins/visualizations/public/embeddable'; import { EmbeddableTypes, @@ -19,13 +19,13 @@ interface Arguments { id: string; } -type Return = EmbeddableExpression; +type Output = EmbeddableExpression; -export function savedVisualization(): ExpressionFunction< +export function savedVisualization(): ExpressionFunctionDefinition< 'savedVisualization', Filter | null, Arguments, - Return + Output > { // @ts-ignore elastic/kibana#44822 Disabling pending filters work const { help, args: argHelp } = getFunctionHelp().savedVisualization; @@ -40,8 +40,8 @@ export function savedVisualization(): ExpressionFunction< }, }, type: EmbeddableExpressionType, - fn: (context, { id }) => { - const filters = context ? context.and : []; + fn: (input, { id }) => { + const filters = input ? input.and : []; return { type: EmbeddableExpressionType, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/seriesStyle.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/seriesStyle.ts index 4ae57878e36fe..6c80eb02f2a8b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/seriesStyle.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/seriesStyle.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; const name = 'seriesStyle'; @@ -20,20 +20,23 @@ interface Arguments { stack: number | null; } -interface Return extends Arguments { +interface Output extends Arguments { type: 'seriesStyle'; } -export function seriesStyle(): ExpressionFunction<'seriesStyle', null, Arguments, Return> { +export function seriesStyle(): ExpressionFunctionDefinition< + 'seriesStyle', + null, + Arguments, + Output +> { const { help, args: argHelp } = getFunctionHelp().seriesStyle; return { name, help, type: 'seriesStyle', - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { bars: { types: ['number'], @@ -71,6 +74,6 @@ export function seriesStyle(): ExpressionFunction<'seriesStyle', null, Arguments help: argHelp.stack, }, }, - fn: (_context, args) => ({ type: name, ...args }), + fn: (input, args) => ({ type: name, ...args }), }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/shape.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/shape.ts index a96d39f9914ec..a3fedebd36cfe 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/shape.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/shape.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common/types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; import { getFunctionHelp } from '../../../i18n'; export enum Shape { @@ -34,21 +34,19 @@ interface Arguments { maintainAspect: boolean; } -interface Return extends Arguments { +interface Output extends Arguments { type: 'shape'; } -export function shape(): ExpressionFunction<'shape', null, Arguments, Return> { +export function shape(): ExpressionFunctionDefinition<'shape', null, Arguments, Output> { const { help, args: argHelp } = getFunctionHelp().shape; return { name: 'shape', aliases: [], type: 'shape', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: { shape: { types: ['string'], @@ -80,7 +78,7 @@ export function shape(): ExpressionFunction<'shape', null, Arguments, Return> { options: [true, false], }, }, - fn: (_context, args) => ({ + fn: (input, args) => ({ type: 'shape', ...args, }), diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/sort.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/sort.ts index a7dcfff87631f..40d7dce844748 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/sort.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/sort.ts @@ -5,7 +5,7 @@ */ import { sortBy } from 'lodash'; -import { ExpressionFunction, Datatable } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -13,16 +13,14 @@ interface Arguments { reverse: boolean; } -export function sort(): ExpressionFunction<'sort', Datatable, Arguments, Datatable> { +export function sort(): ExpressionFunctionDefinition<'sort', Datatable, Arguments, Datatable> { const { help, args: argHelp } = getFunctionHelp().sort; return { name: 'sort', type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { by: { types: ['string'], @@ -37,12 +35,12 @@ export function sort(): ExpressionFunction<'sort', Datatable, Arguments, Datatab default: false, }, }, - fn: (context, args) => { - const column = args.by || context.columns[0].name; + fn: (input, args) => { + const column = args.by || input.columns[0].name; return { - ...context, - rows: args.reverse ? sortBy(context.rows, column).reverse() : sortBy(context.rows, column), + ...input, + rows: args.reverse ? sortBy(input.rows, column).reverse() : sortBy(input.rows, column), }; }, }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts index 3cf879d2b67a4..2354f2405de76 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts @@ -6,7 +6,7 @@ // @ts-ignore untyped Elastic library import { getType } from '@kbn/interpreter/common'; -import { ExpressionFunction, Datatable } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -14,7 +14,7 @@ interface Arguments { value: string | number | boolean | null; } -export function staticColumn(): ExpressionFunction< +export function staticColumn(): ExpressionFunctionDefinition< 'staticColumn', Datatable, Arguments, @@ -25,10 +25,8 @@ export function staticColumn(): ExpressionFunction< return { name: 'staticColumn', type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { name: { types: ['string'], @@ -42,10 +40,10 @@ export function staticColumn(): ExpressionFunction< default: null, }, }, - fn: (context, args) => { - const rows = context.rows.map(row => ({ ...row, [args.name]: args.value })); + fn: (input, args) => { + const rows = input.rows.map(row => ({ ...row, [args.name]: args.value })); const type = getType(args.value); - const columns = [...context.columns]; + const columns = [...input.columns]; const existingColumnIndex = columns.findIndex(({ name }) => name === args.name); const newColumn = { name: args.name, type }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/string.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/string.ts index e1fc567ad009e..c7cee0da2a674 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/string.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/string.ts @@ -3,21 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { value: Array; } -export function string(): ExpressionFunction<'string', null, Arguments, string> { +export function string(): ExpressionFunctionDefinition<'string', null, Arguments, string> { const { help, args: argHelp } = getFunctionHelp().string; return { name: 'string', - context: { - types: ['null'], - }, + inputTypes: ['null'], aliases: [], type: 'string', help, @@ -29,6 +27,6 @@ export function string(): ExpressionFunction<'string', null, Arguments, string> help: argHelp.value, }, }, - fn: (_context, args) => args.value.join(''), + fn: (input, args) => args.value.join(''), }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/switch.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/switch.ts index f6d396361a1ae..bb70bec561a11 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/switch.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/switch.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Case } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; @@ -13,7 +13,7 @@ interface Arguments { default: () => any; } -export function switchFn(): ExpressionFunction<'switch', any, Arguments, any> { +export function switchFn(): ExpressionFunctionDefinition<'switch', unknown, Arguments, unknown> { const { help, args: argHelp } = getFunctionHelp().switch; return { @@ -33,7 +33,7 @@ export function switchFn(): ExpressionFunction<'switch', any, Arguments, any> { help: argHelp.default, }, }, - fn: async (context, args) => { + fn: async (input, args) => { const cases = args.case || []; for (let i = 0; i < cases.length; i++) { @@ -48,7 +48,7 @@ export function switchFn(): ExpressionFunction<'switch', any, Arguments, any> { return await args.default(); } - return context; + return input; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/table.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/table.ts index 45612474fbe53..689f3f969d1c8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/table.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/table.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Datatable, Render, Style } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; @@ -15,17 +15,20 @@ interface Arguments { showHeader: boolean; } -export function table(): ExpressionFunction<'table', Datatable, Arguments, Render> { +export function table(): ExpressionFunctionDefinition< + 'table', + Datatable, + Arguments, + Render +> { const { help, args: argHelp } = getFunctionHelp().table; return { name: 'table', aliases: [], type: 'render', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { font: { types: ['style'], @@ -50,12 +53,12 @@ export function table(): ExpressionFunction<'table', Datatable, Arguments, Rende options: [true, false], }, }, - fn: (context, args) => { + fn: (input, args) => { return { type: 'render', as: 'table', value: { - datatable: context, + datatable: input, ...args, }, }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/tail.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/tail.ts index bd2fc03e8230d..5105beb586f72 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/tail.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/tail.ts @@ -5,24 +5,22 @@ */ import { takeRight } from 'lodash'; -import { Datatable, ExpressionFunction } from '../../../types'; +import { Datatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { count: number; } -export function tail(): ExpressionFunction<'tail', Datatable, Arguments, Datatable> { +export function tail(): ExpressionFunctionDefinition<'tail', Datatable, Arguments, Datatable> { const { help, args: argHelp } = getFunctionHelp().tail; return { name: 'tail', aliases: [], type: 'datatable', + inputTypes: ['datatable'], help, - context: { - types: ['datatable'], - }, args: { count: { aliases: ['_'], @@ -30,9 +28,9 @@ export function tail(): ExpressionFunction<'tail', Datatable, Arguments, Datatab help: argHelp.count, }, }, - fn: (context, args) => ({ - ...context, - rows: takeRight(context.rows, args.count), + fn: (input, args) => ({ + ...input, + rows: takeRight(input.rows, args.count), }), }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/time_range.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/time_range.ts index 716026279ccea..8b311d9be2bbf 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/time_range.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/time_range.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n/functions'; import { TimeRange } from '../../../types'; @@ -13,15 +13,13 @@ interface Args { to: string; } -export function timerange(): ExpressionFunction<'timerange', null, Args, TimeRange> { +export function timerange(): ExpressionFunctionDefinition<'timerange', null, Args, TimeRange> { const { help, args: argHelp } = getFunctionHelp().timerange; return { name: 'timerange', help, type: 'timerange', - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { from: { types: ['string'], @@ -34,7 +32,7 @@ export function timerange(): ExpressionFunction<'timerange', null, Args, TimeRan help: argHelp.to, }, }, - fn: (context, args) => { + fn: (input, args) => { return { type: 'timerange', ...args, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilter.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilter.ts index 92d2183caa298..8afa6eb04ad69 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilter.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilter.ts @@ -5,7 +5,7 @@ */ import dateMath from '@elastic/datemath'; -import { Filter, ExpressionFunction } from '../../../types'; +import { Filter, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; interface Arguments { @@ -15,7 +15,12 @@ interface Arguments { filterGroup: string; } -export function timefilter(): ExpressionFunction<'timefilter', Filter, Arguments, Filter> { +export function timefilter(): ExpressionFunctionDefinition< + 'timefilter', + Filter, + Arguments, + Filter +> { const { help, args: argHelp } = getFunctionHelp().timefilter; const errors = getFunctionErrors().timefilter; @@ -23,9 +28,7 @@ export function timefilter(): ExpressionFunction<'timefilter', Filter, Arguments name: 'timefilter', aliases: [], type: 'filter', - context: { - types: ['filter'], - }, + inputTypes: ['filter'], help, args: { column: { @@ -49,9 +52,9 @@ export function timefilter(): ExpressionFunction<'timefilter', Filter, Arguments help: 'The group name for the filter', }, }, - fn: (context, args) => { + fn: (input, args) => { if (!args.from && !args.to) { - return context; + return input; } const { from, to, column } = args; @@ -79,7 +82,7 @@ export function timefilter(): ExpressionFunction<'timefilter', Filter, Arguments (filter as any).from = parseAndValidate(from); } - return { ...context, and: [...context.and, filter] }; + return { ...input, and: [...input.and, filter] }; }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts index 8e796e47c7c0f..5b6c0cb97b0fd 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Render } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; @@ -13,7 +13,7 @@ interface Arguments { compact: boolean; filterGroup: string; } -export function timefilterControl(): ExpressionFunction< +export function timefilterControl(): ExpressionFunctionDefinition< 'timefilterControl', null, Arguments, @@ -25,9 +25,7 @@ export function timefilterControl(): ExpressionFunction< name: 'timefilterControl', aliases: [], type: 'render', - context: { - types: ['null'], - }, + inputTypes: ['null'], help, args: { column: { @@ -47,7 +45,7 @@ export function timefilterControl(): ExpressionFunction< help: argHelp.filterGroup, }, }, - fn: (_context, args) => { + fn: (input, args) => { return { type: 'render', as: 'time_filter', diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata.test.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata.test.ts index a592127e23948..94b2d5228665b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata.test.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata.test.ts @@ -15,25 +15,28 @@ const nullFilter = { }; const fn = demodata().fn; +const context = {} as any; describe('demodata', () => { it('ci, different object references', () => { - const ci1 = fn(nullFilter, { type: 'ci' }, {}); - const ci2 = fn(nullFilter, { type: 'ci' }, {}); + const ci1 = fn(nullFilter, { type: 'ci' }, context); + const ci2 = fn(nullFilter, { type: 'ci' }, context); expect(ci1).not.toBe(ci2); expect(ci1.rows).not.toBe(ci2.rows); expect(ci1.rows[0]).not.toBe(ci2.rows[0]); }); + it('shirts, different object references', () => { - const shirts1 = fn(nullFilter, { type: 'shirts' }, {}); - const shirts2 = fn(nullFilter, { type: 'shirts' }, {}); + const shirts1 = fn(nullFilter, { type: 'shirts' }, context); + const shirts2 = fn(nullFilter, { type: 'shirts' }, context); expect(shirts1).not.toBe(shirts2); expect(shirts1.rows).not.toBe(shirts2.rows); expect(shirts1.rows[0]).not.toBe(shirts2.rows[0]); }); + it('invalid set', () => { expect(() => { - fn(nullFilter, { type: 'foo' }, {}); + fn(nullFilter, { type: 'foo' }, context); }).toThrowError("Invalid data set: 'foo', use 'ci' or 'shirts'."); }); }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts index a803ca766d861..826c49d328f21 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts @@ -5,7 +5,7 @@ */ import { sortBy } from 'lodash'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; // @ts-ignore unconverted lib file import { queryDatatable } from '../../../../common/lib/datatable/query'; import { DemoRows, getDemoRows } from './get_demo_rows'; @@ -16,17 +16,17 @@ interface Arguments { type: string; } -export function demodata(): ExpressionFunction<'demodata', Filter, Arguments, Datatable> { +export function demodata(): ExpressionFunctionDefinition<'demodata', Filter, Arguments, Datatable> { const { help, args: argHelp } = getFunctionHelp().demodata; return { name: 'demodata', aliases: [], type: 'datatable', - help, context: { types: ['filter'], }, + help, args: { type: { types: ['string'], @@ -36,7 +36,7 @@ export function demodata(): ExpressionFunction<'demodata', Filter, Arguments, Da options: ['ci', 'shirts'], }, }, - fn: (context, args) => { + fn: (input, args) => { const demoRows = getDemoRows(args.type); let set = {} as { columns: DatatableColumn[]; rows: DatatableRow[] }; @@ -76,7 +76,7 @@ export function demodata(): ExpressionFunction<'demodata', Filter, Arguments, Da columns, rows, }, - context + input ); }, }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/escount.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/escount.ts index ad572f15b9870..ffb8bb4f3e2a7 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/escount.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/escount.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction, Filter } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition, Filter } from 'src/plugins/expressions/common'; // @ts-ignore untyped local import { buildESRequest } from '../../../server/lib/build_es_request'; import { getFunctionHelp } from '../../../i18n'; @@ -14,16 +14,16 @@ interface Arguments { query: string; } -export function escount(): ExpressionFunction<'escount', Filter, Arguments, any> { +export function escount(): ExpressionFunctionDefinition<'escount', Filter, Arguments, any> { const { help, args: argHelp } = getFunctionHelp().escount; return { name: 'escount', type: 'number', - help, context: { types: ['filter'], }, + help, args: { query: { types: ['string'], @@ -37,8 +37,8 @@ export function escount(): ExpressionFunction<'escount', Filter, Arguments, any> help: argHelp.index, }, }, - fn: (context, args, handlers) => { - context.and = context.and.concat([ + fn: (input, args, handlers) => { + input.and = input.and.concat([ { type: 'luceneQueryString', query: args.query, @@ -57,10 +57,10 @@ export function escount(): ExpressionFunction<'escount', Filter, Arguments, any> }, }, }, - context + input ); - return handlers + return ((handlers as any) as { elasticsearchClient: any }) .elasticsearchClient('count', esRequest) .then((resp: { count: number }) => resp.count); }, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts index ddd39197eb256..5bff06bb3933b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts @@ -5,7 +5,7 @@ */ import squel from 'squel'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; // @ts-ignore untyped local import { queryEsSQL } from '../../../server/lib/query_es_sql'; import { Filter } from '../../../types'; @@ -20,16 +20,16 @@ interface Arguments { count: number; } -export function esdocs(): ExpressionFunction<'esdocs', Filter, Arguments, any> { +export function esdocs(): ExpressionFunctionDefinition<'esdocs', Filter, Arguments, any> { const { help, args: argHelp } = getFunctionHelp().esdocs; return { name: 'esdocs', type: 'datatable', - help, context: { types: ['filter'], }, + help, args: { query: { types: ['string'], @@ -62,10 +62,10 @@ export function esdocs(): ExpressionFunction<'esdocs', Filter, Arguments, any> { help: argHelp.sort, }, }, - fn: (context, args, handlers) => { + fn: (input, args, context) => { const { count, index, fields, sort } = args; - context.and = context.and.concat([ + input.and = input.and.concat([ { type: 'luceneQueryString', query: args.query, @@ -96,10 +96,10 @@ export function esdocs(): ExpressionFunction<'esdocs', Filter, Arguments, any> { } } - return queryEsSQL(handlers.elasticsearchClient, { + return queryEsSQL(((context as any) as { elasticsearchClient: any }).elasticsearchClient, { count, query: query.toString(), - filter: context.and, + filter: input.and, }); }, }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/essql.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/essql.ts index 2106a4e9877e6..cdb6b5af82015 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/essql.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/essql.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; // @ts-ignore untyped local import { queryEsSQL } from '../../../server/lib/query_es_sql'; import { Filter } from '../../../types'; @@ -16,16 +16,16 @@ interface Arguments { timezone: string; } -export function essql(): ExpressionFunction<'essql', Filter, Arguments, any> { +export function essql(): ExpressionFunctionDefinition<'essql', Filter, Arguments, any> { const { help, args: argHelp } = getFunctionHelp().essql; return { name: 'essql', type: 'datatable', - help, context: { types: ['filter'], }, + help, args: { query: { aliases: ['_', 'q'], @@ -44,7 +44,11 @@ export function essql(): ExpressionFunction<'essql', Filter, Arguments, any> { help: argHelp.timezone, }, }, - fn: (context, args, handlers) => - queryEsSQL(handlers.elasticsearchClient, { ...args, filter: context.and }), + fn: (input, args, context) => { + return queryEsSQL(((context as any) as { elasticsearchClient: any }).elasticsearchClient, { + ...args, + filter: input.and, + }); + }, }; } diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts index da8315c4a4ed7..17f0af4c9689e 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts @@ -10,7 +10,7 @@ import uniqBy from 'lodash.uniqby'; import { evaluate } from 'tinymath'; import { groupBy, zipObject, omit } from 'lodash'; import moment from 'moment'; -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Datatable, DatatableRow, @@ -39,7 +39,7 @@ function keysOf(obj: T): K[] { type Arguments = { [key in PointSeriesColumnName]: string | null }; -export function pointseries(): ExpressionFunction< +export function pointseries(): ExpressionFunctionDefinition< 'pointseries', Datatable, Arguments, @@ -50,10 +50,10 @@ export function pointseries(): ExpressionFunction< return { name: 'pointseries', type: 'pointseries', - help, context: { types: ['datatable'], }, + help, args: { color: { types: ['string'], @@ -78,11 +78,11 @@ export function pointseries(): ExpressionFunction< // In the future it may make sense to add things like shape, or tooltip values, but I think what we have is good for now // The way the function below is written you can add as many arbitrary named args as you want. }, - fn: (context, args) => { + fn: (input, args) => { const errors = getFunctionErrors().pointseries; // Note: can't replace pivotObjectArray with datatableToMathContext, lose name of non-numeric columns - const columnNames = context.columns.map(col => col.name); - const mathScope = pivotObjectArray(context.rows, columnNames); + const columnNames = input.columns.map(col => col.name); + const mathScope = pivotObjectArray(input.rows, columnNames); const autoQuoteColumn = (col: string | null) => { if (!col || !columnNames.includes(col)) { return col; @@ -117,7 +117,7 @@ export function pointseries(): ExpressionFunction< name: argName, value: mathExp, }); - col.type = getExpressionType(context.columns, mathExp); + col.type = getExpressionType(input.columns, mathExp); col.role = 'dimension'; } else { measureNames.push(argName); @@ -131,13 +131,13 @@ export function pointseries(): ExpressionFunction< }); const PRIMARY_KEY = '%%CANVAS_POINTSERIES_PRIMARY_KEY%%'; - const rows: DatatableRow[] = context.rows.map((row, i) => ({ + const rows: DatatableRow[] = input.rows.map((row, i) => ({ ...row, [PRIMARY_KEY]: i, })); function normalizeValue(expression: string, value: string) { - switch (getExpressionType(context.columns, expression)) { + switch (getExpressionType(input.columns, expression)) { case 'string': return String(value); case 'number': @@ -186,7 +186,7 @@ export function pointseries(): ExpressionFunction< // Then compute that 1 value for each measure Object.values(measureKeys).forEach(valueRows => { - const subtable = { type: 'datatable', columns: context.columns, rows: valueRows }; + const subtable = { type: 'datatable', columns: input.columns, rows: valueRows }; const subScope = pivotObjectArray( subtable.rows, subtable.columns.map(col => col.name) diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx index 55f58efa37bf4..e60c99b683f34 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx @@ -7,11 +7,11 @@ import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import React from 'react'; -import { ExpressionAST } from '../../../../../types'; +import { ExpressionAstExpression } from '../../../../../types'; import { ExtendedTemplate } from '../extended_template'; -const defaultExpression: ExpressionAST = { +const defaultExpression: ExpressionAstExpression = { type: 'expression', chain: [ { @@ -29,7 +29,7 @@ const defaultValues = { class Interactive extends React.Component<{}, typeof defaultValues> { public state = defaultValues; - _onValueChange: (argValue: ExpressionAST) => void = argValue => { + _onValueChange: (argValue: ExpressionAstExpression) => void = argValue => { action('onValueChange')(argValue); this.setState({ argValue }); }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx index 806a61042494f..ec92e93368535 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx @@ -9,14 +9,14 @@ import PropTypes from 'prop-types'; import { EuiSelect, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; import immutable from 'object-path-immutable'; import { get } from 'lodash'; -import { ExpressionAST } from '../../../../types'; +import { ExpressionAstExpression } from '../../../../types'; import { ArgumentStrings } from '../../../../i18n/ui'; const { AxisConfig: strings } = ArgumentStrings; const { set } = immutable; -const defaultExpression: ExpressionAST = { +const defaultExpression: ExpressionAstExpression = { type: 'expression', chain: [ { @@ -28,8 +28,8 @@ const defaultExpression: ExpressionAST = { }; export interface Props { - onValueChange: (newValue: ExpressionAST) => void; - argValue: boolean | ExpressionAST; + onValueChange: (newValue: ExpressionAstExpression) => void; + argValue: boolean | ExpressionAstExpression; typeInstance: { name: 'xaxis' | 'yaxis'; }; diff --git a/x-pack/legacy/plugins/canvas/common/lib/autocomplete.test.ts b/x-pack/legacy/plugins/canvas/common/lib/autocomplete.test.ts index dbe81deced36d..31d213f4853ff 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/autocomplete.test.ts +++ b/x-pack/legacy/plugins/canvas/common/lib/autocomplete.test.ts @@ -35,7 +35,7 @@ describe('autocomplete', () => { const expression = 'plot '; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); const plotFn = functionSpecs.find(spec => spec.name === 'plot'); - expect(suggestions.length).toBe(Object.keys(plotFn.args).length); + expect(suggestions.length).toBe(Object.keys(plotFn!.args).length); expect(suggestions[0].start).toBe(expression.length); expect(suggestions[0].end).toBe(expression.length); }); @@ -44,7 +44,7 @@ describe('autocomplete', () => { const expression = 'shape shape='; const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length); const shapeFn = functionSpecs.find(spec => spec.name === 'shape'); - expect(suggestions.length).toBe(shapeFn.args.shape.options.length); + expect(suggestions.length).toBe(shapeFn!.args.shape.options.length); expect(suggestions[0].start).toBe(expression.length); expect(suggestions[0].end).toBe(expression.length); }); @@ -82,27 +82,24 @@ describe('autocomplete', () => { expect(suggestions.length).toBe(functionSpecs.length); expect(suggestions[0].fnDef.type).toBe('datatable'); - expect(suggestions[0].fnDef.context && suggestions[0].fnDef.context.types).toEqual([ - 'datatable', - ]); + expect(suggestions[0].fnDef.inputTypes).toEqual(['datatable']); const withReturnOnly = suggestions.findIndex( suggestion => suggestion.fnDef.type === 'datatable' && - suggestion.fnDef.context && - suggestion.fnDef.context.types && - !(suggestion.fnDef.context.types as string[]).includes('datatable') + suggestion.fnDef.inputTypes && + !(suggestion.fnDef.inputTypes as string[]).includes('datatable') ); const withNeither = suggestions.findIndex( suggestion => suggestion.fnDef.type !== 'datatable' && - (!suggestion.fnDef.context || - !(suggestion.fnDef.context.types as string[]).includes('datatable')) + (!suggestion.fnDef.inputTypes || + !(suggestion.fnDef.inputTypes as string[]).includes('datatable')) ); expect(suggestions[0].fnDef.type).toBe('datatable'); - expect(suggestions[0].fnDef.context?.types).toEqual(['datatable']); + expect(suggestions[0].fnDef.inputTypes).toEqual(['datatable']); expect(withReturnOnly).toBeLessThan(withNeither); }); @@ -115,7 +112,7 @@ describe('autocomplete', () => { expression.length - 1 ); const ltFn = functionSpecs.find(spec => spec.name === 'lt'); - expect(suggestions.length).toBe(Object.keys(ltFn.args).length); + expect(suggestions.length).toBe(Object.keys(ltFn!.args).length); expect(suggestions[0].start).toBe(expression.length - 1); expect(suggestions[0].end).toBe(expression.length - 1); }); @@ -128,7 +125,7 @@ describe('autocomplete', () => { expression.length - 1 ); const shapeFn = functionSpecs.find(spec => spec.name === 'shape'); - expect(suggestions.length).toBe(shapeFn.args.shape.options.length); + expect(suggestions.length).toBe(shapeFn!.args.shape.options.length); expect(suggestions[0].start).toBe(expression.length - 1); expect(suggestions[0].end).toBe(expression.length - 1); }); @@ -141,7 +138,7 @@ describe('autocomplete', () => { expression.length - 1 ); const shapeFn = functionSpecs.find(spec => spec.name === 'shape'); - expect(suggestions.length).toBe(shapeFn.args.shape.options.length); + expect(suggestions.length).toBe(shapeFn!.args.shape.options.length); expect(suggestions[0].start).toBe(expression.length - '"ar"'.length); expect(suggestions[0].end).toBe(expression.length); }); diff --git a/x-pack/legacy/plugins/canvas/common/lib/autocomplete.ts b/x-pack/legacy/plugins/canvas/common/lib/autocomplete.ts index 96917e3e7ed2c..50341c977d6d9 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/autocomplete.ts +++ b/x-pack/legacy/plugins/canvas/common/lib/autocomplete.ts @@ -6,15 +6,15 @@ import { uniq } from 'lodash'; // @ts-ignore Untyped Library -import { parse, getByAlias as untypedGetByAlias } from '@kbn/interpreter/common'; +import { parse } from '@kbn/interpreter/common'; import { - ExpressionAST, - ExpressionFunctionAST, - ExpressionArgAST, - CanvasFunction, - CanvasArg, - CanvasArgValue, -} from '../../types'; + ExpressionAstExpression, + ExpressionAstFunction, + ExpressionAstArgument, + ExpressionFunction, + ExpressionFunctionParameter, + getByAlias, +} from '../../../../../../src/plugins/expressions'; const MARKER = 'CANVAS_SUGGESTION_MARKER'; @@ -26,12 +26,12 @@ interface BaseSuggestion { export interface FunctionSuggestion extends BaseSuggestion { type: 'function'; - fnDef: CanvasFunction; + fnDef: ExpressionFunction; } -type ArgSuggestionValue = CanvasArgValue & { +interface ArgSuggestionValue extends Omit { name: string; -}; +} interface ArgSuggestion extends BaseSuggestion { type: 'argument'; @@ -71,18 +71,18 @@ interface ASTMetaInformation { node: T; } -// Wraps ExpressionArg with meta or replace ExpressionAST with ExpressionASTWithMeta -type WrapExpressionArgWithMeta = T extends ExpressionAST +// Wraps ExpressionArg with meta or replace ExpressionAstExpression with ExpressionASTWithMeta +type WrapExpressionArgWithMeta = T extends ExpressionAstExpression ? ExpressionASTWithMeta : ASTMetaInformation; -type ExpressionArgASTWithMeta = WrapExpressionArgWithMeta; +type ExpressionArgASTWithMeta = WrapExpressionArgWithMeta; type Modify = Pick> & R; // Wrap ExpressionFunctionAST with meta and modify arguments to be wrapped with meta type ExpressionFunctionASTWithMeta = Modify< - ExpressionFunctionAST, + ExpressionAstFunction, { arguments: { [key: string]: ExpressionArgASTWithMeta[]; @@ -93,7 +93,7 @@ type ExpressionFunctionASTWithMeta = Modify< // Wrap ExpressionFunctionAST with meta and modify chain to be wrapped with meta type ExpressionASTWithMeta = ASTMetaInformation< Modify< - ExpressionAST, + ExpressionAstExpression, { chain: Array>; } @@ -107,23 +107,12 @@ function isExpression( return typeof maybeExpression.node === 'object'; } -// Overloads to change return type based on specs -function getByAlias(specs: CanvasFunction[], name: string): CanvasFunction; -// eslint-disable-next-line @typescript-eslint/unified-signatures -function getByAlias(specs: CanvasArg, name: string): CanvasArgValue; -function getByAlias( - specs: CanvasFunction[] | CanvasArg, - name: string -): CanvasFunction | CanvasArgValue { - return untypedGetByAlias(specs, name); -} - /** * Generates the AST with the given expression and then returns the function and argument definitions * at the given position in the expression, if there are any. */ export function getFnArgDefAtPosition( - specs: CanvasFunction[], + specs: ExpressionFunction[], expression: string, position: number ) { @@ -155,7 +144,7 @@ export function getFnArgDefAtPosition( * an unnamed argument, we suggest argument names. If it turns into a value, we suggest values. */ export function getAutocompleteSuggestions( - specs: CanvasFunction[], + specs: ExpressionFunction[], expression: string, position: number ): AutocompleteSuggestion[] { @@ -268,7 +257,7 @@ function getFnArgAtPosition(ast: ExpressionASTWithMeta, position: number): FnArg } function getFnNameSuggestions( - specs: CanvasFunction[], + specs: ExpressionFunction[], ast: ExpressionASTWithMeta, fnIndex: number ): FunctionSuggestion[] { @@ -284,11 +273,11 @@ function getFnNameSuggestions( const prevFnType = prevFnDef && prevFnDef.type; const nextFnDef = nextFn && getByAlias(specs, nextFn.node.function); - const nextFnContext = nextFnDef && nextFnDef.context && nextFnDef.context.types; + const nextFnInputTypes = nextFnDef && nextFnDef.inputTypes; - const fnDefs = specs.sort((a: CanvasFunction, b: CanvasFunction): number => { - const aScore = getScore(a, prevFnType, nextFnContext, false); - const bScore = getScore(b, prevFnType, nextFnContext, false); + const fnDefs = specs.sort((a: ExpressionFunction, b: ExpressionFunction): number => { + const aScore = getScore(a, prevFnType, nextFnInputTypes, false); + const bScore = getScore(b, prevFnType, nextFnInputTypes, false); if (aScore === bScore) { return a.name < b.name ? -1 : 1; @@ -302,7 +291,7 @@ function getFnNameSuggestions( } function getSubFnNameSuggestions( - specs: CanvasFunction[], + specs: ExpressionFunction[], ast: ExpressionASTWithMeta, fnIndex: number, parentFn: string, @@ -315,7 +304,7 @@ function getSubFnNameSuggestions( const matchingFnDefs = specs.filter(({ name }) => textMatches(name, query)); const parentFnDef = getByAlias(specs, parentFn); - const matchingArgDef = getByAlias(parentFnDef.args, parentFnArgName); + const matchingArgDef = getByAlias(parentFnDef!.args, parentFnArgName); if (!matchingArgDef) { return []; @@ -326,7 +315,7 @@ function getSubFnNameSuggestions( const expectedReturnTypes = matchingArgDef.types; - const fnDefs = matchingFnDefs.sort((a: CanvasFunction, b: CanvasFunction) => { + const fnDefs = matchingFnDefs.sort((a: ExpressionFunction, b: ExpressionFunction) => { const aScore = getScore(a, contextFnType, expectedReturnTypes, true); const bScore = getScore(b, contextFnType, expectedReturnTypes, true); @@ -342,7 +331,7 @@ function getSubFnNameSuggestions( } function getScore( - func: CanvasFunction, + func: ExpressionFunction, contextType: any, returnTypes?: any[] | null, isSubFunc?: boolean @@ -352,10 +341,7 @@ function getScore( contextType = 'null'; } - let funcContextTypes = []; - if (func.context && func.context.types && func.context.types.length) { - funcContextTypes = func.context.types; - } + const inputTypesNormalized = (func.inputTypes || []) as string[]; if (isSubFunc) { if (returnTypes && func.type) { @@ -364,21 +350,21 @@ function getScore( if (returnTypes.length && returnTypes.includes(func.type)) { score++; - if (funcContextTypes.includes(contextType)) { + if (inputTypesNormalized.includes(contextType)) { score++; } } } } else { - if (func.context && func.context.types) { - const expectsNull = (funcContextTypes as string[]).includes('null'); + if (func.inputTypes) { + const expectsNull = inputTypesNormalized.includes('null'); if (!expectsNull && contextType !== 'null') { // If not in a sub-expression and there's a preceding function, // favor functions that expect a context with top results matching the passed in context score++; - if (func.context.types.includes(contextType)) { + if (func.inputTypes.includes(contextType)) { score++; } } else if (expectsNull && contextType === 'null') { @@ -397,7 +383,7 @@ function getScore( } function getArgNameSuggestions( - specs: CanvasFunction[], + specs: ExpressionFunction[], ast: ExpressionASTWithMeta, fnIndex: number, argName: string, @@ -420,29 +406,35 @@ function getArgNameSuggestions( } ); - const unusedArgDefs = Object.entries(fnDef.args).filter( - ([matchingArgName, matchingArgDef]) => { - if (matchingArgDef.multi) { - return true; - } - return !argEntries.some(([name, values]) => { - return ( - values.length > 0 && - (name === matchingArgName || (matchingArgDef.aliases || []).includes(name)) - ); - }); + const unusedArgDefs = Object.entries(fnDef.args).filter(([matchingArgName, matchingArgDef]) => { + if (matchingArgDef.multi) { + return true; } - ); + return !argEntries.some(([name, values]) => { + return ( + values.length > 0 && + (name === matchingArgName || (matchingArgDef.aliases || []).includes(name)) + ); + }); + }); - const argDefs = unusedArgDefs.map(([name, arg]) => ({ name, ...arg })).sort(unnamedArgComparator); + const argDefs: ArgSuggestionValue[] = unusedArgDefs + .map(([name, arg]) => ({ name, ...arg })) + .sort(unnamedArgComparator); return argDefs.map(argDef => { - return { type: 'argument', text: argDef.name + '=', start, end: end - MARKER.length, argDef }; + return { + type: 'argument', + text: argDef.name + '=', + start, + end: end - MARKER.length, + argDef, + }; }); } function getArgValueSuggestions( - specs: CanvasFunction[], + specs: ExpressionFunction[], ast: ExpressionASTWithMeta, fnIndex: number, argName: string, @@ -492,7 +484,7 @@ function maybeQuote(value: any) { return value; } -function unnamedArgComparator(a: CanvasArgValue, b: CanvasArgValue): number { +function unnamedArgComparator(a: { aliases?: string[] }, b: { aliases?: string[] }): number { return ( (b.aliases && b.aliases.includes('_') ? 1 : 0) - (a.aliases && a.aliases.includes('_') ? 1 : 0) ); diff --git a/x-pack/legacy/plugins/canvas/i18n/functions/function_help.ts b/x-pack/legacy/plugins/canvas/i18n/functions/function_help.ts index 6e62adb56a572..75a1254831a26 100644 --- a/x-pack/legacy/plugins/canvas/i18n/functions/function_help.ts +++ b/x-pack/legacy/plugins/canvas/i18n/functions/function_help.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; -import { CanvasFunction } from '../../types'; -import { UnionToIntersection } from '../../types'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; +import { UnionToIntersection, CanvasFunction } from '../../types'; import { help as all } from './dict/all'; import { help as alterColumn } from './dict/alter_column'; @@ -109,11 +108,11 @@ import { help as urlparam } from './dict/urlparam'; * This allows one to ensure each argument is present, and no extraneous arguments * remain. */ -export type FunctionHelp = T extends ExpressionFunction< +export type FunctionHelp = T extends ExpressionFunctionDefinition< infer Name, - infer Context, + infer Input, infer Arguments, - infer Return + infer Output > ? { help: string; @@ -137,11 +136,11 @@ export type FunctionHelp = T extends ExpressionFunction< // // Given a collection of functions, the map would contain each entry. // -type FunctionHelpMap = T extends ExpressionFunction< +type FunctionHelpMap = T extends ExpressionFunctionDefinition< infer Name, - infer Context, + infer Input, infer Arguments, - infer Return + infer Output > ? { [key in Name]: FunctionHelp } : never; @@ -155,8 +154,8 @@ type FunctionHelpDict = UnionToIntersection>; /** * Help text for Canvas Functions should be properly localized. This function will - * return a dictionary of help strings, organized by `CanvasFunction` specification - * and then by available arguments within each `CanvasFunction`. + * return a dictionary of help strings, organized by `ExpressionFunctionDefinition` + * specification and then by available arguments within each `ExpressionFunctionDefinition`. * * This a function, rather than an object, to future-proof string initialization, * if ever necessary. diff --git a/x-pack/legacy/plugins/canvas/public/browser_functions.js b/x-pack/legacy/plugins/canvas/public/browser_functions.ts similarity index 67% rename from x-pack/legacy/plugins/canvas/public/browser_functions.js rename to x-pack/legacy/plugins/canvas/public/browser_functions.ts index 5be270362b63f..011fe8b4504bc 100644 --- a/x-pack/legacy/plugins/canvas/public/browser_functions.js +++ b/x-pack/legacy/plugins/canvas/public/browser_functions.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; +import { npSetup } from 'ui/new_platform'; import { functions } from '../canvas_plugin_src/functions/browser'; -functions.forEach(fn => { - functionsRegistry.register(fn); -}); +functions.forEach(npSetup.plugins.expressions.registerFunction); +// eslint-disable-next-line import/no-default-export export default functions; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_content/index.js b/x-pack/legacy/plugins/canvas/public/components/element_content/index.js index bf7b0ce40fc0e..f05222452b1ee 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_content/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_content/index.js @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { get } from 'lodash'; -import { registries } from 'plugins/interpreter/registries'; +import { npStart } from 'ui/new_platform'; import { getSelectedPage, getPageById } from '../../state/selectors/workpad'; import { ElementContent as Component } from './element_content'; @@ -19,7 +19,7 @@ const mapStateToProps = state => ({ export const ElementContent = compose( connect(mapStateToProps), withProps(({ renderable }) => ({ - renderFunction: registries.renderers.get(get(renderable, 'as')), + renderFunction: npStart.plugins.expressions.getRenderer(get(renderable, 'as')), })) )(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/expression_input/expression_input.tsx b/x-pack/legacy/plugins/canvas/public/components/expression_input/expression_input.tsx index c33e91064dc84..9653decb6db97 100644 --- a/x-pack/legacy/plugins/canvas/public/components/expression_input/expression_input.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/expression_input/expression_input.tsx @@ -9,10 +9,8 @@ import PropTypes from 'prop-types'; import { EuiFormRow } from '@elastic/eui'; import { debounce } from 'lodash'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; - +import { ExpressionFunction } from '../../../../../../../src/plugins/expressions'; import { CodeEditor } from '../../../../../../../src/plugins/kibana_react/public'; - -import { CanvasFunction } from '../../../types'; import { AutocompleteSuggestion, getAutocompleteSuggestions, @@ -27,7 +25,7 @@ interface Props { /** Font size of text within the editor */ /** Canvas function defintions */ - functionDefinitions: CanvasFunction[]; + functionDefinitions: ExpressionFunction[]; /** Optional string for displaying error messages */ error?: string; diff --git a/x-pack/legacy/plugins/canvas/public/components/expression_input/reference.ts b/x-pack/legacy/plugins/canvas/public/components/expression_input/reference.ts index 3a5030c492b25..ca3819195fcbd 100644 --- a/x-pack/legacy/plugins/canvas/public/components/expression_input/reference.ts +++ b/x-pack/legacy/plugins/canvas/public/components/expression_input/reference.ts @@ -3,21 +3,23 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CanvasFunction, CanvasArgValue } from '../../../types'; import { ComponentStrings } from '../../../i18n'; +import { + ExpressionFunction, + ExpressionFunctionParameter, +} from '../../../../../../../src/plugins/expressions'; const { ExpressionInput: strings } = ComponentStrings; /** - * Given a function definition, this function returns a markdown string + * Given an expression function, this function returns a markdown string * that includes the context the function accepts, what the function returns * as well as the general help/documentation text associated with the function */ -export function getFunctionReferenceStr(fnDef: CanvasFunction) { - const { help, context, type } = fnDef; - - const acceptTypes = context && context.types ? context.types.join(' | ') : 'null'; +export function getFunctionReferenceStr(fnDef: ExpressionFunction) { + const { help, type, inputTypes } = fnDef; + const acceptTypes = inputTypes ? inputTypes.join(' | ') : 'null'; const returnType = type ? type : 'null'; const doc = `${strings.getFunctionReferenceAcceptsDetail( @@ -29,12 +31,12 @@ export function getFunctionReferenceStr(fnDef: CanvasFunction) { } /** - * Given an argument defintion, this function returns a markdown string + * Given an argument definition, this function returns a markdown string * that includes the aliases of the argument, types accepted for the argument, * the default value of the argument, whether or not its required, and * the general help/documentation text associated with the argument */ -export function getArgReferenceStr(argDef: CanvasArgValue) { +export function getArgReferenceStr(argDef: Omit) { const { aliases, types, default: def, required, help } = argDef; const secondLineArr = []; diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx index 58af29463c3eb..7e00bd4f33a8a 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/extended_template.examples.tsx @@ -10,9 +10,9 @@ import { withKnobs, array, radios, boolean } from '@storybook/addon-knobs'; import React from 'react'; import { ExtendedTemplate } from '../extended_template'; -import { ExpressionAST } from '../../../../../types'; +import { ExpressionAstExpression } from '../../../../../types'; -const defaultExpression: ExpressionAST = { +const defaultExpression: ExpressionAstExpression = { type: 'expression', chain: [ { @@ -27,7 +27,7 @@ const defaultValues = { argValue: defaultExpression, }; -class Interactive extends React.Component<{}, { argValue: ExpressionAST }> { +class Interactive extends React.Component<{}, { argValue: ExpressionAstExpression }> { public state = defaultValues; public render() { diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx index 7a35f4de79809..037b15d5c51e9 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx @@ -11,9 +11,9 @@ import React from 'react'; import { getDefaultWorkpad } from '../../../../state/defaults'; import { SimpleTemplate } from '../simple_template'; -import { ExpressionAST } from '../../../../../types'; +import { ExpressionAstExpression } from '../../../../../types'; -const defaultExpression: ExpressionAST = { +const defaultExpression: ExpressionAstExpression = { type: 'expression', chain: [ { @@ -28,7 +28,7 @@ const defaultValues = { argValue: defaultExpression, }; -class Interactive extends React.Component<{}, { argValue: ExpressionAST }> { +class Interactive extends React.Component<{}, { argValue: ExpressionAstExpression }> { public state = defaultValues; public render() { diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx index 3c0b034da0360..615179a3f6f68 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/extended_template.tsx @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSpacer } from '@elastic/eui'; import immutable from 'object-path-immutable'; import { get } from 'lodash'; -import { ExpressionAST } from '../../../../types'; +import { ExpressionAstExpression } from '../../../../types'; import { ArgTypesStrings } from '../../../../i18n'; const { set, del } = immutable; @@ -24,9 +24,9 @@ export interface Arguments { export type Argument = keyof Arguments; export interface Props { - argValue: ExpressionAST; + argValue: ExpressionAstExpression; labels: string[]; - onValueChange: (argValue: ExpressionAST) => void; + onValueChange: (argValue: ExpressionAstExpression) => void; typeInstance?: { name: string; options: { diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/index.ts b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/index.ts index c3211c27eef75..3e8ef4d89991a 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/index.ts +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/index.ts @@ -10,13 +10,13 @@ import { get } from 'lodash'; import { templateFromReactComponent } from '../../../lib/template_from_react_component'; import { SimpleTemplate } from './simple_template'; import { ExtendedTemplate, Props as ExtendedTemplateProps } from './extended_template'; -import { ExpressionAST } from '../../../../types'; +import { ExpressionAstExpression } from '../../../../types'; import { ArgTypesStrings } from '../../../../i18n'; const { SeriesStyle: strings } = ArgTypesStrings; interface Props { - argValue: ExpressionAST; + argValue: ExpressionAstExpression; renderError: Function; setLabel: Function; label: string; diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx index ba1f4305167a4..226122cf0b25f 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/simple_template.tsx @@ -11,7 +11,7 @@ import immutable from 'object-path-immutable'; import { get } from 'lodash'; import { ColorPickerPopover } from '../../../components/color_picker_popover'; import { TooltipIcon, IconType } from '../../../components/tooltip_icon'; -import { ExpressionAST, CanvasWorkpad } from '../../../../types'; +import { ExpressionAstExpression, CanvasWorkpad } from '../../../../types'; import { ArgTypesStrings } from '../../../../i18n'; const { set, del } = immutable; @@ -23,9 +23,9 @@ interface Arguments { type Argument = keyof Arguments; interface Props { - argValue: ExpressionAST; + argValue: ExpressionAstExpression; labels?: string[]; - onValueChange: (argValue: ExpressionAST) => void; + onValueChange: (argValue: ExpressionAstExpression) => void; typeInstance: { name: string; }; diff --git a/x-pack/legacy/plugins/canvas/public/functions/asset.ts b/x-pack/legacy/plugins/canvas/public/functions/asset.ts index 7f2f56a71756e..2f2ad181b264c 100644 --- a/x-pack/legacy/plugins/canvas/public/functions/asset.ts +++ b/x-pack/legacy/plugins/canvas/public/functions/asset.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/public'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public'; // @ts-ignore unconverted local lib import { getState } from '../state/store'; import { getAssetById } from '../state/selectors/assets'; @@ -14,7 +14,7 @@ interface Arguments { id: string; } -export function asset(): ExpressionFunction<'asset', null, Arguments, string> { +export function asset(): ExpressionFunctionDefinition<'asset', null, Arguments, string> { const { help, args: argHelp } = getFunctionHelp().asset; const errors = getFunctionErrors().asset; @@ -22,10 +22,8 @@ export function asset(): ExpressionFunction<'asset', null, Arguments, string> { name: 'asset', aliases: [], type: 'string', + inputTypes: ['null'], help, - context: { - types: ['null'], - }, args: { id: { aliases: ['_'], @@ -34,7 +32,7 @@ export function asset(): ExpressionFunction<'asset', null, Arguments, string> { required: true, }, }, - fn: (_context, args) => { + fn: (input, args) => { const assetId = args.id; const storedAsset = getAssetById(getState(), assetId); if (storedAsset !== undefined) { diff --git a/x-pack/legacy/plugins/canvas/public/functions/filters.ts b/x-pack/legacy/plugins/canvas/public/functions/filters.ts index 722cf5a9d5eba..44b321e00091a 100644 --- a/x-pack/legacy/plugins/canvas/public/functions/filters.ts +++ b/x-pack/legacy/plugins/canvas/public/functions/filters.ts @@ -10,7 +10,7 @@ import { get } from 'lodash'; import { interpretAst } from 'plugins/interpreter/interpreter'; // @ts-ignore untyped Elastic lib import { registries } from 'plugins/interpreter/registries'; -import { ExpressionFunction } from 'src/plugins/expressions/public'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public'; // @ts-ignore untyped local import { getState } from '../state/store'; import { getGlobalFilters } from '../state/selectors/workpad'; @@ -43,16 +43,14 @@ function getFiltersByGroup(allFilters: string[], groups?: string[], ungrouped = }); } -export function filters(): ExpressionFunction<'filters', null, Arguments, Filter> { +export function filters(): ExpressionFunctionDefinition<'filters', null, Arguments, Filter> { const { help, args: argHelp } = getFunctionHelp().filters; return { name: 'filters', type: 'filter', help, - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { group: { aliases: ['_'], @@ -67,7 +65,7 @@ export function filters(): ExpressionFunction<'filters', null, Arguments, Filter default: false, }, }, - fn: (_context, { group, ungrouped }) => { + fn: (input, { group, ungrouped }) => { const filterList = getFiltersByGroup(getGlobalFilters(getState()), group, ungrouped); if (filterList && filterList.length) { diff --git a/x-pack/legacy/plugins/canvas/public/functions/timelion.ts b/x-pack/legacy/plugins/canvas/public/functions/timelion.ts index 4377f2cb4d53b..ae87e858cf796 100644 --- a/x-pack/legacy/plugins/canvas/public/functions/timelion.ts +++ b/x-pack/legacy/plugins/canvas/public/functions/timelion.ts @@ -9,7 +9,7 @@ import moment from 'moment-timezone'; import chrome from 'ui/chrome'; import { npStart } from 'ui/new_platform'; import { TimeRange } from 'src/plugins/data/common'; -import { ExpressionFunction, DatatableRow } from 'src/plugins/expressions/public'; +import { ExpressionFunctionDefinition, DatatableRow } from 'src/plugins/expressions/public'; import { fetch } from '../../common/lib/fetch'; // @ts-ignore untyped local import { buildBoolArray } from '../../server/lib/build_bool_array'; @@ -44,16 +44,19 @@ function parseDateMath(timeRange: TimeRange, timeZone: string) { return parsedRange; } -export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Promise> { +export function timelion(): ExpressionFunctionDefinition< + 'timelion', + Filter, + Arguments, + Promise +> { const { help, args: argHelp } = getFunctionHelp().timelion; return { name: 'timelion', type: 'datatable', + inputTypes: ['filter'], help, - context: { - types: ['filter'], - }, args: { query: { types: ['string'], @@ -82,10 +85,10 @@ export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Pr default: 'UTC', }, }, - fn: (context, args): Promise => { + fn: (input, args): Promise => { // Timelion requires a time range. Use the time range from the timefilter element in the // workpad, if it exists. Otherwise fall back on the function args. - const timeFilter = context.and.find(and => and.type === 'time'); + const timeFilter = input.and.find(and => and.type === 'time'); const range = timeFilter ? { min: timeFilter.from, max: timeFilter.to } : parseDateMath({ from: args.from, to: args.to }, args.timezone); @@ -95,7 +98,7 @@ export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Pr es: { filter: { bool: { - must: buildBoolArray(context.and), + must: buildBoolArray(input.and), }, }, }, diff --git a/x-pack/legacy/plugins/canvas/public/functions/to.ts b/x-pack/legacy/plugins/canvas/public/functions/to.ts index 35d4ea21097ee..7c24926b5aa6a 100644 --- a/x-pack/legacy/plugins/canvas/public/functions/to.ts +++ b/x-pack/legacy/plugins/canvas/public/functions/to.ts @@ -6,16 +6,15 @@ // @ts-ignore untyped Elastic library import { castProvider } from '@kbn/interpreter/common'; -import { ExpressionFunction } from 'src/plugins/expressions/public'; -// @ts-ignore untyped Elastic library -import { registries } from 'plugins/interpreter/registries'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public'; +import { npStart } from 'ui/new_platform'; import { getFunctionHelp, getFunctionErrors } from '../../i18n'; interface Arguments { type: string[]; } -export function to(): ExpressionFunction<'to', any, Arguments, any> { +export function to(): ExpressionFunctionDefinition<'to', any, Arguments, any> { const { help, args: argHelp } = getFunctionHelp().to; const errors = getFunctionErrors().to; @@ -31,12 +30,12 @@ export function to(): ExpressionFunction<'to', any, Arguments, any> { multi: true, }, }, - fn: (context, args) => { + fn: (input, args) => { if (!args.type) { throw errors.missingType(); } - return castProvider(registries.types.toJS())(context, args.type); + return castProvider(npStart.plugins.expressions.getTypes())(input, args.type); }, }; } diff --git a/x-pack/legacy/plugins/canvas/public/lib/function_definitions.js b/x-pack/legacy/plugins/canvas/public/lib/function_definitions.js index 71d6aac7ad901..36ad0ba0b0015 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/function_definitions.js +++ b/x-pack/legacy/plugins/canvas/public/lib/function_definitions.js @@ -5,10 +5,13 @@ */ import uniqBy from 'lodash.uniqby'; -import { registries } from 'plugins/interpreter/registries'; +import { npStart } from 'ui/new_platform'; import { getServerFunctions } from '../state/selectors/app'; export async function getFunctionDefinitions(state) { const serverFunctions = getServerFunctions(state); - return uniqBy(serverFunctions.concat(registries.browserFunctions.toArray()), 'name'); + return uniqBy( + serverFunctions.concat(Object.values(npStart.plugins.expressions.getFunctions())), + 'name' + ); } diff --git a/x-pack/legacy/plugins/canvas/public/lib/monaco_language_def.ts b/x-pack/legacy/plugins/canvas/public/lib/monaco_language_def.ts index 7e51cb8057658..e15be9a90beb0 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/monaco_language_def.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/monaco_language_def.ts @@ -5,11 +5,7 @@ */ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; - -// @ts-ignore -import { registries } from 'plugins/interpreter/registries'; - -import { CanvasFunction } from '../../types'; +import { npSetup } from 'ui/new_platform'; export const LANGUAGE_ID = 'canvas-expression'; @@ -99,8 +95,8 @@ export const language: Language = { }; export function registerLanguage() { - const functions = registries.browserFunctions.toArray(); - language.keywords = functions.map((fn: CanvasFunction) => fn.name); + const functions = Object.values(npSetup.plugins.expressions.getFunctions()); + language.keywords = functions.map(({ name }) => name); monaco.languages.register({ id: LANGUAGE_ID }); monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, language); diff --git a/x-pack/legacy/plugins/canvas/public/renderers.js b/x-pack/legacy/plugins/canvas/public/renderers.js index 717daae7fa9d0..0c278789bc1aa 100644 --- a/x-pack/legacy/plugins/canvas/public/renderers.js +++ b/x-pack/legacy/plugins/canvas/public/renderers.js @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderersRegistry } from 'plugins/interpreter/registries'; +import { npSetup } from 'ui/new_platform'; import { renderFunctions } from '../canvas_plugin_src/renderers'; -renderFunctions.forEach(r => { - renderersRegistry.register(r); -}); +renderFunctions.forEach(npSetup.plugins.expressions.registerRenderer); export default renderFunctions; diff --git a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts index d47a339cf8afe..84fab0cb0ae6d 100644 --- a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts @@ -11,14 +11,12 @@ import { safeElementFromExpression, fromExpression } from '@kbn/interpreter/comm import { append } from '../../lib/modify_path'; import { getAssets } from './assets'; import { State, CanvasWorkpad, CanvasPage, CanvasElement, ResolvedArgType } from '../../../types'; +import { ExpressionContext, CanvasGroup, PositionedElement } from '../../../types'; import { - ExpressionAST, - ExpressionFunctionAST, - ExpressionArgAST, - ExpressionContext, - CanvasGroup, - PositionedElement, -} from '../../../types'; + ExpressionAstArgument, + ExpressionAstFunction, + ExpressionAstExpression, +} from '../../../../../../../src/plugins/expressions/common'; type Modify = Pick> & R; type WorkpadInfo = Modify; @@ -27,7 +25,7 @@ const workpadRoot = 'persistent.workpad'; const appendAst = (element: CanvasElement): PositionedElement => ({ ...element, - ast: safeElementFromExpression(element.expression) as ExpressionAST, + ast: safeElementFromExpression(element.expression) as ExpressionAstExpression, }); // workpad getters @@ -188,33 +186,35 @@ export function getGlobalFilters(state: State): string[] { } type onValueFunction = ( - argValue: ExpressionArgAST, + argValue: ExpressionAstArgument, argNames?: string, - args?: ExpressionFunctionAST['arguments'] -) => ExpressionArgAST | ExpressionArgAST[] | undefined; + args?: ExpressionAstFunction['arguments'] +) => ExpressionAstArgument | ExpressionAstArgument[] | undefined; -function buildGroupValues(args: ExpressionFunctionAST['arguments'], onValue: onValueFunction) { +function buildGroupValues(args: ExpressionAstFunction['arguments'], onValue: onValueFunction) { const argNames = Object.keys(args); - return argNames.reduce((values, argName) => { + return argNames.reduce((values, argName) => { // we only care about group values if (argName !== '_' && argName !== 'group') { return values; } - return args[argName].reduce((acc, argValue) => { + return args[argName].reduce((acc, argValue) => { // delegate to passed function to buyld list return acc.concat(onValue(argValue, argName, args) || []); }, values); }, []); } -function extractFilterGroups(ast: ExpressionAST): ExpressionArgAST[] { +function extractFilterGroups( + ast: ExpressionAstExpression | ExpressionAstFunction +): ExpressionAstArgument[] { if (ast.type !== 'expression') { throw new Error('AST must be an expression'); } - return ast.chain.reduce((groups, item) => { + return ast.chain.reduce((groups, item) => { // TODO: we always get a function here, right? const { function: fn, arguments: args } = item; @@ -247,8 +247,11 @@ export function getGlobalFilterGroups(state: State) { // check that a filter is defined if (el.filter != null && el.filter.length) { // extract the filter group - const filterAst = fromExpression(el.filter) as ExpressionAST; - const filterGroup: ExpressionArgAST = get(filterAst, `chain[0].arguments.filterGroup[0]`); + const filterAst = fromExpression(el.filter) as ExpressionAstExpression; + const filterGroup: ExpressionAstArgument = get( + filterAst, + `chain[0].arguments.filterGroup[0]` + ); // add any new group to the array if (filterGroup && filterGroup !== '' && !acc.includes(String(filterGroup))) { @@ -258,7 +261,9 @@ export function getGlobalFilterGroups(state: State) { // extract groups from all expressions that use filters function if (el.expression != null && el.expression.length) { - const expressionAst = fromExpression(el.expression) as ExpressionAST; + const expressionAst = fromExpression(el.expression) as + | ExpressionAstFunction + | ExpressionAstExpression; const groups = extractFilterGroups(expressionAst); groups.forEach(group => { if (!acc.includes(String(group))) { diff --git a/x-pack/legacy/plugins/canvas/types/elements.ts b/x-pack/legacy/plugins/canvas/types/elements.ts index 0ceeb7ba60ebc..acb1cb9cd7625 100644 --- a/x-pack/legacy/plugins/canvas/types/elements.ts +++ b/x-pack/legacy/plugins/canvas/types/elements.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionAST } from 'src/plugins/expressions/common'; +import { ExpressionAstExpression } from 'src/plugins/expressions'; import { CanvasElement } from '.'; export interface ElementSpec { @@ -79,4 +79,4 @@ export interface ElementPosition { parent: string | null; } -export type PositionedElement = CanvasElement & { ast: ExpressionAST }; +export type PositionedElement = CanvasElement & { ast: ExpressionAstExpression }; diff --git a/x-pack/legacy/plugins/canvas/types/functions.ts b/x-pack/legacy/plugins/canvas/types/functions.ts index 773c9c3020a85..27b2a04ebd6e3 100644 --- a/x-pack/legacy/plugins/canvas/types/functions.ts +++ b/x-pack/legacy/plugins/canvas/types/functions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionFunction } from 'src/plugins/expressions/common'; +import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { functions as commonFunctions } from '../canvas_plugin_src/functions/common'; import { functions as browserFunctions } from '../canvas_plugin_src/functions/browser'; import { functions as serverFunctions } from '../canvas_plugin_src/functions/server'; @@ -26,9 +26,6 @@ export type UnionToIntersection = */ export type ValuesOf = T[number]; -type valueof = T[keyof T]; -type ValuesOfUnion = T extends any ? valueof : never; - /** * A `ExpressionFunctionFactory` is a powerful type used for any function that produces * an `ExpressionFunction`. If it does not meet the signature for such a function, @@ -88,8 +85,8 @@ type ValuesOfUnion = T extends any ? valueof : never; * in Kibana and Canvas. */ // prettier-ignore -export type ExpressionFunctionFactory = -() => ExpressionFunction; +export type ExpressionFunctionFactory = + () => ExpressionFunctionDefinition; /** * `FunctionFactory` exists as a name shim between the `ExpressionFunction` type and @@ -99,8 +96,8 @@ export type ExpressionFunctionFactory = - FnFactory extends ExpressionFunctionFactory ? - ExpressionFunction : + FnFactory extends ExpressionFunctionFactory ? + ExpressionFunctionDefinition : never; type CommonFunction = FunctionFactory; @@ -111,19 +108,8 @@ type ClientFunctions = FunctionFactory; /** * A collection of all Canvas Functions. */ -export type CanvasFunction = CommonFunction | BrowserFunction | ServerFunction | ClientFunctions; - -/** - * A union type of all Canvas Function names. - */ -export type CanvasFunctionName = CanvasFunction['name']; -/** - * A union type of all Canvas Function argument objects. - */ -export type CanvasArg = CanvasFunction['args']; - -export type CanvasArgValue = ValuesOfUnion; +export type CanvasFunction = CommonFunction | BrowserFunction | ServerFunction | ClientFunctions; /** * Represents a function called by the `case` Function. diff --git a/x-pack/legacy/plugins/canvas/types/state.ts b/x-pack/legacy/plugins/canvas/types/state.ts index 171c5515fbb2a..13c8f7a9176ab 100644 --- a/x-pack/legacy/plugins/canvas/types/state.ts +++ b/x-pack/legacy/plugins/canvas/types/state.ts @@ -8,14 +8,14 @@ import { Datatable, Filter, ExpressionImage, + ExpressionFunction, KibanaContext, KibanaDatatable, PointSeries, Render, Style, Range, -} from 'src/plugins/expressions/common'; -import { CanvasFunction } from './functions'; +} from 'src/plugins/expressions'; import { AssetType } from './assets'; import { CanvasWorkpad } from './canvas'; @@ -33,8 +33,7 @@ export interface AppState { interface StoreAppState { basePath: string; - // TODO: These server functions are actually missing the fn because they are serialized from the server - serverFunctions: CanvasFunction[]; + serverFunctions: ExpressionFunction[]; ready: boolean; } diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx index 5e4b21d9b56d6..440f7bdc42bcb 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx @@ -8,13 +8,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable } from '@elastic/eui'; -import { - ExpressionFunction, - KibanaDatatable, -} from '../../../../../../src/plugins/expressions/common'; import { LensMultiTable } from '../types'; import { - IInterpreterRenderFunction, + ExpressionFunctionDefinition, + ExpressionRenderDefinition, IInterpreterRenderHandlers, } from '../../../../../../src/plugins/expressions/public'; import { FormatFactory } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; @@ -25,7 +22,8 @@ export interface DatatableColumns { } interface Args { - columns: DatatableColumns; + title: string; + columns: DatatableColumns & { type: 'lens_datatable_columns' }; } export interface DatatableProps { @@ -39,14 +37,15 @@ export interface DatatableRender { value: DatatableProps; } -export const datatable: ExpressionFunction< +export const datatable: ExpressionFunctionDefinition< 'lens_datatable', - KibanaDatatable, + LensMultiTable, Args, DatatableRender -> = ({ +> = { name: 'lens_datatable', type: 'render', + inputTypes: ['lens_multitable'], help: i18n.translate('xpack.lens.datatable.expressionHelpLabel', { defaultMessage: 'Datatable renderer', }), @@ -62,10 +61,7 @@ export const datatable: ExpressionFunction< help: '', }, }, - context: { - types: ['lens_multitable'], - }, - fn(data: KibanaDatatable, args: Args) { + fn(data, args) { return { type: 'render', as: 'lens_datatable_renderer', @@ -75,12 +71,11 @@ export const datatable: ExpressionFunction< }, }; }, - // TODO the typings currently don't support custom type args. As soon as they do, this can be removed -} as unknown) as ExpressionFunction<'lens_datatable', KibanaDatatable, Args, DatatableRender>; +}; type DatatableColumnsResult = DatatableColumns & { type: 'lens_datatable_columns' }; -export const datatableColumns: ExpressionFunction< +export const datatableColumns: ExpressionFunctionDefinition< 'lens_datatable_columns', null, DatatableColumns, @@ -90,9 +85,7 @@ export const datatableColumns: ExpressionFunction< aliases: [], type: 'lens_datatable_columns', help: '', - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { columnIds: { types: ['string'], @@ -100,7 +93,7 @@ export const datatableColumns: ExpressionFunction< help: '', }, }, - fn: function fn(_context: unknown, args: DatatableColumns) { + fn: function fn(input: unknown, args: DatatableColumns) { return { type: 'lens_datatable_columns', ...args, @@ -110,13 +103,13 @@ export const datatableColumns: ExpressionFunction< export const getDatatableRenderer = ( formatFactory: FormatFactory -): IInterpreterRenderFunction => ({ +): ExpressionRenderDefinition => ({ name: 'lens_datatable_renderer', displayName: i18n.translate('xpack.lens.datatable.visualizationName', { defaultMessage: 'Datatable', }), help: '', - validate: () => {}, + validate: () => undefined, reuseDomNode: true, render: async ( domNode: Element, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx index c9b9a43376651..0e256d0ab181b 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx @@ -18,7 +18,7 @@ import { createExpressionRendererMock, DatasourceMock, } from '../mocks'; -import { ExpressionRenderer } from 'src/plugins/expressions/public'; +import { ReactExpressionRendererType } from 'src/plugins/expressions/public'; import { DragDrop } from '../../drag_drop'; import { FrameLayout } from './frame_layout'; @@ -66,7 +66,7 @@ describe('editor_frame', () => { let mockVisualization2: jest.Mocked; let mockDatasource2: DatasourceMock; - let expressionRendererMock: ExpressionRenderer; + let expressionRendererMock: ReactExpressionRendererType; beforeEach(() => { mockVisualization = { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx index 3284f69b503c5..399eaf5888286 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx @@ -6,7 +6,7 @@ import React, { useEffect, useReducer } from 'react'; import { CoreSetup, CoreStart } from 'src/core/public'; -import { ExpressionRenderer } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; import { Datasource, DatasourcePublicAPI, @@ -33,7 +33,7 @@ export interface EditorFrameProps { visualizationMap: Record; initialDatasourceId: string | null; initialVisualizationId: string | null; - ExpressionRenderer: ExpressionRenderer; + ExpressionRenderer: ReactExpressionRendererType; onError: (e: { message: string }) => void; core: CoreSetup | CoreStart; dateRange: { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx index c020ce8b3c8d1..9729d6259f84a 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx @@ -15,7 +15,7 @@ import { createMockFramePublicAPI, } from '../mocks'; import { act } from 'react-dom/test-utils'; -import { ExpressionRenderer } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel'; import { getSuggestions, Suggestion } from './suggestion_helpers'; import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; @@ -29,7 +29,7 @@ describe('suggestion_panel', () => { let mockVisualization: Visualization; let mockDatasource: DatasourceMock; - let expressionRendererMock: ExpressionRenderer; + let expressionRendererMock: ReactExpressionRendererType; let dispatchMock: jest.Mock; const suggestion1State = { suggestion1: true }; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx index 46e226afe9c59..1115126792c86 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx @@ -24,7 +24,7 @@ import classNames from 'classnames'; import { Action, PreviewState } from './state_management'; import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; -import { ExpressionRenderer } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers'; import { debouncedComponent } from '../../debounced_component'; import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry'; @@ -49,7 +49,7 @@ export interface SuggestionPanelProps { visualizationMap: Record; visualizationState: unknown; dispatch: (action: Action) => void; - ExpressionRenderer: ExpressionRenderer; + ExpressionRenderer: ReactExpressionRendererType; frame: FramePublicAPI; stagedPreview?: PreviewState; } @@ -61,7 +61,7 @@ const PreviewRenderer = ({ }: { withLabel: boolean; expression: string; - ExpressionRendererComponent: ExpressionRenderer; + ExpressionRendererComponent: ReactExpressionRendererType; }) => { return (
{ diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx index 74dacd50d7a15..929b4667aeb66 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { ExpressionRendererProps } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererProps } from '../../../../../../../src/plugins/expressions/public'; import { FramePublicAPI, TableSuggestion, Visualization } from '../../types'; import { createMockVisualization, @@ -29,7 +29,7 @@ describe('workspace_panel', () => { let mockVisualization2: jest.Mocked; let mockDatasource: DatasourceMock; - let expressionRendererMock: jest.Mock; + let expressionRendererMock: jest.Mock; let instance: ReactWrapper; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx index 1058ccd81d669..c2a5c16e405a2 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx @@ -17,7 +17,7 @@ import { EuiButtonEmpty, } from '@elastic/eui'; import { CoreStart, CoreSetup } from 'src/core/public'; -import { ExpressionRenderer } from '../../../../../../../src/plugins/expressions/public'; +import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; import { Action } from './state_management'; import { Datasource, Visualization, FramePublicAPI } from '../../types'; import { DragDrop, DragContext } from '../../drag_drop'; @@ -41,7 +41,7 @@ export interface WorkspacePanelProps { >; framePublicAPI: FramePublicAPI; dispatch: (action: Action) => void; - ExpressionRenderer: ExpressionRenderer; + ExpressionRenderer: ReactExpressionRendererType; core: CoreStart | CoreSetup; } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx index 1f0620c43f7f7..59e1378e63661 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx @@ -5,7 +5,7 @@ */ import { Embeddable } from './embeddable'; -import { ExpressionRendererProps } from 'src/plugins/expressions/public'; +import { ReactExpressionRendererProps } from 'src/plugins/expressions/public'; import { Query, TimeRange, esFilters } from 'src/plugins/data/public'; import { Document } from '../../persistence'; @@ -31,7 +31,7 @@ const savedVis: Document = { describe('embeddable', () => { let mountpoint: HTMLDivElement; - let expressionRenderer: jest.Mock; + let expressionRenderer: jest.Mock; beforeEach(() => { mountpoint = document.createElement('div'); @@ -104,7 +104,6 @@ describe('embeddable', () => { embeddable.render(mountpoint); expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual({ - type: 'kibana_context', timeRange, query, filters, diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.tsx index 6fcf2bab8921f..117b2a3a949ea 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.tsx @@ -8,15 +8,15 @@ import _ from 'lodash'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Query, TimeRange, esFilters } from 'src/plugins/data/public'; -import { ExpressionRenderer } from 'src/plugins/expressions/public'; import { IIndexPattern } from 'src/plugins/data/public'; import { Subscription } from 'rxjs'; +import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public'; import { Embeddable as AbstractEmbeddable, EmbeddableOutput, IContainer, EmbeddableInput, -} from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; +} from '../../../../../../../src/plugins/embeddable/public'; import { Document, DOC_TYPE } from '../../persistence'; import { ExpressionWrapper } from './expression_wrapper'; @@ -40,7 +40,7 @@ export interface LensEmbeddableOutput extends EmbeddableOutput { export class Embeddable extends AbstractEmbeddable { type = DOC_TYPE; - private expressionRenderer: ExpressionRenderer; + private expressionRenderer: ReactExpressionRendererType; private savedVis: Document; private domNode: HTMLElement | Element | undefined; private subscription: Subscription; @@ -53,7 +53,7 @@ export class Embeddable extends AbstractEmbeddable
{error}
} />
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.test.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.test.ts index ffb8be1deaa9e..9368674de31c5 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.test.ts +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.test.ts @@ -6,7 +6,7 @@ import moment from 'moment'; import { mergeTables } from './merge_tables'; -import { KibanaDatatable } from 'src/plugins/expressions/public'; +import { KibanaDatatable } from 'src/plugins/expressions'; jest.mock('ui/new_platform'); @@ -40,7 +40,8 @@ describe('lens_merge_tables', () => { mergeTables.fn( null, { layerIds: ['first', 'second'], tables: [sampleTable1, sampleTable2] }, - {} + // eslint-disable-next-line + {} as any ) ).toEqual({ tables: { first: sampleTable1, second: sampleTable2 }, @@ -59,7 +60,8 @@ describe('lens_merge_tables', () => { }, }, { layerIds: ['first', 'second'], tables: [] }, - {} + // eslint-disable-next-line + {} as any ) ).toMatchInlineSnapshot(` Object { @@ -83,7 +85,8 @@ describe('lens_merge_tables', () => { }, }, { layerIds: ['first', 'second'], tables: [] }, - {} + // eslint-disable-next-line + {} as any ); expect( diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts index dc03be894a87c..3c466522e1ebe 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/merge_tables.ts @@ -5,7 +5,11 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext, KibanaDatatable } from 'src/plugins/expressions/public'; +import { + ExpressionFunctionDefinition, + ExpressionValueSearchContext, + KibanaDatatable, +} from 'src/plugins/expressions/public'; import { LensMultiTable } from '../types'; import { toAbsoluteDates } from '../indexpattern_plugin/auto_date'; @@ -14,9 +18,9 @@ interface MergeTables { tables: KibanaDatatable[]; } -export const mergeTables: ExpressionFunction< +export const mergeTables: ExpressionFunctionDefinition< 'lens_merge_tables', - KibanaContext | null, + ExpressionValueSearchContext | null, MergeTables, LensMultiTable > = { @@ -37,10 +41,8 @@ export const mergeTables: ExpressionFunction< multi: true, }, }, - context: { - types: ['kibana_context', 'null'], - }, - fn(ctx, { layerIds, tables }: MergeTables) { + inputTypes: ['kibana_context', 'null'], + fn(input, { layerIds, tables }) { const resultTables: Record = {}; tables.forEach((table, index) => { resultTables[layerIds[index]] = table; @@ -48,17 +50,17 @@ export const mergeTables: ExpressionFunction< return { type: 'lens_multitable', tables: resultTables, - dateRange: getDateRange(ctx), + dateRange: getDateRange(input), }; }, }; -function getDateRange(ctx?: KibanaContext | null) { - if (!ctx || !ctx.timeRange) { +function getDateRange(value?: ExpressionValueSearchContext | null) { + if (!value || !value.timeRange) { return; } - const dateRange = toAbsoluteDates({ fromDate: ctx.timeRange.from, toDate: ctx.timeRange.to }); + const dateRange = toAbsoluteDates({ fromDate: value.timeRange.from, toDate: value.timeRange.to }); if (!dateRange) { return; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx index 7257647d5953e..b4fc88cb074c7 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { - ExpressionRendererProps, + ReactExpressionRendererProps, ExpressionsSetup, ExpressionsStart, } from '../../../../../../src/plugins/expressions/public'; @@ -98,7 +98,7 @@ export type MockedStartDependencies = Omit { return jest.fn(_ => ); } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx index 7546ac6509913..e914eb7d7784b 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx @@ -71,7 +71,7 @@ export class EditorFramePlugin { 'lens', new EmbeddableFactory( plugins.chrome, - plugins.expressions.ExpressionRenderer, + plugins.expressions.ReactExpressionRenderer, plugins.data.indexPatterns ) ); @@ -96,7 +96,7 @@ export class EditorFramePlugin { (doc && doc.visualizationType) || firstVisualizationId || null } core={core} - ExpressionRenderer={plugins.expressions.ExpressionRenderer} + ExpressionRenderer={plugins.expressions.ReactExpressionRenderer} doc={doc} dateRange={dateRange} query={query} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.test.ts index 8146bc39ef82e..6611c1a227442 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.test.ts @@ -18,7 +18,8 @@ describe('auto_date', () => { { aggConfigs: 'canttouchthis', }, - {} + // eslint-disable-next-line + {} as any ); expect(result).toEqual('canttouchthis'); @@ -40,7 +41,8 @@ describe('auto_date', () => { { aggConfigs, }, - {} + // eslint-disable-next-line + {} as any ); expect(result).toEqual(aggConfigs); @@ -62,7 +64,8 @@ describe('auto_date', () => { { aggConfigs, }, - {} + // eslint-disable-next-line + {} as any ); const interval = JSON.parse(result).find( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts index 7720af8ee9001..be7929392635f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts @@ -7,7 +7,7 @@ import { TimeBuckets } from 'ui/time_buckets'; import dateMath from '@elastic/datemath'; import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaContext, } from '../../../../../../src/plugins/expressions/public'; import { DateRange } from '../../../../../plugins/lens/common'; @@ -69,7 +69,7 @@ function autoIntervalFromContext(ctx?: KibanaContext | null) { * This allows us to support 'auto' on all date fields, and opens the * door to future customizations (e.g. adjusting the level of detail, etc). */ -export const autoDate: ExpressionFunction< +export const autoDate: ExpressionFunctionDefinition< 'lens_auto_date', KibanaContext | null, LensAutoDateProps, @@ -78,9 +78,7 @@ export const autoDate: ExpressionFunction< name: 'lens_auto_date', aliases: [], help: '', - context: { - types: ['kibana_context', 'null'], - }, + inputTypes: ['kibana_context', 'null'], args: { aggConfigs: { types: ['string'], @@ -88,8 +86,8 @@ export const autoDate: ExpressionFunction< help: '', }, }, - fn(ctx: KibanaContext, args: LensAutoDateProps) { - const interval = autoIntervalFromContext(ctx); + fn(input, args) { + const interval = autoIntervalFromContext(input); if (!interval) { return args.aggConfigs; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.test.ts index a231374b89a42..9da7591305a6c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.test.ts @@ -6,6 +6,7 @@ import { renameColumns } from './rename_columns'; import { KibanaDatatable } from '../../../../../../src/plugins/expressions/public'; +import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; describe('rename_columns', () => { it('should rename columns of a given datatable', () => { @@ -34,7 +35,13 @@ describe('rename_columns', () => { }, }; - expect(renameColumns.fn(input, { idMap: JSON.stringify(idMap) }, {})).toMatchInlineSnapshot(` + const result = renameColumns.fn( + input, + { idMap: JSON.stringify(idMap) }, + createMockExecutionContext() + ); + + expect(result).toMatchInlineSnapshot(` Object { "columns": Array [ Object { @@ -83,9 +90,13 @@ describe('rename_columns', () => { }, }; - expect(renameColumns.fn(input, { idMap: JSON.stringify(idMap) }, {}).rows[0].a).toEqual( - '(empty)' + const result = renameColumns.fn( + input, + { idMap: JSON.stringify(idMap) }, + createMockExecutionContext() ); + + expect(result.rows[0].a).toEqual('(empty)'); }); it('should keep columns which are not mapped', () => { @@ -107,7 +118,13 @@ describe('rename_columns', () => { b: { id: 'c', label: 'Catamaran' }, }; - expect(renameColumns.fn(input, { idMap: JSON.stringify(idMap) }, {})).toMatchInlineSnapshot(` + const result = renameColumns.fn( + input, + { idMap: JSON.stringify(idMap) }, + createMockExecutionContext() + ); + + expect(result).toMatchInlineSnapshot(` Object { "columns": Array [ Object { @@ -161,7 +178,13 @@ describe('rename_columns', () => { b: { id: 'c', label: 'Apple', operationType: 'date_histogram', sourceField: 'banana' }, }; - expect(renameColumns.fn(input, { idMap: JSON.stringify(idMap) }, {})).toMatchInlineSnapshot(` + const result = renameColumns.fn( + input, + { idMap: JSON.stringify(idMap) }, + createMockExecutionContext() + ); + + expect(result).toMatchInlineSnapshot(` Object { "columns": Array [ Object { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.ts index 19dd661409c6f..248eb12ec8026 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/rename_columns.ts @@ -6,10 +6,10 @@ import { i18n } from '@kbn/i18n'; import { - ExpressionFunction, + ExpressionFunctionDefinition, KibanaDatatable, KibanaDatatableColumn, -} from 'src/plugins/expressions/common'; +} from 'src/plugins/expressions'; import { IndexPatternColumn } from './operations'; interface RemapArgs { @@ -18,7 +18,7 @@ interface RemapArgs { export type OriginalColumn = { id: string } & IndexPatternColumn; -export const renameColumns: ExpressionFunction< +export const renameColumns: ExpressionFunctionDefinition< 'lens_rename_columns', KibanaDatatable, RemapArgs, @@ -38,10 +38,8 @@ export const renameColumns: ExpressionFunction< }), }, }, - context: { - types: ['kibana_datatable'], - }, - fn(data: KibanaDatatable, { idMap: encodedIdMap }: RemapArgs) { + inputTypes: ['kibana_datatable'], + fn(data, { idMap: encodedIdMap }) { const idMap = JSON.parse(encodedIdMap) as Record; return { diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index 1e0fce9f538b4..3da38d486aecd 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -9,6 +9,7 @@ import { LensMultiTable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; import { MetricConfig } from './types'; +import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; import { IFieldFormat } from '../../../../../../src/plugins/data/public'; function sampleArgs() { @@ -41,8 +42,9 @@ describe('metric_expression', () => { describe('metricChart', () => { test('it renders with the specified data and args', () => { const { data, args } = sampleArgs(); + const result = metricChart.fn(data, args, createMockExecutionContext()); - expect(metricChart.fn(data, args, {})).toEqual({ + expect(result).toEqual({ type: 'render', as: 'lens_metric_chart_renderer', value: { data, args }, diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index 7fb44a3a37c51..66ed963002f59 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -8,8 +8,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { FormatFactory } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { - ExpressionFunction, - IInterpreterRenderFunction, + ExpressionFunctionDefinition, + ExpressionRenderDefinition, IInterpreterRenderHandlers, } from '../../../../../../src/plugins/expressions/public'; import { MetricConfig } from './types'; @@ -28,12 +28,12 @@ export interface MetricRender { value: MetricChartProps; } -export const metricChart: ExpressionFunction< +export const metricChart: ExpressionFunctionDefinition< 'lens_metric_chart', LensMultiTable, - MetricConfig, + Omit, MetricRender -> = ({ +> = { name: 'lens_metric_chart', type: 'render', help: 'A metric chart', @@ -54,10 +54,8 @@ export const metricChart: ExpressionFunction< 'The display mode of the chart - reduced will only show the metric itself without min size', }, }, - context: { - types: ['lens_multitable'], - }, - fn(data: LensMultiTable, args: MetricChartProps) { + inputTypes: ['lens_multitable'], + fn(data, args) { return { type: 'render', as: 'lens_metric_chart_renderer', @@ -65,23 +63,17 @@ export const metricChart: ExpressionFunction< data, args, }, - }; + } as MetricRender; }, - // TODO the typings currently don't support custom type args. As soon as they do, this can be removed -} as unknown) as ExpressionFunction< - 'lens_metric_chart', - LensMultiTable, - MetricConfig, - MetricRender ->; +}; export const getMetricChartRenderer = ( formatFactory: FormatFactory -): IInterpreterRenderFunction => ({ +): ExpressionRenderDefinition => ({ name: 'lens_metric_chart_renderer', displayName: 'Metric chart', help: 'Metric chart renderer', - validate: () => {}, + validate: () => undefined, reuseDomNode: true, render: (domNode: Element, config: MetricChartProps, handlers: IInterpreterRenderHandlers) => { ReactDOM.render(, domNode, () => { diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx index f0603f021c452..6feece99370ef 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/plugin.tsx @@ -49,7 +49,7 @@ class XyVisualizationPlugin { expressions.registerFunction(() => layerConfig); expressions.registerFunction(() => xyChart); - expressions.registerRenderer(() => + expressions.registerRenderer( getXyChartRenderer({ formatFactory, timeZone: getTimeZone(getUiSettingsClient()), diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts index 6dcd19f1493f2..b49e6fa6b4b6f 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts @@ -6,7 +6,7 @@ import { Position } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, ArgumentType } from 'src/plugins/expressions/common'; +import { ArgumentType, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import chartAreaSVG from '../assets/chart_area.svg'; import chartAreaStackedSVG from '../assets/chart_area_stacked.svg'; import chartBarSVG from '../assets/chart_bar.svg'; @@ -24,7 +24,7 @@ export interface LegendConfig { type LegendConfigResult = LegendConfig & { type: 'lens_xy_legendConfig' }; -export const legendConfig: ExpressionFunction< +export const legendConfig: ExpressionFunctionDefinition< 'lens_xy_legendConfig', null, LegendConfig, @@ -34,9 +34,7 @@ export const legendConfig: ExpressionFunction< aliases: [], type: 'lens_xy_legendConfig', help: `Configure the xy chart's legend`, - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { isVisible: { types: ['boolean'], @@ -52,7 +50,7 @@ export const legendConfig: ExpressionFunction< }), }, }, - fn: function fn(_context: unknown, args: LegendConfig) { + fn: function fn(input: unknown, args: LegendConfig) { return { type: 'lens_xy_legendConfig', ...args, @@ -89,14 +87,17 @@ export interface XConfig extends AxisConfig { type XConfigResult = XConfig & { type: 'lens_xy_xConfig' }; -export const xConfig: ExpressionFunction<'lens_xy_xConfig', null, XConfig, XConfigResult> = { +export const xConfig: ExpressionFunctionDefinition< + 'lens_xy_xConfig', + null, + XConfig, + XConfigResult +> = { name: 'lens_xy_xConfig', aliases: [], type: 'lens_xy_xConfig', help: `Configure the xy chart's x axis`, - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { ...axisConfig, accessor: { @@ -104,7 +105,7 @@ export const xConfig: ExpressionFunction<'lens_xy_xConfig', null, XConfig, XConf help: 'The column to display on the x axis.', }, }, - fn: function fn(_context: unknown, args: XConfig) { + fn: function fn(input: unknown, args: XConfig) { return { type: 'lens_xy_xConfig', ...args, @@ -114,7 +115,7 @@ export const xConfig: ExpressionFunction<'lens_xy_xConfig', null, XConfig, XConf type LayerConfigResult = LayerArgs & { type: 'lens_xy_layer' }; -export const layerConfig: ExpressionFunction< +export const layerConfig: ExpressionFunctionDefinition< 'lens_xy_layer', null, LayerArgs, @@ -124,9 +125,7 @@ export const layerConfig: ExpressionFunction< aliases: [], type: 'lens_xy_layer', help: `Configure a layer in the xy chart`, - context: { - types: ['null'], - }, + inputTypes: ['null'], args: { ...axisConfig, layerId: { @@ -172,7 +171,7 @@ export const layerConfig: ExpressionFunction< help: 'JSON key-value pairs of column ID to label', }, }, - fn: function fn(_context: unknown, args: LayerArgs) { + fn: function fn(input: unknown, args: LayerArgs) { return { type: 'lens_xy_layer', ...args, @@ -209,7 +208,7 @@ export type LayerArgs = LayerConfig & { export interface XYArgs { xTitle: string; yTitle: string; - legend: LegendConfig; + legend: LegendConfig & { type: 'lens_xy_legendConfig' }; layers: LayerArgs[]; } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx index 878db1fe9a458..daedb30db3f3e 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx @@ -11,6 +11,7 @@ import { LensMultiTable } from '../types'; import React from 'react'; import { shallow } from 'enzyme'; import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './types'; +import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; function sampleArgs() { const data: LensMultiTable = { @@ -40,6 +41,7 @@ function sampleArgs() { xTitle: '', yTitle: '', legend: { + type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top, }, @@ -69,7 +71,9 @@ describe('xy_expression', () => { position: Position.Left, }; - expect(legendConfig.fn(null, args, {})).toEqual({ + const result = legendConfig.fn(null, args, createMockExecutionContext()); + + expect(result).toEqual({ type: 'lens_xy_legendConfig', ...args, }); @@ -87,7 +91,9 @@ describe('xy_expression', () => { isHistogram: false, }; - expect(layerConfig.fn(null, args, {})).toEqual({ + const result = layerConfig.fn(null, args, createMockExecutionContext()); + + expect(result).toEqual({ type: 'lens_xy_layer', ...args, }); @@ -97,8 +103,9 @@ describe('xy_expression', () => { describe('xyChart', () => { test('it renders with the specified data and args', () => { const { data, args } = sampleArgs(); + const result = xyChart.fn(data, args, createMockExecutionContext()); - expect(xyChart.fn(data, args, {})).toEqual({ + expect(result).toEqual({ type: 'render', as: 'lens_xy_chart_renderer', value: { data, args }, diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 32c1ace5b1770..c62a8288d6655 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -18,10 +18,11 @@ import { } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { - ExpressionFunction, KibanaDatatable, IInterpreterRenderHandlers, - IInterpreterRenderFunction, + ExpressionRenderDefinition, + ExpressionFunctionDefinition, + ExpressionValueSearchContext, } from 'src/plugins/expressions/public'; import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -52,9 +53,15 @@ type XYChartRenderProps = XYChartProps & { timeZone: string; }; -export const xyChart: ExpressionFunction<'lens_xy_chart', LensMultiTable, XYArgs, XYRender> = ({ +export const xyChart: ExpressionFunctionDefinition< + 'lens_xy_chart', + LensMultiTable | ExpressionValueSearchContext | null, + XYArgs, + XYRender +> = { name: 'lens_xy_chart', type: 'render', + inputTypes: ['lens_multitable', 'kibana_context', 'null'], help: i18n.translate('xpack.lens.xyChart.help', { defaultMessage: 'An X/Y chart', }), @@ -74,14 +81,12 @@ export const xyChart: ExpressionFunction<'lens_xy_chart', LensMultiTable, XYArgs }), }, layers: { - types: ['lens_xy_layer'], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + types: ['lens_xy_layer'] as any, help: 'Layers of visual series', multi: true, }, }, - context: { - types: ['lens_multitable', 'kibana_context', 'null'], - }, fn(data: LensMultiTable, args: XYArgs) { return { type: 'render', @@ -92,19 +97,18 @@ export const xyChart: ExpressionFunction<'lens_xy_chart', LensMultiTable, XYArgs }, }; }, - // TODO the typings currently don't support custom type args. As soon as they do, this can be removed -} as unknown) as ExpressionFunction<'lens_xy_chart', LensMultiTable, XYArgs, XYRender>; +}; export const getXyChartRenderer = (dependencies: { formatFactory: FormatFactory; timeZone: string; -}): IInterpreterRenderFunction => ({ +}): ExpressionRenderDefinition => ({ name: 'lens_xy_chart_renderer', displayName: 'XY chart', help: i18n.translate('xpack.lens.xyChart.renderer.help', { defaultMessage: 'X/Y chart renderer', }), - validate: () => {}, + validate: () => undefined, reuseDomNode: true, render: (domNode: Element, config: XYChartProps, handlers: IInterpreterRenderHandlers) => { handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); diff --git a/x-pack/plugins/canvas/server/collectors/collector_helpers.ts b/x-pack/plugins/canvas/server/collectors/collector_helpers.ts index 784042fb4d94d..73de691dae05f 100644 --- a/x-pack/plugins/canvas/server/collectors/collector_helpers.ts +++ b/x-pack/plugins/canvas/server/collectors/collector_helpers.ts @@ -9,25 +9,30 @@ * @param cb: callback to do something with a function that has been found */ -import { ExpressionAST, ExpressionArgAST } from '../../types'; +import { + ExpressionAstExpression, + ExpressionAstNode, +} from '../../../../../src/plugins/expressions/common'; -function isExpression(maybeExpression: ExpressionArgAST): maybeExpression is ExpressionAST { - return typeof maybeExpression === 'object'; +function isExpression( + maybeExpression: ExpressionAstNode +): maybeExpression is ExpressionAstExpression { + return typeof maybeExpression === 'object' && maybeExpression.type === 'expression'; } -export function collectFns(ast: ExpressionArgAST, cb: (functionName: string) => void) { - if (isExpression(ast)) { - ast.chain.forEach(({ function: cFunction, arguments: cArguments }) => { - cb(cFunction); +export function collectFns(ast: ExpressionAstNode, cb: (functionName: string) => void) { + if (!isExpression(ast)) return; - // recurse the arguments and update the set along the way - Object.keys(cArguments).forEach(argName => { - cArguments[argName].forEach(subAst => { - if (subAst != null) { - collectFns(subAst, cb); - } - }); + ast.chain.forEach(({ function: cFunction, arguments: cArguments }) => { + cb(cFunction); + + // recurse the arguments and update the set along the way + Object.keys(cArguments).forEach(argName => { + cArguments[argName].forEach(subAst => { + if (subAst != null) { + collectFns(subAst, cb); + } }); }); - } + }); } diff --git a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts index ec735e2558d54..00f4687c2d802 100644 --- a/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/custom_element_collector.ts @@ -6,15 +6,14 @@ import { SearchParams } from 'elasticsearch'; import { get } from 'lodash'; -import { fromExpression } from '@kbn/interpreter/common'; import { SearchResponse } from 'elasticsearch'; import { collectFns } from './collector_helpers'; import { - ExpressionAST, TelemetryCollector, TelemetryCustomElement, TelemetryCustomElementDocument, } from '../../types'; +import { parseExpression } from '../../../../../src/plugins/expressions/common'; const CUSTOM_ELEMENT_TYPE = 'canvas-element'; interface CustomElementSearch { @@ -80,7 +79,7 @@ export function summarizeCustomElements( parsedContents.map(contents => { contents.selectedNodes.map(node => { - const ast: ExpressionAST = fromExpression(node.expression) as ExpressionAST; // TODO: Remove once fromExpression is properly typed + const ast = parseExpression(node.expression); collectFns(ast, (cFunction: string) => { functionSet.add(cFunction); }); diff --git a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts index e5984c09c4bd4..565020d79bff6 100644 --- a/x-pack/plugins/canvas/server/collectors/workpad_collector.ts +++ b/x-pack/plugins/canvas/server/collectors/workpad_collector.ts @@ -6,10 +6,10 @@ import { SearchParams, SearchResponse } from 'elasticsearch'; import { sum as arraySum, min as arrayMin, max as arrayMax, get } from 'lodash'; -import { fromExpression } from '@kbn/interpreter/common'; import { CANVAS_TYPE } from '../../../../legacy/plugins/canvas/common/lib/constants'; import { collectFns } from './collector_helpers'; -import { ExpressionAST, TelemetryCollector, CanvasWorkpad } from '../../types'; +import { TelemetryCollector, CanvasWorkpad } from '../../types'; +import { parseExpression } from '../../../../../src/plugins/expressions/common'; interface WorkpadSearch { [CANVAS_TYPE]: CanvasWorkpad; @@ -73,7 +73,7 @@ export function summarizeWorkpads(workpadDocs: CanvasWorkpad[]): WorkpadTelemetr ); const functionCounts = workpad.pages.reduce((accum, page) => { return page.elements.map(element => { - const ast: ExpressionAST = fromExpression(element.expression) as ExpressionAST; // TODO: Remove once fromExpression is properly typed + const ast = parseExpression(element.expression); collectFns(ast, cFunction => { functionSet.add(cFunction); });