mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* feat: 🎸 add ExecutionContract class * test: 💍 add Execution and Executor tests * feat: 🎸 add .execute() method to ExpressionsService * refactor: 💡 replace ExpressionDataHandler by ExecutionContract * fix: 🐛 fix TypeScript typecheck errors * docs: ✏️ add JSDocs to ExecutionContract * refactor: 💡 make .ast and .expresions both optional * test: 💍 fix test * test: 💍 fix a test * test: 💍 fix interpreter functional tests
This commit is contained in:
parent
e34ee17574
commit
c4a2067838
22 changed files with 478 additions and 399 deletions
|
@ -21,6 +21,7 @@ import { Execution } from './execution';
|
|||
import { parseExpression } from '../ast';
|
||||
import { createUnitTestExecutor } from '../test_helpers';
|
||||
import { ExpressionFunctionDefinition } from '../../public';
|
||||
import { ExecutionContract } from './execution_contract';
|
||||
|
||||
const createExecution = (
|
||||
expression: string = 'foo bar=123',
|
||||
|
@ -48,7 +49,7 @@ const run = async (
|
|||
describe('Execution', () => {
|
||||
test('can instantiate', () => {
|
||||
const execution = createExecution('foo bar=123');
|
||||
expect(execution.params.ast.chain[0].arguments.bar).toEqual([123]);
|
||||
expect(execution.state.get().ast.chain[0].arguments.bar).toEqual([123]);
|
||||
});
|
||||
|
||||
test('initial input is null at creation', () => {
|
||||
|
@ -127,6 +128,40 @@ describe('Execution', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('.expression', () => {
|
||||
test('uses expression passed in to constructor', () => {
|
||||
const expression = 'add val="1"';
|
||||
const executor = createUnitTestExecutor();
|
||||
const execution = new Execution({
|
||||
executor,
|
||||
expression,
|
||||
});
|
||||
expect(execution.expression).toBe(expression);
|
||||
});
|
||||
|
||||
test('generates expression from AST if not passed to constructor', () => {
|
||||
const expression = 'add val="1"';
|
||||
const executor = createUnitTestExecutor();
|
||||
const execution = new Execution({
|
||||
ast: parseExpression(expression),
|
||||
executor,
|
||||
});
|
||||
expect(execution.expression).toBe(expression);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.contract', () => {
|
||||
test('is instance of ExecutionContract', () => {
|
||||
const execution = createExecution('add val=1');
|
||||
expect(execution.contract).toBeInstanceOf(ExecutionContract);
|
||||
});
|
||||
|
||||
test('execution returns the same expression string', () => {
|
||||
const execution = createExecution('add val=1');
|
||||
expect(execution.expression).toBe(execution.contract.getExpression());
|
||||
});
|
||||
});
|
||||
|
||||
describe('execution context', () => {
|
||||
test('context.variables is an object', async () => {
|
||||
const { result } = (await run('introspectContext key="variables"')) as any;
|
||||
|
|
|
@ -24,17 +24,25 @@ 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 {
|
||||
ExpressionAstExpression,
|
||||
ExpressionAstFunction,
|
||||
parse,
|
||||
formatExpression,
|
||||
parseExpression,
|
||||
} 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';
|
||||
import { ExecutionContract } from './execution_contract';
|
||||
|
||||
export interface ExecutionParams<
|
||||
ExtraContext extends Record<string, unknown> = Record<string, unknown>
|
||||
> {
|
||||
executor: Executor<any>;
|
||||
ast: ExpressionAstExpression;
|
||||
ast?: ExpressionAstExpression;
|
||||
expression?: string;
|
||||
context?: ExtraContext;
|
||||
}
|
||||
|
||||
|
@ -85,6 +93,19 @@ export class Execution<
|
|||
*/
|
||||
private readonly firstResultFuture = new Defer<Output>();
|
||||
|
||||
/**
|
||||
* Contract is a public representation of `Execution` instances. Contract we
|
||||
* can return to other plugins for their consumption.
|
||||
*/
|
||||
public readonly contract: ExecutionContract<
|
||||
ExtraContext,
|
||||
Input,
|
||||
Output,
|
||||
InspectorAdapters
|
||||
> = new ExecutionContract<ExtraContext, Input, Output, InspectorAdapters>(this);
|
||||
|
||||
public readonly expression: string;
|
||||
|
||||
public get result(): Promise<unknown> {
|
||||
return this.firstResultFuture.promise;
|
||||
}
|
||||
|
@ -94,7 +115,17 @@ export class Execution<
|
|||
}
|
||||
|
||||
constructor(public readonly params: ExecutionParams<ExtraContext>) {
|
||||
const { executor, ast } = params;
|
||||
const { executor } = params;
|
||||
|
||||
if (!params.ast && !params.expression) {
|
||||
throw new TypeError('Execution params should contain at least .ast or .expression key.');
|
||||
} else if (params.ast && params.expression) {
|
||||
throw new TypeError('Execution params cannot contain both .ast and .expression key.');
|
||||
}
|
||||
|
||||
this.expression = params.expression || formatExpression(params.ast!);
|
||||
const ast = params.ast || parseExpression(this.expression);
|
||||
|
||||
this.state = createExecutionContainer<Output>({
|
||||
...executor.state.get(),
|
||||
state: 'not-started',
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 { ExecutionContract } from './execution_contract';
|
||||
|
||||
const createExecution = (
|
||||
expression: string = 'foo bar=123',
|
||||
context: Record<string, unknown> = {}
|
||||
) => {
|
||||
const executor = createUnitTestExecutor();
|
||||
const execution = new Execution({
|
||||
executor,
|
||||
ast: parseExpression(expression),
|
||||
context,
|
||||
});
|
||||
return execution;
|
||||
};
|
||||
|
||||
describe('ExecutionContract', () => {
|
||||
test('can instantiate', () => {
|
||||
const execution = createExecution('foo bar=123');
|
||||
const contract = new ExecutionContract(execution);
|
||||
expect(contract).toBeInstanceOf(ExecutionContract);
|
||||
});
|
||||
|
||||
test('can get the AST of expression', () => {
|
||||
const execution = createExecution('foo bar=123');
|
||||
const contract = new ExecutionContract(execution);
|
||||
expect(contract.getAst()).toMatchObject({
|
||||
type: 'expression',
|
||||
chain: expect.any(Array),
|
||||
});
|
||||
});
|
||||
|
||||
test('can get expression string', () => {
|
||||
const execution = createExecution('foo bar=123');
|
||||
const contract = new ExecutionContract(execution);
|
||||
expect(contract.getExpression()).toBe('foo bar=123');
|
||||
});
|
||||
|
||||
test('can cancel execution', () => {
|
||||
const execution = createExecution('foo bar=123');
|
||||
const spy = jest.spyOn(execution, 'cancel');
|
||||
const contract = new ExecutionContract(execution);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(0);
|
||||
contract.cancel();
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('can get inspector adapters', () => {
|
||||
const execution = createExecution('foo bar=123');
|
||||
const contract = new ExecutionContract(execution);
|
||||
expect(contract.inspect()).toMatchObject({
|
||||
data: expect.any(Object),
|
||||
requests: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
test('can get error result of the expression execution', async () => {
|
||||
const execution = createExecution('foo bar=123');
|
||||
const contract = new ExecutionContract(execution);
|
||||
execution.start();
|
||||
|
||||
const result = await contract.getData();
|
||||
|
||||
expect(result).toMatchObject({
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
test('can get result of the expression execution', async () => {
|
||||
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');
|
||||
});
|
||||
|
||||
describe('isPending', () => {
|
||||
test('is true if execution has not been started', async () => {
|
||||
const execution = createExecution('var_set name="foo" value="bar" | var name="foo"');
|
||||
const contract = new ExecutionContract(execution);
|
||||
expect(contract.isPending).toBe(true);
|
||||
});
|
||||
|
||||
test('is true when execution just started', async () => {
|
||||
const execution = createExecution('var_set name="foo" value="bar" | var name="foo"');
|
||||
const contract = new ExecutionContract(execution);
|
||||
|
||||
execution.start();
|
||||
|
||||
expect(contract.isPending).toBe(true);
|
||||
});
|
||||
|
||||
test('is false when execution finished successfully', async () => {
|
||||
const execution = createExecution('var_set name="foo" value="bar" | var name="foo"');
|
||||
const contract = new ExecutionContract(execution);
|
||||
|
||||
execution.start();
|
||||
await execution.result;
|
||||
|
||||
expect(contract.isPending).toBe(false);
|
||||
expect(execution.state.get().state).toBe('result');
|
||||
});
|
||||
|
||||
test('is false when execution finished with error', async () => {
|
||||
const execution = createExecution('var_set name="foo" value="bar" | var name="foo"');
|
||||
const contract = new ExecutionContract(execution);
|
||||
|
||||
execution.start();
|
||||
await execution.result;
|
||||
execution.state.get().state = 'error';
|
||||
|
||||
expect(contract.isPending).toBe(false);
|
||||
expect(execution.state.get().state).toBe('error');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* `ExecutionContract` is a wrapper around `Execution` class. It provides the
|
||||
* same functionality but does not expose Expressions plugin internals.
|
||||
*/
|
||||
export class ExecutionContract<
|
||||
ExtraContext extends Record<string, unknown> = Record<string, unknown>,
|
||||
Input = unknown,
|
||||
Output = unknown,
|
||||
InspectorAdapters = unknown
|
||||
> {
|
||||
public get isPending(): boolean {
|
||||
const state = this.execution.state.get().state;
|
||||
const finished = state === 'error' || state === 'result';
|
||||
return !finished;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected readonly execution: Execution<ExtraContext, Input, Output, InspectorAdapters>
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
cancel = () => {
|
||||
this.execution.cancel();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = async () => {
|
||||
try {
|
||||
return await this.execution.result;
|
||||
} catch (e) {
|
||||
return {
|
||||
type: 'error',
|
||||
error: {
|
||||
type: e.type,
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
getExpression = () => {
|
||||
return this.execution.expression;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get AST used to execute the expression.
|
||||
*/
|
||||
getAst = () => this.execution.state.get().ast;
|
||||
|
||||
/**
|
||||
* Get Inspector adapters provided to all functions of expression through
|
||||
* execution context.
|
||||
*/
|
||||
inspect = () => this.execution.inspectorAdapters;
|
||||
}
|
|
@ -20,3 +20,4 @@
|
|||
export * from './types';
|
||||
export * from './container';
|
||||
export * from './execution';
|
||||
export * from './execution_contract';
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 { parseExpression } from '../ast';
|
||||
|
||||
// eslint-disable-next-line
|
||||
const { __getArgs } = require('../execution/execution');
|
||||
|
||||
jest.mock('../execution/execution', () => {
|
||||
const mockedModule = {
|
||||
args: undefined,
|
||||
__getArgs: () => mockedModule.args,
|
||||
Execution: function ExecutionMock(...args: any) {
|
||||
mockedModule.args = args;
|
||||
},
|
||||
};
|
||||
|
||||
return mockedModule;
|
||||
});
|
||||
|
||||
describe('Executor mocked execution tests', () => {
|
||||
describe('createExecution()', () => {
|
||||
describe('when execution is created from string', () => {
|
||||
test('passes expression string to Execution', () => {
|
||||
const executor = new Executor();
|
||||
executor.createExecution('foo bar="baz"');
|
||||
|
||||
expect(__getArgs()[0].expression).toBe('foo bar="baz"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when execution is created from AST', () => {
|
||||
test('does not pass in expression string', () => {
|
||||
const executor = new Executor();
|
||||
const ast = parseExpression('foo bar="baz"');
|
||||
executor.createExecution(ast);
|
||||
|
||||
expect(__getArgs()[0].expression).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -130,7 +130,7 @@ describe('Executor', () => {
|
|||
const execution = executor.createExecution('foo bar="baz"');
|
||||
|
||||
expect(execution).toBeInstanceOf(Execution);
|
||||
expect(execution.params.ast.chain[0].function).toBe('foo');
|
||||
expect(execution.state.get().ast.chain[0].function).toBe('foo');
|
||||
});
|
||||
|
||||
test('returns Execution object from AST', () => {
|
||||
|
@ -139,7 +139,7 @@ describe('Executor', () => {
|
|||
const execution = executor.createExecution(ast);
|
||||
|
||||
expect(execution).toBeInstanceOf(Execution);
|
||||
expect(execution.params.ast.chain[0].function).toBe('foo');
|
||||
expect(execution.state.get().ast.chain[0].function).toBe('foo');
|
||||
});
|
||||
|
||||
test('Execution inherits context from Executor', () => {
|
||||
|
|
|
@ -22,12 +22,12 @@
|
|||
import { ExecutorState, ExecutorContainer } from './container';
|
||||
import { createExecutorContainer } from './container';
|
||||
import { AnyExpressionFunctionDefinition, ExpressionFunction } from '../expression_functions';
|
||||
import { Execution } from '../execution/execution';
|
||||
import { Execution, ExecutionParams } 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 { ExpressionAstExpression, ExpressionAstNode } from '../ast';
|
||||
import { typeSpecs } from '../expression_types/specs';
|
||||
import { functionSpecs } from '../expression_functions/specs';
|
||||
|
||||
|
@ -186,19 +186,27 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
|||
return (await execution.result) as Output;
|
||||
}
|
||||
|
||||
public createExecution<ExtraContext extends Record<string, unknown> = Record<string, unknown>>(
|
||||
public createExecution<
|
||||
ExtraContext extends Record<string, unknown> = Record<string, unknown>,
|
||||
Input = unknown,
|
||||
Output = unknown
|
||||
>(
|
||||
ast: string | ExpressionAstExpression,
|
||||
context: ExtraContext = {} as ExtraContext
|
||||
): Execution<Context & ExtraContext> {
|
||||
if (typeof ast === 'string') ast = parseExpression(ast);
|
||||
const execution = new Execution<Context & ExtraContext>({
|
||||
ast,
|
||||
): Execution<Context & ExtraContext, Input, Output> {
|
||||
const params: ExecutionParams<Context & ExtraContext> = {
|
||||
executor: this,
|
||||
context: {
|
||||
...this.context,
|
||||
...context,
|
||||
} as Context & ExtraContext,
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof ast === 'string') params.expression = ast;
|
||||
else params.ast = ast;
|
||||
|
||||
const execution = new Execution<Context & ExtraContext, Input, Output>(params);
|
||||
|
||||
return execution;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import { Executor } from '../executor';
|
||||
import { ExpressionRendererRegistry } from '../expression_renderers';
|
||||
import { ExpressionAstExpression } from '../ast';
|
||||
import { ExecutionContract } from '../execution/execution_contract';
|
||||
|
||||
export type ExpressionsServiceSetup = ReturnType<ExpressionsService['setup']>;
|
||||
export type ExpressionsServiceStart = ReturnType<ExpressionsService['start']>;
|
||||
|
@ -117,6 +118,26 @@ export class ExpressionsService {
|
|||
context?: ExtraContext
|
||||
): Promise<Output> => this.executor.run<Input, Output, ExtraContext>(ast, input, context);
|
||||
|
||||
/**
|
||||
* Starts expression execution and immediately returns `ExecutionContract`
|
||||
* instance that tracks the progress of the execution and can be used to
|
||||
* interact with the execution.
|
||||
*/
|
||||
public readonly execute = <
|
||||
Input = unknown,
|
||||
Output = unknown,
|
||||
ExtraContext extends Record<string, unknown> = Record<string, unknown>
|
||||
>(
|
||||
ast: string | ExpressionAstExpression,
|
||||
// This any is for legacy reasons.
|
||||
input: Input = { type: 'null' } as any,
|
||||
context?: ExtraContext
|
||||
): ExecutionContract<ExtraContext, Input, Output> => {
|
||||
const execution = this.executor.createExecution<ExtraContext, Input, Output>(ast, context);
|
||||
execution.start(input);
|
||||
return execution.contract;
|
||||
};
|
||||
|
||||
public setup() {
|
||||
const { executor, renderers, registerFunction, run } = this;
|
||||
|
||||
|
@ -144,7 +165,7 @@ export class ExpressionsService {
|
|||
}
|
||||
|
||||
public start() {
|
||||
const { executor, renderers, run } = this;
|
||||
const { execute, executor, renderers, run } = this;
|
||||
|
||||
const getFunction = executor.getFunction.bind(executor);
|
||||
const getFunctions = executor.getFunctions.bind(executor);
|
||||
|
@ -154,6 +175,7 @@ export class ExpressionsService {
|
|||
const getTypes = executor.getTypes.bind(executor);
|
||||
|
||||
return {
|
||||
execute,
|
||||
getFunction,
|
||||
getFunctions,
|
||||
getRenderer,
|
||||
|
|
|
@ -1,100 +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 { execute, ExpressionDataHandler } from './execute';
|
||||
import { ExpressionAstExpression, parseExpression } from '../common';
|
||||
|
||||
jest.mock('./services', () => ({
|
||||
getInterpreter: () => {
|
||||
return {
|
||||
interpretAst: async (expression: ExpressionAstExpression) => {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
},
|
||||
getNotifications: jest.fn(() => {
|
||||
return {
|
||||
toasts: {
|
||||
addError: jest.fn(() => {}),
|
||||
},
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('execute helper function', () => {
|
||||
it('returns ExpressionDataHandler instance', () => {
|
||||
const response = execute('test');
|
||||
expect(response).toBeInstanceOf(ExpressionDataHandler);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExpressionDataHandler', () => {
|
||||
const expressionString = 'test';
|
||||
|
||||
describe('constructor', () => {
|
||||
it('accepts expression string', () => {
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionString, {});
|
||||
expect(expressionDataHandler.getExpression()).toEqual(expressionString);
|
||||
});
|
||||
|
||||
it('accepts expression AST', () => {
|
||||
const expressionAST = parseExpression(expressionString) as ExpressionAstExpression;
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionAST, {});
|
||||
expect(expressionDataHandler.getExpression()).toEqual(expressionString);
|
||||
expect(expressionDataHandler.getAst()).toEqual(expressionAST);
|
||||
});
|
||||
|
||||
it('allows passing in context', () => {
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionString, {
|
||||
context: { test: 'hello' },
|
||||
});
|
||||
expect(expressionDataHandler.getExpression()).toEqual(expressionString);
|
||||
});
|
||||
|
||||
it('allows passing in search context', () => {
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionString, {
|
||||
searchContext: { filters: [] },
|
||||
});
|
||||
expect(expressionDataHandler.getExpression()).toEqual(expressionString);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getData()', () => {
|
||||
it('returns a promise', () => {
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionString, {});
|
||||
expect(expressionDataHandler.getData()).toBeInstanceOf(Promise);
|
||||
});
|
||||
|
||||
it('promise resolves with data', async () => {
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionString, {});
|
||||
expect(await expressionDataHandler.getData()).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
it('cancel() aborts request', () => {
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionString, {});
|
||||
expressionDataHandler.cancel();
|
||||
});
|
||||
|
||||
it('inspect() returns correct inspector adapters', () => {
|
||||
const expressionDataHandler = new ExpressionDataHandler(expressionString, {});
|
||||
expect(expressionDataHandler.inspect()).toHaveProperty('requests');
|
||||
expect(expressionDataHandler.inspect()).toHaveProperty('data');
|
||||
});
|
||||
});
|
|
@ -1,138 +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 { DataAdapter, RequestAdapter, Adapters } from '../../inspector/public';
|
||||
import { getInterpreter } from './services';
|
||||
import { IExpressionLoaderParams } from './types';
|
||||
import {
|
||||
ExpressionAstExpression,
|
||||
parseExpression,
|
||||
formatExpression,
|
||||
ExpressionValue,
|
||||
} from '../common';
|
||||
|
||||
/**
|
||||
* The search context describes a specific context (filters, time range and query)
|
||||
* that will be applied to the expression for execution. Not every expression will
|
||||
* be effected by that. You have to use special functions
|
||||
* that will pick up this search context and forward it to following functions that
|
||||
* understand it.
|
||||
*/
|
||||
|
||||
export class ExpressionDataHandler {
|
||||
private abortController: AbortController;
|
||||
private expression: string;
|
||||
private ast: ExpressionAstExpression;
|
||||
|
||||
private inspectorAdapters: Adapters;
|
||||
private promise: Promise<ExpressionValue>;
|
||||
|
||||
public isPending: boolean = true;
|
||||
constructor(expression: string | ExpressionAstExpression, params: IExpressionLoaderParams) {
|
||||
if (typeof expression === 'string') {
|
||||
this.expression = expression;
|
||||
this.ast = parseExpression(expression);
|
||||
} else {
|
||||
this.ast = expression;
|
||||
this.expression = formatExpression(this.ast);
|
||||
}
|
||||
|
||||
this.abortController = new AbortController();
|
||||
this.inspectorAdapters = params.inspectorAdapters || this.getActiveInspectorAdapters();
|
||||
|
||||
const defaultInput = { type: 'null' };
|
||||
const interpreter = getInterpreter();
|
||||
this.promise = interpreter
|
||||
.interpretAst<any, ExpressionValue>(this.ast, params.context || defaultInput, {
|
||||
search: params.searchContext,
|
||||
inspectorAdapters: this.inspectorAdapters,
|
||||
abortSignal: this.abortController.signal,
|
||||
variables: params.variables,
|
||||
})
|
||||
.then(
|
||||
(v: ExpressionValue) => {
|
||||
this.isPending = false;
|
||||
return v;
|
||||
},
|
||||
() => {
|
||||
this.isPending = false;
|
||||
}
|
||||
) as Promise<ExpressionValue>;
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.abortController.abort();
|
||||
};
|
||||
|
||||
getData = async () => {
|
||||
try {
|
||||
return await this.promise;
|
||||
} catch (e) {
|
||||
return {
|
||||
type: 'error',
|
||||
error: {
|
||||
type: e.type,
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
getExpression = () => {
|
||||
return this.expression;
|
||||
};
|
||||
|
||||
getAst = () => {
|
||||
return this.ast;
|
||||
};
|
||||
|
||||
inspect = () => {
|
||||
return this.inspectorAdapters;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object of all inspectors for this vis object.
|
||||
* This must only be called after this.type has properly be initialized,
|
||||
* since we need to read out data from the the vis type to check which
|
||||
* inspectors are available.
|
||||
*/
|
||||
private getActiveInspectorAdapters = (): Adapters => {
|
||||
const adapters: Adapters = {};
|
||||
|
||||
// Add the requests inspector adapters if the vis type explicitly requested it via
|
||||
// inspectorAdapters.requests: true in its definition or if it's using the courier
|
||||
// request handler, since that will automatically log its requests.
|
||||
adapters.requests = new RequestAdapter();
|
||||
|
||||
// Add the data inspector adapter if the vis type requested it or if the
|
||||
// vis is using courier, since we know that courier supports logging
|
||||
// its data.
|
||||
adapters.data = new DataAdapter();
|
||||
|
||||
return adapters;
|
||||
};
|
||||
}
|
||||
|
||||
export function execute(
|
||||
expression: string | ExpressionAstExpression,
|
||||
params: IExpressionLoaderParams = {}
|
||||
): ExpressionDataHandler {
|
||||
return new ExpressionDataHandler(expression, params);
|
||||
}
|
|
@ -37,7 +37,6 @@ export {
|
|||
ReactExpressionRendererProps,
|
||||
ReactExpressionRendererType,
|
||||
} from './react_expression_renderer';
|
||||
export { ExpressionDataHandler } from './execute';
|
||||
export { ExpressionRenderHandler } from './render';
|
||||
export {
|
||||
AnyExpressionFunctionDefinition,
|
||||
|
@ -48,6 +47,7 @@ export {
|
|||
DatatableColumnType,
|
||||
DatatableRow,
|
||||
Execution,
|
||||
ExecutionContract,
|
||||
ExecutionContainer,
|
||||
ExecutionContext,
|
||||
ExecutionParams,
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { first, skip, toArray } from 'rxjs/operators';
|
||||
import { loader, ExpressionLoader } from './loader';
|
||||
import { ExpressionDataHandler } from './execute';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ExpressionAstExpression, parseExpression, IInterpreterRenderHandlers } from '../common';
|
||||
|
||||
// eslint-disable-next-line
|
||||
const { __getLastExecution } = require('./services');
|
||||
|
||||
const element: HTMLElement = null as any;
|
||||
|
||||
jest.mock('./services', () => {
|
||||
|
@ -33,7 +35,13 @@ jest.mock('./services', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
|
||||
// eslint-disable-next-line
|
||||
const service = new (require('../common/service/expressions_services').ExpressionsService as any)();
|
||||
|
||||
const moduleMock = {
|
||||
__execution: undefined,
|
||||
__getLastExecution: () => moduleMock.__execution,
|
||||
getInterpreter: () => {
|
||||
return {
|
||||
interpretAst: async (expression: ExpressionAstExpression) => {
|
||||
|
@ -51,17 +59,19 @@ jest.mock('./services', () => {
|
|||
},
|
||||
};
|
||||
}),
|
||||
getExpressionsService: () => service,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('./execute', () => {
|
||||
const actual = jest.requireActual('./execute');
|
||||
return {
|
||||
ExpressionDataHandler: jest
|
||||
.fn()
|
||||
.mockImplementation((...args) => new actual.ExpressionDataHandler(...args)),
|
||||
execute: jest.fn().mockReturnValue(actual.execute),
|
||||
const execute = service.execute;
|
||||
service.execute = (...args: any) => {
|
||||
const execution = execute(...args);
|
||||
jest.spyOn(execution, 'getData');
|
||||
jest.spyOn(execution, 'cancel');
|
||||
moduleMock.__execution = execution;
|
||||
return execution;
|
||||
};
|
||||
|
||||
return moduleMock;
|
||||
});
|
||||
|
||||
describe('execute helper function', () => {
|
||||
|
@ -97,9 +107,9 @@ describe('ExpressionLoader', () => {
|
|||
});
|
||||
|
||||
it('emits on $data when data is available', async () => {
|
||||
const expressionLoader = new ExpressionLoader(element, expressionString, {});
|
||||
const expressionLoader = new ExpressionLoader(element, 'var foo', { variables: { foo: 123 } });
|
||||
const response = await expressionLoader.data$.pipe(first()).toPromise();
|
||||
expect(response).toEqual({ type: 'render', as: 'test' });
|
||||
expect(response).toBe(123);
|
||||
});
|
||||
|
||||
it('emits on loading$ on initial load and on updates', async () => {
|
||||
|
@ -128,94 +138,13 @@ describe('ExpressionLoader', () => {
|
|||
});
|
||||
|
||||
it('cancels the previous request when the expression is updated', () => {
|
||||
const cancelMock = jest.fn();
|
||||
const expressionLoader = new ExpressionLoader(element, 'var foo', {});
|
||||
const execution = __getLastExecution();
|
||||
jest.spyOn(execution, 'cancel');
|
||||
|
||||
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
|
||||
getData: () => true,
|
||||
cancel: cancelMock,
|
||||
isPending: () => true,
|
||||
inspect: () => {},
|
||||
}));
|
||||
|
||||
const expressionLoader = new ExpressionLoader(element, expressionString, {});
|
||||
expressionLoader.update('new', {});
|
||||
|
||||
expect(cancelMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not send an observable message if a request was aborted', () => {
|
||||
const cancelMock = jest.fn();
|
||||
|
||||
const getData = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
type: 'error',
|
||||
error: {
|
||||
name: 'AbortError',
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
type: 'real',
|
||||
});
|
||||
|
||||
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
|
||||
getData,
|
||||
cancel: cancelMock,
|
||||
isPending: () => true,
|
||||
inspect: () => {},
|
||||
}));
|
||||
|
||||
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
|
||||
getData,
|
||||
cancel: cancelMock,
|
||||
isPending: () => true,
|
||||
inspect: () => {},
|
||||
}));
|
||||
|
||||
const expressionLoader = new ExpressionLoader(element, expressionString, {});
|
||||
|
||||
expect.assertions(2);
|
||||
expressionLoader.data$.subscribe({
|
||||
next(data) {
|
||||
expect(data).toEqual({
|
||||
type: 'real',
|
||||
});
|
||||
},
|
||||
error() {
|
||||
expect(false).toEqual('Should not be called');
|
||||
},
|
||||
});
|
||||
|
||||
expressionLoader.update('new expression', {});
|
||||
|
||||
expect(getData).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('sends an observable error if the data fetching failed', () => {
|
||||
const cancelMock = jest.fn();
|
||||
|
||||
const getData = jest.fn().mockResolvedValue('rejected');
|
||||
|
||||
(ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({
|
||||
getData,
|
||||
cancel: cancelMock,
|
||||
isPending: () => true,
|
||||
inspect: () => {},
|
||||
}));
|
||||
|
||||
const expressionLoader = new ExpressionLoader(element, expressionString, {});
|
||||
|
||||
expect.assertions(2);
|
||||
expressionLoader.data$.subscribe({
|
||||
next(data) {
|
||||
expect(data).toEqual('Should not be called');
|
||||
},
|
||||
error(error) {
|
||||
expect(error.message).toEqual('Could not fetch data');
|
||||
},
|
||||
});
|
||||
|
||||
expect(getData).toHaveBeenCalledTimes(1);
|
||||
expect(execution.cancel).toHaveBeenCalledTimes(0);
|
||||
expressionLoader.update('var bar', {});
|
||||
expect(execution.cancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('inspect() returns correct inspector adapters', () => {
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { Adapters, InspectorSession } from '../../inspector/public';
|
||||
import { ExpressionDataHandler } from './execute';
|
||||
import { ExpressionRenderHandler } from './render';
|
||||
import { IExpressionLoaderParams } from './types';
|
||||
import { ExpressionAstExpression } from '../common';
|
||||
import { getInspector } from './services';
|
||||
import { getInspector, getExpressionsService } from './services';
|
||||
import { ExecutionContract } from '../common/execution/execution_contract';
|
||||
|
||||
type Data = any;
|
||||
|
||||
|
@ -35,7 +35,7 @@ export class ExpressionLoader {
|
|||
events$: ExpressionRenderHandler['events$'];
|
||||
loading$: Observable<void>;
|
||||
|
||||
private dataHandler: ExpressionDataHandler | undefined;
|
||||
private execution: ExecutionContract | undefined;
|
||||
private renderHandler: ExpressionRenderHandler;
|
||||
private dataSubject: Subject<Data>;
|
||||
private loadingSubject: Subject<boolean>;
|
||||
|
@ -93,26 +93,26 @@ export class ExpressionLoader {
|
|||
this.dataSubject.complete();
|
||||
this.loadingSubject.complete();
|
||||
this.renderHandler.destroy();
|
||||
if (this.dataHandler) {
|
||||
this.dataHandler.cancel();
|
||||
if (this.execution) {
|
||||
this.execution.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
if (this.dataHandler) {
|
||||
this.dataHandler.cancel();
|
||||
if (this.execution) {
|
||||
this.execution.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
getExpression(): string | undefined {
|
||||
if (this.dataHandler) {
|
||||
return this.dataHandler.getExpression();
|
||||
if (this.execution) {
|
||||
return this.execution.getExpression();
|
||||
}
|
||||
}
|
||||
|
||||
getAst(): ExpressionAstExpression | undefined {
|
||||
if (this.dataHandler) {
|
||||
return this.dataHandler.getAst();
|
||||
if (this.execution) {
|
||||
return this.execution.getAst();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,9 +130,7 @@ export class ExpressionLoader {
|
|||
}
|
||||
|
||||
inspect(): Adapters | undefined {
|
||||
if (this.dataHandler) {
|
||||
return this.dataHandler.inspect();
|
||||
}
|
||||
return this.execution ? (this.execution.inspect() as Adapters) : undefined;
|
||||
}
|
||||
|
||||
update(expression?: string | ExpressionAstExpression, params?: IExpressionLoaderParams): void {
|
||||
|
@ -150,15 +148,19 @@ export class ExpressionLoader {
|
|||
expression: string | ExpressionAstExpression,
|
||||
params: IExpressionLoaderParams
|
||||
): Promise<void> => {
|
||||
if (this.dataHandler && this.dataHandler.isPending) {
|
||||
this.dataHandler.cancel();
|
||||
if (this.execution && this.execution.isPending) {
|
||||
this.execution.cancel();
|
||||
}
|
||||
this.setParams(params);
|
||||
this.dataHandler = new ExpressionDataHandler(expression, params);
|
||||
if (!params.inspectorAdapters) params.inspectorAdapters = this.dataHandler.inspect();
|
||||
const prevDataHandler = this.dataHandler;
|
||||
this.execution = getExpressionsService().execute(expression, params.context, {
|
||||
search: params.searchContext,
|
||||
variables: params.variables || {},
|
||||
inspectorAdapters: params.inspectorAdapters,
|
||||
});
|
||||
if (!params.inspectorAdapters) params.inspectorAdapters = this.execution.inspect() as Adapters;
|
||||
const prevDataHandler = this.execution;
|
||||
const data = await prevDataHandler.getData();
|
||||
if (this.dataHandler !== prevDataHandler) {
|
||||
if (this.execution !== prevDataHandler) {
|
||||
return;
|
||||
}
|
||||
this.dataSubject.next(data);
|
||||
|
|
|
@ -65,7 +65,6 @@ const createSetupContract = (): Setup => {
|
|||
const createStartContract = (): Start => {
|
||||
return {
|
||||
execute: jest.fn(),
|
||||
ExpressionDataHandler: jest.fn(),
|
||||
ExpressionLoader: jest.fn(),
|
||||
ExpressionRenderHandler: jest.fn(),
|
||||
getFunction: jest.fn(),
|
||||
|
|
|
@ -65,6 +65,15 @@ describe('ExpressionsPublicPlugin', () => {
|
|||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('"kibana" function return value of type "kibana_context"', async () => {
|
||||
const { doStart } = await expressionsPluginMock.createPlugin();
|
||||
const start = await doStart();
|
||||
const execution = start.execute('kibana');
|
||||
const result = await execution.getData();
|
||||
|
||||
expect((result as any).type).toBe('kibana_context');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -36,11 +36,11 @@ import {
|
|||
setInterpreter,
|
||||
setRenderersRegistry,
|
||||
setNotifications,
|
||||
setExpressionsService,
|
||||
} from './services';
|
||||
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';
|
||||
|
||||
export interface ExpressionsSetupDeps {
|
||||
|
@ -92,8 +92,6 @@ export interface ExpressionsSetup extends ExpressionsServiceSetup {
|
|||
}
|
||||
|
||||
export interface ExpressionsStart extends ExpressionsServiceStart {
|
||||
execute: typeof execute;
|
||||
ExpressionDataHandler: typeof ExpressionDataHandler;
|
||||
ExpressionLoader: typeof ExpressionLoader;
|
||||
ExpressionRenderHandler: typeof ExpressionRenderHandler;
|
||||
loader: typeof loader;
|
||||
|
@ -118,6 +116,7 @@ export class ExpressionsPublicPlugin
|
|||
executor.registerFunction(kibanaContextFunction());
|
||||
|
||||
setRenderersRegistry(renderers);
|
||||
setExpressionsService(this.expressions);
|
||||
|
||||
const expressionsSetup = expressions.setup();
|
||||
|
||||
|
@ -180,8 +179,6 @@ export class ExpressionsPublicPlugin
|
|||
|
||||
return {
|
||||
...expressionsStart,
|
||||
execute,
|
||||
ExpressionDataHandler,
|
||||
ExpressionLoader,
|
||||
ExpressionRenderHandler,
|
||||
loader,
|
||||
|
|
|
@ -22,6 +22,7 @@ import { createKibanaUtilsCore, createGetterSetter } from '../../kibana_utils/pu
|
|||
import { ExpressionInterpreter } from './types';
|
||||
import { Start as IInspector } from '../../inspector/public';
|
||||
import { ExpressionsSetup } from './plugin';
|
||||
import { ExpressionsService } from '../common';
|
||||
|
||||
export const { getCoreStart, setCoreStart, savedObjects } = createKibanaUtilsCore();
|
||||
|
||||
|
@ -37,3 +38,7 @@ export const [getNotifications, setNotifications] = createGetterSetter<Notificat
|
|||
export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter<
|
||||
ExpressionsSetup['__LEGACY']['renderers']
|
||||
>('Renderers registry');
|
||||
|
||||
export const [getExpressionsService, setExpressionsService] = createGetterSetter<
|
||||
ExpressionsService
|
||||
>('ExpressionsService');
|
||||
|
|
|
@ -22,7 +22,7 @@ import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@ela
|
|||
import { first } from 'rxjs/operators';
|
||||
import { IInterpreterRenderHandlers, ExpressionValue } from 'src/plugins/expressions';
|
||||
import { RequestAdapter, DataAdapter } from '../../../../../../../../src/plugins/inspector';
|
||||
import { Adapters, ExpressionRenderHandler, ExpressionDataHandler } from '../../types';
|
||||
import { Adapters, ExpressionRenderHandler } from '../../types';
|
||||
import { getExpressions } from '../../services';
|
||||
|
||||
declare global {
|
||||
|
@ -31,7 +31,7 @@ declare global {
|
|||
expressions: string,
|
||||
context?: ExpressionValue,
|
||||
initialContext?: ExpressionValue
|
||||
) => ReturnType<ExpressionDataHandler['getData']>;
|
||||
) => any;
|
||||
renderPipelineResponse: (context?: ExpressionValue) => Promise<any>;
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +61,9 @@ class Main extends React.Component<{}, State> {
|
|||
data: new DataAdapter(),
|
||||
};
|
||||
return getExpressions()
|
||||
.execute(expression, {
|
||||
.execute(expression, context || { type: 'null' }, {
|
||||
inspectorAdapters: adapters,
|
||||
context,
|
||||
// TODO: naming / typing is confusing and doesn't match here
|
||||
// searchContext is also a way to set initialContext and Context can't be set to SearchContext
|
||||
searchContext: initialContext as any,
|
||||
search: initialContext as any,
|
||||
})
|
||||
.getData();
|
||||
};
|
||||
|
|
|
@ -17,12 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ExpressionsStart,
|
||||
ExpressionRenderHandler,
|
||||
ExpressionDataHandler,
|
||||
} from 'src/plugins/expressions/public';
|
||||
|
||||
import { ExpressionsStart, ExpressionRenderHandler } from 'src/plugins/expressions/public';
|
||||
import { Adapters } from 'src/plugins/inspector/public';
|
||||
|
||||
export { ExpressionsStart, ExpressionRenderHandler, ExpressionDataHandler, Adapters };
|
||||
export { ExpressionsStart, ExpressionRenderHandler, Adapters };
|
||||
|
|
|
@ -22,7 +22,7 @@ import { ExpectExpression, expectExpressionProvider } from './helpers';
|
|||
import { FtrProviderContext } from '../../../functional/ftr_provider_context';
|
||||
|
||||
// this file showcases how to use testing utilities defined in helpers.ts together with the kbn_tp_run_pipeline
|
||||
// test plugin to write autmated tests for interprete
|
||||
// test plugin to write automated tests for interpreter
|
||||
export default function({
|
||||
getService,
|
||||
updateBaselines,
|
||||
|
|
|
@ -20,10 +20,8 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { ExpressionValue } from 'src/plugins/expressions';
|
||||
import { FtrProviderContext } from '../../../functional/ftr_provider_context';
|
||||
import { ExpressionDataHandler } from '../../plugins/kbn_tp_run_pipeline/public/np_ready/types';
|
||||
|
||||
type UnWrapPromise<T> = T extends Promise<infer U> ? U : T;
|
||||
export type ExpressionResult = UnWrapPromise<ReturnType<ExpressionDataHandler['getData']>>;
|
||||
export type ExpressionResult = any;
|
||||
|
||||
export type ExpectExpression = (
|
||||
name: string,
|
||||
|
@ -112,7 +110,7 @@ export function expectExpressionProvider({
|
|||
if (!_currentContext.type) _currentContext.type = 'null';
|
||||
return window
|
||||
.runPipeline(_expression, _currentContext, _initialContext)
|
||||
.then(expressionResult => {
|
||||
.then((expressionResult: any) => {
|
||||
done(expressionResult);
|
||||
return expressionResult;
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue