mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[expressions] changes fork to use namespacing (#125957)
This commit is contained in:
parent
8ef8e65ae1
commit
8ee46c2f31
22 changed files with 299 additions and 92 deletions
|
@ -7,7 +7,8 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import type { ExpressionsService, ExpressionsServiceSetup } from 'src/plugins/expressions';
|
||||
import type { ExpressionsServiceSetup } from 'src/plugins/expressions';
|
||||
import { ExpressionsServiceFork } from 'src/plugins/expressions/common/service/expressions_fork';
|
||||
import { AppMountParameters, AppNavLinkStatus, CoreSetup, Plugin } from '../../../src/core/public';
|
||||
import type { DeveloperExamplesSetup } from '../../developer_examples/public';
|
||||
import { App, ExpressionsContext } from './app';
|
||||
|
@ -19,13 +20,15 @@ interface SetupDeps {
|
|||
}
|
||||
|
||||
export class PartialResultsExamplePlugin implements Plugin<void, void, SetupDeps> {
|
||||
private expressions?: ExpressionsService;
|
||||
private expressions?: ExpressionsServiceFork;
|
||||
|
||||
setup({ application }: CoreSetup, { expressions, developerExamples }: SetupDeps) {
|
||||
this.expressions = expressions.fork();
|
||||
this.expressions.registerFunction(countEvent);
|
||||
this.expressions.registerFunction(getEvents);
|
||||
this.expressions.registerFunction(pluck);
|
||||
this.expressions = expressions.fork('test');
|
||||
const expressionsSetup = this.expressions.setup();
|
||||
expressionsSetup.registerFunction(countEvent);
|
||||
expressionsSetup.registerFunction(getEvents);
|
||||
expressionsSetup.registerFunction(pluck);
|
||||
const expressionsStart = this.expressions.start();
|
||||
|
||||
application.register({
|
||||
id: 'partialResultsExample',
|
||||
|
@ -33,7 +36,7 @@ export class PartialResultsExamplePlugin implements Plugin<void, void, SetupDeps
|
|||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
mount: async ({ element }: AppMountParameters) => {
|
||||
ReactDOM.render(
|
||||
<ExpressionsContext.Provider value={this.expressions}>
|
||||
<ExpressionsContext.Provider value={expressionsStart}>
|
||||
<App />
|
||||
</ExpressionsContext.Provider>,
|
||||
element
|
||||
|
|
|
@ -300,7 +300,11 @@ export class Execution<
|
|||
...(chainArr.map((link) =>
|
||||
switchMap((currentInput) => {
|
||||
const { function: fnName, arguments: fnArgs } = link;
|
||||
const fn = getByAlias(this.state.get().functions, fnName);
|
||||
const fn = getByAlias(
|
||||
this.state.get().functions,
|
||||
fnName,
|
||||
this.execution.params.namespace
|
||||
);
|
||||
|
||||
if (!fn) {
|
||||
throw createError({
|
||||
|
|
|
@ -20,7 +20,7 @@ import { ExpressionType } from '../expression_types/expression_type';
|
|||
import { AnyExpressionTypeDefinition } from '../expression_types/types';
|
||||
import { ExpressionAstExpression, ExpressionAstFunction } from '../ast';
|
||||
import { ExpressionValueError, typeSpecs } from '../expression_types/specs';
|
||||
import { getByAlias } from '../util';
|
||||
import { ALL_NAMESPACES, getByAlias } from '../util';
|
||||
import { SavedObjectReference } from '../../../../core/types';
|
||||
import {
|
||||
MigrateFunctionsObject,
|
||||
|
@ -146,15 +146,17 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
|||
this.container.transitions.addFunction(fn);
|
||||
}
|
||||
|
||||
public getFunction(name: string): ExpressionFunction | undefined {
|
||||
return this.container.get().functions[name] ?? this.parent?.getFunction(name);
|
||||
public getFunction(name: string, namespace?: string): ExpressionFunction | undefined {
|
||||
const fn = this.container.get().functions[name];
|
||||
if (!fn?.namespace || fn.namespace === namespace) return fn;
|
||||
}
|
||||
|
||||
public getFunctions(): Record<string, ExpressionFunction> {
|
||||
return {
|
||||
...(this.parent?.getFunctions() ?? {}),
|
||||
...this.container.get().functions,
|
||||
};
|
||||
public getFunctions(namespace?: string): Record<string, ExpressionFunction> {
|
||||
const fns = Object.entries(this.container.get().functions);
|
||||
const filtered = fns.filter(
|
||||
([key, value]) => !value.namespace || value.namespace === namespace
|
||||
);
|
||||
return Object.fromEntries(filtered);
|
||||
}
|
||||
|
||||
public registerType(
|
||||
|
@ -222,9 +224,10 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
|||
ast: ExpressionAstExpression,
|
||||
action: (fn: ExpressionFunction, link: ExpressionAstFunction) => void
|
||||
) {
|
||||
const functions = this.container.get().functions;
|
||||
for (const link of ast.chain) {
|
||||
const { function: fnName, arguments: fnArgs } = link;
|
||||
const fn = getByAlias(this.getFunctions(), fnName);
|
||||
const fn = getByAlias(functions, fnName, ALL_NAMESPACES);
|
||||
|
||||
if (fn) {
|
||||
// if any of arguments are expressions we should migrate those first
|
||||
|
@ -249,12 +252,13 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
|||
) => ExpressionAstFunction | ExpressionAstExpression
|
||||
): ExpressionAstExpression {
|
||||
let additionalFunctions = 0;
|
||||
const functions = this.container.get().functions;
|
||||
return (
|
||||
ast.chain.reduce<ExpressionAstExpression>(
|
||||
(newAst: ExpressionAstExpression, funcAst: ExpressionAstFunction, index: number) => {
|
||||
const realIndex = index + additionalFunctions;
|
||||
const { function: fnName, arguments: fnArgs } = funcAst;
|
||||
const fn = getByAlias(this.getFunctions(), fnName);
|
||||
const fn = getByAlias(functions, fnName, ALL_NAMESPACES);
|
||||
if (!fn) {
|
||||
return newAst;
|
||||
}
|
||||
|
@ -331,7 +335,7 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
|||
|
||||
public getAllMigrations() {
|
||||
const uniqueVersions = new Set(
|
||||
Object.values(this.getFunctions())
|
||||
Object.values(this.container.get().functions)
|
||||
.map((fn) => Object.keys(fn.migrations))
|
||||
.flat(1)
|
||||
);
|
||||
|
|
|
@ -24,6 +24,8 @@ export class ExpressionFunction implements PersistableState<ExpressionAstFunctio
|
|||
*/
|
||||
name: string;
|
||||
|
||||
namespace?: string;
|
||||
|
||||
/**
|
||||
* Aliases that can be used instead of `name`.
|
||||
*/
|
||||
|
@ -90,9 +92,11 @@ export class ExpressionFunction implements PersistableState<ExpressionAstFunctio
|
|||
inject,
|
||||
extract,
|
||||
migrations,
|
||||
namespace,
|
||||
} = functionDefinition;
|
||||
|
||||
this.name = name;
|
||||
this.namespace = namespace;
|
||||
this.type = type;
|
||||
this.aliases = aliases || [];
|
||||
this.fn = fn as ExpressionFunction['fn'];
|
||||
|
|
|
@ -44,6 +44,8 @@ export interface ExpressionFunctionDefinition<
|
|||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
namespace?: string;
|
||||
|
||||
/**
|
||||
* Name of type of value this function outputs.
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ExpressionRenderDefinition } from './types';
|
|||
|
||||
export class ExpressionRenderer<Config = unknown> {
|
||||
public readonly name: string;
|
||||
public readonly namespace?: string;
|
||||
public readonly displayName: string;
|
||||
public readonly help: string;
|
||||
public readonly validate: () => void | Error;
|
||||
|
@ -17,9 +18,10 @@ export class ExpressionRenderer<Config = unknown> {
|
|||
public readonly render: ExpressionRenderDefinition<Config>['render'];
|
||||
|
||||
constructor(config: ExpressionRenderDefinition<Config>) {
|
||||
const { name, displayName, help, validate, reuseDomNode, render } = config;
|
||||
const { name, displayName, help, validate, reuseDomNode, render, namespace } = config;
|
||||
|
||||
this.name = name;
|
||||
this.namespace = namespace;
|
||||
this.displayName = displayName || name;
|
||||
this.help = help || '';
|
||||
this.validate = validate || (() => {});
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface ExpressionRenderDefinition<Config = unknown> {
|
|||
* function that is used to create the `type: render` object.
|
||||
*/
|
||||
name: string;
|
||||
namespace?: string;
|
||||
|
||||
/**
|
||||
* A user friendly name of the renderer as will be displayed to user in UI.
|
||||
|
|
|
@ -12,7 +12,7 @@ import { getType } from './get_type';
|
|||
|
||||
export class ExpressionType {
|
||||
name: string;
|
||||
|
||||
namespace?: string;
|
||||
/**
|
||||
* A short help text.
|
||||
*/
|
||||
|
@ -33,9 +33,10 @@ export class ExpressionType {
|
|||
private readonly definition: AnyExpressionTypeDefinition;
|
||||
|
||||
constructor(definition: AnyExpressionTypeDefinition) {
|
||||
const { name, help, deserialize, serialize, validate } = definition;
|
||||
const { name, help, deserialize, serialize, validate, namespace } = definition;
|
||||
|
||||
this.name = name;
|
||||
this.namespace = namespace;
|
||||
this.help = help || '';
|
||||
this.validate = validate || (() => {});
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ export interface ExpressionTypeDefinition<
|
|||
SerializedType = undefined
|
||||
> {
|
||||
name: Name;
|
||||
namespace?: string;
|
||||
validate?(type: unknown): void | Error;
|
||||
serialize?(type: Value): SerializedType;
|
||||
deserialize?(type: SerializedType): Value;
|
||||
|
|
140
src/plugins/expressions/common/service/expressions_fork.ts
Normal file
140
src/plugins/expressions/common/service/expressions_fork.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
ExpressionExecutionParams,
|
||||
ExpressionsService,
|
||||
ExpressionsServiceSetup,
|
||||
ExpressionsServiceStart,
|
||||
} from '.';
|
||||
import { ExpressionAstExpression } from '../ast';
|
||||
import { AnyExpressionFunctionDefinition } from '../expression_functions';
|
||||
import { AnyExpressionTypeDefinition } from '../expression_types';
|
||||
import { AnyExpressionRenderDefinition } from '../expression_renderers';
|
||||
|
||||
export interface ExpressionServiceFork {
|
||||
setup(): ExpressionsServiceSetup;
|
||||
start(): ExpressionsServiceStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* `ExpressionsService` class is used for multiple purposes:
|
||||
*
|
||||
* 1. It implements the same Expressions service that can be used on both:
|
||||
* (1) server-side and (2) browser-side.
|
||||
* 2. It implements the same Expressions service that users can fork/clone,
|
||||
* thus have their own instance of the Expressions plugin.
|
||||
* 3. `ExpressionsService` defines the public contracts of *setup* and *start*
|
||||
* Kibana Platform life-cycles for ease-of-use on server-side and browser-side.
|
||||
* 4. `ExpressionsService` creates a bound version of all exported contract functions.
|
||||
* 5. Functions are bound the way there are:
|
||||
*
|
||||
* ```ts
|
||||
* registerFunction = (...args: Parameters<Executor['registerFunction']>
|
||||
* ): ReturnType<Executor['registerFunction']> => this.executor.registerFunction(...args);
|
||||
* ```
|
||||
*
|
||||
* so that JSDoc appears in developers IDE when they use those `plugins.expressions.registerFunction(`.
|
||||
*/
|
||||
export class ExpressionsServiceFork implements ExpressionServiceFork {
|
||||
/**
|
||||
* @note Workaround since the expressions service is frozen.
|
||||
*/
|
||||
constructor(private namespace: string, private expressionsService: ExpressionsService) {
|
||||
this.registerFunction = this.registerFunction.bind(this);
|
||||
this.registerRenderer = this.registerRenderer.bind(this);
|
||||
this.registerType = this.registerType.bind(this);
|
||||
this.run = this.run.bind(this);
|
||||
this.execute = this.execute.bind(this);
|
||||
this.getFunction = this.getFunction.bind(this);
|
||||
this.getFunctions = this.getFunctions.bind(this);
|
||||
}
|
||||
|
||||
protected registerFunction(
|
||||
definition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)
|
||||
) {
|
||||
if (typeof definition === 'function') definition = definition();
|
||||
return this.expressionsService.registerFunction({
|
||||
...definition,
|
||||
namespace: this.namespace,
|
||||
});
|
||||
}
|
||||
|
||||
protected registerRenderer(
|
||||
definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)
|
||||
) {
|
||||
if (typeof definition === 'function') definition = definition();
|
||||
return this.expressionsService.registerRenderer({
|
||||
...definition,
|
||||
namespace: this.namespace,
|
||||
});
|
||||
}
|
||||
|
||||
protected registerType(
|
||||
definition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)
|
||||
) {
|
||||
if (typeof definition === 'function') definition = definition();
|
||||
return this.expressionsService.registerType({ ...definition, namespace: this.namespace });
|
||||
}
|
||||
|
||||
protected run<Input, Output>(
|
||||
ast: string | ExpressionAstExpression,
|
||||
input: Input,
|
||||
params?: ExpressionExecutionParams
|
||||
) {
|
||||
return this.expressionsService.run<Input, Output>(ast, input, {
|
||||
...params,
|
||||
namespace: this.namespace,
|
||||
});
|
||||
}
|
||||
|
||||
protected execute<Input = unknown, Output = unknown>(
|
||||
ast: string | ExpressionAstExpression,
|
||||
input: Input,
|
||||
params?: ExpressionExecutionParams
|
||||
) {
|
||||
return this.expressionsService.execute<Input, Output>(ast, input, {
|
||||
...params,
|
||||
namespace: this.namespace,
|
||||
});
|
||||
}
|
||||
|
||||
protected getFunction(name: string) {
|
||||
return this.expressionsService.getFunction(name, this.namespace);
|
||||
}
|
||||
|
||||
protected getFunctions() {
|
||||
return this.expressionsService.getFunctions(this.namespace);
|
||||
}
|
||||
/**
|
||||
* Returns Kibana Platform *setup* life-cycle contract. Useful to return the
|
||||
* same contract on server-side and browser-side.
|
||||
*/
|
||||
public setup(): ExpressionsServiceSetup {
|
||||
return {
|
||||
...this.expressionsService,
|
||||
registerFunction: this.registerFunction,
|
||||
registerRenderer: this.registerRenderer,
|
||||
registerType: this.registerType,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Kibana Platform *start* life-cycle contract. Useful to return the
|
||||
* same contract on server-side and browser-side.
|
||||
*/
|
||||
public start(): ExpressionsServiceStart {
|
||||
return {
|
||||
...this.expressionsService,
|
||||
run: this.run,
|
||||
execute: this.execute,
|
||||
getFunction: this.getFunction,
|
||||
getFunctions: this.getFunctions,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import { ExpressionsService } from './expressions_services';
|
||||
import { ExpressionsServiceFork } from './expressions_fork';
|
||||
|
||||
describe('ExpressionsService', () => {
|
||||
test('can instantiate', () => {
|
||||
|
@ -58,36 +59,31 @@ describe('ExpressionsService', () => {
|
|||
describe('.fork()', () => {
|
||||
test('returns a new ExpressionsService instance', () => {
|
||||
const service = new ExpressionsService();
|
||||
const fork = service.fork();
|
||||
const fork = service.fork('test');
|
||||
|
||||
expect(fork).not.toBe(service);
|
||||
expect(fork).toBeInstanceOf(ExpressionsService);
|
||||
expect(fork).toBeInstanceOf(ExpressionsServiceFork);
|
||||
});
|
||||
|
||||
test('fork keeps all types of the origin service', () => {
|
||||
const service = new ExpressionsService();
|
||||
const fork = service.fork();
|
||||
const fork = service.fork('test');
|
||||
const forkStart = fork.start();
|
||||
|
||||
expect(fork.getTypes()).toEqual(service.getTypes());
|
||||
expect(forkStart.getTypes()).toEqual(service.getTypes());
|
||||
});
|
||||
|
||||
test('fork keeps all functions of the origin service', () => {
|
||||
const service = new ExpressionsService();
|
||||
const fork = service.fork();
|
||||
const fork = service.fork('test');
|
||||
const forkStart = fork.start();
|
||||
|
||||
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.context).toEqual(service.executor.state.context);
|
||||
expect(forkStart.getFunctions()).toEqual(service.getFunctions());
|
||||
});
|
||||
|
||||
test('newly registered functions in origin are also available in fork', () => {
|
||||
const service = new ExpressionsService();
|
||||
const fork = service.fork();
|
||||
const fork = service.fork('test');
|
||||
|
||||
service.registerFunction({
|
||||
name: '__test__',
|
||||
|
@ -96,29 +92,35 @@ describe('ExpressionsService', () => {
|
|||
fn: () => {},
|
||||
});
|
||||
|
||||
expect(fork.getFunctions()).toEqual(service.getFunctions());
|
||||
const forkStart = fork.start();
|
||||
|
||||
expect(forkStart.getFunctions()).toEqual(service.getFunctions());
|
||||
});
|
||||
|
||||
test('newly registered functions in fork are NOT available in origin', () => {
|
||||
const service = new ExpressionsService();
|
||||
const fork = service.fork();
|
||||
const fork = service.fork('test');
|
||||
const forkSetup = fork.setup();
|
||||
|
||||
fork.registerFunction({
|
||||
forkSetup.registerFunction({
|
||||
name: '__test__',
|
||||
args: {},
|
||||
help: '',
|
||||
fn: () => {},
|
||||
});
|
||||
|
||||
expect(Object.values(fork.getFunctions())).toHaveLength(
|
||||
const forkStart = fork.start();
|
||||
|
||||
expect(Object.values(forkStart.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();
|
||||
const fork = service.fork('test');
|
||||
service.start();
|
||||
const forkStart = fork.start();
|
||||
|
||||
service.registerFunction({
|
||||
name: '__test__',
|
||||
|
@ -129,7 +131,7 @@ describe('ExpressionsService', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const { result } = await fork.run('__test__', null).toPromise();
|
||||
const { result } = await forkStart.run('__test__', null).toPromise();
|
||||
|
||||
expect(result).toBe('123');
|
||||
});
|
||||
|
@ -138,7 +140,7 @@ describe('ExpressionsService', () => {
|
|||
const service = new ExpressionsService();
|
||||
service.start();
|
||||
|
||||
expect(() => service.fork()).toThrow();
|
||||
expect(() => service.fork('test')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -19,7 +19,11 @@ import { ExecutionContract, ExecutionResult } from '../execution';
|
|||
import { AnyExpressionTypeDefinition, ExpressionValueError } from '../expression_types';
|
||||
import { AnyExpressionFunctionDefinition } from '../expression_functions';
|
||||
import { SavedObjectReference } from '../../../../core/types';
|
||||
import { PersistableStateService, VersionedState } from '../../../kibana_utils/common';
|
||||
import {
|
||||
MigrateFunctionsObject,
|
||||
PersistableStateService,
|
||||
VersionedState,
|
||||
} from '../../../kibana_utils/common';
|
||||
import { Adapters } from '../../../inspector/common/adapters';
|
||||
import {
|
||||
clog,
|
||||
|
@ -36,6 +40,7 @@ import {
|
|||
math,
|
||||
mathColumn,
|
||||
} from '../expression_functions';
|
||||
import { ExpressionsServiceFork } from './expressions_fork';
|
||||
|
||||
/**
|
||||
* The public contract that `ExpressionsService` provides to other plugins
|
||||
|
@ -49,14 +54,14 @@ export interface ExpressionsServiceSetup {
|
|||
* service - do not mutate that object.
|
||||
* @deprecated Use start contract instead.
|
||||
*/
|
||||
getFunction(name: string): ReturnType<Executor['getFunction']>;
|
||||
getFunction(name: string, namespace?: 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']>;
|
||||
getFunctions(namespace?: string): ReturnType<Executor['getFunctions']>;
|
||||
|
||||
/**
|
||||
* Returns POJO map of all registered expression types, where keys are
|
||||
|
@ -75,7 +80,7 @@ export interface ExpressionsServiceSetup {
|
|||
* service.
|
||||
* @param name A fork name that can be used to get fork instance later.
|
||||
*/
|
||||
fork(name?: string): ExpressionsService;
|
||||
fork(namespace: string): ExpressionsServiceFork;
|
||||
|
||||
/**
|
||||
* Register an expression function, which will be possible to execute as
|
||||
|
@ -120,6 +125,8 @@ export interface ExpressionsServiceSetup {
|
|||
registerRenderer(
|
||||
definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)
|
||||
): void;
|
||||
|
||||
getAllMigrations(): MigrateFunctionsObject;
|
||||
}
|
||||
|
||||
export interface ExpressionExecutionParams {
|
||||
|
@ -148,6 +155,8 @@ export interface ExpressionExecutionParams {
|
|||
inspectorAdapters?: Adapters;
|
||||
|
||||
executionContext?: KibanaExecutionContext;
|
||||
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,13 +170,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, namespace?: 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']>;
|
||||
getFunctions(namespace?: string): ReturnType<Executor['getFunctions']>;
|
||||
|
||||
/**
|
||||
* Get a registered `ExpressionRenderer` by its name, which was registered
|
||||
|
@ -238,6 +247,23 @@ export interface ExpressionsServiceStart {
|
|||
input: Input,
|
||||
params?: ExpressionExecutionParams
|
||||
): ExecutionContract<Input, Output>;
|
||||
|
||||
extract(state: ExpressionAstExpression): {
|
||||
state: ExpressionAstExpression;
|
||||
references: SavedObjectReference[];
|
||||
};
|
||||
|
||||
inject(
|
||||
state: ExpressionAstExpression,
|
||||
references: SavedObjectReference[]
|
||||
): ExpressionAstExpression;
|
||||
|
||||
telemetry(
|
||||
state: ExpressionAstExpression,
|
||||
telemetryData: Record<string, unknown>
|
||||
): Record<string, unknown>;
|
||||
|
||||
getAllMigrations(): MigrateFunctionsObject;
|
||||
}
|
||||
|
||||
export interface ExpressionServiceParams {
|
||||
|
@ -274,9 +300,6 @@ export class ExpressionsService
|
|||
* @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;
|
||||
|
||||
|
@ -289,7 +312,7 @@ export class ExpressionsService
|
|||
}
|
||||
|
||||
private isStarted(): boolean {
|
||||
return !!(ExpressionsService.started.has(this) || this.parent?.isStarted());
|
||||
return ExpressionsService.started.has(this);
|
||||
}
|
||||
|
||||
private assertSetup() {
|
||||
|
@ -304,11 +327,11 @@ export class ExpressionsService
|
|||
}
|
||||
}
|
||||
|
||||
public readonly getFunction: ExpressionsServiceStart['getFunction'] = (name) =>
|
||||
this.executor.getFunction(name);
|
||||
public readonly getFunction: ExpressionsServiceStart['getFunction'] = (name, namespace) =>
|
||||
this.executor.getFunction(name, namespace);
|
||||
|
||||
public readonly getFunctions: ExpressionsServiceStart['getFunctions'] = () =>
|
||||
this.executor.getFunctions();
|
||||
public readonly getFunctions: ExpressionsServiceStart['getFunctions'] = (namespace) =>
|
||||
this.executor.getFunctions(namespace);
|
||||
|
||||
public readonly getRenderer: ExpressionsServiceStart['getRenderer'] = (name) => {
|
||||
this.assertStart();
|
||||
|
@ -342,16 +365,7 @@ export class ExpressionsService
|
|||
|
||||
public readonly fork: ExpressionsServiceSetup['fork'] = (name) => {
|
||||
this.assertSetup();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const fork = new ExpressionsServiceFork(name, this);
|
||||
return fork;
|
||||
};
|
||||
|
||||
|
|
|
@ -11,16 +11,33 @@
|
|||
* the given object/array for a case-insensitive match, which could be either the
|
||||
* `name` itself, or something under the `aliases` property.
|
||||
*/
|
||||
export function getByAlias<T extends { name?: string; aliases?: string[] }>(
|
||||
|
||||
export const ALL_NAMESPACES = '*';
|
||||
|
||||
interface Node {
|
||||
name?: string;
|
||||
aliases?: string[];
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export function getByAlias<T extends Node>(
|
||||
node: T[] | Record<string, T>,
|
||||
nodeName: string
|
||||
nodeName: string,
|
||||
nodeNamespace?: string
|
||||
): T | undefined {
|
||||
const lowerCaseName = nodeName.toLowerCase();
|
||||
return Object.values(node).find(({ name, aliases }) => {
|
||||
return Object.values(node).find(({ name, aliases, namespace }) => {
|
||||
if (!name) return false;
|
||||
if (name.toLowerCase() === lowerCaseName) return true;
|
||||
if (name.toLowerCase() === lowerCaseName) {
|
||||
if (!namespace || nodeNamespace === ALL_NAMESPACES || namespace === nodeNamespace) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (aliases || []).some((alias) => {
|
||||
return alias.toLowerCase() === lowerCaseName;
|
||||
return (
|
||||
alias.toLowerCase() === lowerCaseName &&
|
||||
(!namespace || nodeNamespace === ALL_NAMESPACES || namespace === nodeNamespace)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ const createSetupContract = (): Setup => {
|
|||
registerFunction: jest.fn(),
|
||||
registerRenderer: jest.fn(),
|
||||
registerType: jest.fn(),
|
||||
getAllMigrations: jest.fn(),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
@ -40,6 +41,10 @@ const createStartContract = (): Start => {
|
|||
render: jest.fn(),
|
||||
ReactExpressionRenderer: jest.fn((props) => <></>),
|
||||
run: jest.fn(),
|
||||
telemetry: jest.fn(),
|
||||
extract: jest.fn(),
|
||||
inject: jest.fn(),
|
||||
getAllMigrations: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { expressionsPluginMock } from './mocks';
|
||||
import { add } from '../common/test_helpers/expression_functions/add';
|
||||
import { ExpressionsService } from './services';
|
||||
import { ExpressionsServiceFork } from '../common/service/expressions_fork';
|
||||
|
||||
describe('ExpressionsPublicPlugin', () => {
|
||||
test('can instantiate from mocks', async () => {
|
||||
|
@ -19,9 +19,9 @@ describe('ExpressionsPublicPlugin', () => {
|
|||
describe('setup contract', () => {
|
||||
test('.fork() method returns ExpressionsService', async () => {
|
||||
const { setup } = await expressionsPluginMock.createPlugin();
|
||||
const fork = setup.fork();
|
||||
const fork = setup.fork('test');
|
||||
|
||||
expect(fork).toBeInstanceOf(ExpressionsService);
|
||||
expect(fork).toBeInstanceOf(ExpressionsServiceFork);
|
||||
});
|
||||
|
||||
describe('.registerFunction()', () => {
|
||||
|
|
|
@ -21,6 +21,7 @@ const createSetupContract = (): Setup => ({
|
|||
registerFunction: jest.fn(),
|
||||
registerRenderer: jest.fn(),
|
||||
registerType: jest.fn(),
|
||||
getAllMigrations: jest.fn(),
|
||||
});
|
||||
|
||||
const createStartContract = (): Start =>
|
||||
|
@ -30,6 +31,10 @@ const createStartContract = (): Start =>
|
|||
getRenderer: jest.fn(),
|
||||
getType: jest.fn(),
|
||||
run: jest.fn(),
|
||||
telemetry: jest.fn(),
|
||||
extract: jest.fn(),
|
||||
inject: jest.fn(),
|
||||
getAllMigrations: jest.fn(),
|
||||
} as unknown as Start);
|
||||
|
||||
const createPlugin = async () => {
|
||||
|
|
|
@ -52,7 +52,7 @@ export class ExpressionsService {
|
|||
context?: ExpressionExecutionParams
|
||||
): Promise<ExpressionValue> {
|
||||
return await this.expressions
|
||||
.execute(ast, input, context)
|
||||
.execute(ast, input, { ...context, namespace: 'canvas' })
|
||||
.getData()
|
||||
.pipe(pluck('result'))
|
||||
.toPromise();
|
||||
|
|
|
@ -29,12 +29,13 @@ export const expressionsServiceFactory: CanvasExpressionsServiceFactory = (
|
|||
const placeholder = {} as any;
|
||||
const expressionsPlugin = plugin(placeholder);
|
||||
const setup = expressionsPlugin.setup(placeholder);
|
||||
const expressionsService = setup.fork();
|
||||
const fork = setup.fork('canvas');
|
||||
const expressionsService = fork.setup();
|
||||
|
||||
functionDefinitions.forEach((fn) => expressionsService.registerFunction(fn));
|
||||
renderFunctions.forEach((fn) => {
|
||||
expressionsService.registerRenderer(fn as unknown as AnyExpressionRenderDefinition);
|
||||
setup.registerRenderer(fn as unknown as AnyExpressionRenderDefinition);
|
||||
});
|
||||
|
||||
return new ExpressionsService(expressionsService, requiredServices);
|
||||
return new ExpressionsService(fork.start(), requiredServices);
|
||||
};
|
||||
|
|
|
@ -52,9 +52,9 @@ export class CanvasPlugin implements Plugin {
|
|||
}
|
||||
|
||||
public setup(coreSetup: CoreSetup<PluginsStart>, plugins: PluginsSetup) {
|
||||
const expressionsFork = plugins.expressions.fork();
|
||||
|
||||
setupInterpreter(expressionsFork, {
|
||||
const expressionsFork = plugins.expressions.fork('canvas');
|
||||
const expressionsSetup = expressionsFork.setup();
|
||||
setupInterpreter(expressionsSetup, {
|
||||
embeddablePersistableStateService: {
|
||||
extract: plugins.embeddable.extract,
|
||||
inject: plugins.embeddable.inject,
|
||||
|
@ -62,7 +62,7 @@ export class CanvasPlugin implements Plugin {
|
|||
},
|
||||
});
|
||||
|
||||
const deps: CanvasSavedObjectTypeMigrationsDeps = { expressions: expressionsFork };
|
||||
const deps: CanvasSavedObjectTypeMigrationsDeps = { expressions: expressionsSetup };
|
||||
coreSetup.uiSettings.register(getUISettings());
|
||||
coreSetup.savedObjects.registerType(customElementType(deps));
|
||||
coreSetup.savedObjects.registerType(workpadTypeFactory(deps));
|
||||
|
@ -70,7 +70,8 @@ export class CanvasPlugin implements Plugin {
|
|||
|
||||
plugins.features.registerKibanaFeature(getCanvasFeature(plugins));
|
||||
|
||||
const contextProvider = createWorkpadRouteContext({ expressions: expressionsFork });
|
||||
const expressionsStart = expressionsFork.start();
|
||||
const contextProvider = createWorkpadRouteContext({ expressions: expressionsStart });
|
||||
coreSetup.http.registerRouteHandlerContext<CanvasRouteHandlerContext, 'canvas'>(
|
||||
'canvas',
|
||||
contextProvider
|
||||
|
@ -80,7 +81,7 @@ export class CanvasPlugin implements Plugin {
|
|||
|
||||
initRoutes({
|
||||
router: canvasRouter,
|
||||
expressions: expressionsFork,
|
||||
expressions: expressionsSetup,
|
||||
bfetch: plugins.bfetch,
|
||||
logger: this.logger,
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ export function initializeGetFunctionsRoute(deps: RouteInitializerDeps) {
|
|||
validate: false,
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const functions = expressions.getFunctions();
|
||||
const functions = expressions.getFunctions('canvas');
|
||||
const body = JSON.stringify(functions);
|
||||
return response.ok({
|
||||
body,
|
||||
|
@ -39,7 +39,7 @@ export function initializeBatchFunctionsRoute(deps: RouteInitializerDeps) {
|
|||
const { functionName, args, context } = fnCall;
|
||||
const { deserialize } = serializeProvider(expressions.getTypes());
|
||||
|
||||
const fnDef = expressions.getFunctions()[functionName];
|
||||
const fnDef = expressions.getFunctions('canvas')[functionName];
|
||||
if (!fnDef) throw new Error(`Function "${functionName}" could not be found.`);
|
||||
|
||||
const deserialized = deserialize(context);
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ExpressionsService } from 'src/plugins/expressions/public';
|
||||
import { ExpressionsServiceSetup } from 'src/plugins/expressions/public';
|
||||
|
||||
export interface CanvasSavedObjectTypeMigrationsDeps {
|
||||
expressions: ExpressionsService;
|
||||
expressions: ExpressionsServiceSetup;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
SavedObject,
|
||||
SavedObjectsResolveResponse,
|
||||
} from 'kibana/server';
|
||||
import { ExpressionsService } from 'src/plugins/expressions';
|
||||
import { ExpressionsServiceStart } from 'src/plugins/expressions';
|
||||
import { WorkpadAttributes } from './routes/workpad/workpad_attributes';
|
||||
import { CANVAS_TYPE } from '../common/lib/constants';
|
||||
import { injectReferences, extractReferences } from './saved_objects/workpad_references';
|
||||
|
@ -34,7 +34,7 @@ export interface CanvasRouteHandlerContext extends RequestHandlerContext {
|
|||
}
|
||||
|
||||
interface Deps {
|
||||
expressions: ExpressionsService;
|
||||
expressions: ExpressionsServiceStart;
|
||||
}
|
||||
|
||||
export const createWorkpadRouteContext: (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue