[Expressions] Fix setup and start contracts (#110841)

* Refactor executor forking to implement state inheritance
* Fix setup and start contracts typings
* Add support of named forks
* Add expressions service life-cycle assertions
This commit is contained in:
Michael Dokolin 2021-09-23 08:44:16 +02:00 committed by GitHub
parent 5b73ab1432
commit c06d604785
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 320 additions and 231 deletions

View file

@ -180,7 +180,7 @@ export class Execution<
const ast = execution.ast || parseExpression(this.expression);
this.state = createExecutionContainer({
...executor.state.get(),
...executor.state,
state: 'not-started',
ast,
});

View file

@ -49,7 +49,7 @@ export class TypesRegistry implements IRegistry<ExpressionType> {
}
public get(id: string): ExpressionType | null {
return this.executor.state.selectors.getType(id);
return this.executor.getType(id) ?? null;
}
public toJS(): Record<string, ExpressionType> {
@ -71,7 +71,7 @@ export class FunctionsRegistry implements IRegistry<ExpressionFunction> {
}
public get(id: string): ExpressionFunction | null {
return this.executor.state.selectors.getFunction(id);
return this.executor.getFunction(id) ?? null;
}
public toJS(): Record<string, ExpressionFunction> {
@ -95,22 +95,44 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
return executor;
}
public readonly state: ExecutorContainer<Context>;
public readonly container: ExecutorContainer<Context>;
/**
* @deprecated
*/
public readonly functions: FunctionsRegistry;
public readonly functions = new FunctionsRegistry(this);
/**
* @deprecated
*/
public readonly types: TypesRegistry;
public readonly types = new TypesRegistry(this);
protected parent?: Executor<Context>;
constructor(state?: ExecutorState<Context>) {
this.state = createExecutorContainer<Context>(state);
this.functions = new FunctionsRegistry(this);
this.types = new TypesRegistry(this);
this.container = createExecutorContainer<Context>(state);
}
public get state(): ExecutorState<Context> {
const parent = this.parent?.state;
const state = this.container.get();
return {
...(parent ?? {}),
...state,
types: {
...(parent?.types ?? {}),
...state.types,
},
functions: {
...(parent?.functions ?? {}),
...state.functions,
},
context: {
...(parent?.context ?? {}),
...state.context,
},
};
}
public registerFunction(
@ -119,15 +141,18 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
const fn = new ExpressionFunction(
typeof functionDefinition === 'object' ? functionDefinition : functionDefinition()
);
this.state.transitions.addFunction(fn);
this.container.transitions.addFunction(fn);
}
public getFunction(name: string): ExpressionFunction | undefined {
return this.state.get().functions[name];
return this.container.get().functions[name] ?? this.parent?.getFunction(name);
}
public getFunctions(): Record<string, ExpressionFunction> {
return { ...this.state.get().functions };
return {
...(this.parent?.getFunctions() ?? {}),
...this.container.get().functions,
};
}
public registerType(
@ -136,23 +161,30 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
const type = new ExpressionType(
typeof typeDefinition === 'object' ? typeDefinition : typeDefinition()
);
this.state.transitions.addType(type);
this.container.transitions.addType(type);
}
public getType(name: string): ExpressionType | undefined {
return this.state.get().types[name];
return this.container.get().types[name] ?? this.parent?.getType(name);
}
public getTypes(): Record<string, ExpressionType> {
return { ...this.state.get().types };
return {
...(this.parent?.getTypes() ?? {}),
...this.container.get().types,
};
}
public extendContext(extraContext: Record<string, unknown>) {
this.state.transitions.extendContext(extraContext);
this.container.transitions.extendContext(extraContext);
}
public get context(): Record<string, unknown> {
return this.state.selectors.getContext();
return {
...(this.parent?.context ?? {}),
...this.container.selectors.getContext(),
};
}
/**
@ -199,18 +231,15 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
) {
for (const link of ast.chain) {
const { function: fnName, arguments: fnArgs } = link;
const fn = getByAlias(this.state.get().functions, fnName);
const fn = getByAlias(this.getFunctions(), fnName);
if (fn) {
// if any of arguments are expressions we should migrate those first
link.arguments = mapValues(fnArgs, (asts, argName) => {
return asts.map((arg) => {
if (arg && typeof arg === 'object') {
return this.walkAst(arg, action);
}
return arg;
});
});
link.arguments = mapValues(fnArgs, (asts) =>
asts.map((arg) =>
arg != null && typeof arg === 'object' ? this.walkAst(arg, action) : arg
)
);
action(fn, link);
}
@ -275,39 +304,19 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
private migrate(ast: SerializableRecord, version: string) {
return this.walkAst(cloneDeep(ast) as ExpressionAstExpression, (fn, link) => {
if (!fn.migrations[version]) return link;
const updatedAst = fn.migrations[version](link) as ExpressionAstFunction;
link.arguments = updatedAst.arguments;
link.type = updatedAst.type;
if (!fn.migrations[version]) {
return;
}
({ arguments: link.arguments, type: link.type } = fn.migrations[version](
link
) as ExpressionAstFunction);
});
}
public fork(): Executor<Context> {
const initialState = this.state.get();
const fork = new Executor<Context>(initialState);
/**
* Synchronize registry state - make any new types, functions and context
* also available in the forked instance of `Executor`.
*/
this.state.state$.subscribe(({ types, functions, context }) => {
const state = fork.state.get();
fork.state.set({
...state,
types: {
...types,
...state.types,
},
functions: {
...functions,
...state.functions,
},
context: {
...context,
...state.context,
},
});
});
const fork = new Executor<Context>();
fork.parent = this;
return fork;
}

View file

@ -17,11 +17,16 @@ describe('ExpressionsService', () => {
const expressions = new ExpressionsService();
expect(expressions.setup()).toMatchObject({
getFunction: expect.any(Function),
getFunctions: expect.any(Function),
getRenderer: expect.any(Function),
getRenderers: expect.any(Function),
getType: expect.any(Function),
getTypes: expect.any(Function),
registerFunction: expect.any(Function),
registerType: expect.any(Function),
registerRenderer: expect.any(Function),
run: expect.any(Function),
fork: expect.any(Function),
});
});
@ -30,7 +35,16 @@ describe('ExpressionsService', () => {
expressions.setup();
expect(expressions.start()).toMatchObject({
getFunction: expect.any(Function),
getFunctions: expect.any(Function),
getRenderer: expect.any(Function),
getRenderers: expect.any(Function),
getType: expect.any(Function),
getTypes: expect.any(Function),
registerFunction: expect.any(Function),
registerType: expect.any(Function),
registerRenderer: expect.any(Function),
execute: expect.any(Function),
run: expect.any(Function),
});
});
@ -54,21 +68,21 @@ describe('ExpressionsService', () => {
const service = new ExpressionsService();
const fork = service.fork();
expect(fork.executor.state.get().types).toEqual(service.executor.state.get().types);
expect(fork.getTypes()).toEqual(service.getTypes());
});
test('fork keeps all functions of the origin service', () => {
const service = new ExpressionsService();
const fork = service.fork();
expect(fork.executor.state.get().functions).toEqual(service.executor.state.get().functions);
expect(fork.getFunctions()).toEqual(service.getFunctions());
});
test('fork keeps context of the origin service', () => {
const service = new ExpressionsService();
const fork = service.fork();
expect(fork.executor.state.get().context).toEqual(service.executor.state.get().context);
expect(fork.executor.state.context).toEqual(service.executor.state.context);
});
test('newly registered functions in origin are also available in fork', () => {
@ -82,7 +96,7 @@ describe('ExpressionsService', () => {
fn: () => {},
});
expect(fork.executor.state.get().functions).toEqual(service.executor.state.get().functions);
expect(fork.getFunctions()).toEqual(service.getFunctions());
});
test('newly registered functions in fork are NOT available in origin', () => {
@ -96,14 +110,15 @@ describe('ExpressionsService', () => {
fn: () => {},
});
expect(Object.values(fork.executor.state.get().functions)).toHaveLength(
Object.values(service.executor.state.get().functions).length + 1
expect(Object.values(fork.getFunctions())).toHaveLength(
Object.values(service.getFunctions()).length + 1
);
});
test('fork can execute an expression with newly registered function', async () => {
const service = new ExpressionsService();
const fork = service.fork();
fork.start();
service.registerFunction({
name: '__test__',
@ -118,5 +133,28 @@ describe('ExpressionsService', () => {
expect(result).toBe('123');
});
test('throw on fork if the service is already started', async () => {
const service = new ExpressionsService();
service.start();
expect(() => service.fork()).toThrow();
});
});
describe('.execute()', () => {
test('throw if the service is not started', () => {
const expressions = new ExpressionsService();
expect(() => expressions.execute('foo', null)).toThrow();
});
});
describe('.run()', () => {
test('throw if the service is not started', () => {
const expressions = new ExpressionsService();
expect(() => expressions.run('foo', null)).toThrow();
});
});
});

View file

@ -41,22 +41,86 @@ import {
* The public contract that `ExpressionsService` provides to other plugins
* in Kibana Platform in *setup* life-cycle.
*/
export type ExpressionsServiceSetup = Pick<
ExpressionsService,
| 'getFunction'
| 'getFunctions'
| 'getRenderer'
| 'getRenderers'
| 'getType'
| 'getTypes'
| 'registerFunction'
| 'registerRenderer'
| 'registerType'
| 'run'
| 'fork'
| 'extract'
| 'inject'
>;
export interface ExpressionsServiceSetup {
/**
* 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.
* @deprecated Use start contract instead.
*/
getFunction(name: string): ReturnType<Executor['getFunction']>;
/**
* Returns POJO map of all registered expression functions, where keys are
* names of the functions and values are `ExpressionFunction` instances.
* @deprecated Use start contract instead.
*/
getFunctions(): ReturnType<Executor['getFunctions']>;
/**
* Returns POJO map of all registered expression types, where keys are
* names of the types and values are `ExpressionType` instances.
* @deprecated Use start contract instead.
*/
getTypes(): ReturnType<Executor['getTypes']>;
/**
* Create a new instance of `ExpressionsService`. The new instance inherits
* all state of the original `ExpressionsService`, including all expression
* types, expression functions and context. Also, all new types and functions
* registered in the original services AFTER the forking event will be
* available in the forked instance. However, all new types and functions
* registered in the forked instances will NOT be available to the original
* service.
* @param name A fork name that can be used to get fork instance later.
*/
fork(name?: string): ExpressionsService;
/**
* Register an expression function, which will be possible to execute as
* part of the expression pipeline.
*
* Below we register a function which simply sleeps for given number of
* milliseconds to delay the execution and outputs its input as-is.
*
* ```ts
* expressions.registerFunction({
* name: 'sleep',
* args: {
* time: {
* aliases: ['_'],
* help: 'Time in milliseconds for how long to sleep',
* types: ['number'],
* },
* },
* help: '',
* fn: async (input, args, context) => {
* await new Promise(r => setTimeout(r, args.time));
* return input;
* },
* }
* ```
*
* The actual function is defined in the `fn` key. The function can be *async*.
* It receives three arguments: (1) `input` is the output of the previous function
* or the initial input of the expression if the function is first in chain;
* (2) `args` are function arguments as defined in expression string, that can
* be edited by user (e.g in case of Canvas); (3) `context` is a shared object
* passed to all functions that can be used for side-effects.
*/
registerFunction(
functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)
): void;
registerType(
typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)
): void;
registerRenderer(
definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)
): void;
}
export interface ExpressionExecutionParams {
searchContext?: SerializableRecord;
@ -97,7 +161,13 @@ export interface ExpressionsServiceStart {
* instance is an internal representation of the function in Expressions
* service - do not mutate that object.
*/
getFunction: (name: string) => ReturnType<Executor['getFunction']>;
getFunction(name: string): ReturnType<Executor['getFunction']>;
/**
* Returns POJO map of all registered expression functions, where keys are
* names of the functions and values are `ExpressionFunction` instances.
*/
getFunctions(): ReturnType<Executor['getFunctions']>;
/**
* Get a registered `ExpressionRenderer` by its name, which was registered
@ -105,7 +175,13 @@ export interface ExpressionsServiceStart {
* instance is an internal representation of the renderer in Expressions
* service - do not mutate that object.
*/
getRenderer: (name: string) => ReturnType<ExpressionRendererRegistry['get']>;
getRenderer(name: string): ReturnType<ExpressionRendererRegistry['get']>;
/**
* Returns POJO map of all registered expression renderers, where keys are
* names of the renderers and values are `ExpressionRenderer` instances.
*/
getRenderers(): ReturnType<ExpressionRendererRegistry['toJS']>;
/**
* Get a registered `ExpressionType` by its name, which was registered
@ -113,7 +189,13 @@ export interface ExpressionsServiceStart {
* instance is an internal representation of the type in Expressions
* service - do not mutate that object.
*/
getType: (name: string) => ReturnType<Executor['getType']>;
getType(name: string): ReturnType<Executor['getType']>;
/**
* Returns POJO map of all registered expression types, where keys are
* names of the types and values are `ExpressionType` instances.
*/
getTypes(): ReturnType<Executor['getTypes']>;
/**
* Executes expression string or a parsed expression AST and immediately
@ -139,34 +221,23 @@ export interface ExpressionsServiceStart {
* expressions.run('...', null, { elasticsearchClient });
* ```
*/
run: <Input, Output>(
run<Input, Output>(
ast: string | ExpressionAstExpression,
input: Input,
params?: ExpressionExecutionParams
) => Observable<ExecutionResult<Output | ExpressionValueError>>;
): Observable<ExecutionResult<Output | ExpressionValueError>>;
/**
* Starts expression execution and immediately returns `ExecutionContract`
* instance that tracks the progress of the execution and can be used to
* interact with the execution.
*/
execute: <Input = unknown, Output = unknown>(
execute<Input = unknown, Output = unknown>(
ast: string | ExpressionAstExpression,
// This any is for legacy reasons.
input: Input,
params?: ExpressionExecutionParams
) => ExecutionContract<Input, Output>;
/**
* Create a new instance of `ExpressionsService`. The new instance inherits
* all state of the original `ExpressionsService`, including all expression
* types, expression functions and context. Also, all new types and functions
* registered in the original services AFTER the forking event will be
* available in the forked instance. However, all new types and functions
* registered in the forked instances will NOT be available to the original
* service.
*/
fork: () => ExpressionsService;
): ExecutionContract<Input, Output>;
}
export interface ExpressionServiceParams {
@ -193,7 +264,19 @@ export interface ExpressionServiceParams {
*
* so that JSDoc appears in developers IDE when they use those `plugins.expressions.registerFunction(`.
*/
export class ExpressionsService implements PersistableStateService<ExpressionAstExpression> {
export class ExpressionsService
implements
PersistableStateService<ExpressionAstExpression>,
ExpressionsServiceSetup,
ExpressionsServiceStart
{
/**
* @note Workaround since the expressions service is frozen.
*/
private static started = new WeakSet<ExpressionsService>();
private children = new Map<string, ExpressionsService>();
private parent?: ExpressionsService;
public readonly executor: Executor;
public readonly renderers: ExpressionRendererRegistry;
@ -205,96 +288,87 @@ export class ExpressionsService implements PersistableStateService<ExpressionAst
this.renderers = renderers;
}
/**
* Register an expression function, which will be possible to execute as
* part of the expression pipeline.
*
* Below we register a function which simply sleeps for given number of
* milliseconds to delay the execution and outputs its input as-is.
*
* ```ts
* expressions.registerFunction({
* name: 'sleep',
* args: {
* time: {
* aliases: ['_'],
* help: 'Time in milliseconds for how long to sleep',
* types: ['number'],
* },
* },
* help: '',
* fn: async (input, args, context) => {
* await new Promise(r => setTimeout(r, args.time));
* return input;
* },
* }
* ```
*
* The actual function is defined in the `fn` key. The function can be *async*.
* It receives three arguments: (1) `input` is the output of the previous function
* or the initial input of the expression if the function is first in chain;
* (2) `args` are function arguments as defined in expression string, that can
* be edited by user (e.g in case of Canvas); (3) `context` is a shared object
* passed to all functions that can be used for side-effects.
*/
public readonly registerFunction = (
functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)
): void => this.executor.registerFunction(functionDefinition);
private isStarted(): boolean {
return !!(ExpressionsService.started.has(this) || this.parent?.isStarted());
}
public readonly registerType = (
typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)
): void => this.executor.registerType(typeDefinition);
private assertSetup() {
if (this.isStarted()) {
throw new Error('The expression service is already started and can no longer be configured.');
}
}
public readonly registerRenderer = (
definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)
): void => this.renderers.register(definition);
public readonly run: ExpressionsServiceStart['run'] = (ast, input, params) =>
this.executor.run(ast, input, params);
private assertStart() {
if (!this.isStarted()) {
throw new Error('The expressions service has not started yet.');
}
}
public readonly getFunction: ExpressionsServiceStart['getFunction'] = (name) =>
this.executor.getFunction(name);
/**
* Returns POJO map of all registered expression functions, where keys are
* names of the functions and values are `ExpressionFunction` instances.
*/
public readonly getFunctions = (): ReturnType<Executor['getFunctions']> =>
public readonly getFunctions: ExpressionsServiceStart['getFunctions'] = () =>
this.executor.getFunctions();
public readonly getRenderer: ExpressionsServiceStart['getRenderer'] = (name) =>
this.renderers.get(name);
public readonly getRenderer: ExpressionsServiceStart['getRenderer'] = (name) => {
this.assertStart();
/**
* Returns POJO map of all registered expression renderers, where keys are
* names of the renderers and values are `ExpressionRenderer` instances.
*/
public readonly getRenderers = (): ReturnType<ExpressionRendererRegistry['toJS']> =>
this.renderers.toJS();
return this.renderers.get(name);
};
public readonly getType: ExpressionsServiceStart['getType'] = (name) =>
this.executor.getType(name);
public readonly getRenderers: ExpressionsServiceStart['getRenderers'] = () => {
this.assertStart();
/**
* Returns POJO map of all registered expression types, where keys are
* names of the types and values are `ExpressionType` instances.
*/
public readonly getTypes = (): ReturnType<Executor['getTypes']> => this.executor.getTypes();
return this.renderers.toJS();
};
public readonly execute: ExpressionsServiceStart['execute'] = ((ast, input, params) => {
const execution = this.executor.createExecution(ast, params);
execution.start(input);
return execution.contract;
}) as ExpressionsServiceStart['execute'];
public readonly getType: ExpressionsServiceStart['getType'] = (name) => {
this.assertStart();
return this.executor.getType(name);
};
public readonly getTypes: ExpressionsServiceStart['getTypes'] = () => this.executor.getTypes();
public readonly registerFunction: ExpressionsServiceSetup['registerFunction'] = (
functionDefinition
) => this.executor.registerFunction(functionDefinition);
public readonly registerType: ExpressionsServiceSetup['registerType'] = (typeDefinition) =>
this.executor.registerType(typeDefinition);
public readonly registerRenderer: ExpressionsServiceSetup['registerRenderer'] = (definition) =>
this.renderers.register(definition);
public readonly fork: ExpressionsServiceSetup['fork'] = (name) => {
this.assertSetup();
public readonly fork = () => {
const executor = this.executor.fork();
const renderers = this.renderers;
const fork = new (this.constructor as typeof ExpressionsService)({ executor, renderers });
fork.parent = this;
if (name) {
this.children.set(name, fork);
}
return fork;
};
public readonly execute: ExpressionsServiceStart['execute'] = ((ast, input, params) => {
this.assertStart();
const execution = this.executor.createExecution(ast, params);
execution.start(input);
return execution.contract;
}) as ExpressionsServiceStart['execute'];
public readonly run: ExpressionsServiceStart['run'] = (ast, input, params) => {
this.assertStart();
return this.executor.run(ast, input, params);
};
/**
* Extracts telemetry from expression AST
* @param state expression AST to extract references from
@ -371,8 +445,12 @@ export class ExpressionsService implements PersistableStateService<ExpressionAst
* same contract on server-side and browser-side.
*/
public start(...args: unknown[]): ExpressionsServiceStart {
ExpressionsService.started.add(this);
return this;
}
public stop() {}
public stop() {
ExpressionsService.started.delete(this);
}
}

View file

@ -52,6 +52,8 @@ jest.mock('./services', () => {
service.registerFunction(func);
}
service.start();
const moduleMock = {
__execution: undefined,
__getLastExecution: () => moduleMock.__execution,

View file

@ -16,19 +16,13 @@ export type Start = jest.Mocked<ExpressionsStart>;
const createSetupContract = (): Setup => {
const setupContract: Setup = {
extract: jest.fn(),
fork: jest.fn(),
getFunction: jest.fn(),
getFunctions: jest.fn(),
getRenderer: jest.fn(),
getRenderers: jest.fn(),
getType: jest.fn(),
getTypes: jest.fn(),
inject: jest.fn(),
registerFunction: jest.fn(),
registerRenderer: jest.fn(),
registerType: jest.fn(),
run: jest.fn(),
};
return setupContract;
};
@ -38,10 +32,12 @@ const createStartContract = (): Start => {
execute: jest.fn(),
ExpressionLoader: jest.fn(),
ExpressionRenderHandler: jest.fn(),
fork: jest.fn(),
getFunction: jest.fn(),
getFunctions: jest.fn(),
getRenderer: jest.fn(),
getRenderers: jest.fn(),
getType: jest.fn(),
getTypes: jest.fn(),
loader: jest.fn(),
ReactExpressionRenderer: jest.fn((props) => <></>),
render: jest.fn(),

View file

@ -32,16 +32,6 @@ describe('ExpressionsPublicPlugin', () => {
expect(setup.getFunctions().add.name).toBe('add');
});
});
describe('.run()', () => {
test('can execute simple expression', async () => {
const { setup } = await expressionsPluginMock.createPlugin();
const { result } = await setup
.run('var_set name="foo" value="bar" | var name="foo"', null)
.toPromise();
expect(result).toBe('bar');
});
});
});
describe('start contract', () => {

View file

@ -13,37 +13,24 @@ import { coreMock } from '../../../core/server/mocks';
export type Setup = jest.Mocked<ExpressionsServerSetup>;
export type Start = jest.Mocked<ExpressionsServerStart>;
const createSetupContract = (): Setup => {
const setupContract: Setup = {
extract: jest.fn(),
fork: jest.fn(),
getFunction: jest.fn(),
getFunctions: jest.fn(),
getRenderer: jest.fn(),
getRenderers: jest.fn(),
getType: jest.fn(),
getTypes: jest.fn(),
inject: jest.fn(),
registerFunction: jest.fn(),
registerRenderer: jest.fn(),
registerType: jest.fn(),
run: jest.fn(),
};
return setupContract;
};
const createSetupContract = (): Setup => ({
fork: jest.fn(),
getFunction: jest.fn(),
getFunctions: jest.fn(),
getTypes: jest.fn(),
registerFunction: jest.fn(),
registerRenderer: jest.fn(),
registerType: jest.fn(),
});
const createStartContract = (): Start => {
const startContract: Start = {
const createStartContract = (): Start =>
({
execute: jest.fn(),
fork: jest.fn(),
getFunction: jest.fn(),
getRenderer: jest.fn(),
getType: jest.fn(),
run: jest.fn(),
};
return startContract;
};
} as unknown as Start);
const createPlugin = async () => {
const pluginInitializerContext = coreMock.createPluginInitializerContext();

View file

@ -24,15 +24,5 @@ describe('ExpressionsServerPlugin', () => {
expect(setup.getFunctions().add.name).toBe('add');
});
});
describe('.run()', () => {
test('can execute simple expression', async () => {
const { setup } = await expressionsPluginMock.createPlugin();
const { result } = await setup
.run('var_set name="foo" value="bar" | var name="foo"', null)
.toPromise();
expect(result).toBe('bar');
});
});
});
});

