mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[Expressions] Remove the any
type usages (#113477)
* Update ESLint config to disallow usage of the any type * Remove the any type usages from the expressions plugin * Update plugins using expressions according to the updated public API
This commit is contained in:
parent
fed0dc6563
commit
0d9825d03c
66 changed files with 415 additions and 296 deletions
|
@ -47,11 +47,11 @@ export function ActionsExpressionsExample({ expressions, actions }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEvents = (event: any) => {
|
const handleEvents = (event: any) => {
|
||||||
if (event.id !== 'NAVIGATE') return;
|
if (event.name !== 'NAVIGATE') return;
|
||||||
// enrich event context with some extra data
|
// enrich event context with some extra data
|
||||||
event.baseUrl = 'http://www.google.com';
|
event.baseUrl = 'http://www.google.com';
|
||||||
|
|
||||||
actions.executeTriggerActions(NAVIGATE_TRIGGER_ID, event.value);
|
actions.executeTriggerActions(NAVIGATE_TRIGGER_ID, event.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -50,7 +50,7 @@ export function ActionsExpressionsExample2({ expressions, actions }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEvents = (event: any) => {
|
const handleEvents = (event: any) => {
|
||||||
updateVariables({ color: event.value.href === 'http://www.google.com' ? 'red' : 'blue' });
|
updateVariables({ color: event.data.href === 'http://www.google.com' ? 'red' : 'blue' });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -18,8 +18,8 @@ export const buttonRenderer: ExpressionRenderDefinition<any> = {
|
||||||
render(domNode, config, handlers) {
|
render(domNode, config, handlers) {
|
||||||
const buttonClick = () => {
|
const buttonClick = () => {
|
||||||
handlers.event({
|
handlers.event({
|
||||||
id: 'NAVIGATE',
|
name: 'NAVIGATE',
|
||||||
value: {
|
data: {
|
||||||
href: config.href,
|
href: config.href,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { functionWrapper } from '../../../../expressions/common/expression_funct
|
||||||
import { ExpressionValueVisDimension } from '../../../../visualizations/public';
|
import { ExpressionValueVisDimension } from '../../../../visualizations/public';
|
||||||
import { Datatable } from '../../../../expressions/common/expression_types/specs';
|
import { Datatable } from '../../../../expressions/common/expression_types/specs';
|
||||||
|
|
||||||
|
type Arguments = Parameters<ReturnType<typeof tagcloudFunction>['fn']>[1];
|
||||||
|
|
||||||
describe('interpreter/functions#tagcloud', () => {
|
describe('interpreter/functions#tagcloud', () => {
|
||||||
const fn = functionWrapper(tagcloudFunction());
|
const fn = functionWrapper(tagcloudFunction());
|
||||||
const column1 = 'Count';
|
const column1 = 'Count';
|
||||||
|
@ -26,7 +28,7 @@ describe('interpreter/functions#tagcloud', () => {
|
||||||
{ [column1]: 0, [column2]: 'US' },
|
{ [column1]: 0, [column2]: 'US' },
|
||||||
{ [column1]: 10, [column2]: 'UK' },
|
{ [column1]: 10, [column2]: 'UK' },
|
||||||
],
|
],
|
||||||
};
|
} as unknown as Datatable;
|
||||||
const visConfig = {
|
const visConfig = {
|
||||||
scale: 'linear',
|
scale: 'linear',
|
||||||
orientation: 'single',
|
orientation: 'single',
|
||||||
|
@ -73,12 +75,12 @@ describe('interpreter/functions#tagcloud', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it('returns an object with the correct structure for number accessors', () => {
|
it('returns an object with the correct structure for number accessors', () => {
|
||||||
const actual = fn(context, { ...visConfig, ...numberAccessors }, undefined);
|
const actual = fn(context, { ...visConfig, ...numberAccessors } as Arguments, undefined);
|
||||||
expect(actual).toMatchSnapshot();
|
expect(actual).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an object with the correct structure for string accessors', () => {
|
it('returns an object with the correct structure for string accessors', () => {
|
||||||
const actual = fn(context, { ...visConfig, ...stringAccessors }, undefined);
|
const actual = fn(context, { ...visConfig, ...stringAccessors } as Arguments, undefined);
|
||||||
expect(actual).toMatchSnapshot();
|
expect(actual).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,7 +95,7 @@ describe('interpreter/functions#tagcloud', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await fn(context, { ...visConfig, ...numberAccessors }, handlers as any);
|
await fn(context, { ...visConfig, ...numberAccessors } as Arguments, handlers as any);
|
||||||
|
|
||||||
expect(loggedTable!).toMatchSnapshot();
|
expect(loggedTable!).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/consistent-type-definitions": 0
|
"@typescript-eslint/consistent-type-definitions": 0,
|
||||||
|
"@typescript-eslint/no-explicit-any": ["error", { "ignoreRestArgs": true }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,13 +32,13 @@ import { parse } from './parse';
|
||||||
* @param val Value you want to check.
|
* @param val Value you want to check.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
export function isExpressionAstBuilder(val: any): val is ExpressionAstExpressionBuilder {
|
export function isExpressionAstBuilder(val: unknown): val is ExpressionAstExpressionBuilder {
|
||||||
return val?.type === 'expression_builder';
|
return (val as Record<string, unknown> | undefined)?.type === 'expression_builder';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function isExpressionAst(val: any): val is ExpressionAstExpression {
|
export function isExpressionAst(val: unknown): val is ExpressionAstExpression {
|
||||||
return val?.type === 'expression';
|
return (val as Record<string, unknown> | undefined)?.type === 'expression';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExpressionAstExpressionBuilder {
|
export interface ExpressionAstExpressionBuilder {
|
||||||
|
|
|
@ -64,7 +64,7 @@ export type ExpressionAstFunctionDebug = {
|
||||||
/**
|
/**
|
||||||
* Raw error that was thrown by the function, if any.
|
* Raw error that was thrown by the function, if any.
|
||||||
*/
|
*/
|
||||||
rawError?: any | Error;
|
rawError?: any | Error; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time in milliseconds it took to execute the function. Duration can be
|
* Time in milliseconds it took to execute the function. Duration can be
|
||||||
|
|
|
@ -90,7 +90,7 @@ describe('Execution abortion tests', () => {
|
||||||
const completed = jest.fn();
|
const completed = jest.fn();
|
||||||
const aborted = jest.fn();
|
const aborted = jest.fn();
|
||||||
|
|
||||||
const defer: ExpressionFunctionDefinition<'defer', any, { time: number }, any> = {
|
const defer: ExpressionFunctionDefinition<'defer', unknown, { time: number }, unknown> = {
|
||||||
name: 'defer',
|
name: 'defer',
|
||||||
args: {
|
args: {
|
||||||
time: {
|
time: {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { ExecutionContract } from './execution_contract';
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
if (typeof performance === 'undefined') {
|
if (typeof performance === 'undefined') {
|
||||||
(global as any).performance = { now: Date.now };
|
global.performance = { now: Date.now } as typeof performance;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ const createExecution = (
|
||||||
const run = async (
|
const run = async (
|
||||||
expression: string = 'foo bar=123',
|
expression: string = 'foo bar=123',
|
||||||
context?: Record<string, unknown>,
|
context?: Record<string, unknown>,
|
||||||
input: any = null
|
input: unknown = null
|
||||||
) => {
|
) => {
|
||||||
const execution = createExecution(expression, context);
|
const execution = createExecution(expression, context);
|
||||||
execution.start(input);
|
execution.start(input);
|
||||||
|
@ -262,45 +262,45 @@ describe('Execution', () => {
|
||||||
|
|
||||||
describe('execution context', () => {
|
describe('execution context', () => {
|
||||||
test('context.variables is an object', async () => {
|
test('context.variables is an object', async () => {
|
||||||
const { result } = (await run('introspectContext key="variables"')) as any;
|
const { result } = await run('introspectContext key="variables"');
|
||||||
|
|
||||||
expect(result).toHaveProperty('result', expect.any(Object));
|
expect(result).toHaveProperty('result', expect.any(Object));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('context.types is an object', async () => {
|
test('context.types is an object', async () => {
|
||||||
const { result } = (await run('introspectContext key="types"')) as any;
|
const { result } = await run('introspectContext key="types"');
|
||||||
|
|
||||||
expect(result).toHaveProperty('result', expect.any(Object));
|
expect(result).toHaveProperty('result', expect.any(Object));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('context.abortSignal is an object', async () => {
|
test('context.abortSignal is an object', async () => {
|
||||||
const { result } = (await run('introspectContext key="abortSignal"')) as any;
|
const { result } = await run('introspectContext key="abortSignal"');
|
||||||
|
|
||||||
expect(result).toHaveProperty('result', expect.any(Object));
|
expect(result).toHaveProperty('result', expect.any(Object));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('context.inspectorAdapters is an object', async () => {
|
test('context.inspectorAdapters is an object', async () => {
|
||||||
const { result } = (await run('introspectContext key="inspectorAdapters"')) as any;
|
const { result } = await run('introspectContext key="inspectorAdapters"');
|
||||||
|
|
||||||
expect(result).toHaveProperty('result', expect.any(Object));
|
expect(result).toHaveProperty('result', expect.any(Object));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('context.getKibanaRequest is a function if provided', async () => {
|
test('context.getKibanaRequest is a function if provided', async () => {
|
||||||
const { result } = (await run('introspectContext key="getKibanaRequest"', {
|
const { result } = await run('introspectContext key="getKibanaRequest"', {
|
||||||
kibanaRequest: {},
|
kibanaRequest: {},
|
||||||
})) as any;
|
});
|
||||||
|
|
||||||
expect(result).toHaveProperty('result', expect.any(Function));
|
expect(result).toHaveProperty('result', expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('context.getKibanaRequest is undefined if not provided', async () => {
|
test('context.getKibanaRequest is undefined if not provided', async () => {
|
||||||
const { result } = (await run('introspectContext key="getKibanaRequest"')) as any;
|
const { result } = await run('introspectContext key="getKibanaRequest"');
|
||||||
|
|
||||||
expect(result).toHaveProperty('result', undefined);
|
expect(result).toHaveProperty('result', undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('unknown context key is undefined', async () => {
|
test('unknown context key is undefined', async () => {
|
||||||
const { result } = (await run('introspectContext key="foo"')) as any;
|
const { result } = await run('introspectContext key="foo"');
|
||||||
|
|
||||||
expect(result).toHaveProperty('result', undefined);
|
expect(result).toHaveProperty('result', undefined);
|
||||||
});
|
});
|
||||||
|
@ -314,7 +314,7 @@ describe('Execution', () => {
|
||||||
|
|
||||||
describe('inspector adapters', () => {
|
describe('inspector adapters', () => {
|
||||||
test('by default, "tables" and "requests" inspector adapters are available', async () => {
|
test('by default, "tables" and "requests" inspector adapters are available', async () => {
|
||||||
const { result } = (await run('introspectContext key="inspectorAdapters"')) as any;
|
const { result } = await run('introspectContext key="inspectorAdapters"');
|
||||||
expect(result).toHaveProperty(
|
expect(result).toHaveProperty(
|
||||||
'result',
|
'result',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
@ -326,9 +326,9 @@ describe('Execution', () => {
|
||||||
|
|
||||||
test('can set custom inspector adapters', async () => {
|
test('can set custom inspector adapters', async () => {
|
||||||
const inspectorAdapters = {};
|
const inspectorAdapters = {};
|
||||||
const { result } = (await run('introspectContext key="inspectorAdapters"', {
|
const { result } = await run('introspectContext key="inspectorAdapters"', {
|
||||||
inspectorAdapters,
|
inspectorAdapters,
|
||||||
})) as any;
|
});
|
||||||
expect(result).toHaveProperty('result', inspectorAdapters);
|
expect(result).toHaveProperty('result', inspectorAdapters);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -351,7 +351,7 @@ describe('Execution', () => {
|
||||||
|
|
||||||
describe('expression abortion', () => {
|
describe('expression abortion', () => {
|
||||||
test('context has abortSignal object', async () => {
|
test('context has abortSignal object', async () => {
|
||||||
const { result } = (await run('introspectContext key="abortSignal"')) as any;
|
const { result } = await run('introspectContext key="abortSignal"');
|
||||||
|
|
||||||
expect(result).toHaveProperty('result.aborted', false);
|
expect(result).toHaveProperty('result.aborted', false);
|
||||||
});
|
});
|
||||||
|
@ -400,7 +400,7 @@ describe('Execution', () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
const arg = cold(' -a-b-c|', { a: 1, b: 2, c: 3 });
|
const arg = cold(' -a-b-c|', { a: 1, b: 2, c: 3 });
|
||||||
const expected = ' -a-b-c|';
|
const expected = ' -a-b-c|';
|
||||||
const observable: ExpressionFunctionDefinition<'observable', any, {}, any> = {
|
const observable: ExpressionFunctionDefinition<'observable', unknown, {}, unknown> = {
|
||||||
name: 'observable',
|
name: 'observable',
|
||||||
args: {},
|
args: {},
|
||||||
help: '',
|
help: '',
|
||||||
|
@ -468,7 +468,7 @@ describe('Execution', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not execute remaining functions in pipeline', async () => {
|
test('does not execute remaining functions in pipeline', async () => {
|
||||||
const spy: ExpressionFunctionDefinition<'spy', any, {}, any> = {
|
const spy: ExpressionFunctionDefinition<'spy', unknown, {}, unknown> = {
|
||||||
name: 'spy',
|
name: 'spy',
|
||||||
args: {},
|
args: {},
|
||||||
help: '',
|
help: '',
|
||||||
|
@ -621,7 +621,12 @@ describe('Execution', () => {
|
||||||
help: '',
|
help: '',
|
||||||
fn: () => arg2,
|
fn: () => arg2,
|
||||||
};
|
};
|
||||||
const max: ExpressionFunctionDefinition<'max', any, { val1: number; val2: number }, any> = {
|
const max: ExpressionFunctionDefinition<
|
||||||
|
'max',
|
||||||
|
unknown,
|
||||||
|
{ val1: number; val2: number },
|
||||||
|
unknown
|
||||||
|
> = {
|
||||||
name: 'max',
|
name: 'max',
|
||||||
args: {
|
args: {
|
||||||
val1: { help: '', types: ['number'] },
|
val1: { help: '', types: ['number'] },
|
||||||
|
@ -679,7 +684,12 @@ describe('Execution', () => {
|
||||||
|
|
||||||
describe('when arguments are missing', () => {
|
describe('when arguments are missing', () => {
|
||||||
it('when required argument is missing and has not alias, returns error', async () => {
|
it('when required argument is missing and has not alias, returns error', async () => {
|
||||||
const requiredArg: ExpressionFunctionDefinition<'requiredArg', any, { arg: any }, any> = {
|
const requiredArg: ExpressionFunctionDefinition<
|
||||||
|
'requiredArg',
|
||||||
|
unknown,
|
||||||
|
{ arg: unknown },
|
||||||
|
unknown
|
||||||
|
> = {
|
||||||
name: 'requiredArg',
|
name: 'requiredArg',
|
||||||
args: {
|
args: {
|
||||||
arg: {
|
arg: {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { isPromise } from '@kbn/std';
|
import { isPromise } from '@kbn/std';
|
||||||
|
import { ObservableLike, UnwrapObservable, UnwrapPromiseOrReturn } from '@kbn/utility-types';
|
||||||
import { keys, last, mapValues, reduce, zipObject } from 'lodash';
|
import { keys, last, mapValues, reduce, zipObject } from 'lodash';
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
combineLatest,
|
||||||
|
@ -44,6 +45,18 @@ import { ExecutionContract } from './execution_contract';
|
||||||
import { ExpressionExecutionParams } from '../service';
|
import { ExpressionExecutionParams } from '../service';
|
||||||
import { createDefaultInspectorAdapters } from '../util/create_default_inspector_adapters';
|
import { createDefaultInspectorAdapters } from '../util/create_default_inspector_adapters';
|
||||||
|
|
||||||
|
type UnwrapReturnType<Function extends (...args: any[]) => unknown> =
|
||||||
|
ReturnType<Function> extends ObservableLike<unknown>
|
||||||
|
? UnwrapObservable<ReturnType<Function>>
|
||||||
|
: UnwrapPromiseOrReturn<ReturnType<Function>>;
|
||||||
|
|
||||||
|
// type ArgumentsOf<Function extends ExpressionFunction> = Function extends ExpressionFunction<
|
||||||
|
// unknown,
|
||||||
|
// infer Arguments
|
||||||
|
// >
|
||||||
|
// ? Arguments
|
||||||
|
// : never;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result returned after an expression function execution.
|
* The result returned after an expression function execution.
|
||||||
*/
|
*/
|
||||||
|
@ -83,7 +96,7 @@ const createAbortErrorValue = () =>
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface ExecutionParams {
|
export interface ExecutionParams {
|
||||||
executor: Executor<any>;
|
executor: Executor;
|
||||||
ast?: ExpressionAstExpression;
|
ast?: ExpressionAstExpression;
|
||||||
expression?: string;
|
expression?: string;
|
||||||
params: ExpressionExecutionParams;
|
params: ExpressionExecutionParams;
|
||||||
|
@ -107,7 +120,7 @@ export class Execution<
|
||||||
* N.B. It is initialized to `null` rather than `undefined` for legacy reasons,
|
* N.B. It is initialized to `null` rather than `undefined` for legacy reasons,
|
||||||
* because in legacy interpreter it was set to `null` by default.
|
* because in legacy interpreter it was set to `null` by default.
|
||||||
*/
|
*/
|
||||||
public input: Input = null as any;
|
public input = null as unknown as Input;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input of the started execution.
|
* Input of the started execution.
|
||||||
|
@ -186,13 +199,13 @@ export class Execution<
|
||||||
});
|
});
|
||||||
|
|
||||||
const inspectorAdapters =
|
const inspectorAdapters =
|
||||||
execution.params.inspectorAdapters || createDefaultInspectorAdapters();
|
(execution.params.inspectorAdapters as InspectorAdapters) || createDefaultInspectorAdapters();
|
||||||
|
|
||||||
this.context = {
|
this.context = {
|
||||||
getSearchContext: () => this.execution.params.searchContext || {},
|
getSearchContext: () => this.execution.params.searchContext || {},
|
||||||
getSearchSessionId: () => execution.params.searchSessionId,
|
getSearchSessionId: () => execution.params.searchSessionId,
|
||||||
getKibanaRequest: execution.params.kibanaRequest
|
getKibanaRequest: execution.params.kibanaRequest
|
||||||
? () => execution.params.kibanaRequest
|
? () => execution.params.kibanaRequest!
|
||||||
: undefined,
|
: undefined,
|
||||||
variables: execution.params.variables || {},
|
variables: execution.params.variables || {},
|
||||||
types: executor.getTypes(),
|
types: executor.getTypes(),
|
||||||
|
@ -201,14 +214,14 @@ export class Execution<
|
||||||
logDatatable: (name: string, datatable: Datatable) => {
|
logDatatable: (name: string, datatable: Datatable) => {
|
||||||
inspectorAdapters.tables[name] = datatable;
|
inspectorAdapters.tables[name] = datatable;
|
||||||
},
|
},
|
||||||
isSyncColorsEnabled: () => execution.params.syncColors,
|
isSyncColorsEnabled: () => execution.params.syncColors!,
|
||||||
...(execution.params as any).extraContext,
|
...execution.params.extraContext,
|
||||||
getExecutionContext: () => execution.params.executionContext,
|
getExecutionContext: () => execution.params.executionContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.result = this.input$.pipe(
|
this.result = this.input$.pipe(
|
||||||
switchMap((input) =>
|
switchMap((input) =>
|
||||||
this.race(this.invokeChain(this.state.get().ast.chain, input)).pipe(
|
this.race(this.invokeChain<Output>(this.state.get().ast.chain, input)).pipe(
|
||||||
(source) =>
|
(source) =>
|
||||||
new Observable<ExecutionResult<Output>>((subscriber) => {
|
new Observable<ExecutionResult<Output>>((subscriber) => {
|
||||||
let latest: ExecutionResult<Output> | undefined;
|
let latest: ExecutionResult<Output> | undefined;
|
||||||
|
@ -270,8 +283,8 @@ export class Execution<
|
||||||
* N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons,
|
* N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons,
|
||||||
* because in legacy interpreter it was set to `null` by default.
|
* because in legacy interpreter it was set to `null` by default.
|
||||||
*/
|
*/
|
||||||
public start(
|
start(
|
||||||
input: Input = null as any,
|
input = null as unknown as Input,
|
||||||
isSubExpression?: boolean
|
isSubExpression?: boolean
|
||||||
): Observable<ExecutionResult<Output | ExpressionValueError>> {
|
): Observable<ExecutionResult<Output | ExpressionValueError>> {
|
||||||
if (this.hasStarted) throw new Error('Execution already started.');
|
if (this.hasStarted) throw new Error('Execution already started.');
|
||||||
|
@ -294,7 +307,10 @@ export class Execution<
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable<any> {
|
invokeChain<ChainOutput = unknown>(
|
||||||
|
chainArr: ExpressionAstFunction[],
|
||||||
|
input: unknown
|
||||||
|
): Observable<ChainOutput> {
|
||||||
return of(input).pipe(
|
return of(input).pipe(
|
||||||
...(chainArr.map((link) =>
|
...(chainArr.map((link) =>
|
||||||
switchMap((currentInput) => {
|
switchMap((currentInput) => {
|
||||||
|
@ -364,19 +380,24 @@ export class Execution<
|
||||||
})
|
})
|
||||||
) as Parameters<Observable<unknown>['pipe']>),
|
) as Parameters<Observable<unknown>['pipe']>),
|
||||||
catchError((error) => of(error))
|
catchError((error) => of(error))
|
||||||
);
|
) as Observable<ChainOutput>;
|
||||||
}
|
}
|
||||||
|
|
||||||
invokeFunction(
|
invokeFunction<Fn extends ExpressionFunction>(
|
||||||
fn: ExpressionFunction,
|
fn: Fn,
|
||||||
input: unknown,
|
input: unknown,
|
||||||
args: Record<string, unknown>
|
args: Record<string, unknown>
|
||||||
): Observable<any> {
|
): Observable<UnwrapReturnType<Fn['fn']>> {
|
||||||
return of(input).pipe(
|
return of(input).pipe(
|
||||||
map((currentInput) => this.cast(currentInput, fn.inputTypes)),
|
map((currentInput) => this.cast(currentInput, fn.inputTypes)),
|
||||||
switchMap((normalizedInput) => this.race(of(fn.fn(normalizedInput, args, this.context)))),
|
switchMap((normalizedInput) => this.race(of(fn.fn(normalizedInput, args, this.context)))),
|
||||||
switchMap((fnResult: any) =>
|
switchMap(
|
||||||
isObservable(fnResult) ? fnResult : from(isPromise(fnResult) ? fnResult : [fnResult])
|
(fnResult) =>
|
||||||
|
(isObservable(fnResult)
|
||||||
|
? fnResult
|
||||||
|
: from(isPromise(fnResult) ? fnResult : [fnResult])) as Observable<
|
||||||
|
UnwrapReturnType<Fn['fn']>
|
||||||
|
>
|
||||||
),
|
),
|
||||||
map((output) => {
|
map((output) => {
|
||||||
// Validate that the function returned the type it said it would.
|
// Validate that the function returned the type it said it would.
|
||||||
|
@ -405,39 +426,49 @@ export class Execution<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public cast(value: any, toTypeNames?: string[]) {
|
public cast<Type = unknown>(value: unknown, toTypeNames?: string[]): Type {
|
||||||
// If you don't give us anything to cast to, you'll get your input back
|
// If you don't give us anything to cast to, you'll get your input back
|
||||||
if (!toTypeNames || toTypeNames.length === 0) return value;
|
if (!toTypeNames?.length) {
|
||||||
|
return value as Type;
|
||||||
|
}
|
||||||
|
|
||||||
// No need to cast if node is already one of the valid types
|
// No need to cast if node is already one of the valid types
|
||||||
const fromTypeName = getType(value);
|
const fromTypeName = getType(value);
|
||||||
if (toTypeNames.includes(fromTypeName)) return value;
|
if (toTypeNames.includes(fromTypeName)) {
|
||||||
|
return value as Type;
|
||||||
|
}
|
||||||
|
|
||||||
const { types } = this.state.get();
|
const { types } = this.state.get();
|
||||||
const fromTypeDef = types[fromTypeName];
|
const fromTypeDef = types[fromTypeName];
|
||||||
|
|
||||||
for (const toTypeName of toTypeNames) {
|
for (const toTypeName of toTypeNames) {
|
||||||
// First check if the current type can cast to this type
|
// First check if the current type can cast to this type
|
||||||
if (fromTypeDef && fromTypeDef.castsTo(toTypeName)) {
|
if (fromTypeDef?.castsTo(toTypeName)) {
|
||||||
return fromTypeDef.to(value, toTypeName, types);
|
return fromTypeDef.to(value, toTypeName, types);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If that isn't possible, check if this type can cast from the current type
|
// If that isn't possible, check if this type can cast from the current type
|
||||||
const toTypeDef = types[toTypeName];
|
const toTypeDef = types[toTypeName];
|
||||||
if (toTypeDef && toTypeDef.castsFrom(fromTypeName)) return toTypeDef.from(value, types);
|
if (toTypeDef?.castsFrom(fromTypeName)) {
|
||||||
|
return toTypeDef.from(value, types);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`);
|
throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processes the multi-valued AST argument values into arguments that can be passed to the function
|
// Processes the multi-valued AST argument values into arguments that can be passed to the function
|
||||||
resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable<any> {
|
resolveArgs<Fn extends ExpressionFunction>(
|
||||||
|
fnDef: Fn,
|
||||||
|
input: unknown,
|
||||||
|
argAsts: Record<string, ExpressionAstArgument[]>
|
||||||
|
): Observable<Record<string, unknown>> {
|
||||||
return defer(() => {
|
return defer(() => {
|
||||||
const { args: argDefs } = fnDef;
|
const { args: argDefs } = fnDef;
|
||||||
|
|
||||||
// Use the non-alias name from the argument definition
|
// Use the non-alias name from the argument definition
|
||||||
const dealiasedArgAsts = reduce(
|
const dealiasedArgAsts = reduce(
|
||||||
argAsts as Record<string, ExpressionAstArgument>,
|
argAsts,
|
||||||
(acc, argAst, argName) => {
|
(acc, argAst, argName) => {
|
||||||
const argDef = getByAlias(argDefs, argName);
|
const argDef = getByAlias(argDefs, argName);
|
||||||
if (!argDef) {
|
if (!argDef) {
|
||||||
|
@ -452,7 +483,7 @@ export class Execution<
|
||||||
// Check for missing required arguments.
|
// Check for missing required arguments.
|
||||||
for (const { aliases, default: argDefault, name, required } of Object.values(argDefs)) {
|
for (const { aliases, default: argDefault, name, required } of Object.values(argDefs)) {
|
||||||
if (!(name in dealiasedArgAsts) && typeof argDefault !== 'undefined') {
|
if (!(name in dealiasedArgAsts) && typeof argDefault !== 'undefined') {
|
||||||
dealiasedArgAsts[name] = [parse(argDefault, 'argument')];
|
dealiasedArgAsts[name] = [parse(argDefault as string, 'argument')];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!required || name in dealiasedArgAsts) {
|
if (!required || name in dealiasedArgAsts) {
|
||||||
|
@ -490,7 +521,7 @@ export class Execution<
|
||||||
const argNames = keys(resolveArgFns);
|
const argNames = keys(resolveArgFns);
|
||||||
|
|
||||||
if (!argNames.length) {
|
if (!argNames.length) {
|
||||||
return from([[]]);
|
return from([{}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolvedArgValuesObservable = combineLatest(
|
const resolvedArgValuesObservable = combineLatest(
|
||||||
|
@ -523,7 +554,7 @@ export class Execution<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public interpret<T>(ast: ExpressionAstNode, input: T): Observable<ExecutionResult<unknown>> {
|
interpret<T>(ast: ExpressionAstNode, input: T): Observable<ExecutionResult<unknown>> {
|
||||||
switch (getType(ast)) {
|
switch (getType(ast)) {
|
||||||
case 'expression':
|
case 'expression':
|
||||||
const execution = this.execution.executor.createExecution(
|
const execution = this.execution.executor.createExecution(
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { SerializableRecord } from '@kbn/utility-types';
|
||||||
import type { KibanaRequest } from 'src/core/server';
|
import type { KibanaRequest } from 'src/core/server';
|
||||||
import type { KibanaExecutionContext } from 'src/core/public';
|
import type { KibanaExecutionContext } from 'src/core/public';
|
||||||
|
|
||||||
import { ExpressionType } from '../expression_types';
|
import { Datatable, ExpressionType } from '../expression_types';
|
||||||
import { Adapters, RequestAdapter } from '../../../inspector/common';
|
import { Adapters, RequestAdapter } from '../../../inspector/common';
|
||||||
import { TablesAdapter } from '../util/tables_adapter';
|
import { TablesAdapter } from '../util/tables_adapter';
|
||||||
|
|
||||||
|
@ -69,6 +69,11 @@ export interface ExecutionContext<
|
||||||
* Contains the meta-data about the source of the expression.
|
* Contains the meta-data about the source of the expression.
|
||||||
*/
|
*/
|
||||||
getExecutionContext: () => KibanaExecutionContext | undefined;
|
getExecutionContext: () => KibanaExecutionContext | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs datatable.
|
||||||
|
*/
|
||||||
|
logDatatable?(name: string, datatable: Datatable): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,7 +19,7 @@ export interface ExecutorState<Context extends Record<string, unknown> = Record<
|
||||||
context: Context;
|
context: Context;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultState: ExecutorState<any> = {
|
export const defaultState: ExecutorState = {
|
||||||
functions: {},
|
functions: {},
|
||||||
types: {},
|
types: {},
|
||||||
context: {},
|
context: {},
|
||||||
|
@ -61,7 +61,7 @@ export type ExecutorContainer<Context extends Record<string, unknown> = Record<s
|
||||||
export const createExecutorContainer = <
|
export const createExecutorContainer = <
|
||||||
Context extends Record<string, unknown> = Record<string, unknown>
|
Context extends Record<string, unknown> = Record<string, unknown>
|
||||||
>(
|
>(
|
||||||
state: ExecutorState<Context> = defaultState
|
state = defaultState as ExecutorState<Context>
|
||||||
): ExecutorContainer<Context> => {
|
): ExecutorContainer<Context> => {
|
||||||
const container = createStateContainer<
|
const container = createStateContainer<
|
||||||
ExecutorState<Context>,
|
ExecutorState<Context>,
|
||||||
|
|
|
@ -8,20 +8,14 @@
|
||||||
|
|
||||||
import { Executor } from './executor';
|
import { Executor } from './executor';
|
||||||
import { parseExpression } from '../ast';
|
import { parseExpression } from '../ast';
|
||||||
|
import { Execution } from '../execution/execution';
|
||||||
|
|
||||||
// eslint-disable-next-line
|
jest.mock('../execution/execution', () => ({
|
||||||
const { __getArgs } = require('../execution/execution');
|
Execution: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('../execution/execution', () => {
|
beforeEach(() => {
|
||||||
const mockedModule = {
|
jest.clearAllMocks();
|
||||||
args: undefined,
|
|
||||||
__getArgs: () => mockedModule.args,
|
|
||||||
Execution: function ExecutionMock(...args: any) {
|
|
||||||
mockedModule.args = args;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return mockedModule;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Executor mocked execution tests', () => {
|
describe('Executor mocked execution tests', () => {
|
||||||
|
@ -31,7 +25,9 @@ describe('Executor mocked execution tests', () => {
|
||||||
const executor = new Executor();
|
const executor = new Executor();
|
||||||
executor.createExecution('foo bar="baz"');
|
executor.createExecution('foo bar="baz"');
|
||||||
|
|
||||||
expect(__getArgs()[0].expression).toBe('foo bar="baz"');
|
expect(Execution).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ expression: 'foo bar="baz"' })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,7 +37,9 @@ describe('Executor mocked execution tests', () => {
|
||||||
const ast = parseExpression('foo bar="baz"');
|
const ast = parseExpression('foo bar="baz"');
|
||||||
executor.createExecution(ast);
|
executor.createExecution(ast);
|
||||||
|
|
||||||
expect(__getArgs()[0].expression).toBe(undefined);
|
expect(Execution).toHaveBeenCalledWith(
|
||||||
|
expect.not.objectContaining({ expression: expect.anything() })
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -145,7 +145,7 @@ describe('Executor', () => {
|
||||||
executor.extendContext({ foo });
|
executor.extendContext({ foo });
|
||||||
const execution = executor.createExecution('foo bar="baz"');
|
const execution = executor.createExecution('foo bar="baz"');
|
||||||
|
|
||||||
expect((execution.context as any).foo).toBe(foo);
|
expect(execution.context).toHaveProperty('foo', foo);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -175,10 +175,10 @@ describe('Executor', () => {
|
||||||
migrations: {
|
migrations: {
|
||||||
'7.10.0': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => {
|
'7.10.0': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => {
|
||||||
return migrateFn(state, version);
|
return migrateFn(state, version);
|
||||||
}) as any as MigrateFunction,
|
}) as unknown as MigrateFunction,
|
||||||
'7.10.1': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => {
|
'7.10.1': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => {
|
||||||
return migrateFn(state, version);
|
return migrateFn(state, version);
|
||||||
}) as any as MigrateFunction,
|
}) as unknown as MigrateFunction,
|
||||||
},
|
},
|
||||||
fn: jest.fn(),
|
fn: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,7 +40,7 @@ export interface ExpressionExecOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TypesRegistry implements IRegistry<ExpressionType> {
|
export class TypesRegistry implements IRegistry<ExpressionType> {
|
||||||
constructor(private readonly executor: Executor<any>) {}
|
constructor(private readonly executor: Executor) {}
|
||||||
|
|
||||||
public register(
|
public register(
|
||||||
typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)
|
typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)
|
||||||
|
@ -62,7 +62,7 @@ export class TypesRegistry implements IRegistry<ExpressionType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FunctionsRegistry implements IRegistry<ExpressionFunction> {
|
export class FunctionsRegistry implements IRegistry<ExpressionFunction> {
|
||||||
constructor(private readonly executor: Executor<any>) {}
|
constructor(private readonly executor: Executor) {}
|
||||||
|
|
||||||
public register(
|
public register(
|
||||||
functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)
|
functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)
|
||||||
|
@ -100,12 +100,12 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
public readonly functions = new FunctionsRegistry(this);
|
public readonly functions = new FunctionsRegistry(this as Executor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
public readonly types = new TypesRegistry(this);
|
public readonly types = new TypesRegistry(this as Executor);
|
||||||
|
|
||||||
protected parent?: Executor<Context>;
|
protected parent?: Executor<Context>;
|
||||||
|
|
||||||
|
@ -207,15 +207,15 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
||||||
ast: string | ExpressionAstExpression,
|
ast: string | ExpressionAstExpression,
|
||||||
params: ExpressionExecutionParams = {}
|
params: ExpressionExecutionParams = {}
|
||||||
): Execution<Input, Output> {
|
): Execution<Input, Output> {
|
||||||
const executionParams: ExecutionParams = {
|
const executionParams = {
|
||||||
executor: this,
|
executor: this,
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
// for canvas we are passing this in,
|
// for canvas we are passing this in,
|
||||||
// canvas should be refactored to not pass any extra context in
|
// canvas should be refactored to not pass any extra context in
|
||||||
extraContext: this.context,
|
extraContext: this.context,
|
||||||
} as any,
|
},
|
||||||
};
|
} as ExecutionParams;
|
||||||
|
|
||||||
if (typeof ast === 'string') executionParams.expression = ast;
|
if (typeof ast === 'string') executionParams.expression = ast;
|
||||||
else executionParams.ast = ast;
|
else executionParams.ast = ast;
|
||||||
|
@ -273,7 +273,7 @@ export class Executor<Context extends Record<string, unknown> = Record<string, u
|
||||||
return { state: newAst, references: allReferences };
|
return { state: newAst, references: allReferences };
|
||||||
}
|
}
|
||||||
|
|
||||||
public telemetry(ast: ExpressionAstExpression, telemetryData: Record<string, any>) {
|
public telemetry(ast: ExpressionAstExpression, telemetryData: Record<string, unknown>) {
|
||||||
this.walkAst(cloneDeep(ast), (fn, link) => {
|
this.walkAst(cloneDeep(ast), (fn, link) => {
|
||||||
telemetryData = fn.telemetry(link.arguments, telemetryData);
|
telemetryData = fn.telemetry(link.arguments, telemetryData);
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,7 +35,7 @@ type ArrayTypeToArgumentString<T> =
|
||||||
*/
|
*/
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type UnresolvedTypeToArgumentString<T> =
|
type UnresolvedTypeToArgumentString<T> =
|
||||||
T extends (...args: any) => infer ElementType ? TypeString<ElementType> :
|
T extends (...args: any[]) => infer ElementType ? TypeString<ElementType> :
|
||||||
T extends null ? 'null' :
|
T extends null ? 'null' :
|
||||||
never;
|
never;
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ type UnresolvedTypeToArgumentString<T> =
|
||||||
*/
|
*/
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type UnresolvedArrayTypeToArgumentString<T> =
|
type UnresolvedArrayTypeToArgumentString<T> =
|
||||||
T extends Array<(...args: any) => infer ElementType> ? TypeString<ElementType> :
|
T extends Array<(...args: any[]) => infer ElementType> ? TypeString<ElementType> :
|
||||||
T extends (...args: any) => infer ElementType ? ArrayTypeToArgumentString<ElementType> :
|
T extends (...args: any[]) => infer ElementType ? ArrayTypeToArgumentString<ElementType> :
|
||||||
T extends null ? 'null' :
|
T extends null ? 'null' :
|
||||||
never;
|
never;
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,11 @@ export class ExpressionFunction implements PersistableState<ExpressionAstFunctio
|
||||||
/**
|
/**
|
||||||
* Function to run function (context, args)
|
* Function to run function (context, args)
|
||||||
*/
|
*/
|
||||||
fn: (input: ExpressionValue, params: Record<string, any>, handlers: object) => ExpressionValue;
|
fn: (
|
||||||
|
input: ExpressionValue,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
handlers: object
|
||||||
|
) => ExpressionValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A short help text.
|
* A short help text.
|
||||||
|
@ -56,8 +60,8 @@ export class ExpressionFunction implements PersistableState<ExpressionAstFunctio
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
telemetry: (
|
telemetry: (
|
||||||
state: ExpressionAstFunction['arguments'],
|
state: ExpressionAstFunction['arguments'],
|
||||||
telemetryData: Record<string, any>
|
telemetryData: Record<string, unknown>
|
||||||
) => Record<string, any>;
|
) => Record<string, unknown>;
|
||||||
extract: (state: ExpressionAstFunction['arguments']) => {
|
extract: (state: ExpressionAstFunction['arguments']) => {
|
||||||
state: ExpressionAstFunction['arguments'];
|
state: ExpressionAstFunction['arguments'];
|
||||||
references: SavedObjectReference[];
|
references: SavedObjectReference[];
|
||||||
|
@ -100,13 +104,12 @@ export class ExpressionFunction implements PersistableState<ExpressionAstFunctio
|
||||||
this.migrations = migrations || {};
|
this.migrations = migrations || {};
|
||||||
|
|
||||||
for (const [key, arg] of Object.entries(args || {})) {
|
for (const [key, arg] of Object.entries(args || {})) {
|
||||||
this.args[key] = new ExpressionFunctionParameter(key, arg);
|
this.args[key as keyof typeof args] = new ExpressionFunctionParameter(key, arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accepts = (type: string): boolean => {
|
accepts = (type: string): boolean => {
|
||||||
// If you don't tell us input types, we'll assume you don't care what you get.
|
// If you don't tell us input types, we'll assume you don't care what you get.
|
||||||
if (!this.inputTypes) return true;
|
return this.inputTypes?.includes(type) ?? true;
|
||||||
return this.inputTypes.indexOf(type) > -1;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,20 +6,21 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { KnownTypeToString } from '../types';
|
||||||
import { ArgumentType } from './arguments';
|
import { ArgumentType } from './arguments';
|
||||||
|
|
||||||
export class ExpressionFunctionParameter {
|
export class ExpressionFunctionParameter<T = unknown> {
|
||||||
name: string;
|
name: string;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
help: string;
|
help: string;
|
||||||
types: string[];
|
types: ArgumentType<T>['types'];
|
||||||
default: any;
|
default?: ArgumentType<T>['default'];
|
||||||
aliases: string[];
|
aliases: string[];
|
||||||
multi: boolean;
|
multi: boolean;
|
||||||
resolve: boolean;
|
resolve: boolean;
|
||||||
options: any[];
|
options: T[];
|
||||||
|
|
||||||
constructor(name: string, arg: ArgumentType<any>) {
|
constructor(name: string, arg: ArgumentType<T>) {
|
||||||
const { required, help, types, aliases, multi, resolve, options } = arg;
|
const { required, help, types, aliases, multi, resolve, options } = arg;
|
||||||
|
|
||||||
if (name === '_') {
|
if (name === '_') {
|
||||||
|
@ -38,7 +39,6 @@ export class ExpressionFunctionParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
accepts(type: string) {
|
accepts(type: string) {
|
||||||
if (!this.types.length) return true;
|
return !this.types?.length || this.types.includes(type as KnownTypeToString<T>);
|
||||||
return this.types.indexOf(type) > -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe('ExpressionFunctionParameter', () => {
|
||||||
const param = new ExpressionFunctionParameter('foo', {
|
const param = new ExpressionFunctionParameter('foo', {
|
||||||
help: 'bar',
|
help: 'bar',
|
||||||
types: ['baz', 'quux'],
|
types: ['baz', 'quux'],
|
||||||
});
|
} as ConstructorParameters<typeof ExpressionFunctionParameter>[1]);
|
||||||
|
|
||||||
expect(param.accepts('baz')).toBe(true);
|
expect(param.accepts('baz')).toBe(true);
|
||||||
expect(param.accepts('quux')).toBe(true);
|
expect(param.accepts('quux')).toBe(true);
|
||||||
|
|
|
@ -11,9 +11,9 @@ import { ExpressionFunctionDefinition } from '../types';
|
||||||
import { Datatable, DatatableColumn } from '../../expression_types';
|
import { Datatable, DatatableColumn } from '../../expression_types';
|
||||||
|
|
||||||
export interface CreateTableArguments {
|
export interface CreateTableArguments {
|
||||||
ids: string[];
|
ids?: string[];
|
||||||
names: string[] | null;
|
names?: string[] | null;
|
||||||
rowCount: number;
|
rowCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTable: ExpressionFunctionDefinition<
|
export const createTable: ExpressionFunctionDefinition<
|
||||||
|
|
|
@ -30,7 +30,7 @@ const inlineStyle = (obj: Record<string, string | number>) => {
|
||||||
return styles.join(';');
|
return styles.join(';');
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Arguments {
|
export interface FontArguments {
|
||||||
align?: TextAlignment;
|
align?: TextAlignment;
|
||||||
color?: string;
|
color?: string;
|
||||||
family?: FontFamily;
|
family?: FontFamily;
|
||||||
|
@ -41,7 +41,12 @@ interface Arguments {
|
||||||
weight?: FontWeight;
|
weight?: FontWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExpressionFunctionFont = ExpressionFunctionDefinition<'font', null, Arguments, Style>;
|
export type ExpressionFunctionFont = ExpressionFunctionDefinition<
|
||||||
|
'font',
|
||||||
|
null,
|
||||||
|
FontArguments,
|
||||||
|
Style
|
||||||
|
>;
|
||||||
|
|
||||||
export const font: ExpressionFunctionFont = {
|
export const font: ExpressionFunctionFont = {
|
||||||
name: 'font',
|
name: 'font',
|
||||||
|
|
|
@ -110,7 +110,7 @@ export const mapColumn: ExpressionFunctionDefinition<
|
||||||
map((rows) => {
|
map((rows) => {
|
||||||
let type: DatatableColumnType = 'null';
|
let type: DatatableColumnType = 'null';
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const rowType = getType(row[id]);
|
const rowType = getType(row[id]) as DatatableColumnType;
|
||||||
if (rowType !== 'null') {
|
if (rowType !== 'null') {
|
||||||
type = rowType;
|
type = rowType;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { map, zipObject } from 'lodash';
|
import { map, zipObject, isString } from 'lodash';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { evaluate } from '@kbn/tinymath';
|
import { evaluate } from '@kbn/tinymath';
|
||||||
import { ExpressionFunctionDefinition } from '../types';
|
import { ExpressionFunctionDefinition } from '../types';
|
||||||
|
@ -23,19 +23,18 @@ const TINYMATH = '`TinyMath`';
|
||||||
const TINYMATH_URL =
|
const TINYMATH_URL =
|
||||||
'https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html';
|
'https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html';
|
||||||
|
|
||||||
const isString = (val: any): boolean => typeof val === 'string';
|
|
||||||
|
|
||||||
function pivotObjectArray<
|
function pivotObjectArray<
|
||||||
RowType extends { [key: string]: any },
|
RowType extends { [key: string]: unknown },
|
||||||
ReturnColumns extends string | number | symbol = keyof RowType
|
ReturnColumns extends keyof RowType & string
|
||||||
>(rows: RowType[], columns?: string[]): Record<string, ReturnColumns[]> {
|
>(rows: RowType[], columns?: ReturnColumns[]) {
|
||||||
const columnNames = columns || Object.keys(rows[0]);
|
const columnNames = columns || Object.keys(rows[0]);
|
||||||
if (!columnNames.every(isString)) {
|
if (!columnNames.every(isString)) {
|
||||||
throw new Error('Columns should be an array of strings');
|
throw new Error('Columns should be an array of strings');
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnValues = map(columnNames, (name) => map(rows, name));
|
const columnValues = map(columnNames, (name) => map(rows, name));
|
||||||
return zipObject(columnNames, columnValues);
|
|
||||||
|
return zipObject(columnNames, columnValues) as { [K in ReturnColumns]: Array<RowType[K]> };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const errors = {
|
export const errors = {
|
||||||
|
|
|
@ -107,7 +107,7 @@ export const mathColumn: ExpressionFunctionDefinition<
|
||||||
let type: DatatableColumnType = 'null';
|
let type: DatatableColumnType = 'null';
|
||||||
if (newRows.length) {
|
if (newRows.length) {
|
||||||
for (const row of newRows) {
|
for (const row of newRows) {
|
||||||
const rowType = getType(row[args.id]);
|
const rowType = getType(row[args.id]) as DatatableColumnType;
|
||||||
if (rowType !== 'null') {
|
if (rowType !== 'null') {
|
||||||
type = rowType;
|
type = rowType;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { openSans } from '../../../fonts';
|
import { openSans } from '../../../fonts';
|
||||||
import { font } from '../font';
|
import { FontWeight, TextAlignment } from '../../../types';
|
||||||
|
import { font, FontArguments } from '../font';
|
||||||
import { functionWrapper } from './utils';
|
import { functionWrapper } from './utils';
|
||||||
|
|
||||||
describe('font', () => {
|
describe('font', () => {
|
||||||
|
@ -22,7 +23,7 @@ describe('font', () => {
|
||||||
size: 14,
|
size: 14,
|
||||||
underline: false,
|
underline: false,
|
||||||
weight: 'normal',
|
weight: 'normal',
|
||||||
};
|
} as unknown as FontArguments;
|
||||||
|
|
||||||
describe('default output', () => {
|
describe('default output', () => {
|
||||||
const result = fn(null, args);
|
const result = fn(null, args);
|
||||||
|
@ -63,7 +64,7 @@ describe('font', () => {
|
||||||
|
|
||||||
describe('family', () => {
|
describe('family', () => {
|
||||||
it('sets font family', () => {
|
it('sets font family', () => {
|
||||||
const result = fn(null, { ...args, family: 'Optima, serif' });
|
const result = fn(null, { ...args, family: 'Optima, serif' } as unknown as FontArguments);
|
||||||
expect(result.spec.fontFamily).toBe('Optima, serif');
|
expect(result.spec.fontFamily).toBe('Optima, serif');
|
||||||
expect(result.css).toContain('font-family:Optima, serif');
|
expect(result.css).toContain('font-family:Optima, serif');
|
||||||
});
|
});
|
||||||
|
@ -79,29 +80,29 @@ describe('font', () => {
|
||||||
|
|
||||||
describe('weight', () => {
|
describe('weight', () => {
|
||||||
it('sets font weight', () => {
|
it('sets font weight', () => {
|
||||||
let result = fn(null, { ...args, weight: 'normal' });
|
let result = fn(null, { ...args, weight: FontWeight.NORMAL });
|
||||||
expect(result.spec.fontWeight).toBe('normal');
|
expect(result.spec.fontWeight).toBe('normal');
|
||||||
expect(result.css).toContain('font-weight:normal');
|
expect(result.css).toContain('font-weight:normal');
|
||||||
|
|
||||||
result = fn(null, { ...args, weight: 'bold' });
|
result = fn(null, { ...args, weight: FontWeight.BOLD });
|
||||||
expect(result.spec.fontWeight).toBe('bold');
|
expect(result.spec.fontWeight).toBe('bold');
|
||||||
expect(result.css).toContain('font-weight:bold');
|
expect(result.css).toContain('font-weight:bold');
|
||||||
|
|
||||||
result = fn(null, { ...args, weight: 'bolder' });
|
result = fn(null, { ...args, weight: FontWeight.BOLDER });
|
||||||
expect(result.spec.fontWeight).toBe('bolder');
|
expect(result.spec.fontWeight).toBe('bolder');
|
||||||
expect(result.css).toContain('font-weight:bolder');
|
expect(result.css).toContain('font-weight:bolder');
|
||||||
|
|
||||||
result = fn(null, { ...args, weight: 'lighter' });
|
result = fn(null, { ...args, weight: FontWeight.LIGHTER });
|
||||||
expect(result.spec.fontWeight).toBe('lighter');
|
expect(result.spec.fontWeight).toBe('lighter');
|
||||||
expect(result.css).toContain('font-weight:lighter');
|
expect(result.css).toContain('font-weight:lighter');
|
||||||
|
|
||||||
result = fn(null, { ...args, weight: '400' });
|
result = fn(null, { ...args, weight: FontWeight.FOUR });
|
||||||
expect(result.spec.fontWeight).toBe('400');
|
expect(result.spec.fontWeight).toBe('400');
|
||||||
expect(result.css).toContain('font-weight:400');
|
expect(result.css).toContain('font-weight:400');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when provided an invalid weight', () => {
|
it('throws when provided an invalid weight', () => {
|
||||||
expect(() => fn(null, { ...args, weight: 'foo' })).toThrow();
|
expect(() => fn(null, { ...args, weight: 'foo' as FontWeight })).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -131,25 +132,25 @@ describe('font', () => {
|
||||||
|
|
||||||
describe('align', () => {
|
describe('align', () => {
|
||||||
it('sets text alignment', () => {
|
it('sets text alignment', () => {
|
||||||
let result = fn(null, { ...args, align: 'left' });
|
let result = fn(null, { ...args, align: TextAlignment.LEFT });
|
||||||
expect(result.spec.textAlign).toBe('left');
|
expect(result.spec.textAlign).toBe('left');
|
||||||
expect(result.css).toContain('text-align:left');
|
expect(result.css).toContain('text-align:left');
|
||||||
|
|
||||||
result = fn(null, { ...args, align: 'center' });
|
result = fn(null, { ...args, align: TextAlignment.CENTER });
|
||||||
expect(result.spec.textAlign).toBe('center');
|
expect(result.spec.textAlign).toBe('center');
|
||||||
expect(result.css).toContain('text-align:center');
|
expect(result.css).toContain('text-align:center');
|
||||||
|
|
||||||
result = fn(null, { ...args, align: 'right' });
|
result = fn(null, { ...args, align: TextAlignment.RIGHT });
|
||||||
expect(result.spec.textAlign).toBe('right');
|
expect(result.spec.textAlign).toBe('right');
|
||||||
expect(result.css).toContain('text-align:right');
|
expect(result.css).toContain('text-align:right');
|
||||||
|
|
||||||
result = fn(null, { ...args, align: 'justify' });
|
result = fn(null, { ...args, align: TextAlignment.JUSTIFY });
|
||||||
expect(result.spec.textAlign).toBe('justify');
|
expect(result.spec.textAlign).toBe('justify');
|
||||||
expect(result.css).toContain('text-align:justify');
|
expect(result.css).toContain('text-align:justify');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when provided an invalid alignment', () => {
|
it('throws when provided an invalid alignment', () => {
|
||||||
expect(() => fn(null, { ...args, align: 'foo' })).toThrow();
|
expect(() => fn(null, { ...args, align: 'foo' as TextAlignment })).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,19 +6,19 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { errors, math } from '../math';
|
import { errors, math, MathArguments, MathInput } from '../math';
|
||||||
import { emptyTable, functionWrapper, testTable } from './utils';
|
import { emptyTable, functionWrapper, testTable } from './utils';
|
||||||
|
|
||||||
describe('math', () => {
|
describe('math', () => {
|
||||||
const fn = functionWrapper<unknown>(math);
|
const fn = functionWrapper(math);
|
||||||
|
|
||||||
it('evaluates math expressions without reference to context', () => {
|
it('evaluates math expressions without reference to context', () => {
|
||||||
expect(fn(null, { expression: '10.5345' })).toBe(10.5345);
|
expect(fn(null as unknown as MathInput, { expression: '10.5345' })).toBe(10.5345);
|
||||||
expect(fn(null, { expression: '123 + 456' })).toBe(579);
|
expect(fn(null as unknown as MathInput, { expression: '123 + 456' })).toBe(579);
|
||||||
expect(fn(null, { expression: '100 - 46' })).toBe(54);
|
expect(fn(null as unknown as MathInput, { expression: '100 - 46' })).toBe(54);
|
||||||
expect(fn(1, { expression: '100 / 5' })).toBe(20);
|
expect(fn(1, { expression: '100 / 5' })).toBe(20);
|
||||||
expect(fn('foo', { expression: '100 / 5' })).toBe(20);
|
expect(fn('foo' as unknown as MathInput, { expression: '100 / 5' })).toBe(20);
|
||||||
expect(fn(true, { expression: '100 / 5' })).toBe(20);
|
expect(fn(true as unknown as MathInput, { expression: '100 / 5' })).toBe(20);
|
||||||
expect(fn(testTable, { expression: '100 * 5' })).toBe(500);
|
expect(fn(testTable, { expression: '100 * 5' })).toBe(500);
|
||||||
expect(fn(emptyTable, { expression: '100 * 5' })).toBe(500);
|
expect(fn(emptyTable, { expression: '100 * 5' })).toBe(500);
|
||||||
});
|
});
|
||||||
|
@ -54,7 +54,7 @@ describe('math', () => {
|
||||||
describe('args', () => {
|
describe('args', () => {
|
||||||
describe('expression', () => {
|
describe('expression', () => {
|
||||||
it('sets the math expression to be evaluted', () => {
|
it('sets the math expression to be evaluted', () => {
|
||||||
expect(fn(null, { expression: '10' })).toBe(10);
|
expect(fn(null as unknown as MathInput, { expression: '10' })).toBe(10);
|
||||||
expect(fn(23.23, { expression: 'floor(value)' })).toBe(23);
|
expect(fn(23.23, { expression: 'floor(value)' })).toBe(23);
|
||||||
expect(fn(testTable, { expression: 'count(price)' })).toBe(9);
|
expect(fn(testTable, { expression: 'count(price)' })).toBe(9);
|
||||||
expect(fn(testTable, { expression: 'count(name)' })).toBe(9);
|
expect(fn(testTable, { expression: 'count(name)' })).toBe(9);
|
||||||
|
@ -99,11 +99,11 @@ describe('math', () => {
|
||||||
it('throws when missing expression', () => {
|
it('throws when missing expression', () => {
|
||||||
expect(() => fn(testTable)).toThrow(new RegExp(errors.emptyExpression().message));
|
expect(() => fn(testTable)).toThrow(new RegExp(errors.emptyExpression().message));
|
||||||
|
|
||||||
expect(() => fn(testTable, { expession: '' })).toThrow(
|
expect(() => fn(testTable, { expession: '' } as unknown as MathArguments)).toThrow(
|
||||||
new RegExp(errors.emptyExpression().message)
|
new RegExp(errors.emptyExpression().message)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(() => fn(testTable, { expession: ' ' })).toThrow(
|
expect(() => fn(testTable, { expession: ' ' } as unknown as MathArguments)).toThrow(
|
||||||
new RegExp(errors.emptyExpression().message)
|
new RegExp(errors.emptyExpression().message)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,14 +26,14 @@ describe('expression_functions', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
getSearchContext: () => ({} as any),
|
getSearchContext: () => ({}),
|
||||||
getSearchSessionId: () => undefined,
|
getSearchSessionId: () => undefined,
|
||||||
getExecutionContext: () => undefined,
|
getExecutionContext: () => undefined,
|
||||||
types: {},
|
types: {},
|
||||||
variables: { theme: themeProps },
|
variables: { theme: themeProps },
|
||||||
abortSignal: {} as any,
|
abortSignal: {},
|
||||||
inspectorAdapters: {} as any,
|
inspectorAdapters: {},
|
||||||
};
|
} as unknown as typeof context;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the selected variable', () => {
|
it('returns the selected variable', () => {
|
||||||
|
|
|
@ -10,13 +10,15 @@ jest.mock('../../../../common');
|
||||||
|
|
||||||
import { IUiSettingsClient } from 'src/core/public';
|
import { IUiSettingsClient } from 'src/core/public';
|
||||||
import { getUiSettingFn } from '../ui_setting';
|
import { getUiSettingFn } from '../ui_setting';
|
||||||
|
import { functionWrapper } from './utils';
|
||||||
|
|
||||||
describe('uiSetting', () => {
|
describe('uiSetting', () => {
|
||||||
describe('fn', () => {
|
describe('fn', () => {
|
||||||
let getStartDependencies: jest.MockedFunction<
|
let getStartDependencies: jest.MockedFunction<
|
||||||
Parameters<typeof getUiSettingFn>[0]['getStartDependencies']
|
Parameters<typeof getUiSettingFn>[0]['getStartDependencies']
|
||||||
>;
|
>;
|
||||||
let uiSetting: ReturnType<typeof getUiSettingFn>;
|
const uiSettingWrapper = () => functionWrapper(getUiSettingFn({ getStartDependencies }));
|
||||||
|
let uiSetting: ReturnType<typeof uiSettingWrapper>;
|
||||||
let uiSettings: jest.Mocked<IUiSettingsClient>;
|
let uiSettings: jest.Mocked<IUiSettingsClient>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -27,13 +29,13 @@ describe('uiSetting', () => {
|
||||||
uiSettings,
|
uiSettings,
|
||||||
})) as unknown as typeof getStartDependencies;
|
})) as unknown as typeof getStartDependencies;
|
||||||
|
|
||||||
uiSetting = getUiSettingFn({ getStartDependencies });
|
uiSetting = uiSettingWrapper();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a value', () => {
|
it('should return a value', () => {
|
||||||
uiSettings.get.mockReturnValueOnce('value');
|
uiSettings.get.mockReturnValueOnce('value');
|
||||||
|
|
||||||
expect(uiSetting.fn(null, { parameter: 'something' }, {} as any)).resolves.toEqual({
|
expect(uiSetting(null, { parameter: 'something' })).resolves.toEqual({
|
||||||
type: 'ui_setting',
|
type: 'ui_setting',
|
||||||
key: 'something',
|
key: 'something',
|
||||||
value: 'value',
|
value: 'value',
|
||||||
|
@ -41,7 +43,7 @@ describe('uiSetting', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass a default value', async () => {
|
it('should pass a default value', async () => {
|
||||||
await uiSetting.fn(null, { parameter: 'something', default: 'default' }, {} as any);
|
await uiSetting(null, { parameter: 'something', default: 'default' });
|
||||||
|
|
||||||
expect(uiSettings.get).toHaveBeenCalledWith('something', 'default');
|
expect(uiSettings.get).toHaveBeenCalledWith('something', 'default');
|
||||||
});
|
});
|
||||||
|
@ -51,16 +53,16 @@ describe('uiSetting', () => {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(uiSetting.fn(null, { parameter: 'something' }, {} as any)).rejects.toEqual(
|
expect(uiSetting(null, { parameter: 'something' })).rejects.toEqual(
|
||||||
new Error('Invalid parameter "something".')
|
new Error('Invalid parameter "something".')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get a request instance on the server-side', async () => {
|
it('should get a request instance on the server-side', async () => {
|
||||||
const request = {};
|
const request = {};
|
||||||
await uiSetting.fn(null, { parameter: 'something' }, {
|
await uiSetting(null, { parameter: 'something' }, {
|
||||||
getKibanaRequest: () => request,
|
getKibanaRequest: () => request,
|
||||||
} as any);
|
} as Parameters<typeof uiSetting>[2]);
|
||||||
|
|
||||||
const [[getKibanaRequest]] = getStartDependencies.mock.calls;
|
const [[getKibanaRequest]] = getStartDependencies.mock.calls;
|
||||||
|
|
||||||
|
@ -68,7 +70,7 @@ describe('uiSetting', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if request is not provided on the server-side', async () => {
|
it('should throw an error if request is not provided on the server-side', async () => {
|
||||||
await uiSetting.fn(null, { parameter: 'something' }, {} as any);
|
await uiSetting(null, { parameter: 'something' });
|
||||||
|
|
||||||
const [[getKibanaRequest]] = getStartDependencies.mock.calls;
|
const [[getKibanaRequest]] = getStartDependencies.mock.calls;
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,15 @@ import { Datatable } from '../../../expression_types';
|
||||||
* Takes a function spec and passes in default args,
|
* Takes a function spec and passes in default args,
|
||||||
* overriding with any provided args.
|
* overriding with any provided args.
|
||||||
*/
|
*/
|
||||||
export const functionWrapper = <ContextType = object | null>(
|
export const functionWrapper = <
|
||||||
spec: AnyExpressionFunctionDefinition
|
ExpressionFunctionDefinition extends AnyExpressionFunctionDefinition
|
||||||
|
>(
|
||||||
|
spec: ExpressionFunctionDefinition
|
||||||
) => {
|
) => {
|
||||||
const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default);
|
const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default);
|
||||||
return (
|
return (
|
||||||
context: ContextType,
|
context?: Parameters<ExpressionFunctionDefinition['fn']>[0] | null,
|
||||||
args: Record<string, any> = {},
|
args: Parameters<ExpressionFunctionDefinition['fn']>[1] = {},
|
||||||
handlers: ExecutionContext = {} as ExecutionContext
|
handlers: ExecutionContext = {} as ExecutionContext
|
||||||
) => spec.fn(context, { ...defaultArgs, ...args }, handlers);
|
) => spec.fn(context, { ...defaultArgs, ...args }, handlers);
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,9 +24,9 @@ describe('expression_functions', () => {
|
||||||
getExecutionContext: () => undefined,
|
getExecutionContext: () => undefined,
|
||||||
types: {},
|
types: {},
|
||||||
variables: { test: 1 },
|
variables: { test: 1 },
|
||||||
abortSignal: {} as any,
|
abortSignal: {},
|
||||||
inspectorAdapters: {} as any,
|
inspectorAdapters: {},
|
||||||
};
|
} as unknown as typeof context;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the selected variable', () => {
|
it('returns the selected variable', () => {
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('expression_functions', () => {
|
||||||
const fn = functionWrapper(variableSet);
|
const fn = functionWrapper(variableSet);
|
||||||
let input: Partial<ReturnType<ExecutionContext['getSearchContext']>>;
|
let input: Partial<ReturnType<ExecutionContext['getSearchContext']>>;
|
||||||
let context: ExecutionContext;
|
let context: ExecutionContext;
|
||||||
let variables: Record<string, any>;
|
let variables: Record<string, unknown>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
input = { timeRange: { from: '0', to: '1' } };
|
input = { timeRange: { from: '0', to: '1' } };
|
||||||
|
@ -27,9 +27,9 @@ describe('expression_functions', () => {
|
||||||
getExecutionContext: () => undefined,
|
getExecutionContext: () => undefined,
|
||||||
types: {},
|
types: {},
|
||||||
variables: { test: 1 },
|
variables: { test: 1 },
|
||||||
abortSignal: {} as any,
|
abortSignal: {},
|
||||||
inspectorAdapters: {} as any,
|
inspectorAdapters: {},
|
||||||
};
|
} as unknown as typeof context;
|
||||||
|
|
||||||
variables = context.variables;
|
variables = context.variables;
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,10 +12,10 @@ import { ExpressionFunctionDefinition } from '../types';
|
||||||
|
|
||||||
interface Arguments {
|
interface Arguments {
|
||||||
variable: string;
|
variable: string;
|
||||||
default: string | number | boolean;
|
default?: string | number | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Output = any;
|
type Output = unknown;
|
||||||
|
|
||||||
export type ExpressionFunctionTheme = ExpressionFunctionDefinition<
|
export type ExpressionFunctionTheme = ExpressionFunctionDefinition<
|
||||||
'theme',
|
'theme',
|
||||||
|
|
|
@ -36,7 +36,8 @@ export const variable: ExpressionFunctionVar = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn(input, args, context) {
|
fn(input, args, context) {
|
||||||
const variables: Record<string, any> = context.variables;
|
const { variables } = context;
|
||||||
|
|
||||||
return variables[args.name];
|
return variables[args.name];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import type { Serializable } from '@kbn/utility-types';
|
||||||
import { ExpressionFunctionDefinition } from '../types';
|
import { ExpressionFunctionDefinition } from '../types';
|
||||||
|
|
||||||
interface Arguments {
|
interface Arguments {
|
||||||
name: string[];
|
name: string[];
|
||||||
value: any[];
|
value: Serializable[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExpressionFunctionVarSet = ExpressionFunctionDefinition<
|
export type ExpressionFunctionVarSet = ExpressionFunctionDefinition<
|
||||||
|
@ -46,10 +47,11 @@ export const variableSet: ExpressionFunctionVarSet = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn(input, args, context) {
|
fn(input, args, context) {
|
||||||
const variables: Record<string, any> = context.variables;
|
const { variables } = context;
|
||||||
args.name.forEach((name, i) => {
|
args.name.forEach((name, i) => {
|
||||||
variables[name] = args.value[i] === undefined ? input : args.value[i];
|
variables[name] = args.value[i] === undefined ? input : args.value[i];
|
||||||
});
|
});
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { PersistableStateDefinition } from '../../../kibana_utils/common';
|
||||||
export interface ExpressionFunctionDefinition<
|
export interface ExpressionFunctionDefinition<
|
||||||
Name extends string,
|
Name extends string,
|
||||||
Input,
|
Input,
|
||||||
Arguments extends Record<string, any>,
|
Arguments extends Record<keyof unknown, unknown>,
|
||||||
Output,
|
Output,
|
||||||
Context extends ExecutionContext = ExecutionContext
|
Context extends ExecutionContext = ExecutionContext
|
||||||
> extends PersistableStateDefinition<ExpressionAstFunction['arguments']> {
|
> extends PersistableStateDefinition<ExpressionAstFunction['arguments']> {
|
||||||
|
@ -99,12 +99,14 @@ export interface ExpressionFunctionDefinition<
|
||||||
/**
|
/**
|
||||||
* Type to capture every possible expression function definition.
|
* Type to capture every possible expression function definition.
|
||||||
*/
|
*/
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
export type AnyExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
export type AnyExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||||
string,
|
string,
|
||||||
any,
|
any,
|
||||||
Record<string, any>,
|
Record<string, any>,
|
||||||
any
|
any
|
||||||
>;
|
>;
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping of `ExpressionFunctionDefinition`s for functions which the
|
* A mapping of `ExpressionFunctionDefinition`s for functions which the
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ExpressionAstExpression } from '../ast';
|
||||||
|
|
||||||
export interface ExpressionRenderDefinition<Config = unknown> {
|
export interface ExpressionRenderDefinition<Config = unknown> {
|
||||||
/**
|
/**
|
||||||
* Technical name of the renderer, used as ID to identify renderer in
|
* Technical name of the renderer, used as ID to identify renderer in
|
||||||
|
@ -46,6 +48,7 @@ export interface ExpressionRenderDefinition<Config = unknown> {
|
||||||
) => void | Promise<void>;
|
) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type AnyExpressionRenderDefinition = ExpressionRenderDefinition<any>;
|
export type AnyExpressionRenderDefinition = ExpressionRenderDefinition<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,24 +62,34 @@ export type AnyExpressionRenderDefinition = ExpressionRenderDefinition<any>;
|
||||||
*/
|
*/
|
||||||
export type RenderMode = 'edit' | 'preview' | 'view';
|
export type RenderMode = 'edit' | 'preview' | 'view';
|
||||||
|
|
||||||
|
export interface IInterpreterRenderUpdateParams<Params = unknown> {
|
||||||
|
newExpression?: string | ExpressionAstExpression;
|
||||||
|
newParams: Params;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInterpreterRenderEvent<Context = unknown> {
|
||||||
|
name: string;
|
||||||
|
data?: Context;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IInterpreterRenderHandlers {
|
export interface IInterpreterRenderHandlers {
|
||||||
/**
|
/**
|
||||||
* Done increments the number of rendering successes
|
* Done increments the number of rendering successes
|
||||||
*/
|
*/
|
||||||
done: () => void;
|
done(): void;
|
||||||
onDestroy: (fn: () => void) => void;
|
onDestroy(fn: () => void): void;
|
||||||
reload: () => void;
|
reload(): void;
|
||||||
update: (params: any) => void;
|
update(params: IInterpreterRenderUpdateParams): void;
|
||||||
event: (event: any) => void;
|
event(event: IInterpreterRenderEvent): void;
|
||||||
hasCompatibleActions?: (event: any) => Promise<boolean>;
|
hasCompatibleActions?(event: IInterpreterRenderEvent): Promise<boolean>;
|
||||||
getRenderMode: () => RenderMode;
|
getRenderMode(): RenderMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The chart is rendered in a non-interactive environment and should not provide any affordances for interaction like brushing.
|
* The chart is rendered in a non-interactive environment and should not provide any affordances for interaction like brushing.
|
||||||
*/
|
*/
|
||||||
isInteractive: () => boolean;
|
isInteractive(): boolean;
|
||||||
|
|
||||||
isSyncColorsEnabled: () => boolean;
|
isSyncColorsEnabled(): boolean;
|
||||||
/**
|
/**
|
||||||
* This uiState interface is actually `PersistedState` from the visualizations plugin,
|
* This uiState interface is actually `PersistedState` from the visualizations plugin,
|
||||||
* but expressions cannot know about vis or it creates a mess of circular dependencies.
|
* but expressions cannot know about vis or it creates a mess of circular dependencies.
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Serializable } from '@kbn/utility-types';
|
||||||
import { AnyExpressionTypeDefinition, ExpressionValue, ExpressionValueConverter } from './types';
|
import { AnyExpressionTypeDefinition, ExpressionValue, ExpressionValueConverter } from './types';
|
||||||
import { getType } from './get_type';
|
import { getType } from './get_type';
|
||||||
|
|
||||||
|
@ -20,15 +21,15 @@ export class ExpressionType {
|
||||||
/**
|
/**
|
||||||
* Type validation, useful for checking function output.
|
* Type validation, useful for checking function output.
|
||||||
*/
|
*/
|
||||||
validate: (type: any) => void | Error;
|
validate: (type: unknown) => void | Error;
|
||||||
|
|
||||||
create: unknown;
|
create: unknown;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional serialization (used when passing context around client/server).
|
* Optional serialization (used when passing context around client/server).
|
||||||
*/
|
*/
|
||||||
serialize?: (value: ExpressionValue) => any;
|
serialize?: (value: Serializable) => unknown;
|
||||||
deserialize?: (serialized: any) => ExpressionValue;
|
deserialize?: (serialized: unknown[]) => Serializable;
|
||||||
|
|
||||||
constructor(private readonly definition: AnyExpressionTypeDefinition) {
|
constructor(private readonly definition: AnyExpressionTypeDefinition) {
|
||||||
const { name, help, deserialize, serialize, validate } = definition;
|
const { name, help, deserialize, serialize, validate } = definition;
|
||||||
|
@ -38,7 +39,7 @@ export class ExpressionType {
|
||||||
this.validate = validate || (() => {});
|
this.validate = validate || (() => {});
|
||||||
|
|
||||||
// Optional
|
// Optional
|
||||||
this.create = (definition as any).create;
|
this.create = (definition as unknown as Record<'create', unknown>).create;
|
||||||
|
|
||||||
this.serialize = serialize;
|
this.serialize = serialize;
|
||||||
this.deserialize = deserialize;
|
this.deserialize = deserialize;
|
||||||
|
|
|
@ -6,14 +6,24 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function getType(node: any) {
|
export function getType(node: unknown): string {
|
||||||
if (node == null) return 'null';
|
if (node == null) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(node)) {
|
if (Array.isArray(node)) {
|
||||||
throw new Error('Unexpected array value encountered.');
|
throw new Error('Unexpected array value encountered.');
|
||||||
}
|
}
|
||||||
if (typeof node === 'object') {
|
|
||||||
if (!node.type) throw new Error('Objects must have a type property');
|
if (typeof node !== 'object') {
|
||||||
return node.type;
|
|
||||||
}
|
|
||||||
return typeof node;
|
return typeof node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { type } = node as Record<string, unknown>;
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
throw new Error('Objects must have a type property');
|
||||||
|
}
|
||||||
|
|
||||||
|
return type as string;
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import type { SerializableRecord } from '@kbn/utility-types';
|
import type { SerializableRecord } from '@kbn/utility-types';
|
||||||
import { map, pick, zipObject } from 'lodash';
|
import { map, pick, zipObject } from 'lodash';
|
||||||
|
|
||||||
import { ExpressionTypeDefinition } from '../types';
|
import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types';
|
||||||
import { PointSeries, PointSeriesColumn } from './pointseries';
|
import { PointSeries, PointSeriesColumn } from './pointseries';
|
||||||
import { ExpressionValueRender } from './render';
|
import { ExpressionValueRender } from './render';
|
||||||
import { SerializedFieldFormat } from '../../types';
|
import { SerializedFieldFormat } from '../../types';
|
||||||
|
@ -21,7 +21,7 @@ const name = 'datatable';
|
||||||
* @param datatable
|
* @param datatable
|
||||||
*/
|
*/
|
||||||
export const isDatatable = (datatable: unknown): datatable is Datatable =>
|
export const isDatatable = (datatable: unknown): datatable is Datatable =>
|
||||||
!!datatable && typeof datatable === 'object' && (datatable as any).type === 'datatable';
|
(datatable as ExpressionValueBoxed | undefined)?.type === 'datatable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This type represents the `type` of any `DatatableColumn` in a `Datatable`.
|
* This type represents the `type` of any `DatatableColumn` in a `Datatable`.
|
||||||
|
@ -48,6 +48,7 @@ export type DatatableColumnType =
|
||||||
/**
|
/**
|
||||||
* This type represents a row in a `Datatable`.
|
* This type represents a row in a `Datatable`.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type DatatableRow = Record<string, any>;
|
export type DatatableRow = Record<string, any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +113,7 @@ interface RenderedDatatable {
|
||||||
|
|
||||||
export const datatable: ExpressionTypeDefinition<typeof name, Datatable, SerializedDatatable> = {
|
export const datatable: ExpressionTypeDefinition<typeof name, Datatable, SerializedDatatable> = {
|
||||||
name,
|
name,
|
||||||
validate: (table) => {
|
validate: (table: Record<string, unknown>) => {
|
||||||
// TODO: Check columns types. Only string, boolean, number, date, allowed for now.
|
// TODO: Check columns types. Only string, boolean, number, date, allowed for now.
|
||||||
if (!table.columns) {
|
if (!table.columns) {
|
||||||
throw new Error('datatable must have a columns array, even if it is empty');
|
throw new Error('datatable must have a columns array, even if it is empty');
|
||||||
|
|
|
@ -22,7 +22,7 @@ export type ExpressionValueError = ExpressionValueBoxed<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const isExpressionValueError = (value: any): value is ExpressionValueError =>
|
export const isExpressionValueError = (value: unknown): value is ExpressionValueError =>
|
||||||
getType(value) === 'error';
|
getType(value) === 'error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types';
|
import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types';
|
||||||
import { Datatable } from './datatable';
|
import { Datatable, DatatableRow } from './datatable';
|
||||||
import { ExpressionValueRender } from './render';
|
import { ExpressionValueRender } from './render';
|
||||||
|
|
||||||
const name = 'pointseries';
|
const name = 'pointseries';
|
||||||
|
@ -31,7 +31,7 @@ export interface PointSeriesColumn {
|
||||||
*/
|
*/
|
||||||
export type PointSeriesColumns = Record<PointSeriesColumnName, PointSeriesColumn> | {};
|
export type PointSeriesColumns = Record<PointSeriesColumnName, PointSeriesColumn> | {};
|
||||||
|
|
||||||
export type PointSeriesRow = Record<string, any>;
|
export type PointSeriesRow = DatatableRow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `PointSeries` is a unique structure that represents dots on a chart.
|
* A `PointSeries` is a unique structure that represents dots on a chart.
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { ExpressionValueRender } from './render';
|
||||||
|
|
||||||
const name = 'shape';
|
const name = 'shape';
|
||||||
|
|
||||||
export const shape: ExpressionTypeDefinition<typeof name, ExpressionValueRender<any>> = {
|
export const shape: ExpressionTypeDefinition<typeof name, ExpressionValueRender<unknown>> = {
|
||||||
name: 'shape',
|
name: 'shape',
|
||||||
to: {
|
to: {
|
||||||
render: (input) => {
|
render: (input) => {
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { ExpressionType } from './expression_type';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type ExpressionValueUnboxed = any;
|
export type ExpressionValueUnboxed = any;
|
||||||
|
|
||||||
export type ExpressionValueBoxed<Type extends string = string, Value extends object = object> = {
|
export type ExpressionValueBoxed<Type extends string = string, Value extends object = object> = {
|
||||||
|
@ -16,7 +19,7 @@ export type ExpressionValue = ExpressionValueUnboxed | ExpressionValueBoxed;
|
||||||
|
|
||||||
export type ExpressionValueConverter<I extends ExpressionValue, O extends ExpressionValue> = (
|
export type ExpressionValueConverter<I extends ExpressionValue, O extends ExpressionValue> = (
|
||||||
input: I,
|
input: I,
|
||||||
availableTypes: Record<string, any>
|
availableTypes: Record<string, ExpressionType>
|
||||||
) => O;
|
) => O;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,18 +32,19 @@ export interface ExpressionTypeDefinition<
|
||||||
SerializedType = undefined
|
SerializedType = undefined
|
||||||
> {
|
> {
|
||||||
name: Name;
|
name: Name;
|
||||||
validate?: (type: any) => void | Error;
|
validate?(type: unknown): void | Error;
|
||||||
serialize?: (type: Value) => SerializedType;
|
serialize?(type: Value): SerializedType;
|
||||||
deserialize?: (type: SerializedType) => Value;
|
deserialize?(type: SerializedType): Value;
|
||||||
// TODO: Update typings for the `availableTypes` parameter once interfaces for this
|
// TODO: Update typings for the `availableTypes` parameter once interfaces for this
|
||||||
// have been added elsewhere in the interpreter.
|
// have been added elsewhere in the interpreter.
|
||||||
from?: {
|
from?: {
|
||||||
[type: string]: ExpressionValueConverter<any, Value>;
|
[type: string]: ExpressionValueConverter<ExpressionValue, Value>;
|
||||||
};
|
};
|
||||||
to?: {
|
to?: {
|
||||||
[type: string]: ExpressionValueConverter<Value, any>;
|
[type: string]: ExpressionValueConverter<Value, ExpressionValue>;
|
||||||
};
|
};
|
||||||
help?: string;
|
help?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnyExpressionTypeDefinition = ExpressionTypeDefinition<any, any, any>;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export type AnyExpressionTypeDefinition = ExpressionTypeDefinition<string, any, any>;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { ExecutionContext } from './execution/types';
|
||||||
export const createMockExecutionContext = <ExtraContext extends object = object>(
|
export const createMockExecutionContext = <ExtraContext extends object = object>(
|
||||||
extraContext: ExtraContext = {} as ExtraContext
|
extraContext: ExtraContext = {} as ExtraContext
|
||||||
): ExecutionContext & ExtraContext => {
|
): ExecutionContext & ExtraContext => {
|
||||||
const executionContext: ExecutionContext = {
|
const executionContext = {
|
||||||
getSearchContext: jest.fn(),
|
getSearchContext: jest.fn(),
|
||||||
getSearchSessionId: jest.fn(),
|
getSearchSessionId: jest.fn(),
|
||||||
getExecutionContext: jest.fn(),
|
getExecutionContext: jest.fn(),
|
||||||
|
@ -25,10 +25,10 @@ export const createMockExecutionContext = <ExtraContext extends object = object>
|
||||||
removeEventListener: jest.fn(),
|
removeEventListener: jest.fn(),
|
||||||
},
|
},
|
||||||
inspectorAdapters: {
|
inspectorAdapters: {
|
||||||
requests: {} as any,
|
requests: {},
|
||||||
data: {} as any,
|
data: {},
|
||||||
},
|
},
|
||||||
};
|
} as unknown as ExecutionContext;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...executionContext,
|
...executionContext,
|
||||||
|
|
|
@ -125,7 +125,7 @@ export interface ExpressionsServiceSetup {
|
||||||
export interface ExpressionExecutionParams {
|
export interface ExpressionExecutionParams {
|
||||||
searchContext?: SerializableRecord;
|
searchContext?: SerializableRecord;
|
||||||
|
|
||||||
variables?: Record<string, any>;
|
variables?: Record<string, unknown>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to execute expression in *debug mode*. In *debug mode* inputs and
|
* Whether to execute expression in *debug mode*. In *debug mode* inputs and
|
||||||
|
@ -148,6 +148,8 @@ export interface ExpressionExecutionParams {
|
||||||
inspectorAdapters?: Adapters;
|
inspectorAdapters?: Adapters;
|
||||||
|
|
||||||
executionContext?: KibanaExecutionContext;
|
executionContext?: KibanaExecutionContext;
|
||||||
|
|
||||||
|
extraContext?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -375,7 +377,7 @@ export class ExpressionsService
|
||||||
*/
|
*/
|
||||||
public readonly telemetry = (
|
public readonly telemetry = (
|
||||||
state: ExpressionAstExpression,
|
state: ExpressionAstExpression,
|
||||||
telemetryData: Record<string, any> = {}
|
telemetryData: Record<string, unknown> = {}
|
||||||
) => {
|
) => {
|
||||||
return this.executor.telemetry(state, telemetryData);
|
return this.executor.telemetry(state, telemetryData);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import { ExpressionFunctionDefinition } from '../../expression_functions';
|
import { ExpressionFunctionDefinition } from '../../expression_functions';
|
||||||
|
|
||||||
export const access: ExpressionFunctionDefinition<'access', any, { key: string }, any> = {
|
export const access: ExpressionFunctionDefinition<'access', unknown, { key: string }, unknown> = {
|
||||||
name: 'access',
|
name: 'access',
|
||||||
help: 'Access key on input object or return the input, if it is not an object',
|
help: 'Access key on input object or return the input, if it is not an object',
|
||||||
args: {
|
args: {
|
||||||
|
@ -19,6 +19,10 @@ export const access: ExpressionFunctionDefinition<'access', any, { key: string }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn: (input, { key }, context) => {
|
fn: (input, { key }, context) => {
|
||||||
return !input ? input : typeof input === 'object' ? input[key] : input;
|
return !input
|
||||||
|
? input
|
||||||
|
: typeof input === 'object'
|
||||||
|
? (input as Record<string, unknown>)[key]
|
||||||
|
: input;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,11 +26,11 @@ export const add: ExpressionFunctionDefinition<
|
||||||
types: ['null', 'number', 'string'],
|
types: ['null', 'number', 'string'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn: ({ value: value1 }, { val: input2 }, context) => {
|
fn: ({ value: value1 }, { val: input2 }) => {
|
||||||
const value2 = !input2
|
const value2 = !input2
|
||||||
? 0
|
? 0
|
||||||
: typeof input2 === 'object'
|
: typeof input2 === 'object'
|
||||||
? (input2 as any).value
|
? (input2 as ExpressionValueNum).value
|
||||||
: Number(input2);
|
: Number(input2);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -10,9 +10,9 @@ import { ExpressionFunctionDefinition } from '../../expression_functions';
|
||||||
|
|
||||||
export const introspectContext: ExpressionFunctionDefinition<
|
export const introspectContext: ExpressionFunctionDefinition<
|
||||||
'introspectContext',
|
'introspectContext',
|
||||||
any,
|
unknown,
|
||||||
{ key: string },
|
{ key: string },
|
||||||
any
|
unknown
|
||||||
> = {
|
> = {
|
||||||
name: 'introspectContext',
|
name: 'introspectContext',
|
||||||
args: {
|
args: {
|
||||||
|
@ -25,7 +25,7 @@ export const introspectContext: ExpressionFunctionDefinition<
|
||||||
fn: (input, args, context) => {
|
fn: (input, args, context) => {
|
||||||
return {
|
return {
|
||||||
type: 'any',
|
type: 'any',
|
||||||
result: (context as any)[args.key],
|
result: context[args.key as keyof typeof context],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import { ExpressionFunctionDefinition } from '../../expression_functions';
|
import { ExpressionFunctionDefinition } from '../../expression_functions';
|
||||||
|
|
||||||
export const sleep: ExpressionFunctionDefinition<'sleep', any, { time: number }, any> = {
|
export const sleep: ExpressionFunctionDefinition<'sleep', unknown, { time: number }, unknown> = {
|
||||||
name: 'sleep',
|
name: 'sleep',
|
||||||
args: {
|
args: {
|
||||||
time: {
|
time: {
|
||||||
|
|
|
@ -37,7 +37,7 @@ export type KnownTypeToString<T> =
|
||||||
* `someArgument: Promise<boolean | string>` results in `types: ['boolean', 'string']`
|
* `someArgument: Promise<boolean | string>` results in `types: ['boolean', 'string']`
|
||||||
*/
|
*/
|
||||||
export type TypeString<T> = KnownTypeToString<
|
export type TypeString<T> = KnownTypeToString<
|
||||||
T extends ObservableLike<any> ? UnwrapObservable<T> : UnwrapPromiseOrReturn<T>
|
T extends ObservableLike<unknown> ? UnwrapObservable<T> : UnwrapPromiseOrReturn<T>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,6 +52,7 @@ export type UnmappedTypeStrings = 'date' | 'filter';
|
||||||
* Is used to carry information about how to format data in
|
* Is used to carry information about how to format data in
|
||||||
* a data table as part of the column definition.
|
* a data table as part of the column definition.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export interface SerializedFieldFormat<TParams = Record<string, any>> {
|
export interface SerializedFieldFormat<TParams = Record<string, any>> {
|
||||||
id?: string;
|
id?: string;
|
||||||
params?: TParams;
|
params?: TParams;
|
||||||
|
|
|
@ -7,16 +7,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import { ExpressionAstNode } from '..';
|
||||||
|
|
||||||
export class ExpressionsInspectorAdapter extends EventEmitter {
|
export class ExpressionsInspectorAdapter extends EventEmitter {
|
||||||
private _ast: any = {};
|
private _ast = {} as ExpressionAstNode;
|
||||||
|
|
||||||
public logAST(ast: any): void {
|
logAST(ast: ExpressionAstNode): void {
|
||||||
this._ast = ast;
|
this._ast = ast;
|
||||||
this.emit('change', this._ast);
|
this.emit('change', this._ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get ast() {
|
public get ast(): ExpressionAstNode {
|
||||||
return this._ast;
|
return this._ast;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const createMockContext = () => {
|
||||||
getSearchSessionId: () => undefined,
|
getSearchSessionId: () => undefined,
|
||||||
types: {},
|
types: {},
|
||||||
variables: {},
|
variables: {},
|
||||||
abortSignal: {} as any,
|
abortSignal: {},
|
||||||
inspectorAdapters: {} as any,
|
inspectorAdapters: {},
|
||||||
} as ExecutionContext;
|
} as ExecutionContext;
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,12 +16,14 @@ import {
|
||||||
IInterpreterRenderHandlers,
|
IInterpreterRenderHandlers,
|
||||||
RenderMode,
|
RenderMode,
|
||||||
AnyExpressionFunctionDefinition,
|
AnyExpressionFunctionDefinition,
|
||||||
|
ExpressionsService,
|
||||||
|
ExecutionContract,
|
||||||
} from '../common';
|
} from '../common';
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const { __getLastExecution, __getLastRenderMode } = require('./services');
|
const { __getLastExecution, __getLastRenderMode } = require('./services');
|
||||||
|
|
||||||
const element: HTMLElement = null as any;
|
const element = null as unknown as HTMLElement;
|
||||||
|
|
||||||
let testScheduler: TestScheduler;
|
let testScheduler: TestScheduler;
|
||||||
|
|
||||||
|
@ -36,8 +38,9 @@ jest.mock('./services', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line
|
const service: ExpressionsService =
|
||||||
const service = new (require('../common/service/expressions_services').ExpressionsService as any)();
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
new (require('../common/service/expressions_services').ExpressionsService)();
|
||||||
|
|
||||||
const testFn: AnyExpressionFunctionDefinition = {
|
const testFn: AnyExpressionFunctionDefinition = {
|
||||||
fn: () => ({ type: 'render', as: 'test' }),
|
fn: () => ({ type: 'render', as: 'test' }),
|
||||||
|
@ -54,9 +57,9 @@ jest.mock('./services', () => {
|
||||||
|
|
||||||
service.start();
|
service.start();
|
||||||
|
|
||||||
|
let execution: ExecutionContract;
|
||||||
const moduleMock = {
|
const moduleMock = {
|
||||||
__execution: undefined,
|
__getLastExecution: () => execution,
|
||||||
__getLastExecution: () => moduleMock.__execution,
|
|
||||||
__getLastRenderMode: () => renderMode,
|
__getLastRenderMode: () => renderMode,
|
||||||
getRenderersRegistry: () => ({
|
getRenderersRegistry: () => ({
|
||||||
get: (id: string) => renderers[id],
|
get: (id: string) => renderers[id],
|
||||||
|
@ -72,13 +75,14 @@ jest.mock('./services', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const execute = service.execute;
|
const execute = service.execute;
|
||||||
service.execute = (...args: any) => {
|
|
||||||
const execution = execute(...args);
|
jest.spyOn(service, 'execute').mockImplementation((...args) => {
|
||||||
|
execution = execute(...args);
|
||||||
jest.spyOn(execution, 'getData');
|
jest.spyOn(execution, 'getData');
|
||||||
jest.spyOn(execution, 'cancel');
|
jest.spyOn(execution, 'cancel');
|
||||||
moduleMock.__execution = execution;
|
|
||||||
return execution;
|
return execution;
|
||||||
};
|
});
|
||||||
|
|
||||||
return moduleMock;
|
return moduleMock;
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import { BehaviorSubject, Observable, Subject, Subscription, asyncScheduler, identity } from 'rxjs';
|
import { BehaviorSubject, Observable, Subject, Subscription, asyncScheduler, identity } from 'rxjs';
|
||||||
import { filter, map, delay, throttleTime } from 'rxjs/operators';
|
import { filter, map, delay, throttleTime } from 'rxjs/operators';
|
||||||
import { defaults } from 'lodash';
|
import { defaults } from 'lodash';
|
||||||
import { UnwrapObservable } from '@kbn/utility-types';
|
import { SerializableRecord, UnwrapObservable } from '@kbn/utility-types';
|
||||||
import { Adapters } from '../../inspector/public';
|
import { Adapters } from '../../inspector/public';
|
||||||
import { IExpressionLoaderParams } from './types';
|
import { IExpressionLoaderParams } from './types';
|
||||||
import { ExpressionAstExpression } from '../common';
|
import { ExpressionAstExpression } from '../common';
|
||||||
|
@ -18,7 +18,7 @@ import { ExecutionContract } from '../common/execution/execution_contract';
|
||||||
import { ExpressionRenderHandler } from './render';
|
import { ExpressionRenderHandler } from './render';
|
||||||
import { getExpressionsService } from './services';
|
import { getExpressionsService } from './services';
|
||||||
|
|
||||||
type Data = any;
|
type Data = unknown;
|
||||||
|
|
||||||
export class ExpressionLoader {
|
export class ExpressionLoader {
|
||||||
data$: ReturnType<ExecutionContract['getData']>;
|
data$: ReturnType<ExecutionContract['getData']>;
|
||||||
|
@ -156,7 +156,7 @@ export class ExpressionLoader {
|
||||||
};
|
};
|
||||||
|
|
||||||
private render(data: Data): void {
|
private render(data: Data): void {
|
||||||
this.renderHandler.render(data, this.params.uiState);
|
this.renderHandler.render(data as SerializableRecord, this.params.uiState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setParams(params?: IExpressionLoaderParams) {
|
private setParams(params?: IExpressionLoaderParams) {
|
||||||
|
@ -169,7 +169,7 @@ export class ExpressionLoader {
|
||||||
{},
|
{},
|
||||||
params.searchContext,
|
params.searchContext,
|
||||||
this.params.searchContext || {}
|
this.params.searchContext || {}
|
||||||
) as any;
|
);
|
||||||
}
|
}
|
||||||
if (params.uiState && this.params) {
|
if (params.uiState && this.params) {
|
||||||
this.params.uiState = params.uiState;
|
this.params.uiState = params.uiState;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { ReactExpressionRenderer } from './react_expression_renderer';
|
||||||
import { ExpressionLoader } from './loader';
|
import { ExpressionLoader } from './loader';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { EuiProgress } from '@elastic/eui';
|
import { EuiProgress } from '@elastic/eui';
|
||||||
|
import { IInterpreterRenderHandlers } from '../common';
|
||||||
import { RenderErrorHandlerFnType } from './types';
|
import { RenderErrorHandlerFnType } from './types';
|
||||||
import { ExpressionRendererEvent } from './render';
|
import { ExpressionRendererEvent } from './render';
|
||||||
|
|
||||||
|
@ -234,7 +235,7 @@ describe('ExpressionRenderer', () => {
|
||||||
done: () => {
|
done: () => {
|
||||||
renderSubject.next(1);
|
renderSubject.next(1);
|
||||||
},
|
},
|
||||||
} as any);
|
} as IInterpreterRenderHandlers);
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.update();
|
instance.update();
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import { ExpressionRenderHandler, render } from './render';
|
import { ExpressionRenderHandler, render } from './render';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { SerializableRecord } from '@kbn/utility-types';
|
||||||
import { ExpressionRenderError } from './types';
|
import { ExpressionRenderError } from './types';
|
||||||
import { getRenderersRegistry } from './services';
|
import { getRenderersRegistry } from './services';
|
||||||
import { first, take, toArray } from 'rxjs/operators';
|
import { first, take, toArray } from 'rxjs/operators';
|
||||||
|
@ -79,11 +80,11 @@ describe('ExpressionRenderHandler', () => {
|
||||||
|
|
||||||
it('in case of error render$ should emit when error renderer is finished', async () => {
|
it('in case of error render$ should emit when error renderer is finished', async () => {
|
||||||
const expressionRenderHandler = new ExpressionRenderHandler(element);
|
const expressionRenderHandler = new ExpressionRenderHandler(element);
|
||||||
expressionRenderHandler.render(false);
|
expressionRenderHandler.render(false as unknown as SerializableRecord);
|
||||||
const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise();
|
const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise();
|
||||||
await expect(promise1).resolves.toEqual(1);
|
await expect(promise1).resolves.toEqual(1);
|
||||||
|
|
||||||
expressionRenderHandler.render(false);
|
expressionRenderHandler.render(false as unknown as SerializableRecord);
|
||||||
const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise();
|
const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise();
|
||||||
await expect(promise2).resolves.toEqual(2);
|
await expect(promise2).resolves.toEqual(2);
|
||||||
});
|
});
|
||||||
|
@ -92,7 +93,7 @@ describe('ExpressionRenderHandler', () => {
|
||||||
const expressionRenderHandler = new ExpressionRenderHandler(element, {
|
const expressionRenderHandler = new ExpressionRenderHandler(element, {
|
||||||
onRenderError: mockMockErrorRenderFunction,
|
onRenderError: mockMockErrorRenderFunction,
|
||||||
});
|
});
|
||||||
await expressionRenderHandler.render(false);
|
await expressionRenderHandler.render(false as unknown as SerializableRecord);
|
||||||
expect(getHandledError()!.message).toEqual(
|
expect(getHandledError()!.message).toEqual(
|
||||||
`invalid data provided to the expression renderer`
|
`invalid data provided to the expression renderer`
|
||||||
);
|
);
|
||||||
|
@ -122,7 +123,8 @@ describe('ExpressionRenderHandler', () => {
|
||||||
get: () => ({
|
get: () => ({
|
||||||
render: (domNode: HTMLElement, config: unknown, handlers: IInterpreterRenderHandlers) => {
|
render: (domNode: HTMLElement, config: unknown, handlers: IInterpreterRenderHandlers) => {
|
||||||
handlers.hasCompatibleActions!({
|
handlers.hasCompatibleActions!({
|
||||||
foo: 'bar',
|
name: 'something',
|
||||||
|
data: 'bar',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -136,7 +138,8 @@ describe('ExpressionRenderHandler', () => {
|
||||||
await expressionRenderHandler.render({ type: 'render', as: 'something' });
|
await expressionRenderHandler.render({ type: 'render', as: 'something' });
|
||||||
expect(hasCompatibleActions).toHaveBeenCalledTimes(1);
|
expect(hasCompatibleActions).toHaveBeenCalledTimes(1);
|
||||||
expect(hasCompatibleActions.mock.calls[0][0]).toEqual({
|
expect(hasCompatibleActions.mock.calls[0][0]).toEqual({
|
||||||
foo: 'bar',
|
name: 'something',
|
||||||
|
data: 'bar',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,7 +159,7 @@ describe('ExpressionRenderHandler', () => {
|
||||||
it('default renderer should use notification service', async () => {
|
it('default renderer should use notification service', async () => {
|
||||||
const expressionRenderHandler = new ExpressionRenderHandler(element);
|
const expressionRenderHandler = new ExpressionRenderHandler(element);
|
||||||
const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise();
|
const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise();
|
||||||
expressionRenderHandler.render(false);
|
expressionRenderHandler.render(false as unknown as SerializableRecord);
|
||||||
await expect(promise1).resolves.toEqual(1);
|
await expect(promise1).resolves.toEqual(1);
|
||||||
expect(mockNotificationService.toasts.addError).toBeCalledWith(
|
expect(mockNotificationService.toasts.addError).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
@ -175,7 +178,7 @@ describe('ExpressionRenderHandler', () => {
|
||||||
const expressionRenderHandler1 = new ExpressionRenderHandler(element, {
|
const expressionRenderHandler1 = new ExpressionRenderHandler(element, {
|
||||||
onRenderError: mockMockErrorRenderFunction,
|
onRenderError: mockMockErrorRenderFunction,
|
||||||
});
|
});
|
||||||
expressionRenderHandler1.render(false);
|
expressionRenderHandler1.render(false as unknown as SerializableRecord);
|
||||||
const renderPromiseAfterRender = expressionRenderHandler1.render$.pipe(first()).toPromise();
|
const renderPromiseAfterRender = expressionRenderHandler1.render$.pipe(first()).toPromise();
|
||||||
await expect(renderPromiseAfterRender).resolves.toEqual(1);
|
await expect(renderPromiseAfterRender).resolves.toEqual(1);
|
||||||
expect(getHandledError()!.message).toEqual(
|
expect(getHandledError()!.message).toEqual(
|
||||||
|
@ -188,7 +191,7 @@ describe('ExpressionRenderHandler', () => {
|
||||||
onRenderError: mockMockErrorRenderFunction,
|
onRenderError: mockMockErrorRenderFunction,
|
||||||
});
|
});
|
||||||
const renderPromiseBeforeRender = expressionRenderHandler2.render$.pipe(first()).toPromise();
|
const renderPromiseBeforeRender = expressionRenderHandler2.render$.pipe(first()).toPromise();
|
||||||
expressionRenderHandler2.render(false);
|
expressionRenderHandler2.render(false as unknown as SerializableRecord);
|
||||||
await expect(renderPromiseBeforeRender).resolves.toEqual(1);
|
await expect(renderPromiseBeforeRender).resolves.toEqual(1);
|
||||||
expect(getHandledError()!.message).toEqual(
|
expect(getHandledError()!.message).toEqual(
|
||||||
'invalid data provided to the expression renderer'
|
'invalid data provided to the expression renderer'
|
||||||
|
@ -199,9 +202,9 @@ describe('ExpressionRenderHandler', () => {
|
||||||
// that observables will emit previous result if subscription happens after render
|
// that observables will emit previous result if subscription happens after render
|
||||||
it('should emit previous render and error results', async () => {
|
it('should emit previous render and error results', async () => {
|
||||||
const expressionRenderHandler = new ExpressionRenderHandler(element);
|
const expressionRenderHandler = new ExpressionRenderHandler(element);
|
||||||
expressionRenderHandler.render(false);
|
expressionRenderHandler.render(false as unknown as SerializableRecord);
|
||||||
const renderPromise = expressionRenderHandler.render$.pipe(take(2), toArray()).toPromise();
|
const renderPromise = expressionRenderHandler.render$.pipe(take(2), toArray()).toPromise();
|
||||||
expressionRenderHandler.render(false);
|
expressionRenderHandler.render(false as unknown as SerializableRecord);
|
||||||
await expect(renderPromise).resolves.toEqual([1, 2]);
|
await expect(renderPromise).resolves.toEqual([1, 2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,13 +9,20 @@
|
||||||
import * as Rx from 'rxjs';
|
import * as Rx from 'rxjs';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
|
import { isNumber } from 'lodash';
|
||||||
|
import { SerializableRecord } from '@kbn/utility-types';
|
||||||
import { ExpressionRenderError, RenderErrorHandlerFnType, IExpressionLoaderParams } from './types';
|
import { ExpressionRenderError, RenderErrorHandlerFnType, IExpressionLoaderParams } from './types';
|
||||||
import { renderErrorHandler as defaultRenderErrorHandler } from './render_error_handler';
|
import { renderErrorHandler as defaultRenderErrorHandler } from './render_error_handler';
|
||||||
import { IInterpreterRenderHandlers, ExpressionAstExpression, RenderMode } from '../common';
|
import {
|
||||||
|
IInterpreterRenderHandlers,
|
||||||
|
IInterpreterRenderEvent,
|
||||||
|
IInterpreterRenderUpdateParams,
|
||||||
|
RenderMode,
|
||||||
|
} from '../common';
|
||||||
|
|
||||||
import { getRenderersRegistry } from './services';
|
import { getRenderersRegistry } from './services';
|
||||||
|
|
||||||
export type IExpressionRendererExtraHandlers = Record<string, any>;
|
export type IExpressionRendererExtraHandlers = Record<string, unknown>;
|
||||||
|
|
||||||
export interface ExpressionRenderHandlerParams {
|
export interface ExpressionRenderHandlerParams {
|
||||||
onRenderError?: RenderErrorHandlerFnType;
|
onRenderError?: RenderErrorHandlerFnType;
|
||||||
|
@ -25,15 +32,10 @@ export interface ExpressionRenderHandlerParams {
|
||||||
hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise<boolean>;
|
hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExpressionRendererEvent {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
name: string;
|
export type ExpressionRendererEvent = IInterpreterRenderEvent<any>;
|
||||||
data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UpdateValue {
|
type UpdateValue = IInterpreterRenderUpdateParams<IExpressionLoaderParams>;
|
||||||
newExpression?: string | ExpressionAstExpression;
|
|
||||||
newParams: IExpressionLoaderParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ExpressionRenderHandler {
|
export class ExpressionRenderHandler {
|
||||||
render$: Observable<number>;
|
render$: Observable<number>;
|
||||||
|
@ -41,7 +43,7 @@ export class ExpressionRenderHandler {
|
||||||
events$: Observable<ExpressionRendererEvent>;
|
events$: Observable<ExpressionRendererEvent>;
|
||||||
|
|
||||||
private element: HTMLElement;
|
private element: HTMLElement;
|
||||||
private destroyFn?: any;
|
private destroyFn?: Function;
|
||||||
private renderCount: number = 0;
|
private renderCount: number = 0;
|
||||||
private renderSubject: Rx.BehaviorSubject<number | null>;
|
private renderSubject: Rx.BehaviorSubject<number | null>;
|
||||||
private eventsSubject: Rx.Subject<unknown>;
|
private eventsSubject: Rx.Subject<unknown>;
|
||||||
|
@ -66,16 +68,14 @@ export class ExpressionRenderHandler {
|
||||||
|
|
||||||
this.onRenderError = onRenderError || defaultRenderErrorHandler;
|
this.onRenderError = onRenderError || defaultRenderErrorHandler;
|
||||||
|
|
||||||
this.renderSubject = new Rx.BehaviorSubject(null as any | null);
|
this.renderSubject = new Rx.BehaviorSubject<number | null>(null);
|
||||||
this.render$ = this.renderSubject
|
this.render$ = this.renderSubject.asObservable().pipe(filter(isNumber));
|
||||||
.asObservable()
|
|
||||||
.pipe(filter((_) => _ !== null)) as Observable<any>;
|
|
||||||
|
|
||||||
this.updateSubject = new Rx.Subject();
|
this.updateSubject = new Rx.Subject();
|
||||||
this.update$ = this.updateSubject.asObservable();
|
this.update$ = this.updateSubject.asObservable();
|
||||||
|
|
||||||
this.handlers = {
|
this.handlers = {
|
||||||
onDestroy: (fn: any) => {
|
onDestroy: (fn: Function) => {
|
||||||
this.destroyFn = fn;
|
this.destroyFn = fn;
|
||||||
},
|
},
|
||||||
done: () => {
|
done: () => {
|
||||||
|
@ -104,14 +104,14 @@ export class ExpressionRenderHandler {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render = async (value: any, uiState?: any) => {
|
render = async (value: SerializableRecord, uiState?: unknown) => {
|
||||||
if (!value || typeof value !== 'object') {
|
if (!value || typeof value !== 'object') {
|
||||||
return this.handleRenderError(new Error('invalid data provided to the expression renderer'));
|
return this.handleRenderError(new Error('invalid data provided to the expression renderer'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.type !== 'render' || !value.as) {
|
if (value.type !== 'render' || !value.as) {
|
||||||
if (value.type === 'error') {
|
if (value.type === 'error') {
|
||||||
return this.handleRenderError(value.error);
|
return this.handleRenderError(value.error as unknown as ExpressionRenderError);
|
||||||
} else {
|
} else {
|
||||||
return this.handleRenderError(
|
return this.handleRenderError(
|
||||||
new Error('invalid data provided to the expression renderer')
|
new Error('invalid data provided to the expression renderer')
|
||||||
|
@ -119,20 +119,20 @@ export class ExpressionRenderHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getRenderersRegistry().get(value.as)) {
|
if (!getRenderersRegistry().get(value.as as string)) {
|
||||||
return this.handleRenderError(new Error(`invalid renderer id '${value.as}'`));
|
return this.handleRenderError(new Error(`invalid renderer id '${value.as}'`));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Rendering is asynchronous, completed by handlers.done()
|
// Rendering is asynchronous, completed by handlers.done()
|
||||||
await getRenderersRegistry()
|
await getRenderersRegistry()
|
||||||
.get(value.as)!
|
.get(value.as as string)!
|
||||||
.render(this.element, value.value, {
|
.render(this.element, value.value, {
|
||||||
...this.handlers,
|
...this.handlers,
|
||||||
uiState,
|
uiState,
|
||||||
} as any);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return this.handleRenderError(e);
|
return this.handleRenderError(e as ExpressionRenderError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -156,10 +156,10 @@ export class ExpressionRenderHandler {
|
||||||
|
|
||||||
export function render(
|
export function render(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
data: any,
|
data: unknown,
|
||||||
options?: ExpressionRenderHandlerParams
|
options?: ExpressionRenderHandlerParams
|
||||||
): ExpressionRenderHandler {
|
): ExpressionRenderHandler {
|
||||||
const handler = new ExpressionRenderHandler(element, options);
|
const handler = new ExpressionRenderHandler(element, options);
|
||||||
handler.render(data);
|
handler.render(data as SerializableRecord);
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export interface ExpressionInterpreter {
|
||||||
export interface IExpressionLoaderParams {
|
export interface IExpressionLoaderParams {
|
||||||
searchContext?: SerializableRecord;
|
searchContext?: SerializableRecord;
|
||||||
context?: ExpressionValue;
|
context?: ExpressionValue;
|
||||||
variables?: Record<string, any>;
|
variables?: Record<string, unknown>;
|
||||||
// Enables debug tracking on each expression in the AST
|
// Enables debug tracking on each expression in the AST
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
disableCaching?: boolean;
|
disableCaching?: boolean;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { createMarkdownVisFn } from './markdown_fn';
|
import { createMarkdownVisFn } from './markdown_fn';
|
||||||
|
import { Arguments } from './types';
|
||||||
|
|
||||||
describe('interpreter/functions#markdown', () => {
|
describe('interpreter/functions#markdown', () => {
|
||||||
const fn = functionWrapper(createMarkdownVisFn());
|
const fn = functionWrapper(createMarkdownVisFn());
|
||||||
|
@ -15,7 +16,7 @@ describe('interpreter/functions#markdown', () => {
|
||||||
font: { spec: { fontSize: 12 } },
|
font: { spec: { fontSize: 12 } },
|
||||||
openLinksInNewTab: true,
|
openLinksInNewTab: true,
|
||||||
markdown: '## hello _markdown_',
|
markdown: '## hello _markdown_',
|
||||||
};
|
} as unknown as Arguments;
|
||||||
|
|
||||||
it('returns an object with the correct structure', async () => {
|
it('returns an object with the correct structure', async () => {
|
||||||
const actual = await fn(null, args, undefined);
|
const actual = await fn(null, args, undefined);
|
||||||
|
|
|
@ -10,13 +10,15 @@ import { createMetricVisFn } from './metric_vis_fn';
|
||||||
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { Datatable } from '../../../expressions/common/expression_types/specs';
|
import { Datatable } from '../../../expressions/common/expression_types/specs';
|
||||||
|
|
||||||
|
type Arguments = Parameters<ReturnType<typeof createMetricVisFn>['fn']>[1];
|
||||||
|
|
||||||
describe('interpreter/functions#metric', () => {
|
describe('interpreter/functions#metric', () => {
|
||||||
const fn = functionWrapper(createMetricVisFn());
|
const fn = functionWrapper(createMetricVisFn());
|
||||||
const context = {
|
const context = {
|
||||||
type: 'datatable',
|
type: 'datatable',
|
||||||
rows: [{ 'col-0-1': 0 }],
|
rows: [{ 'col-0-1': 0 }],
|
||||||
columns: [{ id: 'col-0-1', name: 'Count' }],
|
columns: [{ id: 'col-0-1', name: 'Count' }],
|
||||||
};
|
} as unknown as Datatable;
|
||||||
const args = {
|
const args = {
|
||||||
percentageMode: false,
|
percentageMode: false,
|
||||||
useRanges: false,
|
useRanges: false,
|
||||||
|
@ -50,7 +52,7 @@ describe('interpreter/functions#metric', () => {
|
||||||
aggType: 'count',
|
aggType: 'count',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
} as unknown as Arguments;
|
||||||
|
|
||||||
it('returns an object with the correct structure', () => {
|
it('returns an object with the correct structure', () => {
|
||||||
const actual = fn(context, args, undefined);
|
const actual = fn(context, args, undefined);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { createPieVisFn } from './pie_fn';
|
import { createPieVisFn } from './pie_fn';
|
||||||
|
import { PieVisConfig } from './types';
|
||||||
import { Datatable } from '../../../expressions/common/expression_types/specs';
|
import { Datatable } from '../../../expressions/common/expression_types/specs';
|
||||||
|
|
||||||
describe('interpreter/functions#pie', () => {
|
describe('interpreter/functions#pie', () => {
|
||||||
|
@ -16,7 +17,7 @@ describe('interpreter/functions#pie', () => {
|
||||||
type: 'datatable',
|
type: 'datatable',
|
||||||
rows: [{ 'col-0-1': 0 }],
|
rows: [{ 'col-0-1': 0 }],
|
||||||
columns: [{ id: 'col-0-1', name: 'Count' }],
|
columns: [{ id: 'col-0-1', name: 'Count' }],
|
||||||
};
|
} as unknown as Datatable;
|
||||||
const visConfig = {
|
const visConfig = {
|
||||||
addTooltip: true,
|
addTooltip: true,
|
||||||
addLegend: true,
|
addLegend: true,
|
||||||
|
@ -43,7 +44,7 @@ describe('interpreter/functions#pie', () => {
|
||||||
params: {},
|
params: {},
|
||||||
aggType: 'count',
|
aggType: 'count',
|
||||||
},
|
},
|
||||||
};
|
} as unknown as PieVisConfig;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import { createTableVisFn } from './table_vis_fn';
|
import { createTableVisFn } from './table_vis_fn';
|
||||||
import { tableVisResponseHandler } from './utils';
|
import { tableVisResponseHandler } from './utils';
|
||||||
|
import { TableVisConfig } from './types';
|
||||||
|
|
||||||
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { Datatable } from '../../../expressions/common/expression_types/specs';
|
import { Datatable } from '../../../expressions/common/expression_types/specs';
|
||||||
|
@ -24,7 +25,7 @@ describe('interpreter/functions#table', () => {
|
||||||
type: 'datatable',
|
type: 'datatable',
|
||||||
rows: [{ 'col-0-1': 0 }],
|
rows: [{ 'col-0-1': 0 }],
|
||||||
columns: [{ id: 'col-0-1', name: 'Count' }],
|
columns: [{ id: 'col-0-1', name: 'Count' }],
|
||||||
};
|
} as unknown as Datatable;
|
||||||
const visConfig = {
|
const visConfig = {
|
||||||
title: 'My Chart title',
|
title: 'My Chart title',
|
||||||
perPage: 10,
|
perPage: 10,
|
||||||
|
@ -52,7 +53,7 @@ describe('interpreter/functions#table', () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
buckets: [],
|
buckets: [],
|
||||||
};
|
} as unknown as TableVisConfig;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Datatable } from 'src/plugins/expressions';
|
||||||
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { createPieVisFn } from './pie_fn';
|
import { createPieVisFn } from './pie_fn';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -34,7 +35,7 @@ describe('interpreter/functions#pie', () => {
|
||||||
type: 'datatable',
|
type: 'datatable',
|
||||||
rows: [{ 'col-0-1': 0 }],
|
rows: [{ 'col-0-1': 0 }],
|
||||||
columns: [{ id: 'col-0-1', name: 'Count' }],
|
columns: [{ id: 'col-0-1', name: 'Count' }],
|
||||||
};
|
} as unknown as Datatable;
|
||||||
const visConfig = {
|
const visConfig = {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
addTooltip: true,
|
addTooltip: true,
|
||||||
|
|
|
@ -5,7 +5,12 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Datatable, ExpressionFunctionDefinition, getType } from '../../../types';
|
import {
|
||||||
|
Datatable,
|
||||||
|
DatatableColumnType,
|
||||||
|
ExpressionFunctionDefinition,
|
||||||
|
getType,
|
||||||
|
} from '../../../types';
|
||||||
import { getFunctionHelp } from '../../../i18n';
|
import { getFunctionHelp } from '../../../i18n';
|
||||||
|
|
||||||
interface Arguments {
|
interface Arguments {
|
||||||
|
@ -30,14 +35,14 @@ export function asFn(): ExpressionFunctionDefinition<'as', Input, Arguments, Dat
|
||||||
default: 'value',
|
default: 'value',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn: (input, args) => {
|
fn: (input, args): Datatable => {
|
||||||
return {
|
return {
|
||||||
type: 'datatable',
|
type: 'datatable',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
id: args.name,
|
id: args.name,
|
||||||
name: args.name,
|
name: args.name,
|
||||||
meta: { type: getType(input) },
|
meta: { type: getType(input) as DatatableColumnType },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
rows: [
|
rows: [
|
||||||
|
|
|
@ -156,7 +156,7 @@ ${examplesBlock}
|
||||||
*Returns:* ${output ? wrapInBackTicks(output) : 'Depends on your input and arguments'}\n\n`;
|
*Returns:* ${output ? wrapInBackTicks(output) : 'Depends on your input and arguments'}\n\n`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getArgsTable = (args: { [key: string]: ExpressionFunctionParameter }) => {
|
const getArgsTable = (args: { [key: string]: ExpressionFunctionParameter<any> }) => {
|
||||||
if (!args || Object.keys(args).length === 0) {
|
if (!args || Object.keys(args).length === 0) {
|
||||||
return 'None';
|
return 'None';
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,10 @@
|
||||||
|
|
||||||
import { Datatable, DatatableColumn } from 'src/plugins/expressions/public';
|
import { Datatable, DatatableColumn } from 'src/plugins/expressions/public';
|
||||||
import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { FormatColumnArgs, formatColumn } from './index';
|
import { formatColumn } from './index';
|
||||||
|
|
||||||
describe('format_column', () => {
|
describe('format_column', () => {
|
||||||
const fn: (input: Datatable, args: FormatColumnArgs) => Promise<Datatable> =
|
const fn = functionWrapper(formatColumn);
|
||||||
functionWrapper(formatColumn);
|
|
||||||
|
|
||||||
let datatable: Datatable;
|
let datatable: Datatable;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue