From 975e361d502c9c23a9d64fdf99a43407bf7955e8 Mon Sep 17 00:00:00 2001 From: Michael Dokolin Date: Thu, 1 Jul 2021 22:48:47 +0200 Subject: [PATCH] [Expressions] Update expressions public API to expose partial results support (#102403) * Add partial result flag to the execution result * Update expressions plugin run method to return observable * Update data getter in the execution contract to return observable * Update the expression loader to take into account the partial results flag --- ...-expressions-public.execution.interpret.md | 4 +- ...in-plugins-expressions-public.execution.md | 4 +- ...ins-expressions-public.execution.result.md | 2 +- ...gins-expressions-public.execution.start.md | 4 +- ...gins-expressions-public.execution.state.md | 2 +- ...ssions-public.executioncontract.getdata.md | 2 +- ...ns-expressions-public.executioncontract.md | 2 +- ...plugins-expressions-public.executor.run.md | 4 +- ...ressions-public.expressionsservicestart.md | 2 +- ...ions-public.expressionsservicestart.run.md | 2 +- ...ressions-public.iexpressionloaderparams.md | 1 + ...-public.iexpressionloaderparams.partial.md | 11 + ...ons-public.reactexpressionrendererprops.md | 2 +- ...ic.reactexpressionrendererprops.ondata_.md | 2 +- ...-expressions-server.execution.interpret.md | 4 +- ...in-plugins-expressions-server.execution.md | 4 +- ...ins-expressions-server.execution.result.md | 2 +- ...gins-expressions-server.execution.start.md | 4 +- ...gins-expressions-server.execution.state.md | 2 +- ...plugins-expressions-server.executor.run.md | 4 +- .../public/run_expressions.tsx | 19 +- .../execution/execution.abortion.test.ts | 13 +- .../common/execution/execution.test.ts | 235 +++++++++--------- .../expressions/common/execution/execution.ts | 78 ++++-- .../execution/execution_contract.test.ts | 17 +- .../common/execution/execution_contract.ts | 24 +- .../expressions/common/executor/executor.ts | 4 +- .../specs/tests/var_set.test.ts | 2 +- .../service/expressions_services.test.ts | 2 +- .../common/service/expressions_services.ts | 10 +- src/plugins/expressions/public/loader.test.ts | 26 +- src/plugins/expressions/public/loader.ts | 60 ++--- src/plugins/expressions/public/plugin.test.ts | 6 +- src/plugins/expressions/public/public.api.md | 19 +- .../public/react_expression_renderer.test.tsx | 6 +- .../public/react_expression_renderer.tsx | 10 +- src/plugins/expressions/public/types/index.ts | 1 + src/plugins/expressions/server/plugin.test.ts | 6 +- src/plugins/expressions/server/server.api.md | 11 +- .../components/vis_editor_visualization.js | 5 +- .../public/app/components/main.tsx | 6 +- .../kbn_tp_run_pipeline/server/plugin.ts | 12 +- .../canvas/public/lib/run_interpreter.ts | 15 +- 43 files changed, 370 insertions(+), 281 deletions(-) create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md index 46934e119aee0..434f2660e7eff 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md @@ -7,7 +7,7 @@ Signature: ```typescript -interpret(ast: ExpressionAstNode, input: T): Observable; +interpret(ast: ExpressionAstNode, input: T): Observable>; ``` ## Parameters @@ -19,5 +19,5 @@ interpret(ast: ExpressionAstNode, input: T): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md index 30fe9f497f7ee..edaf1c9a9ce9e 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md @@ -26,8 +26,8 @@ export declare class Executionstring | | | [input](./kibana-plugin-plugins-expressions-public.execution.input.md) | | Input | 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. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.execution.inspectoradapters.md) | | InspectorAdapters | | -| [result](./kibana-plugin-plugins-expressions-public.execution.result.md) | | Observable<Output | ExpressionValueError> | Future that tracks result or error of this execution. | -| [state](./kibana-plugin-plugins-expressions-public.execution.state.md) | | ExecutionContainer<Output | ExpressionValueError> | Dynamic state of the execution. | +| [result](./kibana-plugin-plugins-expressions-public.execution.result.md) | | Observable<ExecutionResult<Output | ExpressionValueError>> | Future that tracks result or error of this execution. | +| [state](./kibana-plugin-plugins-expressions-public.execution.state.md) | | ExecutionContainer<ExecutionResult<Output | ExpressionValueError>> | Dynamic state of the execution. | ## Methods diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md index 94f60ccee0f00..a386302a62805 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md @@ -9,5 +9,5 @@ Future that tracks result or error of this execution. Signature: ```typescript -readonly result: Observable; +readonly result: Observable>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md index 64cf81b376948..352226da6d72a 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md @@ -11,7 +11,7 @@ N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons Signature: ```typescript -start(input?: Input): Observable; +start(input?: Input): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ start(input?: Input): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md index ca8b57b760f29..61aa0cf4c5b5d 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.state.md @@ -9,5 +9,5 @@ Dynamic state of the execution. Signature: ```typescript -readonly state: ExecutionContainer; +readonly state: ExecutionContainer>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md index dcd96cf5767bf..852e1f58cc6f3 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.getdata.md @@ -9,5 +9,5 @@ Returns the final output of expression, if any error happens still wraps that er Signature: ```typescript -getData: () => Promise; +getData: () => Observable>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md index f2c050bbfe0ba..0ac776e4be2b8 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontract.md @@ -25,7 +25,7 @@ export declare class ExecutionContract() => void | Cancel the execution of the expression. This will set abort signal (available in execution context) to aborted state, letting expression functions to stop their execution. | | [execution](./kibana-plugin-plugins-expressions-public.executioncontract.execution.md) | | Execution<Input, Output, InspectorAdapters> | | | [getAst](./kibana-plugin-plugins-expressions-public.executioncontract.getast.md) | | () => ExpressionAstExpression | Get AST used to execute the expression. | -| [getData](./kibana-plugin-plugins-expressions-public.executioncontract.getdata.md) | | () => Promise<Output | ExpressionValueError> | Returns the final output of expression, if any error happens still wraps that error into ExpressionValueError type and returns that. This function never throws. | +| [getData](./kibana-plugin-plugins-expressions-public.executioncontract.getdata.md) | | () => Observable<ExecutionResult<Output | ExpressionValueError>> | Returns the final output of expression, if any error happens still wraps that error into ExpressionValueError type and returns that. This function never throws. | | [getExpression](./kibana-plugin-plugins-expressions-public.executioncontract.getexpression.md) | | () => string | Get string representation of the expression. Returns the original string if execution was started from a string. If execution was started from an AST this method returns a string generated from AST. | | [inspect](./kibana-plugin-plugins-expressions-public.executioncontract.inspect.md) | | () => InspectorAdapters | Get Inspector adapters provided to all functions of expression through execution context. | | [isPending](./kibana-plugin-plugins-expressions-public.executioncontract.ispending.md) | | boolean | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md index 307e6b6bcd5c8..4eefc63d714d1 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md @@ -9,7 +9,7 @@ Execute expression and return result. Signature: ```typescript -run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; +run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ run(ast: string | ExpressionAstExpression, input: Input, params?: Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md index 6b678fc4fbc26..9821f0f921e4d 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.md @@ -21,7 +21,7 @@ export interface ExpressionsServiceStart | [getFunction](./kibana-plugin-plugins-expressions-public.expressionsservicestart.getfunction.md) | (name: string) => ReturnType<Executor['getFunction']> | Get a registered ExpressionFunction by its name, which was registered using the registerFunction method. The returned ExpressionFunction instance is an internal representation of the function in Expressions service - do not mutate that object. | | [getRenderer](./kibana-plugin-plugins-expressions-public.expressionsservicestart.getrenderer.md) | (name: string) => ReturnType<ExpressionRendererRegistry['get']> | Get a registered ExpressionRenderer by its name, which was registered using the registerRenderer method. The returned ExpressionRenderer instance is an internal representation of the renderer in Expressions service - do not mutate that object. | | [getType](./kibana-plugin-plugins-expressions-public.expressionsservicestart.gettype.md) | (name: string) => ReturnType<Executor['getType']> | Get a registered ExpressionType by its name, which was registered using the registerType method. The returned ExpressionType instance is an internal representation of the type in Expressions service - do not mutate that object. | -| [run](./kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md) | <Input, Output>(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Promise<Output> | 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. +| [run](./kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md) | <Input, Output>(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Observable<ExecutionResult<Output | ExpressionValueError>> | 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); diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md index 9efca0011174c..0838d640d54e4 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservicestart.run.md @@ -24,5 +24,5 @@ expressions.run('...', null, { elasticsearchClient }); Signature: ```typescript -run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Promise; +run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Observable>; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md index 4ef1225ae0d7e..69f9d380422b6 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md @@ -22,6 +22,7 @@ export interface IExpressionLoaderParams | [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md) | ExpressionRenderHandlerParams['hasCompatibleActions'] | | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.inspectoradapters.md) | Adapters | | | [onRenderError](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.onrendererror.md) | RenderErrorHandlerFnType | | +| [partial](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md) | boolean | | | [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | RenderMode | | | [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | SerializableState | | | [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | string | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md new file mode 100644 index 0000000000000..84c42c3f59f26 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) > [partial](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md) + +## IExpressionLoaderParams.partial property + +Signature: + +```typescript +partial?: boolean; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md index 92ea071b23dfc..d38027753a6ff 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md @@ -18,7 +18,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams | [dataAttrs](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.dataattrs.md) | string[] | | | [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) | number | | | [expression](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.expression.md) | string | ExpressionAstExpression | | -| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void | | +| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters, partial?: boolean) => void | | | [onEvent](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.onevent.md) | (event: ExpressionRendererEvent) => void | | | [padding](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.padding.md) | 'xs' | 's' | 'm' | 'l' | 'xl' | | | [reload$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.reload_.md) | Observable<unknown> | An observable which can be used to re-run the expression without destroying the component | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md index 05ddb0b13a5be..47559d0f7653c 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md @@ -7,5 +7,5 @@ Signature: ```typescript -onData$?: (data: TData, adapters?: TInspectorAdapters) => void; +onData$?: (data: TData, adapters?: TInspectorAdapters, partial?: boolean) => void; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md index 936e98be589a3..99804dd20841d 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md @@ -7,7 +7,7 @@ Signature: ```typescript -interpret(ast: ExpressionAstNode, input: T): Observable; +interpret(ast: ExpressionAstNode, input: T): Observable>; ``` ## Parameters @@ -19,5 +19,5 @@ interpret(ast: ExpressionAstNode, input: T): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md index a4e324eef6674..47963e5e5ef46 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md @@ -26,8 +26,8 @@ export declare class Executionstring | | | [input](./kibana-plugin-plugins-expressions-server.execution.input.md) | | Input | 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. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-server.execution.inspectoradapters.md) | | InspectorAdapters | | -| [result](./kibana-plugin-plugins-expressions-server.execution.result.md) | | Observable<Output | ExpressionValueError> | Future that tracks result or error of this execution. | -| [state](./kibana-plugin-plugins-expressions-server.execution.state.md) | | ExecutionContainer<Output | ExpressionValueError> | Dynamic state of the execution. | +| [result](./kibana-plugin-plugins-expressions-server.execution.result.md) | | Observable<ExecutionResult<Output | ExpressionValueError>> | Future that tracks result or error of this execution. | +| [state](./kibana-plugin-plugins-expressions-server.execution.state.md) | | ExecutionContainer<ExecutionResult<Output | ExpressionValueError>> | Dynamic state of the execution. | ## Methods diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md index 06cf047ac4160..b3baac5be2fa3 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md @@ -9,5 +9,5 @@ Future that tracks result or error of this execution. Signature: ```typescript -readonly result: Observable; +readonly result: Observable>; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md index dd0456ac09950..0eef7013cb3c6 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md @@ -11,7 +11,7 @@ N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons Signature: ```typescript -start(input?: Input): Observable; +start(input?: Input): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ start(input?: Input): Observable; Returns: -`Observable` +`Observable>` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md index 41e7e693a1da4..b7c26e9dee85a 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.state.md @@ -9,5 +9,5 @@ Dynamic state of the execution. Signature: ```typescript -readonly state: ExecutionContainer; +readonly state: ExecutionContainer>; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md index 2ab534eac2f3a..7b169d05dc31d 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md @@ -9,7 +9,7 @@ Execute expression and return result. Signature: ```typescript -run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; +run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; ``` ## Parameters @@ -22,5 +22,5 @@ run(ast: string | ExpressionAstExpression, input: Input, params?: Returns: -`Observable` +`Observable>` diff --git a/examples/expressions_explorer/public/run_expressions.tsx b/examples/expressions_explorer/public/run_expressions.tsx index 05749a0e89735..a635fab7ec8ae 100644 --- a/examples/expressions_explorer/public/run_expressions.tsx +++ b/examples/expressions_explorer/public/run_expressions.tsx @@ -7,6 +7,7 @@ */ import React, { useState, useEffect, useMemo } from 'react'; +import { pluck } from 'rxjs/operators'; import { EuiCodeBlock, EuiFlexItem, @@ -35,7 +36,7 @@ interface Props { export function RunExpressionsExample({ expressions, inspector }: Props) { const [expression, updateExpression] = useState('markdown "## expressions explorer"'); - const [result, updateResult] = useState({}); + const [result, updateResult] = useState({}); const expressionChanged = (value: string) => { updateExpression(value); @@ -49,17 +50,13 @@ export function RunExpressionsExample({ expressions, inspector }: Props) { ); useEffect(() => { - const runExpression = async () => { - const execution = expressions.execute(expression, null, { - debug: true, - inspectorAdapters, - }); + const execution = expressions.execute(expression, null, { + debug: true, + inspectorAdapters, + }); + const subscription = execution.getData().pipe(pluck('result')).subscribe(updateResult); - const data: any = await execution.getData(); - updateResult(data); - }; - - runExpression(); + return () => subscription.unsubscribe(); }, [expression, expressions, inspectorAdapters]); return ( diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 514086e9b19ee..798558ba7ffb6 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { first } from 'rxjs/operators'; import { waitFor } from '@testing-library/react'; import { Execution } from './execution'; import { parseExpression } from '../ast'; @@ -40,9 +39,9 @@ describe('Execution abortion tests', () => { execution.start(); execution.cancel(); - const result = await execution.result.pipe(first()).toPromise(); + const result = await execution.result.toPromise(); - expect(result).toMatchObject({ + expect(result).toHaveProperty('result', { type: 'error', error: { message: 'The expression was aborted.', @@ -58,9 +57,9 @@ describe('Execution abortion tests', () => { jest.advanceTimersByTime(100); execution.cancel(); - const result = await execution.result.pipe(first()).toPromise(); + const result = await execution.result.toPromise(); - expect(result).toMatchObject({ + expect(result).toHaveProperty('result', { type: 'error', error: { message: 'The expression was aborted.', @@ -76,7 +75,7 @@ describe('Execution abortion tests', () => { execution.start(); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); execution.cancel(); @@ -136,7 +135,7 @@ describe('Execution abortion tests', () => { await waitFor(() => expect(started).toHaveBeenCalledTimes(1)); execution.cancel(); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toMatchObject({ type: 'error', error: { diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index feff425cc48ed..8c6f457105d42 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -7,7 +7,7 @@ */ import { of } from 'rxjs'; -import { first, scan } from 'rxjs/operators'; +import { scan } from 'rxjs/operators'; import { TestScheduler } from 'rxjs/testing'; import { Execution } from './execution'; import { parseExpression, ExpressionAstExpression } from '../ast'; @@ -45,7 +45,7 @@ const run = async ( ) => { const execution = createExecution(expression, context); execution.start(input); - return await execution.result.pipe(first()).toPromise(); + return await execution.result.toPromise(); }; let testScheduler: TestScheduler; @@ -84,7 +84,7 @@ describe('Execution', () => { /* eslint-enable no-console */ execution.start(123); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toBe(123); expect(spy).toHaveBeenCalledTimes(1); @@ -102,7 +102,7 @@ describe('Execution', () => { value: -1, }); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -117,7 +117,7 @@ describe('Execution', () => { value: 0, }); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -131,7 +131,7 @@ describe('Execution', () => { // Below 1 is cast to { type: 'num', value: 1 }. execution.start(1); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -143,7 +143,7 @@ describe('Execution', () => { const execution = createExecution('add val=1'); execution.start(Promise.resolve(1)); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -155,7 +155,7 @@ describe('Execution', () => { const execution = createExecution('add val=1'); execution.start(of(1)); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -167,14 +167,14 @@ describe('Execution', () => { const execution = createExecution('add val=1'); testScheduler.run(({ cold, expectObservable }) => { - const input = cold(' -a--b-c-', { a: 1, b: 2, c: 3 }); + const input = cold(' -a--b-c|', { a: 1, b: 2, c: 3 }); const subscription = ' ---^---!'; - const expected = ' ---ab-c-'; + const expected = ' ---ab-c|'; expectObservable(execution.start(input), subscription).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { type: 'num', value: 3 }, - c: { type: 'num', value: 4 }, + a: { partial: false, result: { type: 'num', value: 2 } }, + b: { partial: false, result: { type: 'num', value: 3 } }, + c: { partial: false, result: { type: 'num', value: 4 } }, }); }); }); @@ -187,21 +187,21 @@ describe('Execution', () => { const expected = ' -a-#'; expectObservable(execution.start(input)).toBe(expected, { - a: { type: 'num', value: 2 }, + a: { partial: false, result: { type: 'num', value: 2 } }, }); }); }); - test('does not complete when input completes', () => { + test('completes when input completes', () => { const execution = createExecution('add val=1'); testScheduler.run(({ cold, expectObservable }) => { const input = cold('-a-b|', { a: 1, b: 2 }); - const expected = ' -a-b-'; + const expected = ' -a-b|'; expectObservable(execution.start(input)).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { type: 'num', value: 3 }, + a: expect.objectContaining({ result: { type: 'num', value: 2 } }), + b: expect.objectContaining({ result: { type: 'num', value: 3 } }), }); }); }); @@ -216,9 +216,9 @@ describe('Execution', () => { const input = items.pipe(scan((result, value) => [...result, value], new Array())); expectObservable(execution.start(input), subscription).toBe(expected, { - a: { type: 'num', value: 1 }, - b: { type: 'num', value: 3 }, - c: { type: 'num', value: 6 }, + a: { partial: false, result: { type: 'num', value: 1 } }, + b: { partial: false, result: { type: 'num', value: 3 } }, + c: { partial: false, result: { type: 'num', value: 6 } }, }); }); }); @@ -263,44 +263,51 @@ describe('Execution', () => { describe('execution context', () => { test('context.variables is an object', async () => { const { result } = (await run('introspectContext key="variables"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.types is an object', async () => { const { result } = (await run('introspectContext key="types"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.abortSignal is an object', async () => { const { result } = (await run('introspectContext key="abortSignal"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.inspectorAdapters is an object', async () => { const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; - expect(typeof result).toBe('object'); + + expect(result).toHaveProperty('result', expect.any(Object)); }); test('context.getKibanaRequest is a function if provided', async () => { const { result } = (await run('introspectContext key="getKibanaRequest"', { kibanaRequest: {}, })) as any; - expect(typeof result).toBe('function'); + + expect(result).toHaveProperty('result', expect.any(Function)); }); test('context.getKibanaRequest is undefined if not provided', async () => { const { result } = (await run('introspectContext key="getKibanaRequest"')) as any; - expect(typeof result).toBe('undefined'); + + expect(result).toHaveProperty('result', undefined); }); test('unknown context key is undefined', async () => { const { result } = (await run('introspectContext key="foo"')) as any; - expect(typeof result).toBe('undefined'); + + expect(result).toHaveProperty('result', undefined); }); test('can set context variables', async () => { const variables = { foo: 'bar' }; - const result = await run('var name="foo"', { variables }); + const { result } = await run('var name="foo"', { variables }); expect(result).toBe('bar'); }); }); @@ -308,10 +315,13 @@ describe('Execution', () => { describe('inspector adapters', () => { test('by default, "tables" and "requests" inspector adapters are available', async () => { const { result } = (await run('introspectContext key="inspectorAdapters"')) as any; - expect(result).toMatchObject({ - tables: expect.any(Object), - requests: expect.any(Object), - }); + expect(result).toHaveProperty( + 'result', + expect.objectContaining({ + tables: expect.any(Object), + requests: expect.any(Object), + }) + ); }); test('can set custom inspector adapters', async () => { @@ -319,7 +329,7 @@ describe('Execution', () => { const { result } = (await run('introspectContext key="inspectorAdapters"', { inspectorAdapters, })) as any; - expect(result).toBe(inspectorAdapters); + expect(result).toHaveProperty('result', inspectorAdapters); }); test('can access custom inspector adapters on Execution object', async () => { @@ -335,8 +345,7 @@ describe('Execution', () => { 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); + expect(result).toHaveProperty('result.aborted', false); }); }); @@ -348,7 +357,7 @@ describe('Execution', () => { value: 0, }); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -357,8 +366,8 @@ describe('Execution', () => { }); test('can execute async functions', async () => { - const res = await run('sleep 10 | sleep 10'); - expect(res).toBe(null); + const { result } = await run('sleep 10 | sleep 10'); + expect(result).toBe(null); }); test('result is undefined until execution completes', async () => { @@ -374,7 +383,7 @@ describe('Execution', () => { jest.advanceTimersByTime(10); await new Promise(process.nextTick); - expect(execution.state.get().result).toBe(null); + expect(execution.state.get().result).toHaveProperty('result', null); jest.useRealTimers(); }); @@ -382,7 +391,7 @@ describe('Execution', () => { test('handles functions returning observables', () => { testScheduler.run(({ cold, expectObservable }) => { const arg = cold(' -a-b-c|', { a: 1, b: 2, c: 3 }); - const expected = ' -a-b-c-'; + const expected = ' -a-b-c|'; const observable: ExpressionFunctionDefinition<'observable', any, {}, any> = { name: 'observable', args: {}, @@ -394,14 +403,18 @@ describe('Execution', () => { const result = executor.run('observable', null, {}); - expectObservable(result).toBe(expected, { a: 1, b: 2, c: 3 }); + expectObservable(result).toBe(expected, { + a: { result: 1, partial: true }, + b: { result: 2, partial: true }, + c: { result: 3, partial: false }, + }); }); }); }); describe('when function throws', () => { test('error is reported in output object', async () => { - const result = await run('error "foobar"'); + const { result } = await run('error "foobar"'); expect(result).toMatchObject({ type: 'error', @@ -409,7 +422,7 @@ describe('Execution', () => { }); test('error message is prefixed with function name', async () => { - const result = await run('error "foobar"'); + const { result } = await run('error "foobar"'); expect(result).toMatchObject({ error: { @@ -419,7 +432,7 @@ describe('Execution', () => { }); test('returns error of the first function that throws', async () => { - const result = await run('error "foo" | error "bar"'); + const { result } = await run('error "foo" | error "bar"'); expect(result).toMatchObject({ error: { @@ -432,15 +445,18 @@ describe('Execution', () => { const execution = await createExecution('error "foo"'); execution.start(null); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toMatchObject({ type: 'error', }); expect(execution.state.get().state).toBe('result'); - expect(execution.state.get().result).toMatchObject({ - type: 'error', - }); + expect(execution.state.get().result).toHaveProperty( + 'result', + expect.objectContaining({ + type: 'error', + }) + ); }); test('does not execute remaining functions in pipeline', async () => { @@ -453,7 +469,7 @@ describe('Execution', () => { const executor = createUnitTestExecutor(); executor.registerFunction(spy); - await executor.run('error "..." | spy', null).pipe(first()).toPromise(); + await executor.run('error "..." | spy', null).toPromise(); expect(spy.fn).toHaveBeenCalledTimes(0); }); @@ -483,21 +499,21 @@ describe('Execution', () => { test('execution state is "result" when execution successfully completes', async () => { const execution = createExecution('sleep 1'); execution.start(null); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); 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.pipe(first()).toPromise(); + await execution.result.toPromise(); 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); + const { result } = await run('add val={add 5 | access "value"}', {}, null); expect(result).toMatchObject({ type: 'num', @@ -506,7 +522,7 @@ describe('Execution', () => { }); test('can use global variables', async () => { - const result = await run( + const { result } = await run( 'add val={var foo}', { variables: { @@ -523,7 +539,7 @@ describe('Execution', () => { }); test('can modify global variables', async () => { - const result = await run( + const { result } = await run( 'add val={var_set name=foo value=66 | var bar} | var foo', { variables: { @@ -547,18 +563,20 @@ describe('Execution', () => { const executor = createUnitTestExecutor(); executor.registerFunction(observable); - expect( - executor.run('add val={observable}', 1, {}).pipe(first()).toPromise() - ).resolves.toEqual({ - type: 'num', - value: 2, - }); + expect(executor.run('add val={observable}', 1, {}).toPromise()).resolves.toEqual( + expect.objectContaining({ + result: { + type: 'num', + value: 2, + }, + }) + ); }); test('supports observables in arguments emitting multiple values', () => { testScheduler.run(({ cold, expectObservable }) => { - const arg = cold('-a-b-c-', { a: 1, b: 2, c: 3 }); - const expected = '-a-b-c-'; + const arg = cold('-a-b-c|', { a: 1, b: 2, c: 3 }); + const expected = '-a-b-c|'; const observable = { name: 'observable', args: {}, @@ -571,18 +589,18 @@ describe('Execution', () => { const result = executor.run('add val={observable}', 1, {}); expectObservable(result).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { type: 'num', value: 3 }, - c: { type: 'num', value: 4 }, + a: { partial: true, result: { type: 'num', value: 2 } }, + b: { partial: true, result: { type: 'num', value: 3 } }, + c: { partial: false, result: { type: 'num', value: 4 } }, }); }); }); test('combines multiple observables in arguments', () => { testScheduler.run(({ cold, expectObservable }) => { - const arg1 = cold('--ab-c-', { a: 0, b: 2, c: 4 }); - const arg2 = cold('-a--bc-', { a: 1, b: 3, c: 5 }); - const expected = ' --abc(de)-'; + const arg1 = cold('--ab-c---|', { a: 0, b: 2, c: 4 }); + const arg2 = cold('-a--bc---|', { a: 1, b: 3, c: 5 }); + const expected = ' --abc(de)|'; const observable1 = { name: 'observable1', args: {}, @@ -612,32 +630,11 @@ describe('Execution', () => { const result = executor.run('max val1={observable1} val2={observable2}', {}); expectObservable(result).toBe(expected, { - a: { type: 'num', value: 1 }, - b: { type: 'num', value: 2 }, - c: { type: 'num', value: 3 }, - d: { type: 'num', value: 4 }, - e: { type: 'num', value: 5 }, - }); - }); - }); - - test('does not complete when an argument completes', () => { - testScheduler.run(({ cold, expectObservable }) => { - const arg = cold('-a|', { a: 1 }); - const expected = '-a-'; - const observable = { - name: 'observable', - args: {}, - help: '', - fn: () => arg, - }; - const executor = createUnitTestExecutor(); - executor.registerFunction(observable); - - const result = executor.run('add val={observable}', 1, {}); - - expectObservable(result).toBe(expected, { - a: { type: 'num', value: 2 }, + a: { partial: true, result: { type: 'num', value: 1 } }, + b: { partial: true, result: { type: 'num', value: 2 } }, + c: { partial: true, result: { type: 'num', value: 3 } }, + d: { partial: true, result: { type: 'num', value: 4 } }, + e: { partial: false, result: { type: 'num', value: 5 } }, }); }); }); @@ -645,7 +642,7 @@ describe('Execution', () => { test('handles error in observable arguments', () => { testScheduler.run(({ cold, expectObservable }) => { const arg = cold('-a-#', { a: 1 }, new Error('some error')); - const expected = '-a-b'; + const expected = '-a-(b|)'; const observable = { name: 'observable', args: {}, @@ -658,13 +655,15 @@ describe('Execution', () => { const result = executor.run('add val={observable}', 1, {}); expectObservable(result).toBe(expected, { - a: { type: 'num', value: 2 }, - b: { - error: expect.objectContaining({ - message: '[add] > [observable] > some error', - }), - type: 'error', - }, + a: expect.objectContaining({ result: { type: 'num', value: 2 } }), + b: expect.objectContaining({ + result: { + error: expect.objectContaining({ + message: '[add] > [observable] > some error', + }), + type: 'error', + }, + }), }); }); }); @@ -685,7 +684,7 @@ describe('Execution', () => { }; const executor = createUnitTestExecutor(); executor.registerFunction(requiredArg); - const result = await executor.run('requiredArg', null, {}).pipe(first()).toPromise(); + const { result } = await executor.run('requiredArg', null, {}).toPromise(); expect(result).toMatchObject({ type: 'error', @@ -696,7 +695,7 @@ describe('Execution', () => { }); test('when required argument is missing and has alias, returns error', async () => { - const result = await run('var_set', {}); + const { result } = await run('var_set', {}); expect(result).toMatchObject({ type: 'error', @@ -711,7 +710,7 @@ describe('Execution', () => { test('can execute expression in debug mode', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -726,7 +725,7 @@ describe('Execution', () => { true ); execution.start(0); - const result = await execution.result.pipe(first()).toPromise(); + const { result } = await execution.result.toPromise(); expect(result).toEqual({ type: 'num', @@ -738,7 +737,7 @@ describe('Execution', () => { test('sets "success" flag on all functions to true', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(node.debug?.success).toBe(true); @@ -748,7 +747,7 @@ describe('Execution', () => { test('stores "fn" reference to the function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(node.debug?.fn).toBe('add'); @@ -758,7 +757,7 @@ describe('Execution', () => { test('saves duration it took to execute each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(typeof node.debug?.duration).toBe('number'); @@ -770,7 +769,7 @@ describe('Execution', () => { test('adds .debug field in expression AST on each executed function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); for (const node of execution.state.get().ast.chain) { expect(typeof node.debug).toBe('object'); @@ -781,7 +780,7 @@ describe('Execution', () => { test('stores input of each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast; @@ -799,7 +798,7 @@ describe('Execution', () => { test('stores output of each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast; @@ -824,7 +823,7 @@ describe('Execution', () => { true ); execution.start(-1); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast; @@ -847,7 +846,7 @@ describe('Execution', () => { true ); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const { chain } = execution.state.get().ast.chain[0].arguments .val[0] as ExpressionAstExpression; @@ -882,7 +881,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const node1 = execution.state.get().ast.chain[0]; const node2 = execution.state.get().ast.chain[1]; @@ -900,7 +899,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const node2 = execution.state.get().ast.chain[1]; @@ -921,7 +920,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result.pipe(first()).toPromise(); + await execution.result.toPromise(); const node2 = execution.state.get().ast.chain[1]; diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index b70f261ea4b20..47209c348257e 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -20,7 +20,7 @@ import { Observable, ReplaySubject, } from 'rxjs'; -import { catchError, finalize, map, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { catchError, finalize, map, pluck, shareReplay, switchMap, tap } from 'rxjs/operators'; import { Executor } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; @@ -45,6 +45,21 @@ import { ExpressionExecutionParams } from '../service'; import { TablesAdapter } from '../util/tables_adapter'; import { ExpressionsInspectorAdapter } from '../util/expressions_inspector_adapter'; +/** + * The result returned after an expression function execution. + */ +export interface ExecutionResult { + /** + * Partial result flag. + */ + partial: boolean; + + /** + * The expression function result. + */ + result: Output; +} + /** * AbortController is not available in Node until v15, so we * need to temporarily mock it for plugins using expressions @@ -91,7 +106,7 @@ export class Execution< /** * Dynamic state of the execution. */ - public readonly state: ExecutionContainer; + public readonly state: ExecutionContainer>; /** * Initial input of the execution. @@ -137,7 +152,7 @@ export class Execution< /** * Future that tracks result or error of this execution. */ - public readonly result: Observable; + public readonly result: Observable>; /** * Keeping track of any child executions @@ -174,7 +189,7 @@ export class Execution< this.expression = execution.expression || formatExpression(execution.ast!); const ast = execution.ast || parseExpression(this.expression); - this.state = createExecutionContainer({ + this.state = createExecutionContainer({ ...executor.state.get(), state: 'not-started', ast, @@ -201,12 +216,40 @@ export class Execution< }; this.result = this.input$.pipe( - switchMap((input) => this.race(this.invokeChain(this.state.get().ast.chain, input))), + switchMap((input) => + this.race(this.invokeChain(this.state.get().ast.chain, input)).pipe( + (source) => + new Observable>((subscriber) => { + let latest: ExecutionResult | undefined; + + subscriber.add( + source.subscribe({ + next: (result) => { + latest = { result, partial: true }; + subscriber.next(latest); + }, + error: (error) => subscriber.error(error), + complete: () => { + if (latest) { + latest.partial = false; + } + + subscriber.complete(); + }, + }) + ); + + subscriber.add(() => { + latest = undefined; + }); + }) + ) + ), catchError((error) => { if (this.abortController.signal.aborted) { this.childExecutions.forEach((childExecution) => childExecution.cancel()); - return of(createAbortErrorValue()); + return of({ result: createAbortErrorValue(), partial: false }); } return throwError(error); @@ -236,25 +279,20 @@ export class 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): Observable { + public start( + input: Input = null as any + ): Observable> { if (this.hasStarted) throw new Error('Execution already started.'); this.hasStarted = true; this.input = input; this.state.transitions.start(); if (isObservable(input)) { - // `input$` should never complete - input.subscribe( - (value) => this.input$.next(value), - (error) => this.input$.error(error) - ); + input.subscribe(this.input$); } else if (isPromise(input)) { - input.then( - (value) => this.input$.next(value), - (error) => this.input$.error(error) - ); + from(input).subscribe(this.input$); } else { - this.input$.next(input); + of(input).subscribe(this.input$); } return this.result; @@ -439,6 +477,7 @@ export class Execution< const resolveArgFns = mapValues(dealiasedArgAsts, (asts, argName) => asts.map((item) => (subInput = input) => this.interpret(item, subInput).pipe( + pluck('result'), map((output) => { if (isExpressionValueError(output)) { throw output.error; @@ -486,7 +525,7 @@ export class Execution< }); } - public interpret(ast: ExpressionAstNode, input: T): Observable { + public interpret(ast: ExpressionAstNode, input: T): Observable> { switch (getType(ast)) { case 'expression': const execution = this.execution.executor.createExecution( @@ -494,12 +533,13 @@ export class Execution< this.execution.params ); this.childExecutions.push(execution); + return execution.start(input); case 'string': case 'number': case 'null': case 'boolean': - return of(ast); + return of({ result: ast, partial: false }); default: return throwError(new Error(`Unknown AST object: ${JSON.stringify(ast)}`)); } diff --git a/src/plugins/expressions/common/execution/execution_contract.test.ts b/src/plugins/expressions/common/execution/execution_contract.test.ts index 99a5c80de3c46..de209f1dfb4a1 100644 --- a/src/plugins/expressions/common/execution/execution_contract.test.ts +++ b/src/plugins/expressions/common/execution/execution_contract.test.ts @@ -66,26 +66,23 @@ describe('ExecutionContract', () => { }); }); - test('can get error result of the expression execution', async () => { + test('can get error result of the expression execution', () => { const execution = createExecution('foo bar=123'); const contract = new ExecutionContract(execution); execution.start(); - const result = await contract.getData(); - - expect(result).toMatchObject({ - type: 'error', - }); + expect(contract.getData().toPromise()).resolves.toHaveProperty( + 'result', + expect.objectContaining({ type: 'error' }) + ); }); - test('can get result of the expression execution', async () => { + test('can get result of the expression execution', () => { const execution = createExecution('var_set name="foo" value="bar" | var name="foo"'); const contract = new ExecutionContract(execution); execution.start(); - const result = await contract.getData(); - - expect(result).toBe('bar'); + expect(contract.getData().toPromise()).resolves.toHaveProperty('result', 'bar'); }); describe('isPending', () => { diff --git a/src/plugins/expressions/common/execution/execution_contract.ts b/src/plugins/expressions/common/execution/execution_contract.ts index 3cad9cef5e09a..445ceb30b58db 100644 --- a/src/plugins/expressions/common/execution/execution_contract.ts +++ b/src/plugins/expressions/common/execution/execution_contract.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; -import { catchError, take } from 'rxjs/operators'; -import { Execution } from './execution'; +import { of, Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { Execution, ExecutionResult } from './execution'; import { ExpressionValueError } from '../expression_types/specs'; import { ExpressionAstExpression } from '../ast'; @@ -39,22 +39,22 @@ export class ExecutionContract => { - return this.execution.result - .pipe( - take(1), - catchError(({ name, message, stack }) => - of({ + getData = (): Observable> => { + return this.execution.result.pipe( + catchError(({ name, message, stack }) => + of({ + partial: false, + result: { type: 'error', error: { name, message, stack, }, - } as ExpressionValueError) - ) + } as ExpressionValueError, + }) ) - .toPromise(); + ); }; /** diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index a307172aff973..7ca5a005991bd 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -13,7 +13,7 @@ import { Observable } from 'rxjs'; import { ExecutorState, ExecutorContainer } from './container'; import { createExecutorContainer } from './container'; import { AnyExpressionFunctionDefinition, ExpressionFunction } from '../expression_functions'; -import { Execution, ExecutionParams } from '../execution/execution'; +import { Execution, ExecutionParams, ExecutionResult } from '../execution/execution'; import { IRegistry } from '../types'; import { ExpressionType } from '../expression_types/expression_type'; import { AnyExpressionTypeDefinition } from '../expression_types/types'; @@ -160,7 +160,7 @@ export class Executor = Record { + ): Observable> { return this.createExecution(ast, params).start(input); } 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 index cdcae61215fa4..1b060cde7482d 100644 --- 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 @@ -65,7 +65,7 @@ describe('expression_functions', () => { it('sets the variables', async () => { const vars = {}; - const result = await executor + const { result } = await executor .run('var_set name=test1 name=test2 value=1', 2, { variables: vars }) .pipe(first()) .toPromise(); diff --git a/src/plugins/expressions/common/service/expressions_services.test.ts b/src/plugins/expressions/common/service/expressions_services.test.ts index 8f86e81f9d1d5..db73d300e1273 100644 --- a/src/plugins/expressions/common/service/expressions_services.test.ts +++ b/src/plugins/expressions/common/service/expressions_services.test.ts @@ -114,7 +114,7 @@ describe('ExpressionsService', () => { }, }); - const result = await fork.run('__test__', null); + const { result } = await fork.run('__test__', null).toPromise(); expect(result).toBe('123'); }); diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index b3c0167262661..9e569c66bbe16 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import { take } from 'rxjs/operators'; +import { Observable } from 'rxjs'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { KibanaRequest } from 'src/core/server'; import { Executor } from '../executor'; import { AnyExpressionRenderDefinition, ExpressionRendererRegistry } from '../expression_renderers'; import { ExpressionAstExpression } from '../ast'; -import { ExecutionContract } from '../execution/execution_contract'; -import { AnyExpressionTypeDefinition } from '../expression_types'; +import { ExecutionContract, ExecutionResult } from '../execution'; +import { AnyExpressionTypeDefinition, ExpressionValueError } from '../expression_types'; import { AnyExpressionFunctionDefinition } from '../expression_functions'; import { SavedObjectReference } from '../../../../core/types'; import { PersistableStateService, SerializableState } from '../../../kibana_utils/common'; @@ -136,7 +136,7 @@ export interface ExpressionsServiceStart { ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams - ) => Promise; + ) => Observable>; /** * Starts expression execution and immediately returns `ExecutionContract` @@ -243,7 +243,7 @@ export class ExpressionsService implements PersistableStateService this.renderers.register(definition); public readonly run: ExpressionsServiceStart['run'] = (ast, input, params) => - this.executor.run(ast, input, params).pipe(take(1)).toPromise(); + this.executor.run(ast, input, params); public readonly getFunction: ExpressionsServiceStart['getFunction'] = (name) => this.executor.getFunction(name); diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts index 98adec285afd5..86477e53dc1a1 100644 --- a/src/plugins/expressions/public/loader.test.ts +++ b/src/plugins/expressions/public/loader.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { of } from 'rxjs'; import { first, skip, toArray } from 'rxjs/operators'; import { loader, ExpressionLoader } from './loader'; import { Observable } from 'rxjs'; @@ -111,8 +112,29 @@ describe('ExpressionLoader', () => { it('emits on $data when data is available', async () => { const expressionLoader = new ExpressionLoader(element, 'var foo', { variables: { foo: 123 } }); - const response = await expressionLoader.data$.pipe(first()).toPromise(); - expect(response).toBe(123); + const { result } = await expressionLoader.data$.pipe(first()).toPromise(); + expect(result).toBe(123); + }); + + it('ignores partial results by default', async () => { + const expressionLoader = new ExpressionLoader(element, 'var foo', { + variables: { foo: of(1, 2) }, + }); + const { result, partial } = await expressionLoader.data$.pipe(first()).toPromise(); + + expect(partial).toBe(false); + expect(result).toBe(2); + }); + + it('emits partial results if enabled', async () => { + const expressionLoader = new ExpressionLoader(element, 'var foo', { + variables: { foo: of(1, 2) }, + partial: true, + }); + const { result, partial } = await expressionLoader.data$.pipe(first()).toPromise(); + + expect(partial).toBe(true); + expect(result).toBe(1); }); it('emits on loading$ on initial load and on updates', async () => { diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 4165b8906a20e..a51ce35c68180 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { filter, map, delay } from 'rxjs/operators'; import { defaults } from 'lodash'; +import { UnwrapObservable } from '@kbn/utility-types'; import { Adapters } from '../../inspector/public'; import { IExpressionLoaderParams } from './types'; import { ExpressionAstExpression } from '../common'; @@ -20,7 +21,7 @@ import { getExpressionsService } from './services'; type Data = any; export class ExpressionLoader { - data$: Observable; + data$: ReturnType; update$: ExpressionRenderHandler['update$']; render$: ExpressionRenderHandler['render$']; events$: ExpressionRenderHandler['events$']; @@ -28,10 +29,11 @@ export class ExpressionLoader { private execution: ExecutionContract | undefined; private renderHandler: ExpressionRenderHandler; - private dataSubject: Subject; + private dataSubject: Subject>; private loadingSubject: Subject; private data: Data; private params: IExpressionLoaderParams = {}; + private subscription?: Subscription; constructor( element: HTMLElement, @@ -67,8 +69,8 @@ export class ExpressionLoader { } }); - this.data$.subscribe((data) => { - this.render(data); + this.data$.subscribe(({ result }) => { + this.render(result); }); this.render$.subscribe(() => { @@ -87,27 +89,20 @@ export class ExpressionLoader { this.dataSubject.complete(); this.loadingSubject.complete(); this.renderHandler.destroy(); - if (this.execution) { - this.execution.cancel(); - } + this.cancel(); + this.subscription?.unsubscribe(); } cancel() { - if (this.execution) { - this.execution.cancel(); - } + this.execution?.cancel(); } getExpression(): string | undefined { - if (this.execution) { - return this.execution.getExpression(); - } + return this.execution?.getExpression(); } getAst(): ExpressionAstExpression | undefined { - if (this.execution) { - return this.execution.getAst(); - } + return this.execution?.getAst(); } getElement(): HTMLElement { @@ -115,27 +110,25 @@ export class ExpressionLoader { } inspect(): Adapters | undefined { - return this.execution ? (this.execution.inspect() as Adapters) : undefined; + return this.execution?.inspect() as Adapters; } - async update( - expression?: string | ExpressionAstExpression, - params?: IExpressionLoaderParams - ): Promise { + update(expression?: string | ExpressionAstExpression, params?: IExpressionLoaderParams): void { this.setParams(params); this.loadingSubject.next(true); if (expression) { - await this.loadData(expression, this.params); + this.loadData(expression, this.params); } else if (this.data) { this.render(this.data); } } - private loadData = async ( + private loadData = ( expression: string | ExpressionAstExpression, params: IExpressionLoaderParams - ): Promise => { + ) => { + this.subscription?.unsubscribe(); if (this.execution && this.execution.isPending) { this.execution.cancel(); } @@ -148,13 +141,13 @@ export class ExpressionLoader { debug: params.debug, syncColors: params.syncColors, }); - - const prevDataHandler = this.execution; - const data = await prevDataHandler.getData(); - if (this.execution !== prevDataHandler) { - return; - } - this.dataSubject.next(data); + this.subscription = this.execution + .getData() + .pipe( + delay(0), // delaying until the next tick since we execute the expression in the constructor + filter(({ partial }) => params.partial || !partial) + ) + .subscribe((value) => this.dataSubject.next(value)); }; private render(data: Data): void { @@ -184,6 +177,7 @@ export class ExpressionLoader { } this.params.syncColors = params.syncColors; this.params.debug = Boolean(params.debug); + this.params.partial = Boolean(params.partial); this.params.inspectorAdapters = (params.inspectorAdapters || this.execution?.inspect()) as Adapters; diff --git a/src/plugins/expressions/public/plugin.test.ts b/src/plugins/expressions/public/plugin.test.ts index 93353ebbb51b6..1963eb1f1b3f7 100644 --- a/src/plugins/expressions/public/plugin.test.ts +++ b/src/plugins/expressions/public/plugin.test.ts @@ -36,8 +36,10 @@ describe('ExpressionsPublicPlugin', () => { 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'); + const { result } = await setup + .run('var_set name="foo" value="bar" | var name="foo"', null) + .toPromise(); + expect(result).toBe('bar'); }); }); }); diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 97009ae543b97..2d9c6d94cfa6d 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -112,16 +112,17 @@ export class Execution(ast: ExpressionAstNode, input: T): Observable; + interpret(ast: ExpressionAstNode, input: T): Observable>; // (undocumented) invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; // (undocumented) invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; // (undocumented) resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; - readonly result: Observable; - start(input?: Input): Observable; - readonly state: ExecutionContainer; + readonly result: Observable>; + start(input?: Input): Observable>; + // Warning: (ae-forgotten-export) The symbol "ExecutionResult" needs to be exported by the entry point index.d.ts + readonly state: ExecutionContainer>; } // Warning: (ae-forgotten-export) The symbol "StateContainer" needs to be exported by the entry point index.d.ts @@ -155,7 +156,7 @@ export class ExecutionContract; getAst: () => ExpressionAstExpression; - getData: () => Promise; + getData: () => Observable>; getExpression: () => string; inspect: () => InspectorAdapters; // (undocumented) @@ -230,7 +231,7 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) registerType(typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)): void; - run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; + run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; // (undocumented) readonly state: ExecutorContainer; // (undocumented) @@ -637,7 +638,7 @@ export interface ExpressionsServiceStart { getFunction: (name: string) => ReturnType; getRenderer: (name: string) => ReturnType; getType: (name: string) => ReturnType; - run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Promise; + run: (ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams) => Observable>; } // Warning: (ae-missing-release-tag) "ExpressionsSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -907,6 +908,8 @@ export interface IExpressionLoaderParams { // // (undocumented) onRenderError?: RenderErrorHandlerFnType; + // (undocumented) + partial?: boolean; // Warning: (ae-forgotten-export) The symbol "RenderMode" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1073,7 +1076,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { // (undocumented) expression: string | ExpressionAstExpression; // (undocumented) - onData$?: (data: TData, adapters?: TInspectorAdapters) => void; + onData$?: (data: TData, adapters?: TInspectorAdapters, partial?: boolean) => void; // (undocumented) onEvent?: (event: ExpressionRendererEvent) => void; // (undocumented) diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index e7ae6ee45ab60..d31a4c947b09d 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -255,7 +255,7 @@ describe('ExpressionRenderer', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); - const newData = {}; + const result = {}; const inspectData = {}; const onData$ = jest.fn(); @@ -275,11 +275,11 @@ describe('ExpressionRenderer', () => { expect(onData$).toHaveBeenCalledTimes(0); act(() => { - dataSubject.next(newData); + dataSubject.next({ result }); }); expect(onData$).toHaveBeenCalledTimes(1); - expect(onData$.mock.calls[0][0]).toBe(newData); + expect(onData$.mock.calls[0][0]).toBe(result); expect(onData$.mock.calls[0][1]).toBe(inspectData); }); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index ce8547f132c27..719af1b39f89e 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -30,7 +30,11 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { ) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; - onData$?: (data: TData, adapters?: TInspectorAdapters) => void; + onData$?: ( + data: TData, + adapters?: TInspectorAdapters, + partial?: boolean + ) => void; /** * An observable which can be used to re-run the expression without destroying the component */ @@ -135,8 +139,8 @@ export const ReactExpressionRenderer = ({ } if (onData$) { subs.push( - expressionLoaderRef.current.data$.subscribe((newData) => { - onData$(newData, expressionLoaderRef.current?.inspect()); + expressionLoaderRef.current.data$.subscribe(({ partial, result }) => { + onData$(result, expressionLoaderRef.current?.inspect(), partial); }) ); } diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 947b84ec152ac..2375252e82784 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -48,6 +48,7 @@ export interface IExpressionLoaderParams { renderMode?: RenderMode; syncColors?: boolean; hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions']; + partial?: boolean; } export interface ExpressionRenderError extends Error { diff --git a/src/plugins/expressions/server/plugin.test.ts b/src/plugins/expressions/server/plugin.test.ts index d967f9e614678..c41cda36e7623 100644 --- a/src/plugins/expressions/server/plugin.test.ts +++ b/src/plugins/expressions/server/plugin.test.ts @@ -28,8 +28,10 @@ describe('ExpressionsServerPlugin', () => { 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'); + const { result } = await setup + .run('var_set name="foo" value="bar" | var name="foo"', null) + .toPromise(); + expect(result).toBe('bar'); }); }); }); diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 8d2e113e6b6ed..ec16d95ea8a3f 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -110,16 +110,17 @@ export class Execution(ast: ExpressionAstNode, input: T): Observable; + interpret(ast: ExpressionAstNode, input: T): Observable>; // (undocumented) invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; // (undocumented) invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; // (undocumented) resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; - readonly result: Observable; - start(input?: Input): Observable; - readonly state: ExecutionContainer; + readonly result: Observable>; + start(input?: Input): Observable>; + // Warning: (ae-forgotten-export) The symbol "ExecutionResult" needs to be exported by the entry point index.d.ts + readonly state: ExecutionContainer>; } // Warning: (ae-forgotten-export) The symbol "StateContainer" needs to be exported by the entry point index.d.ts @@ -212,7 +213,7 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) registerType(typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)): void; - run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; + run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable>; // (undocumented) readonly state: ExecutorContainer; // (undocumented) diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js index bb264aaacbfbf..801681dbd531f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js @@ -10,6 +10,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { keys, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { pluck } from 'rxjs/operators'; const MIN_CHART_HEIGHT = 300; @@ -55,7 +56,9 @@ class VisEditorVisualizationUI extends Component { await this._handler.render(this._visEl.current); this.props.eventEmitter.emit('embeddableRendered'); - this._subscription = this._handler.handler.data$.subscribe((data) => onDataChange(data.value)); + this._subscription = this._handler.handler.data$ + .pipe(pluck('result')) + .subscribe((data) => onDataChange(data.value)); } /** diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx index cf720657291f8..b03451bdebad2 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx @@ -9,7 +9,7 @@ import './main.scss'; import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@elastic/eui'; -import { first } from 'rxjs/operators'; +import { first, pluck } from 'rxjs/operators'; import { IInterpreterRenderHandlers, ExpressionValue, @@ -59,7 +59,9 @@ class Main extends React.Component<{}, State> { inspectorAdapters: adapters, searchContext: initialContext as any, }) - .getData(); + .getData() + .pipe(pluck('result')) + .toPromise(); }; let lastRenderHandler: ExpressionRenderHandler; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts index 923113cc99ba8..a4903fedd22d5 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/server/plugin.ts @@ -7,6 +7,7 @@ */ import { schema } from '@kbn/config-schema'; +import { pluck } from 'rxjs/operators'; import { CoreSetup, Plugin, HttpResponsePayload } from '../../../../../src/core/server'; import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; import { ExpressionsServerStart } from '../../../../../src/plugins/expressions/server'; @@ -32,13 +33,12 @@ export class TestPlugin implements Plugin { const [, { expressions }] = await core.getStartServices(); - const output = await expressions.run( - req.body.expression, - req.body.input, - { + const output = await expressions + .run(req.body.expression, req.body.input, { kibanaRequest: req, - } - ); + }) + .pipe(pluck('result')) + .toPromise(); return res.ok({ body: output }); } ); diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/plugins/canvas/public/lib/run_interpreter.ts index 149e90a8f6b73..d69f89566cfc9 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.ts @@ -6,6 +6,7 @@ */ import { fromExpression, getType } from '@kbn/interpreter/common'; +import { pluck } from 'rxjs/operators'; import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public'; import { pluginServices, expressionsService } from '../services'; @@ -21,7 +22,12 @@ export async function interpretAst( variables: Record ): Promise { const context = { variables }; - return await expressionsService.getService().execute(ast, null, context).getData(); + return await expressionsService + .getService() + .execute(ast, null, context) + .getData() + .pipe(pluck('result')) + .toPromise(); } /** @@ -43,7 +49,12 @@ export async function runInterpreter( const context = { variables }; try { - const renderable = await expressionsService.getService().execute(ast, input, context).getData(); + const renderable = await expressionsService + .getService() + .execute(ast, input, context) + .getData() + .pipe(pluck('result')) + .toPromise(); if (getType(renderable) === 'render') { return renderable;