View file

@ -98,11 +98,10 @@ export const initializeCanvas = async (
setupPlugins: CanvasSetupDeps,
startPlugins: CanvasStartDeps,
registries: SetupRegistries,
appUpdater: BehaviorSubject<AppUpdater>,
pluginServices: PluginServices<CanvasPluginServices>
appUpdater: BehaviorSubject<AppUpdater>
) => {
await startLegacyServices(coreSetup, coreStart, setupPlugins, startPlugins, appUpdater);
const { expressions } = pluginServices.getServices();
const { expressions } = setupPlugins;
// Adding these functions here instead of in plugin.ts.
// Some of these functions have deep dependencies into Canvas, which was bulking up the size

View file

@ -132,8 +132,7 @@ export class CanvasPlugin
setupPlugins,
startPlugins,
registries,
this.appUpdater,
pluginServices
this.appUpdater
);
const unmount = renderApp({ coreStart, startPlugins, params, canvasStore, pluginServices });

View file

@ -5,6 +5,6 @@
* 2.0.
*/
import { ExpressionsService } from '../../../../../src/plugins/expressions/public';
import { ExpressionsServiceStart } from '../../../../../src/plugins/expressions/public';
export type CanvasExpressionsService = ExpressionsService;
export type CanvasExpressionsService = ExpressionsServiceStart;

View file

@ -16,4 +16,4 @@ export type CanvasExpressionsServiceFactory = KibanaPluginServiceFactory<
>;
export const expressionsServiceFactory: CanvasExpressionsServiceFactory = ({ startPlugins }) =>
startPlugins.expressions.fork();
startPlugins.expressions;

View file

@ -6,14 +6,15 @@
*/
import { fromExpression, toExpression } from '@kbn/interpreter/common';
import { PersistableStateService } from '../../../../../src/plugins/kibana_utils/common';
import { SavedObjectReference } from '../../../../../src/core/server';
import { WorkpadAttributes } from '../routes/workpad/workpad_attributes';
import { ExpressionsServerSetup } from '../../../../../src/plugins/expressions/server';
import type { ExpressionAstExpression } from '../../../../../src/plugins/expressions';
export const extractReferences = (
workpad: WorkpadAttributes,
expressions: ExpressionsServerSetup
expressions: PersistableStateService<ExpressionAstExpression>
): { workpad: WorkpadAttributes; references: SavedObjectReference[] } => {
// We need to find every element in the workpad and extract references
const references: SavedObjectReference[] = [];
@ -42,7 +43,7 @@ export const extractReferences = (
export const injectReferences = (
workpad: WorkpadAttributes,
references: SavedObjectReference[],
expressions: ExpressionsServerSetup
expressions: PersistableStateService<ExpressionAstExpression>
) => {
const pages = workpad.pages.map((page) => {
const elements = page.elements.map((element) => {