[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:
Michael Dokolin 2021-10-04 18:30:10 +02:00 committed by GitHub
parent fed0dc6563
commit 0d9825d03c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 415 additions and 296 deletions

View file

@ -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 (

View file

@ -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 (

View file

@ -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,
}, },
}); });

View file

@ -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();
}); });

View file

@ -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 }]
} }
} }

View file

@ -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 {

View file

@ -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

View file

@ -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: {

View file

@ -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: {

View file

@ -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(

View file

@ -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;
} }
/** /**

View file

@ -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>,

View file

@ -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() })
);
}); });
}); });
}); });

View file

@ -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(),
}; };

View file

@ -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);
}); });

View file

@ -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;

View file

@ -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;
}; };
} }

View file

@ -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;
} }
} }

View file

@ -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);

View file

@ -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<

View file

@ -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',

View file

@ -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;

View file

@ -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 = {

View file

@ -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;

View file

@ -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();
}); });
}); });
}); });

View file

@ -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)
); );
}); });

View file

@ -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', () => {

View file

@ -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;

View file

@ -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);
}; };

View file

@ -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', () => {

View file

@ -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;
}); });

View file

@ -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',

View file

@ -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];
}, },
}; };

View file

@ -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;
}, },
}; };

View file

@ -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

View file

@ -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.

View file

@ -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;

View file

@ -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;
}

View file

@ -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');

View file

@ -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';
/** /**

View file

@ -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.

View file

@ -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) => {

View file

@ -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>;

View file

@ -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,

View file

@ -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);
}; };

View file

@ -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;
}, },
}; };

View file

@ -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 {

View file

@ -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],
}; };
}, },
}; };

View file

@ -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: {

View file

@ -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;

View file

@ -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;
} }
} }

View file

@ -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;
}; };

View file

@ -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;
}); });

View file

@ -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;

View file

@ -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();

View file

@ -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]);
}); });
}); });

View file

@ -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;
} }

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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();

View file

@ -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();

View file

@ -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,

View file

@ -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: [

View file

@ -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';
} }

View file

@ -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;