Skip to content

Commit

Permalink
cleaning up expression service types
Browse files Browse the repository at this point in the history
  • Loading branch information
ppisljar committed Oct 20, 2020
1 parent f57518a commit 90b1f5b
Show file tree
Hide file tree
Showing 16 changed files with 110 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ const createExecution = (
const execution = new Execution({
executor,
ast: parseExpression(expression),
context,
debug,
params: { ...context, debug },
});
return execution;
};
Expand Down
14 changes: 9 additions & 5 deletions src/plugins/expressions/common/execution/execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ const createExecution = (
const execution = new Execution({
executor,
ast: parseExpression(expression),
context,
debug,
params: {
...context,
debug,
},
});
return execution;
};
Expand Down Expand Up @@ -143,6 +145,7 @@ describe('Execution', () => {
const execution = new Execution({
executor,
expression,
params: {},
});
expect(execution.expression).toBe(expression);
});
Expand All @@ -153,6 +156,7 @@ describe('Execution', () => {
const execution = new Execution({
ast: parseExpression(expression),
executor,
params: {},
});
expect(execution.expression).toBe(expression);
});
Expand Down Expand Up @@ -619,7 +623,7 @@ describe('Execution', () => {
const execution = new Execution({
executor,
ast: parseExpression('add val=1 | throws | add val=3'),
debug: true,
params: { debug: true },
});
execution.start(0);
await execution.result;
Expand All @@ -637,7 +641,7 @@ describe('Execution', () => {
const execution = new Execution({
executor,
ast: parseExpression('add val=1 | throws | add val=3'),
debug: true,
params: { debug: true },
});
execution.start(0);
await execution.result;
Expand All @@ -658,7 +662,7 @@ describe('Execution', () => {
const execution = new Execution({
executor,
ast: parseExpression('add val=1 | throws | add val=3'),
debug: true,
params: { debug: true },
});
execution.start(0);
await execution.result;
Expand Down
74 changes: 29 additions & 45 deletions src/plugins/expressions/common/execution/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import { i18n } from '@kbn/i18n';
import { keys, last, mapValues, reduce, zipObject } from 'lodash';
import { Executor, ExpressionExecOptions } from '../executor';
import { Executor } from '../executor';
import { createExecutionContainer, ExecutionContainer } from './container';
import { createError } from '../util';
import { Defer, now } from '../../../kibana_utils/common';
Expand All @@ -39,27 +39,19 @@ import { getType, ExpressionValue } from '../expression_types';
import { ArgumentType, ExpressionFunction } from '../expression_functions';
import { getByAlias } from '../util/get_by_alias';
import { ExecutionContract } from './execution_contract';
import { ExpressionExecutionParams } from '../service';

const createAbortErrorValue = () =>
createError({
message: 'The expression was aborted.',
name: 'AbortError',
});

export interface ExecutionParams<
ExtraContext extends Record<string, unknown> = Record<string, unknown>
> {
export interface ExecutionParams {
executor: Executor<any>;
ast?: ExpressionAstExpression;
expression?: string;
context?: ExtraContext;

/**
* Whether to execute expression in *debug mode*. In *debug mode* inputs and
* outputs as well as all resolved arguments and time it took to execute each
* function are saved and are available for introspection.
*/
debug?: boolean;
params: ExpressionExecutionParams;
}

const createDefaultInspectorAdapters = (): DefaultInspectorAdapters => ({
Expand All @@ -68,11 +60,10 @@ const createDefaultInspectorAdapters = (): DefaultInspectorAdapters => ({
});

export class Execution<
ExtraContext extends Record<string, unknown> = Record<string, unknown>,
Input = unknown,
Output = unknown,
InspectorAdapters extends Adapters = ExtraContext['inspectorAdapters'] extends object
? ExtraContext['inspectorAdapters']
InspectorAdapters extends Adapters = ExpressionExecutionParams['inspectorAdapters'] extends object
? ExpressionExecutionParams['inspectorAdapters']
: DefaultInspectorAdapters
> {
/**
Expand All @@ -92,7 +83,7 @@ export class Execution<
* Execution context - object that allows to do side-effects. Context is passed
* to every function.
*/
public readonly context: ExecutionContext<Input, InspectorAdapters> & ExtraContext;
public readonly context: ExecutionContext<Input, InspectorAdapters>;

/**
* AbortController to cancel this Execution.
Expand Down Expand Up @@ -126,11 +117,10 @@ export class Execution<
* can return to other plugins for their consumption.
*/
public readonly contract: ExecutionContract<
ExtraContext,
Input,
Output,
InspectorAdapters
> = new ExecutionContract<ExtraContext, Input, Output, InspectorAdapters>(this);
> = new ExecutionContract<Input, Output, InspectorAdapters>(this);

public readonly expression: string;

Expand All @@ -142,17 +132,17 @@ export class Execution<
return this.context.inspectorAdapters;
}

constructor(public readonly params: ExecutionParams<ExtraContext>) {
const { executor } = params;
constructor(public readonly execution: ExecutionParams) {
const { executor } = execution;

if (!params.ast && !params.expression) {
if (!execution.ast && !execution.expression) {
throw new TypeError('Execution params should contain at least .ast or .expression key.');
} else if (params.ast && params.expression) {
} else if (execution.ast && execution.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.expression = execution.expression || formatExpression(execution.ast!);
const ast = execution.ast || parseExpression(this.expression);

this.state = createExecutionContainer<Output | ExpressionValueError>({
...executor.state.get(),
Expand All @@ -161,14 +151,15 @@ export class Execution<
});

this.context = {
getInitialInput: () => this.input,
variables: {},
getInitialInput: () => this.execution.params.searchContext || {},
getSessionId: () => execution.params.sessionId,
variables: execution.params.variables || {},
types: executor.getTypes(),
abortSignal: this.abortController.signal,
...(params.context || ({} as ExtraContext)),
inspectorAdapters: (params.context && params.context.inspectorAdapters
? params.context.inspectorAdapters
: createDefaultInspectorAdapters()) as InspectorAdapters,
inspectorAdapters:
execution.params.inspectorAdapters ||
((createDefaultInspectorAdapters() as any) as InspectorAdapters),
...(execution.params as any).extraContext,
};
}

Expand Down Expand Up @@ -249,10 +240,10 @@ export class Execution<
// actually have a `then` function which would be treated as a `Promise`.
const { resolvedArgs } = await this.race(this.resolveArgs(fn, input, fnArgs));
args = resolvedArgs;
timeStart = this.params.debug ? now() : 0;
timeStart = this.execution.params.debug ? now() : 0;
const output = await this.race(this.invokeFunction(fn, input, resolvedArgs));

if (this.params.debug) {
if (this.execution.params.debug) {
const timeEnd: number = now();
(link as ExpressionAstFunction).debug = {
success: true,
Expand All @@ -267,11 +258,11 @@ export class Execution<
if (getType(output) === 'error') return output;
input = output;
} catch (rawError) {
const timeEnd: number = this.params.debug ? now() : 0;
const timeEnd: number = this.execution.params.debug ? now() : 0;
const error = createError(rawError) as ExpressionValueError;
error.error.message = `[${fnName}] > ${error.error.message}`;

if (this.params.debug) {
if (this.execution.params.debug) {
(link as ExpressionAstFunction).debug = {
success: false,
fn: fn.name,
Expand Down Expand Up @@ -404,9 +395,7 @@ export class Execution<
const resolveArgFns = mapValues(argAstsWithDefaults, (asts, argName) => {
return asts.map((item: ExpressionAstExpression) => {
return async (subInput = input) => {
const output = await this.interpret(item, subInput, {
debug: this.params.debug,
});
const output = await this.interpret(item, subInput);
if (isExpressionValueError(output)) throw output.error;
const casted = this.cast(output, argDefs[argName as any].types);
return casted;
Expand Down Expand Up @@ -438,17 +427,12 @@ export class Execution<
return { resolvedArgs };
}

public async interpret<T>(
ast: ExpressionAstNode,
input: T,
options?: ExpressionExecOptions
): Promise<unknown> {
public async interpret<T>(ast: ExpressionAstNode, input: T): Promise<unknown> {
switch (getType(ast)) {
case 'expression':
const execution = this.params.executor.createExecution(
const execution = this.execution.executor.createExecution(
ast as ExpressionAstExpression,
this.context,
options
this.execution.params
);
execution.start(input);
return await execution.result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const createExecution = (
const execution = new Execution({
executor,
ast: parseExpression(expression),
context,
params: { ...context },
});
return execution;
};
Expand Down
11 changes: 2 additions & 9 deletions src/plugins/expressions/common/execution/execution_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,14 @@ import { ExpressionAstExpression } from '../ast';
* `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
> {
export class ExecutionContract<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>
) {}
constructor(protected readonly execution: Execution<Input, Output, InspectorAdapters>) {}

/**
* Cancel the execution of the expression. This will set abort signal
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/expressions/common/execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export interface ExecutionContext<Input = unknown, InspectorAdapters extends Ada
*/
search?: ExecutionContextSearch;

getSessionId: () => string | undefined;

/**
* Allows to fetch saved objects from ElasticSearch. In browser `getSavedObject`
* function is provided automatically by the Expressions plugin. On the server
Expand Down
42 changes: 17 additions & 25 deletions src/plugins/expressions/common/executor/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { functionSpecs } from '../expression_functions/specs';
import { getByAlias } from '../util';
import { SavedObjectReference } from '../../../../core/types';
import { PersistableState } from '../../../kibana_utils/common';
import { ExpressionExecutionParams } from '../service';

export interface ExpressionExecOptions {
/**
Expand Down Expand Up @@ -166,43 +167,34 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
* @param context Extra global context object that will be merged into the
* expression global context object that is provided to each function to allow side-effects.
*/
public async run<
Input,
Output,
ExtraContext extends Record<string, unknown> = Record<string, unknown>
>(
public async run<Input, Output>(
ast: string | ExpressionAstExpression,
input: Input,
context?: ExtraContext,
options?: ExpressionExecOptions
params: ExpressionExecutionParams = {}
) {
const execution = this.createExecution(ast, context, options);
const execution = this.createExecution(ast, params);
execution.start(input);
return (await execution.result) as Output;
}

public createExecution<
ExtraContext extends Record<string, unknown> = Record<string, unknown>,
Input = unknown,
Output = unknown
>(
public createExecution<Input = unknown, Output = unknown>(
ast: string | ExpressionAstExpression,
context: ExtraContext = {} as ExtraContext,
{ debug }: ExpressionExecOptions = {} as ExpressionExecOptions
): Execution<Context & ExtraContext, Input, Output> {
const params: ExecutionParams<Context & ExtraContext> = {
params: ExpressionExecutionParams = {}
): Execution<Input, Output> {
const executionParams: ExecutionParams = {
executor: this,
context: {
...this.context,
...context,
} as Context & ExtraContext,
debug,
params: {
...params,
// for canvas we are passing this in,
// canvas should be refactored to not pass any extra context in
extraContext: this.context,
} as any,
};

if (typeof ast === 'string') params.expression = ast;
else params.ast = ast;
if (typeof ast === 'string') executionParams.expression = ast;
else executionParams.ast = ast;

const execution = new Execution<Context & ExtraContext, Input, Output>(params);
const execution = new Execution<Input, Output>(executionParams);

return execution;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ export const kibana: ExpressionFunctionKibana = {

args: {},

fn(input, _, { search = {} }) {
fn(input, _, { getInitialInput }) {
const output: ExpressionValueSearchContext = {
// TODO: This spread is left here for legacy reasons, possibly Lens uses it.
// TODO: But it shouldn't be need.
...input,
type: 'kibana_context',
query: [...toArray(search.query), ...toArray((input || {}).query)],
filters: [...(search.filters || []), ...((input || {}).filters || [])],
timeRange: search.timeRange || (input ? input.timeRange : undefined),
query: [...toArray((getInitialInput() as any).query), ...toArray((input || {}).query)],
filters: [...((getInitialInput() as any).filters || []), ...((input || {}).filters || [])],
timeRange: (getInitialInput() as any).timeRange || (input ? input.timeRange : undefined),
};

return output;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('interpreter/functions#kibana', () => {
context = {
search,
getInitialInput: () => input,
getSessionId: () => undefined,
types: {},
variables: {},
abortSignal: {} as any,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('expression_functions', () => {

context = {
getInitialInput: () => {},
getSessionId: () => undefined,
types: {},
variables: { theme: themeProps },
abortSignal: {} as any,
Expand Down
Loading

0 comments on commit 90b1f5b

Please sign in to comment.