diff --git a/examples/expressions_explorer/public/actions_and_expressions.tsx b/examples/expressions_explorer/public/actions_and_expressions.tsx index f802a78faf06..6d0c8886a79f 100644 --- a/examples/expressions_explorer/public/actions_and_expressions.tsx +++ b/examples/expressions_explorer/public/actions_and_expressions.tsx @@ -47,11 +47,11 @@ export function ActionsExpressionsExample({ expressions, actions }: Props) { }; const handleEvents = (event: any) => { - if (event.id !== 'NAVIGATE') return; + if (event.name !== 'NAVIGATE') return; // enrich event context with some extra data event.baseUrl = 'http://www.google.com'; - actions.executeTriggerActions(NAVIGATE_TRIGGER_ID, event.value); + actions.executeTriggerActions(NAVIGATE_TRIGGER_ID, event.data); }; return ( diff --git a/examples/expressions_explorer/public/actions_and_expressions2.tsx b/examples/expressions_explorer/public/actions_and_expressions2.tsx index 31ba903ad91a..e7dc28b8b97c 100644 --- a/examples/expressions_explorer/public/actions_and_expressions2.tsx +++ b/examples/expressions_explorer/public/actions_and_expressions2.tsx @@ -50,7 +50,7 @@ export function ActionsExpressionsExample2({ expressions, actions }: Props) { }; 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 ( diff --git a/examples/expressions_explorer/public/renderers/button.tsx b/examples/expressions_explorer/public/renderers/button.tsx index 68add91c3cbc..557180ab73a3 100644 --- a/examples/expressions_explorer/public/renderers/button.tsx +++ b/examples/expressions_explorer/public/renderers/button.tsx @@ -18,8 +18,8 @@ export const buttonRenderer: ExpressionRenderDefinition = { render(domNode, config, handlers) { const buttonClick = () => { handlers.event({ - id: 'NAVIGATE', - value: { + name: 'NAVIGATE', + data: { href: config.href, }, }); diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts index 8abdc36704b4..86a371afd691 100644 --- a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts @@ -12,6 +12,8 @@ import { functionWrapper } from '../../../../expressions/common/expression_funct import { ExpressionValueVisDimension } from '../../../../visualizations/public'; import { Datatable } from '../../../../expressions/common/expression_types/specs'; +type Arguments = Parameters['fn']>[1]; + describe('interpreter/functions#tagcloud', () => { const fn = functionWrapper(tagcloudFunction()); const column1 = 'Count'; @@ -26,7 +28,7 @@ describe('interpreter/functions#tagcloud', () => { { [column1]: 0, [column2]: 'US' }, { [column1]: 10, [column2]: 'UK' }, ], - }; + } as unknown as Datatable; const visConfig = { scale: 'linear', orientation: 'single', @@ -73,12 +75,12 @@ describe('interpreter/functions#tagcloud', () => { }; 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(); }); 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(); }); @@ -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(); }); diff --git a/src/plugins/expressions/.eslintrc.json b/src/plugins/expressions/.eslintrc.json index 2aab6c2d9093..d1dbca41acc8 100644 --- a/src/plugins/expressions/.eslintrc.json +++ b/src/plugins/expressions/.eslintrc.json @@ -1,5 +1,6 @@ { "rules": { - "@typescript-eslint/consistent-type-definitions": 0 + "@typescript-eslint/consistent-type-definitions": 0, + "@typescript-eslint/no-explicit-any": ["error", { "ignoreRestArgs": true }] } } diff --git a/src/plugins/expressions/common/ast/build_expression.ts b/src/plugins/expressions/common/ast/build_expression.ts index 0f4618b3e699..6e84594022fd 100644 --- a/src/plugins/expressions/common/ast/build_expression.ts +++ b/src/plugins/expressions/common/ast/build_expression.ts @@ -32,13 +32,13 @@ import { parse } from './parse'; * @param val Value you want to check. * @return boolean */ -export function isExpressionAstBuilder(val: any): val is ExpressionAstExpressionBuilder { - return val?.type === 'expression_builder'; +export function isExpressionAstBuilder(val: unknown): val is ExpressionAstExpressionBuilder { + return (val as Record | undefined)?.type === 'expression_builder'; } /** @internal */ -export function isExpressionAst(val: any): val is ExpressionAstExpression { - return val?.type === 'expression'; +export function isExpressionAst(val: unknown): val is ExpressionAstExpression { + return (val as Record | undefined)?.type === 'expression'; } export interface ExpressionAstExpressionBuilder { diff --git a/src/plugins/expressions/common/ast/types.ts b/src/plugins/expressions/common/ast/types.ts index e5a79a0a5dda..8f376ac547d2 100644 --- a/src/plugins/expressions/common/ast/types.ts +++ b/src/plugins/expressions/common/ast/types.ts @@ -64,7 +64,7 @@ export type ExpressionAstFunctionDebug = { /** * 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 diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 798558ba7ffb..fca030fb9a08 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -90,7 +90,7 @@ describe('Execution abortion tests', () => { const completed = jest.fn(); const aborted = jest.fn(); - const defer: ExpressionFunctionDefinition<'defer', any, { time: number }, any> = { + const defer: ExpressionFunctionDefinition<'defer', unknown, { time: number }, unknown> = { name: 'defer', args: { time: { diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index c478977f6076..9b889c62e9ff 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -17,7 +17,7 @@ import { ExecutionContract } from './execution_contract'; beforeAll(() => { 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 ( expression: string = 'foo bar=123', context?: Record, - input: any = null + input: unknown = null ) => { const execution = createExecution(expression, context); execution.start(input); @@ -262,45 +262,45 @@ describe('Execution', () => { describe('execution context', () => { 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)); }); 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)); }); 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)); }); 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)); }); test('context.getKibanaRequest is a function if provided', async () => { - const { result } = (await run('introspectContext key="getKibanaRequest"', { + const { result } = await run('introspectContext key="getKibanaRequest"', { kibanaRequest: {}, - })) as any; + }); expect(result).toHaveProperty('result', expect.any(Function)); }); 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); }); 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); }); @@ -314,7 +314,7 @@ describe('Execution', () => { describe('inspector adapters', () => { 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( 'result', expect.objectContaining({ @@ -326,9 +326,9 @@ describe('Execution', () => { test('can set custom inspector adapters', async () => { const inspectorAdapters = {}; - const { result } = (await run('introspectContext key="inspectorAdapters"', { + const { result } = await run('introspectContext key="inspectorAdapters"', { inspectorAdapters, - })) as any; + }); expect(result).toHaveProperty('result', inspectorAdapters); }); @@ -351,7 +351,7 @@ describe('Execution', () => { describe('expression abortion', () => { 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); }); @@ -400,7 +400,7 @@ describe('Execution', () => { testScheduler.run(({ cold, expectObservable }) => { const arg = cold(' -a-b-c|', { a: 1, b: 2, c: 3 }); const expected = ' -a-b-c|'; - const observable: ExpressionFunctionDefinition<'observable', any, {}, any> = { + const observable: ExpressionFunctionDefinition<'observable', unknown, {}, unknown> = { name: 'observable', args: {}, help: '', @@ -468,7 +468,7 @@ describe('Execution', () => { }); test('does not execute remaining functions in pipeline', async () => { - const spy: ExpressionFunctionDefinition<'spy', any, {}, any> = { + const spy: ExpressionFunctionDefinition<'spy', unknown, {}, unknown> = { name: 'spy', args: {}, help: '', @@ -621,7 +621,12 @@ describe('Execution', () => { help: '', fn: () => arg2, }; - const max: ExpressionFunctionDefinition<'max', any, { val1: number; val2: number }, any> = { + const max: ExpressionFunctionDefinition< + 'max', + unknown, + { val1: number; val2: number }, + unknown + > = { name: 'max', args: { val1: { help: '', types: ['number'] }, @@ -679,7 +684,12 @@ describe('Execution', () => { describe('when arguments are missing', () => { 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', args: { arg: { diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 0bb12951202a..54a4800ec7c3 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { isPromise } from '@kbn/std'; +import { ObservableLike, UnwrapObservable, UnwrapPromiseOrReturn } from '@kbn/utility-types'; import { keys, last, mapValues, reduce, zipObject } from 'lodash'; import { combineLatest, @@ -44,6 +45,18 @@ import { ExecutionContract } from './execution_contract'; import { ExpressionExecutionParams } from '../service'; import { createDefaultInspectorAdapters } from '../util/create_default_inspector_adapters'; +type UnwrapReturnType unknown> = + ReturnType extends ObservableLike + ? UnwrapObservable> + : UnwrapPromiseOrReturn>; + +// type ArgumentsOf = Function extends ExpressionFunction< +// unknown, +// infer Arguments +// > +// ? Arguments +// : never; + /** * The result returned after an expression function execution. */ @@ -83,7 +96,7 @@ const createAbortErrorValue = () => }); export interface ExecutionParams { - executor: Executor; + executor: Executor; ast?: ExpressionAstExpression; expression?: string; params: ExpressionExecutionParams; @@ -107,7 +120,7 @@ export class Execution< * N.B. It is initialized to `null` rather than `undefined` for legacy reasons, * 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. @@ -186,13 +199,13 @@ export class Execution< }); const inspectorAdapters = - execution.params.inspectorAdapters || createDefaultInspectorAdapters(); + (execution.params.inspectorAdapters as InspectorAdapters) || createDefaultInspectorAdapters(); this.context = { getSearchContext: () => this.execution.params.searchContext || {}, getSearchSessionId: () => execution.params.searchSessionId, getKibanaRequest: execution.params.kibanaRequest - ? () => execution.params.kibanaRequest + ? () => execution.params.kibanaRequest! : undefined, variables: execution.params.variables || {}, types: executor.getTypes(), @@ -201,14 +214,14 @@ export class Execution< logDatatable: (name: string, datatable: Datatable) => { inspectorAdapters.tables[name] = datatable; }, - isSyncColorsEnabled: () => execution.params.syncColors, - ...(execution.params as any).extraContext, + isSyncColorsEnabled: () => execution.params.syncColors!, + ...execution.params.extraContext, getExecutionContext: () => execution.params.executionContext, }; this.result = this.input$.pipe( switchMap((input) => - this.race(this.invokeChain(this.state.get().ast.chain, input)).pipe( + this.race(this.invokeChain(this.state.get().ast.chain, input)).pipe( (source) => new Observable>((subscriber) => { let latest: ExecutionResult | undefined; @@ -270,8 +283,8 @@ export class Execution< * N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons, * because in legacy interpreter it was set to `null` by default. */ - public start( - input: Input = null as any, + start( + input = null as unknown as Input, isSubExpression?: boolean ): Observable> { if (this.hasStarted) throw new Error('Execution already started.'); @@ -294,7 +307,10 @@ export class Execution< return this.result; } - invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable { + invokeChain( + chainArr: ExpressionAstFunction[], + input: unknown + ): Observable { return of(input).pipe( ...(chainArr.map((link) => switchMap((currentInput) => { @@ -364,19 +380,24 @@ export class Execution< }) ) as Parameters['pipe']>), catchError((error) => of(error)) - ); + ) as Observable; } - invokeFunction( - fn: ExpressionFunction, + invokeFunction( + fn: Fn, input: unknown, args: Record - ): Observable { + ): Observable> { return of(input).pipe( map((currentInput) => this.cast(currentInput, fn.inputTypes)), switchMap((normalizedInput) => this.race(of(fn.fn(normalizedInput, args, this.context)))), - switchMap((fnResult: any) => - isObservable(fnResult) ? fnResult : from(isPromise(fnResult) ? fnResult : [fnResult]) + switchMap( + (fnResult) => + (isObservable(fnResult) + ? fnResult + : from(isPromise(fnResult) ? fnResult : [fnResult])) as Observable< + UnwrapReturnType + > ), map((output) => { // 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(value: unknown, toTypeNames?: string[]): Type { // 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 const fromTypeName = getType(value); - if (toTypeNames.includes(fromTypeName)) return value; + if (toTypeNames.includes(fromTypeName)) { + return value as Type; + } const { types } = this.state.get(); const fromTypeDef = types[fromTypeName]; for (const toTypeName of toTypeNames) { // 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); } // If that isn't possible, check if this type can cast from the current type 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(', ')}'`); } // Processes the multi-valued AST argument values into arguments that can be passed to the function - resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable { + resolveArgs( + fnDef: Fn, + input: unknown, + argAsts: Record + ): Observable> { return defer(() => { const { args: argDefs } = fnDef; // Use the non-alias name from the argument definition const dealiasedArgAsts = reduce( - argAsts as Record, + argAsts, (acc, argAst, argName) => { const argDef = getByAlias(argDefs, argName); if (!argDef) { @@ -452,7 +483,7 @@ export class Execution< // Check for missing required arguments. for (const { aliases, default: argDefault, name, required } of Object.values(argDefs)) { if (!(name in dealiasedArgAsts) && typeof argDefault !== 'undefined') { - dealiasedArgAsts[name] = [parse(argDefault, 'argument')]; + dealiasedArgAsts[name] = [parse(argDefault as string, 'argument')]; } if (!required || name in dealiasedArgAsts) { @@ -490,7 +521,7 @@ export class Execution< const argNames = keys(resolveArgFns); if (!argNames.length) { - return from([[]]); + return from([{}]); } const resolvedArgValuesObservable = combineLatest( @@ -523,7 +554,7 @@ export class Execution< }); } - public interpret(ast: ExpressionAstNode, input: T): Observable> { + interpret(ast: ExpressionAstNode, input: T): Observable> { switch (getType(ast)) { case 'expression': const execution = this.execution.executor.createExecution( diff --git a/src/plugins/expressions/common/execution/types.ts b/src/plugins/expressions/common/execution/types.ts index 06eac98feba6..9264891b2e0b 100644 --- a/src/plugins/expressions/common/execution/types.ts +++ b/src/plugins/expressions/common/execution/types.ts @@ -11,7 +11,7 @@ import type { SerializableRecord } from '@kbn/utility-types'; import type { KibanaRequest } from 'src/core/server'; 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 { TablesAdapter } from '../util/tables_adapter'; @@ -69,6 +69,11 @@ export interface ExecutionContext< * Contains the meta-data about the source of the expression. */ getExecutionContext: () => KibanaExecutionContext | undefined; + + /** + * Logs datatable. + */ + logDatatable?(name: string, datatable: Datatable): void; } /** diff --git a/src/plugins/expressions/common/executor/container.ts b/src/plugins/expressions/common/executor/container.ts index 9d3796ac64f4..c8e24974126f 100644 --- a/src/plugins/expressions/common/executor/container.ts +++ b/src/plugins/expressions/common/executor/container.ts @@ -19,7 +19,7 @@ export interface ExecutorState = Record< context: Context; } -export const defaultState: ExecutorState = { +export const defaultState: ExecutorState = { functions: {}, types: {}, context: {}, @@ -61,7 +61,7 @@ export type ExecutorContainer = Record = Record >( - state: ExecutorState = defaultState + state = defaultState as ExecutorState ): ExecutorContainer => { const container = createStateContainer< ExecutorState, diff --git a/src/plugins/expressions/common/executor/executor.execution.test.ts b/src/plugins/expressions/common/executor/executor.execution.test.ts index 38022c0f7dc4..ad7e6e6a014c 100644 --- a/src/plugins/expressions/common/executor/executor.execution.test.ts +++ b/src/plugins/expressions/common/executor/executor.execution.test.ts @@ -8,20 +8,14 @@ import { Executor } from './executor'; import { parseExpression } from '../ast'; +import { Execution } from '../execution/execution'; -// eslint-disable-next-line -const { __getArgs } = require('../execution/execution'); +jest.mock('../execution/execution', () => ({ + Execution: jest.fn(), +})); -jest.mock('../execution/execution', () => { - const mockedModule = { - args: undefined, - __getArgs: () => mockedModule.args, - Execution: function ExecutionMock(...args: any) { - mockedModule.args = args; - }, - }; - - return mockedModule; +beforeEach(() => { + jest.clearAllMocks(); }); describe('Executor mocked execution tests', () => { @@ -31,7 +25,9 @@ describe('Executor mocked execution tests', () => { const executor = new Executor(); 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"'); executor.createExecution(ast); - expect(__getArgs()[0].expression).toBe(undefined); + expect(Execution).toHaveBeenCalledWith( + expect.not.objectContaining({ expression: expect.anything() }) + ); }); }); }); diff --git a/src/plugins/expressions/common/executor/executor.test.ts b/src/plugins/expressions/common/executor/executor.test.ts index 4a3d6045a7b4..60f0f0da4e15 100644 --- a/src/plugins/expressions/common/executor/executor.test.ts +++ b/src/plugins/expressions/common/executor/executor.test.ts @@ -145,7 +145,7 @@ describe('Executor', () => { executor.extendContext({ foo }); 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: { '7.10.0': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { return migrateFn(state, version); - }) as any as MigrateFunction, + }) as unknown as MigrateFunction, '7.10.1': ((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { return migrateFn(state, version); - }) as any as MigrateFunction, + }) as unknown as MigrateFunction, }, fn: jest.fn(), }; diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index ce411ea94eaf..f4913c4953ba 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -40,7 +40,7 @@ export interface ExpressionExecOptions { } export class TypesRegistry implements IRegistry { - constructor(private readonly executor: Executor) {} + constructor(private readonly executor: Executor) {} public register( typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition) @@ -62,7 +62,7 @@ export class TypesRegistry implements IRegistry { } export class FunctionsRegistry implements IRegistry { - constructor(private readonly executor: Executor) {} + constructor(private readonly executor: Executor) {} public register( functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition) @@ -100,12 +100,12 @@ export class Executor = Record; @@ -207,15 +207,15 @@ export class Executor = Record { - const executionParams: ExecutionParams = { + const executionParams = { executor: this, params: { ...params, // for canvas we are passing this in, // canvas should be refactored to not pass any extra context in extraContext: this.context, - } as any, - }; + }, + } as ExecutionParams; if (typeof ast === 'string') executionParams.expression = ast; else executionParams.ast = ast; @@ -273,7 +273,7 @@ export class Executor = Record) { + public telemetry(ast: ExpressionAstExpression, telemetryData: Record) { this.walkAst(cloneDeep(ast), (fn, link) => { telemetryData = fn.telemetry(link.arguments, telemetryData); }); diff --git a/src/plugins/expressions/common/expression_functions/arguments.ts b/src/plugins/expressions/common/expression_functions/arguments.ts index 67f83cabc645..7e39f019f00c 100644 --- a/src/plugins/expressions/common/expression_functions/arguments.ts +++ b/src/plugins/expressions/common/expression_functions/arguments.ts @@ -24,9 +24,9 @@ export type ArgumentType = * representation of the type. */ // prettier-ignore -type ArrayTypeToArgumentString = - T extends Array ? TypeString : - T extends null ? 'null' : +type ArrayTypeToArgumentString = + T extends Array ? TypeString : + T extends null ? 'null' : never; /** @@ -34,9 +34,9 @@ type ArrayTypeToArgumentString = * string-based representation of the return type. */ // prettier-ignore -type UnresolvedTypeToArgumentString = - T extends (...args: any) => infer ElementType ? TypeString : - T extends null ? 'null' : +type UnresolvedTypeToArgumentString = + T extends (...args: any[]) => infer ElementType ? TypeString : + T extends null ? 'null' : never; /** @@ -44,10 +44,10 @@ type UnresolvedTypeToArgumentString = * string-based representation of the return type. */ // prettier-ignore -type UnresolvedArrayTypeToArgumentString = - T extends Array<(...args: any) => infer ElementType> ? TypeString : - T extends (...args: any) => infer ElementType ? ArrayTypeToArgumentString : - T extends null ? 'null' : +type UnresolvedArrayTypeToArgumentString = + T extends Array<(...args: any[]) => infer ElementType> ? TypeString : + T extends (...args: any[]) => infer ElementType ? ArrayTypeToArgumentString : + T extends null ? 'null' : never; /** A type containing properties common to all Function Arguments. */ diff --git a/src/plugins/expressions/common/expression_functions/expression_function.ts b/src/plugins/expressions/common/expression_functions/expression_function.ts index 05a5dbb638c0..8154534b32ab 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function.ts @@ -36,7 +36,11 @@ export class ExpressionFunction implements PersistableState, handlers: object) => ExpressionValue; + fn: ( + input: ExpressionValue, + params: Record, + handlers: object + ) => ExpressionValue; /** * A short help text. @@ -56,8 +60,8 @@ export class ExpressionFunction implements PersistableState - ) => Record; + telemetryData: Record + ) => Record; extract: (state: ExpressionAstFunction['arguments']) => { state: ExpressionAstFunction['arguments']; references: SavedObjectReference[]; @@ -100,13 +104,12 @@ export class ExpressionFunction implements PersistableState { // 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.indexOf(type) > -1; + return this.inputTypes?.includes(type) ?? true; }; } diff --git a/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts b/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts index bfc45d65f1c9..9942c9af7ff7 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function_parameter.ts @@ -6,20 +6,21 @@ * Side Public License, v 1. */ +import { KnownTypeToString } from '../types'; import { ArgumentType } from './arguments'; -export class ExpressionFunctionParameter { +export class ExpressionFunctionParameter { name: string; required: boolean; help: string; - types: string[]; - default: any; + types: ArgumentType['types']; + default?: ArgumentType['default']; aliases: string[]; multi: boolean; resolve: boolean; - options: any[]; + options: T[]; - constructor(name: string, arg: ArgumentType) { + constructor(name: string, arg: ArgumentType) { const { required, help, types, aliases, multi, resolve, options } = arg; if (name === '_') { @@ -38,7 +39,6 @@ export class ExpressionFunctionParameter { } accepts(type: string) { - if (!this.types.length) return true; - return this.types.indexOf(type) > -1; + return !this.types?.length || this.types.includes(type as KnownTypeToString); } } diff --git a/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts b/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts index 6e47634f5aac..28ef5243e0fe 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function_parameters.test.ts @@ -21,7 +21,7 @@ describe('ExpressionFunctionParameter', () => { const param = new ExpressionFunctionParameter('foo', { help: 'bar', types: ['baz', 'quux'], - }); + } as ConstructorParameters[1]); expect(param.accepts('baz')).toBe(true); expect(param.accepts('quux')).toBe(true); diff --git a/src/plugins/expressions/common/expression_functions/specs/create_table.ts b/src/plugins/expressions/common/expression_functions/specs/create_table.ts index 5174b258a4d9..0ce427e817e2 100644 --- a/src/plugins/expressions/common/expression_functions/specs/create_table.ts +++ b/src/plugins/expressions/common/expression_functions/specs/create_table.ts @@ -11,9 +11,9 @@ import { ExpressionFunctionDefinition } from '../types'; import { Datatable, DatatableColumn } from '../../expression_types'; export interface CreateTableArguments { - ids: string[]; - names: string[] | null; - rowCount: number; + ids?: string[]; + names?: string[] | null; + rowCount?: number; } export const createTable: ExpressionFunctionDefinition< diff --git a/src/plugins/expressions/common/expression_functions/specs/font.ts b/src/plugins/expressions/common/expression_functions/specs/font.ts index fa8fc8d387af..3d189a68119d 100644 --- a/src/plugins/expressions/common/expression_functions/specs/font.ts +++ b/src/plugins/expressions/common/expression_functions/specs/font.ts @@ -30,7 +30,7 @@ const inlineStyle = (obj: Record) => { return styles.join(';'); }; -interface Arguments { +export interface FontArguments { align?: TextAlignment; color?: string; family?: FontFamily; @@ -41,7 +41,12 @@ interface Arguments { weight?: FontWeight; } -export type ExpressionFunctionFont = ExpressionFunctionDefinition<'font', null, Arguments, Style>; +export type ExpressionFunctionFont = ExpressionFunctionDefinition< + 'font', + null, + FontArguments, + Style +>; export const font: ExpressionFunctionFont = { name: 'font', diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index 23aeee6f9581..7b2266637bfb 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -110,7 +110,7 @@ export const mapColumn: ExpressionFunctionDefinition< map((rows) => { let type: DatatableColumnType = 'null'; for (const row of rows) { - const rowType = getType(row[id]); + const rowType = getType(row[id]) as DatatableColumnType; if (rowType !== 'null') { type = rowType; break; diff --git a/src/plugins/expressions/common/expression_functions/specs/math.ts b/src/plugins/expressions/common/expression_functions/specs/math.ts index 92a10976428a..f843f53e4dd8 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { map, zipObject } from 'lodash'; +import { map, zipObject, isString } from 'lodash'; import { i18n } from '@kbn/i18n'; import { evaluate } from '@kbn/tinymath'; import { ExpressionFunctionDefinition } from '../types'; @@ -23,19 +23,18 @@ const TINYMATH = '`TinyMath`'; const TINYMATH_URL = 'https://www.elastic.co/guide/en/kibana/current/canvas-tinymath-functions.html'; -const isString = (val: any): boolean => typeof val === 'string'; - function pivotObjectArray< - RowType extends { [key: string]: any }, - ReturnColumns extends string | number | symbol = keyof RowType ->(rows: RowType[], columns?: string[]): Record { + RowType extends { [key: string]: unknown }, + ReturnColumns extends keyof RowType & string +>(rows: RowType[], columns?: ReturnColumns[]) { const columnNames = columns || Object.keys(rows[0]); if (!columnNames.every(isString)) { throw new Error('Columns should be an array of strings'); } const columnValues = map(columnNames, (name) => map(rows, name)); - return zipObject(columnNames, columnValues); + + return zipObject(columnNames, columnValues) as { [K in ReturnColumns]: Array }; } export const errors = { diff --git a/src/plugins/expressions/common/expression_functions/specs/math_column.ts b/src/plugins/expressions/common/expression_functions/specs/math_column.ts index c59016cd260a..a2a79ef3f028 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math_column.ts @@ -107,7 +107,7 @@ export const mathColumn: ExpressionFunctionDefinition< let type: DatatableColumnType = 'null'; if (newRows.length) { for (const row of newRows) { - const rowType = getType(row[args.id]); + const rowType = getType(row[args.id]) as DatatableColumnType; if (rowType !== 'null') { type = rowType; break; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts index 03b610126660..e095f4e1bec6 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/font.test.ts @@ -7,7 +7,8 @@ */ import { openSans } from '../../../fonts'; -import { font } from '../font'; +import { FontWeight, TextAlignment } from '../../../types'; +import { font, FontArguments } from '../font'; import { functionWrapper } from './utils'; describe('font', () => { @@ -22,7 +23,7 @@ describe('font', () => { size: 14, underline: false, weight: 'normal', - }; + } as unknown as FontArguments; describe('default output', () => { const result = fn(null, args); @@ -63,7 +64,7 @@ describe('font', () => { describe('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.css).toContain('font-family:Optima, serif'); }); @@ -79,29 +80,29 @@ describe('font', () => { describe('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.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.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.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.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.css).toContain('font-weight:400'); }); 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', () => { 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.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.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.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.css).toContain('text-align:justify'); }); it('throws when provided an invalid alignment', () => { - expect(() => fn(null, { ...args, align: 'foo' })).toThrow(); + expect(() => fn(null, { ...args, align: 'foo' as TextAlignment })).toThrow(); }); }); }); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts index 6da00061244d..3761fe0a4f90 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/math.test.ts @@ -6,19 +6,19 @@ * Side Public License, v 1. */ -import { errors, math } from '../math'; +import { errors, math, MathArguments, MathInput } from '../math'; import { emptyTable, functionWrapper, testTable } from './utils'; describe('math', () => { - const fn = functionWrapper(math); + const fn = functionWrapper(math); it('evaluates math expressions without reference to context', () => { - expect(fn(null, { expression: '10.5345' })).toBe(10.5345); - expect(fn(null, { expression: '123 + 456' })).toBe(579); - expect(fn(null, { expression: '100 - 46' })).toBe(54); + expect(fn(null as unknown as MathInput, { expression: '10.5345' })).toBe(10.5345); + expect(fn(null as unknown as MathInput, { expression: '123 + 456' })).toBe(579); + expect(fn(null as unknown as MathInput, { expression: '100 - 46' })).toBe(54); expect(fn(1, { expression: '100 / 5' })).toBe(20); - expect(fn('foo', { expression: '100 / 5' })).toBe(20); - expect(fn(true, { expression: '100 / 5' })).toBe(20); + expect(fn('foo' as unknown as MathInput, { 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(emptyTable, { expression: '100 * 5' })).toBe(500); }); @@ -54,7 +54,7 @@ describe('math', () => { describe('args', () => { describe('expression', () => { 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(testTable, { expression: 'count(price)' })).toBe(9); expect(fn(testTable, { expression: 'count(name)' })).toBe(9); @@ -99,11 +99,11 @@ describe('math', () => { it('throws when missing expression', () => { 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) ); - expect(() => fn(testTable, { expession: ' ' })).toThrow( + expect(() => fn(testTable, { expession: ' ' } as unknown as MathArguments)).toThrow( new RegExp(errors.emptyExpression().message) ); }); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts index 0733b1c77bf4..3f535b7fb7ac 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/theme.test.ts @@ -26,14 +26,14 @@ describe('expression_functions', () => { }; context = { - getSearchContext: () => ({} as any), + getSearchContext: () => ({}), getSearchSessionId: () => undefined, getExecutionContext: () => undefined, types: {}, variables: { theme: themeProps }, - abortSignal: {} as any, - inspectorAdapters: {} as any, - }; + abortSignal: {}, + inspectorAdapters: {}, + } as unknown as typeof context; }); it('returns the selected variable', () => { diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts index 6b3a458aa7e5..053f97ffc8fb 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/ui_setting.test.ts @@ -10,13 +10,15 @@ jest.mock('../../../../common'); import { IUiSettingsClient } from 'src/core/public'; import { getUiSettingFn } from '../ui_setting'; +import { functionWrapper } from './utils'; describe('uiSetting', () => { describe('fn', () => { let getStartDependencies: jest.MockedFunction< Parameters[0]['getStartDependencies'] >; - let uiSetting: ReturnType; + const uiSettingWrapper = () => functionWrapper(getUiSettingFn({ getStartDependencies })); + let uiSetting: ReturnType; let uiSettings: jest.Mocked; beforeEach(() => { @@ -27,13 +29,13 @@ describe('uiSetting', () => { uiSettings, })) as unknown as typeof getStartDependencies; - uiSetting = getUiSettingFn({ getStartDependencies }); + uiSetting = uiSettingWrapper(); }); it('should return a 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', key: 'something', value: 'value', @@ -41,7 +43,7 @@ describe('uiSetting', () => { }); 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'); }); @@ -51,16 +53,16 @@ describe('uiSetting', () => { 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".') ); }); it('should get a request instance on the server-side', async () => { const request = {}; - await uiSetting.fn(null, { parameter: 'something' }, { + await uiSetting(null, { parameter: 'something' }, { getKibanaRequest: () => request, - } as any); + } as Parameters[2]); 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 () => { - await uiSetting.fn(null, { parameter: 'something' }, {} as any); + await uiSetting(null, { parameter: 'something' }); const [[getKibanaRequest]] = getStartDependencies.mock.calls; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts index ca41b427a28f..e3f581d1ae35 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts @@ -15,13 +15,15 @@ import { Datatable } from '../../../expression_types'; * Takes a function spec and passes in default args, * overriding with any provided args. */ -export const functionWrapper = ( - spec: AnyExpressionFunctionDefinition +export const functionWrapper = < + ExpressionFunctionDefinition extends AnyExpressionFunctionDefinition +>( + spec: ExpressionFunctionDefinition ) => { const defaultArgs = mapValues(spec.args, (argSpec) => argSpec.default); return ( - context: ContextType, - args: Record = {}, + context?: Parameters[0] | null, + args: Parameters[1] = {}, handlers: ExecutionContext = {} as ExecutionContext ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); }; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts index 0c5c6b148020..ca9c9c257f70 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var.test.ts @@ -24,9 +24,9 @@ describe('expression_functions', () => { getExecutionContext: () => undefined, types: {}, variables: { test: 1 }, - abortSignal: {} as any, - inspectorAdapters: {} as any, - }; + abortSignal: {}, + inspectorAdapters: {}, + } as unknown as typeof context; }); it('returns the selected variable', () => { diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts index 95287f71907a..b98e8285a1a8 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts @@ -17,7 +17,7 @@ describe('expression_functions', () => { const fn = functionWrapper(variableSet); let input: Partial>; let context: ExecutionContext; - let variables: Record; + let variables: Record; beforeEach(() => { input = { timeRange: { from: '0', to: '1' } }; @@ -27,9 +27,9 @@ describe('expression_functions', () => { getExecutionContext: () => undefined, types: {}, variables: { test: 1 }, - abortSignal: {} as any, - inspectorAdapters: {} as any, - }; + abortSignal: {}, + inspectorAdapters: {}, + } as unknown as typeof context; variables = context.variables; }); diff --git a/src/plugins/expressions/common/expression_functions/specs/theme.ts b/src/plugins/expressions/common/expression_functions/specs/theme.ts index 914e5d330bdd..76e97b12a967 100644 --- a/src/plugins/expressions/common/expression_functions/specs/theme.ts +++ b/src/plugins/expressions/common/expression_functions/specs/theme.ts @@ -12,10 +12,10 @@ import { ExpressionFunctionDefinition } from '../types'; interface Arguments { variable: string; - default: string | number | boolean; + default?: string | number | boolean; } -type Output = any; +type Output = unknown; export type ExpressionFunctionTheme = ExpressionFunctionDefinition< 'theme', diff --git a/src/plugins/expressions/common/expression_functions/specs/var.ts b/src/plugins/expressions/common/expression_functions/specs/var.ts index d0e43a687147..0ba4d5f43935 100644 --- a/src/plugins/expressions/common/expression_functions/specs/var.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var.ts @@ -36,7 +36,8 @@ export const variable: ExpressionFunctionVar = { }, }, fn(input, args, context) { - const variables: Record = context.variables; + const { variables } = context; + return variables[args.name]; }, }; diff --git a/src/plugins/expressions/common/expression_functions/specs/var_set.ts b/src/plugins/expressions/common/expression_functions/specs/var_set.ts index f3ac6a2ab80d..aa257940f9ad 100644 --- a/src/plugins/expressions/common/expression_functions/specs/var_set.ts +++ b/src/plugins/expressions/common/expression_functions/specs/var_set.ts @@ -7,11 +7,12 @@ */ import { i18n } from '@kbn/i18n'; +import type { Serializable } from '@kbn/utility-types'; import { ExpressionFunctionDefinition } from '../types'; interface Arguments { name: string[]; - value: any[]; + value: Serializable[]; } export type ExpressionFunctionVarSet = ExpressionFunctionDefinition< @@ -46,10 +47,11 @@ export const variableSet: ExpressionFunctionVarSet = { }, }, fn(input, args, context) { - const variables: Record = context.variables; + const { variables } = context; args.name.forEach((name, i) => { variables[name] = args.value[i] === undefined ? input : args.value[i]; }); + return input; }, }; diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts index 0ec61b39608a..cb3677ed1668 100644 --- a/src/plugins/expressions/common/expression_functions/types.ts +++ b/src/plugins/expressions/common/expression_functions/types.ts @@ -30,7 +30,7 @@ import { PersistableStateDefinition } from '../../../kibana_utils/common'; export interface ExpressionFunctionDefinition< Name extends string, Input, - Arguments extends Record, + Arguments extends Record, Output, Context extends ExecutionContext = ExecutionContext > extends PersistableStateDefinition { @@ -99,12 +99,14 @@ export interface ExpressionFunctionDefinition< /** * Type to capture every possible expression function definition. */ +/* eslint-disable @typescript-eslint/no-explicit-any */ export type AnyExpressionFunctionDefinition = ExpressionFunctionDefinition< string, any, Record, any >; +/* eslint-enable @typescript-eslint/no-explicit-any */ /** * A mapping of `ExpressionFunctionDefinition`s for functions which the diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts index 8547c1a1bec9..6c889a81a1f8 100644 --- a/src/plugins/expressions/common/expression_renderers/types.ts +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { ExpressionAstExpression } from '../ast'; + export interface ExpressionRenderDefinition { /** * Technical name of the renderer, used as ID to identify renderer in @@ -46,6 +48,7 @@ export interface ExpressionRenderDefinition { ) => void | Promise; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyExpressionRenderDefinition = ExpressionRenderDefinition; /** @@ -59,24 +62,34 @@ export type AnyExpressionRenderDefinition = ExpressionRenderDefinition; */ export type RenderMode = 'edit' | 'preview' | 'view'; +export interface IInterpreterRenderUpdateParams { + newExpression?: string | ExpressionAstExpression; + newParams: Params; +} + +export interface IInterpreterRenderEvent { + name: string; + data?: Context; +} + export interface IInterpreterRenderHandlers { /** * Done increments the number of rendering successes */ - done: () => void; - onDestroy: (fn: () => void) => void; - reload: () => void; - update: (params: any) => void; - event: (event: any) => void; - hasCompatibleActions?: (event: any) => Promise; - getRenderMode: () => RenderMode; + done(): void; + onDestroy(fn: () => void): void; + reload(): void; + update(params: IInterpreterRenderUpdateParams): void; + event(event: IInterpreterRenderEvent): void; + hasCompatibleActions?(event: IInterpreterRenderEvent): Promise; + getRenderMode(): RenderMode; /** * 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, * but expressions cannot know about vis or it creates a mess of circular dependencies. diff --git a/src/plugins/expressions/common/expression_types/expression_type.ts b/src/plugins/expressions/common/expression_types/expression_type.ts index 1c22b9f13b97..d179beeb7686 100644 --- a/src/plugins/expressions/common/expression_types/expression_type.ts +++ b/src/plugins/expressions/common/expression_types/expression_type.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { Serializable } from '@kbn/utility-types'; import { AnyExpressionTypeDefinition, ExpressionValue, ExpressionValueConverter } from './types'; import { getType } from './get_type'; @@ -20,15 +21,15 @@ export class ExpressionType { /** * Type validation, useful for checking function output. */ - validate: (type: any) => void | Error; + validate: (type: unknown) => void | Error; create: unknown; /** * Optional serialization (used when passing context around client/server). */ - serialize?: (value: ExpressionValue) => any; - deserialize?: (serialized: any) => ExpressionValue; + serialize?: (value: Serializable) => unknown; + deserialize?: (serialized: unknown[]) => Serializable; constructor(private readonly definition: AnyExpressionTypeDefinition) { const { name, help, deserialize, serialize, validate } = definition; @@ -38,7 +39,7 @@ export class ExpressionType { this.validate = validate || (() => {}); // Optional - this.create = (definition as any).create; + this.create = (definition as unknown as Record<'create', unknown>).create; this.serialize = serialize; this.deserialize = deserialize; diff --git a/src/plugins/expressions/common/expression_types/get_type.ts b/src/plugins/expressions/common/expression_types/get_type.ts index 052508df4132..17089c78816b 100644 --- a/src/plugins/expressions/common/expression_types/get_type.ts +++ b/src/plugins/expressions/common/expression_types/get_type.ts @@ -6,14 +6,24 @@ * Side Public License, v 1. */ -export function getType(node: any) { - if (node == null) return 'null'; +export function getType(node: unknown): string { + if (node == null) { + return 'null'; + } + if (Array.isArray(node)) { throw new Error('Unexpected array value encountered.'); } - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; + + if (typeof node !== 'object') { + return typeof node; } - return typeof node; + + const { type } = node as Record; + + if (!type) { + throw new Error('Objects must have a type property'); + } + + return type as string; } diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts index c268557936ac..b45c36950f87 100644 --- a/src/plugins/expressions/common/expression_types/specs/datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts @@ -9,7 +9,7 @@ import type { SerializableRecord } from '@kbn/utility-types'; import { map, pick, zipObject } from 'lodash'; -import { ExpressionTypeDefinition } from '../types'; +import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; import { PointSeries, PointSeriesColumn } from './pointseries'; import { ExpressionValueRender } from './render'; import { SerializedFieldFormat } from '../../types'; @@ -21,7 +21,7 @@ const name = 'datatable'; * @param 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`. @@ -48,6 +48,7 @@ export type DatatableColumnType = /** * This type represents a row in a `Datatable`. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type DatatableRow = Record; /** @@ -112,7 +113,7 @@ interface RenderedDatatable { export const datatable: ExpressionTypeDefinition = { name, - validate: (table) => { + validate: (table: Record) => { // TODO: Check columns types. Only string, boolean, number, date, allowed for now. if (!table.columns) { throw new Error('datatable must have a columns array, even if it is empty'); diff --git a/src/plugins/expressions/common/expression_types/specs/error.ts b/src/plugins/expressions/common/expression_types/specs/error.ts index 75e49633866f..b170675b8f48 100644 --- a/src/plugins/expressions/common/expression_types/specs/error.ts +++ b/src/plugins/expressions/common/expression_types/specs/error.ts @@ -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'; /** diff --git a/src/plugins/expressions/common/expression_types/specs/pointseries.ts b/src/plugins/expressions/common/expression_types/specs/pointseries.ts index 1c8bddf14cff..ef2079bd387a 100644 --- a/src/plugins/expressions/common/expression_types/specs/pointseries.ts +++ b/src/plugins/expressions/common/expression_types/specs/pointseries.ts @@ -7,7 +7,7 @@ */ import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; -import { Datatable } from './datatable'; +import { Datatable, DatatableRow } from './datatable'; import { ExpressionValueRender } from './render'; const name = 'pointseries'; @@ -31,7 +31,7 @@ export interface PointSeriesColumn { */ export type PointSeriesColumns = Record | {}; -export type PointSeriesRow = Record; +export type PointSeriesRow = DatatableRow; /** * A `PointSeries` is a unique structure that represents dots on a chart. diff --git a/src/plugins/expressions/common/expression_types/specs/shape.ts b/src/plugins/expressions/common/expression_types/specs/shape.ts index 2b62dc6458c1..5ad86366a26c 100644 --- a/src/plugins/expressions/common/expression_types/specs/shape.ts +++ b/src/plugins/expressions/common/expression_types/specs/shape.ts @@ -11,7 +11,7 @@ import { ExpressionValueRender } from './render'; const name = 'shape'; -export const shape: ExpressionTypeDefinition> = { +export const shape: ExpressionTypeDefinition> = { name: 'shape', to: { render: (input) => { diff --git a/src/plugins/expressions/common/expression_types/types.ts b/src/plugins/expressions/common/expression_types/types.ts index a829c2adc923..15ec82e40314 100644 --- a/src/plugins/expressions/common/expression_types/types.ts +++ b/src/plugins/expressions/common/expression_types/types.ts @@ -6,6 +6,9 @@ * 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 ExpressionValueBoxed = { @@ -16,7 +19,7 @@ export type ExpressionValue = ExpressionValueUnboxed | ExpressionValueBoxed; export type ExpressionValueConverter = ( input: I, - availableTypes: Record + availableTypes: Record ) => O; /** @@ -29,18 +32,19 @@ export interface ExpressionTypeDefinition< SerializedType = undefined > { name: Name; - validate?: (type: any) => void | Error; - serialize?: (type: Value) => SerializedType; - deserialize?: (type: SerializedType) => Value; + validate?(type: unknown): void | Error; + serialize?(type: Value): SerializedType; + deserialize?(type: SerializedType): Value; // TODO: Update typings for the `availableTypes` parameter once interfaces for this // have been added elsewhere in the interpreter. from?: { - [type: string]: ExpressionValueConverter; + [type: string]: ExpressionValueConverter; }; to?: { - [type: string]: ExpressionValueConverter; + [type: string]: ExpressionValueConverter; }; help?: string; } -export type AnyExpressionTypeDefinition = ExpressionTypeDefinition; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyExpressionTypeDefinition = ExpressionTypeDefinition; diff --git a/src/plugins/expressions/common/mocks.ts b/src/plugins/expressions/common/mocks.ts index 681fa2826882..4141da06ec04 100644 --- a/src/plugins/expressions/common/mocks.ts +++ b/src/plugins/expressions/common/mocks.ts @@ -11,7 +11,7 @@ import { ExecutionContext } from './execution/types'; export const createMockExecutionContext = ( extraContext: ExtraContext = {} as ExtraContext ): ExecutionContext & ExtraContext => { - const executionContext: ExecutionContext = { + const executionContext = { getSearchContext: jest.fn(), getSearchSessionId: jest.fn(), getExecutionContext: jest.fn(), @@ -25,10 +25,10 @@ export const createMockExecutionContext = removeEventListener: jest.fn(), }, inspectorAdapters: { - requests: {} as any, - data: {} as any, + requests: {}, + data: {}, }, - }; + } as unknown as ExecutionContext; return { ...executionContext, diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index f21eaa34d786..453ea656ec43 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -125,7 +125,7 @@ export interface ExpressionsServiceSetup { export interface ExpressionExecutionParams { searchContext?: SerializableRecord; - variables?: Record; + variables?: Record; /** * Whether to execute expression in *debug mode*. In *debug mode* inputs and @@ -148,6 +148,8 @@ export interface ExpressionExecutionParams { inspectorAdapters?: Adapters; executionContext?: KibanaExecutionContext; + + extraContext?: object; } /** @@ -375,7 +377,7 @@ export class ExpressionsService */ public readonly telemetry = ( state: ExpressionAstExpression, - telemetryData: Record = {} + telemetryData: Record = {} ) => { return this.executor.telemetry(state, telemetryData); }; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/access.ts b/src/plugins/expressions/common/test_helpers/expression_functions/access.ts index 5498e6741bd9..6ddc99b13ee5 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/access.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/access.ts @@ -8,7 +8,7 @@ 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', help: 'Access key on input object or return the input, if it is not an object', args: { @@ -19,6 +19,10 @@ export const access: ExpressionFunctionDefinition<'access', any, { key: string } }, }, fn: (input, { key }, context) => { - return !input ? input : typeof input === 'object' ? input[key] : input; + return !input + ? input + : typeof input === 'object' + ? (input as Record)[key] + : input; }, }; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/add.ts b/src/plugins/expressions/common/test_helpers/expression_functions/add.ts index 03c72733166b..5e646ead7b62 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/add.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/add.ts @@ -26,11 +26,11 @@ export const add: ExpressionFunctionDefinition< types: ['null', 'number', 'string'], }, }, - fn: ({ value: value1 }, { val: input2 }, context) => { + fn: ({ value: value1 }, { val: input2 }) => { const value2 = !input2 ? 0 : typeof input2 === 'object' - ? (input2 as any).value + ? (input2 as ExpressionValueNum).value : Number(input2); return { diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts b/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts index 9556872d5734..8b2c22df8e08 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/introspect_context.ts @@ -10,9 +10,9 @@ import { ExpressionFunctionDefinition } from '../../expression_functions'; export const introspectContext: ExpressionFunctionDefinition< 'introspectContext', - any, + unknown, { key: string }, - any + unknown > = { name: 'introspectContext', args: { @@ -25,7 +25,7 @@ export const introspectContext: ExpressionFunctionDefinition< fn: (input, args, context) => { return { type: 'any', - result: (context as any)[args.key], + result: context[args.key as keyof typeof context], }; }, }; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts index 04b1c9822a3b..71a14b3dac10 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/sleep.ts @@ -8,7 +8,7 @@ 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', args: { time: { diff --git a/src/plugins/expressions/common/types/common.ts b/src/plugins/expressions/common/types/common.ts index d8d1a9a4b256..64b3d00895f5 100644 --- a/src/plugins/expressions/common/types/common.ts +++ b/src/plugins/expressions/common/types/common.ts @@ -37,7 +37,7 @@ export type KnownTypeToString = * `someArgument: Promise` results in `types: ['boolean', 'string']` */ export type TypeString = KnownTypeToString< - T extends ObservableLike ? UnwrapObservable : UnwrapPromiseOrReturn + T extends ObservableLike ? UnwrapObservable : UnwrapPromiseOrReturn >; /** @@ -52,6 +52,7 @@ export type UnmappedTypeStrings = 'date' | 'filter'; * Is used to carry information about how to format data in * a data table as part of the column definition. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export interface SerializedFieldFormat> { id?: string; params?: TParams; diff --git a/src/plugins/expressions/common/util/expressions_inspector_adapter.ts b/src/plugins/expressions/common/util/expressions_inspector_adapter.ts index 34fa1c8713f5..bf16635792c1 100644 --- a/src/plugins/expressions/common/util/expressions_inspector_adapter.ts +++ b/src/plugins/expressions/common/util/expressions_inspector_adapter.ts @@ -7,16 +7,17 @@ */ import { EventEmitter } from 'events'; +import { ExpressionAstNode } from '..'; 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.emit('change', this._ast); } - public get ast() { + public get ast(): ExpressionAstNode { return this._ast; } } diff --git a/src/plugins/expressions/common/util/test_utils.ts b/src/plugins/expressions/common/util/test_utils.ts index 59bd0a4235d9..a6a4771bdf89 100644 --- a/src/plugins/expressions/common/util/test_utils.ts +++ b/src/plugins/expressions/common/util/test_utils.ts @@ -14,7 +14,7 @@ export const createMockContext = () => { getSearchSessionId: () => undefined, types: {}, variables: {}, - abortSignal: {} as any, - inspectorAdapters: {} as any, + abortSignal: {}, + inspectorAdapters: {}, } as ExecutionContext; }; diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts index ff960f4a3a80..f22963cedf61 100644 --- a/src/plugins/expressions/public/loader.test.ts +++ b/src/plugins/expressions/public/loader.test.ts @@ -16,12 +16,14 @@ import { IInterpreterRenderHandlers, RenderMode, AnyExpressionFunctionDefinition, + ExpressionsService, + ExecutionContract, } from '../common'; // eslint-disable-next-line const { __getLastExecution, __getLastRenderMode } = require('./services'); -const element: HTMLElement = null as any; +const element = null as unknown as HTMLElement; let testScheduler: TestScheduler; @@ -36,8 +38,9 @@ jest.mock('./services', () => { }, }; - // eslint-disable-next-line - const service = new (require('../common/service/expressions_services').ExpressionsService as any)(); + const service: ExpressionsService = + // eslint-disable-next-line @typescript-eslint/no-var-requires + new (require('../common/service/expressions_services').ExpressionsService)(); const testFn: AnyExpressionFunctionDefinition = { fn: () => ({ type: 'render', as: 'test' }), @@ -54,9 +57,9 @@ jest.mock('./services', () => { service.start(); + let execution: ExecutionContract; const moduleMock = { - __execution: undefined, - __getLastExecution: () => moduleMock.__execution, + __getLastExecution: () => execution, __getLastRenderMode: () => renderMode, getRenderersRegistry: () => ({ get: (id: string) => renderers[id], @@ -72,13 +75,14 @@ jest.mock('./services', () => { }; 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, 'cancel'); - moduleMock.__execution = execution; + return execution; - }; + }); return moduleMock; }); diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 3ab7473d8d73..b0a54e3dec35 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -9,7 +9,7 @@ import { BehaviorSubject, Observable, Subject, Subscription, asyncScheduler, identity } from 'rxjs'; import { filter, map, delay, throttleTime } from 'rxjs/operators'; import { defaults } from 'lodash'; -import { UnwrapObservable } from '@kbn/utility-types'; +import { SerializableRecord, UnwrapObservable } from '@kbn/utility-types'; import { Adapters } from '../../inspector/public'; import { IExpressionLoaderParams } from './types'; import { ExpressionAstExpression } from '../common'; @@ -18,7 +18,7 @@ import { ExecutionContract } from '../common/execution/execution_contract'; import { ExpressionRenderHandler } from './render'; import { getExpressionsService } from './services'; -type Data = any; +type Data = unknown; export class ExpressionLoader { data$: ReturnType; @@ -156,7 +156,7 @@ export class ExpressionLoader { }; 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) { @@ -169,7 +169,7 @@ export class ExpressionLoader { {}, params.searchContext, this.params.searchContext || {} - ) as any; + ); } if (params.uiState && this.params) { this.params.uiState = params.uiState; diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index d31a4c947b09..f1932ce7dd6b 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -14,6 +14,7 @@ import { ReactExpressionRenderer } from './react_expression_renderer'; import { ExpressionLoader } from './loader'; import { mount } from 'enzyme'; import { EuiProgress } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from '../common'; import { RenderErrorHandlerFnType } from './types'; import { ExpressionRendererEvent } from './render'; @@ -234,7 +235,7 @@ describe('ExpressionRenderer', () => { done: () => { renderSubject.next(1); }, - } as any); + } as IInterpreterRenderHandlers); }); instance.update(); diff --git a/src/plugins/expressions/public/render.test.ts b/src/plugins/expressions/public/render.test.ts index 61dc0a25439b..8d4298785572 100644 --- a/src/plugins/expressions/public/render.test.ts +++ b/src/plugins/expressions/public/render.test.ts @@ -8,6 +8,7 @@ import { ExpressionRenderHandler, render } from './render'; import { Observable } from 'rxjs'; +import { SerializableRecord } from '@kbn/utility-types'; import { ExpressionRenderError } from './types'; import { getRenderersRegistry } from './services'; 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 () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); await expect(promise1).resolves.toEqual(1); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise(); await expect(promise2).resolves.toEqual(2); }); @@ -92,7 +93,7 @@ describe('ExpressionRenderHandler', () => { const expressionRenderHandler = new ExpressionRenderHandler(element, { onRenderError: mockMockErrorRenderFunction, }); - await expressionRenderHandler.render(false); + await expressionRenderHandler.render(false as unknown as SerializableRecord); expect(getHandledError()!.message).toEqual( `invalid data provided to the expression renderer` ); @@ -122,7 +123,8 @@ describe('ExpressionRenderHandler', () => { get: () => ({ render: (domNode: HTMLElement, config: unknown, handlers: IInterpreterRenderHandlers) => { handlers.hasCompatibleActions!({ - foo: 'bar', + name: 'something', + data: 'bar', }); }, }), @@ -136,7 +138,8 @@ describe('ExpressionRenderHandler', () => { await expressionRenderHandler.render({ type: 'render', as: 'something' }); expect(hasCompatibleActions).toHaveBeenCalledTimes(1); 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 () => { const expressionRenderHandler = new ExpressionRenderHandler(element); const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); await expect(promise1).resolves.toEqual(1); expect(mockNotificationService.toasts.addError).toBeCalledWith( expect.objectContaining({ @@ -175,7 +178,7 @@ describe('ExpressionRenderHandler', () => { const expressionRenderHandler1 = new ExpressionRenderHandler(element, { onRenderError: mockMockErrorRenderFunction, }); - expressionRenderHandler1.render(false); + expressionRenderHandler1.render(false as unknown as SerializableRecord); const renderPromiseAfterRender = expressionRenderHandler1.render$.pipe(first()).toPromise(); await expect(renderPromiseAfterRender).resolves.toEqual(1); expect(getHandledError()!.message).toEqual( @@ -188,7 +191,7 @@ describe('ExpressionRenderHandler', () => { onRenderError: mockMockErrorRenderFunction, }); const renderPromiseBeforeRender = expressionRenderHandler2.render$.pipe(first()).toPromise(); - expressionRenderHandler2.render(false); + expressionRenderHandler2.render(false as unknown as SerializableRecord); await expect(renderPromiseBeforeRender).resolves.toEqual(1); expect(getHandledError()!.message).toEqual( 'invalid data provided to the expression renderer' @@ -199,9 +202,9 @@ describe('ExpressionRenderHandler', () => { // that observables will emit previous result if subscription happens after render it('should emit previous render and error results', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - expressionRenderHandler.render(false); + expressionRenderHandler.render(false as unknown as SerializableRecord); 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]); }); }); diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index e9a65d1e8f12..8635a4033bde 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -9,13 +9,20 @@ import * as Rx from 'rxjs'; import { Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; +import { isNumber } from 'lodash'; +import { SerializableRecord } from '@kbn/utility-types'; import { ExpressionRenderError, RenderErrorHandlerFnType, IExpressionLoaderParams } from './types'; 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'; -export type IExpressionRendererExtraHandlers = Record; +export type IExpressionRendererExtraHandlers = Record; export interface ExpressionRenderHandlerParams { onRenderError?: RenderErrorHandlerFnType; @@ -25,15 +32,10 @@ export interface ExpressionRenderHandlerParams { hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise; } -export interface ExpressionRendererEvent { - name: string; - data: any; -} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ExpressionRendererEvent = IInterpreterRenderEvent; -interface UpdateValue { - newExpression?: string | ExpressionAstExpression; - newParams: IExpressionLoaderParams; -} +type UpdateValue = IInterpreterRenderUpdateParams; export class ExpressionRenderHandler { render$: Observable; @@ -41,7 +43,7 @@ export class ExpressionRenderHandler { events$: Observable; private element: HTMLElement; - private destroyFn?: any; + private destroyFn?: Function; private renderCount: number = 0; private renderSubject: Rx.BehaviorSubject; private eventsSubject: Rx.Subject; @@ -66,16 +68,14 @@ export class ExpressionRenderHandler { this.onRenderError = onRenderError || defaultRenderErrorHandler; - this.renderSubject = new Rx.BehaviorSubject(null as any | null); - this.render$ = this.renderSubject - .asObservable() - .pipe(filter((_) => _ !== null)) as Observable; + this.renderSubject = new Rx.BehaviorSubject(null); + this.render$ = this.renderSubject.asObservable().pipe(filter(isNumber)); this.updateSubject = new Rx.Subject(); this.update$ = this.updateSubject.asObservable(); this.handlers = { - onDestroy: (fn: any) => { + onDestroy: (fn: Function) => { this.destroyFn = fn; }, 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') { return this.handleRenderError(new Error('invalid data provided to the expression renderer')); } if (value.type !== 'render' || !value.as) { if (value.type === 'error') { - return this.handleRenderError(value.error); + return this.handleRenderError(value.error as unknown as ExpressionRenderError); } else { return this.handleRenderError( 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}'`)); } try { // Rendering is asynchronous, completed by handlers.done() await getRenderersRegistry() - .get(value.as)! + .get(value.as as string)! .render(this.element, value.value, { ...this.handlers, uiState, - } as any); + }); } catch (e) { - return this.handleRenderError(e); + return this.handleRenderError(e as ExpressionRenderError); } }; @@ -156,10 +156,10 @@ export class ExpressionRenderHandler { export function render( element: HTMLElement, - data: any, + data: unknown, options?: ExpressionRenderHandlerParams ): ExpressionRenderHandler { const handler = new ExpressionRenderHandler(element, options); - handler.render(data); + handler.render(data as SerializableRecord); return handler; } diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 172f322f8892..ea47403332c7 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -36,7 +36,7 @@ export interface ExpressionInterpreter { export interface IExpressionLoaderParams { searchContext?: SerializableRecord; context?: ExpressionValue; - variables?: Record; + variables?: Record; // Enables debug tracking on each expression in the AST debug?: boolean; disableCaching?: boolean; diff --git a/src/plugins/vis_type_markdown/public/markdown_fn.test.ts b/src/plugins/vis_type_markdown/public/markdown_fn.test.ts index bef0b32e392f..148a15fb9fc8 100644 --- a/src/plugins/vis_type_markdown/public/markdown_fn.test.ts +++ b/src/plugins/vis_type_markdown/public/markdown_fn.test.ts @@ -8,6 +8,7 @@ import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; import { createMarkdownVisFn } from './markdown_fn'; +import { Arguments } from './types'; describe('interpreter/functions#markdown', () => { const fn = functionWrapper(createMarkdownVisFn()); @@ -15,7 +16,7 @@ describe('interpreter/functions#markdown', () => { font: { spec: { fontSize: 12 } }, openLinksInNewTab: true, markdown: '## hello _markdown_', - }; + } as unknown as Arguments; it('returns an object with the correct structure', async () => { const actual = await fn(null, args, undefined); diff --git a/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts b/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts index 3844c0f21ed0..28124a653629 100644 --- a/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts +++ b/src/plugins/vis_types/metric/public/metric_vis_fn.test.ts @@ -10,13 +10,15 @@ import { createMetricVisFn } from './metric_vis_fn'; import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; import { Datatable } from '../../../expressions/common/expression_types/specs'; +type Arguments = Parameters['fn']>[1]; + describe('interpreter/functions#metric', () => { const fn = functionWrapper(createMetricVisFn()); const context = { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const args = { percentageMode: false, useRanges: false, @@ -50,7 +52,7 @@ describe('interpreter/functions#metric', () => { aggType: 'count', }, ], - }; + } as unknown as Arguments; it('returns an object with the correct structure', () => { const actual = fn(context, args, undefined); diff --git a/src/plugins/vis_types/pie/public/pie_fn.test.ts b/src/plugins/vis_types/pie/public/pie_fn.test.ts index d0e0af9807f3..9ba21cdc847e 100644 --- a/src/plugins/vis_types/pie/public/pie_fn.test.ts +++ b/src/plugins/vis_types/pie/public/pie_fn.test.ts @@ -8,6 +8,7 @@ import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; import { createPieVisFn } from './pie_fn'; +import { PieVisConfig } from './types'; import { Datatable } from '../../../expressions/common/expression_types/specs'; describe('interpreter/functions#pie', () => { @@ -16,7 +17,7 @@ describe('interpreter/functions#pie', () => { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const visConfig = { addTooltip: true, addLegend: true, @@ -43,7 +44,7 @@ describe('interpreter/functions#pie', () => { params: {}, aggType: 'count', }, - }; + } as unknown as PieVisConfig; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/plugins/vis_types/table/public/table_vis_fn.test.ts b/src/plugins/vis_types/table/public/table_vis_fn.test.ts index 8b08bca16047..d0d9a414bbcc 100644 --- a/src/plugins/vis_types/table/public/table_vis_fn.test.ts +++ b/src/plugins/vis_types/table/public/table_vis_fn.test.ts @@ -8,6 +8,7 @@ import { createTableVisFn } from './table_vis_fn'; import { tableVisResponseHandler } from './utils'; +import { TableVisConfig } from './types'; import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; import { Datatable } from '../../../expressions/common/expression_types/specs'; @@ -24,7 +25,7 @@ describe('interpreter/functions#table', () => { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const visConfig = { title: 'My Chart title', perPage: 10, @@ -52,7 +53,7 @@ describe('interpreter/functions#table', () => { }, ], buckets: [], - }; + } as unknown as TableVisConfig; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/plugins/vis_types/vislib/public/pie_fn.test.ts b/src/plugins/vis_types/vislib/public/pie_fn.test.ts index 0df7bf1365be..9c317f9e72dc 100644 --- a/src/plugins/vis_types/vislib/public/pie_fn.test.ts +++ b/src/plugins/vis_types/vislib/public/pie_fn.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { Datatable } from 'src/plugins/expressions'; import { functionWrapper } from '../../../expressions/common/expression_functions/specs/tests/utils'; import { createPieVisFn } from './pie_fn'; // @ts-ignore @@ -34,7 +35,7 @@ describe('interpreter/functions#pie', () => { type: 'datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], - }; + } as unknown as Datatable; const visConfig = { type: 'pie', addTooltip: true, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts index 4c37d9acc486..63c3f1dcabca 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { Datatable, ExpressionFunctionDefinition, getType } from '../../../types'; +import { + Datatable, + DatatableColumnType, + ExpressionFunctionDefinition, + getType, +} from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { @@ -30,14 +35,14 @@ export function asFn(): ExpressionFunctionDefinition<'as', Input, Arguments, Dat default: 'value', }, }, - fn: (input, args) => { + fn: (input, args): Datatable => { return { type: 'datatable', columns: [ { id: args.name, name: args.name, - meta: { type: getType(input) }, + meta: { type: getType(input) as DatatableColumnType }, }, ], rows: [ diff --git a/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts b/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts index 075e65bc24da..289704ae7953 100644 --- a/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts +++ b/x-pack/plugins/canvas/public/components/function_reference_generator/generate_function_reference.ts @@ -156,7 +156,7 @@ ${examplesBlock} *Returns:* ${output ? wrapInBackTicks(output) : 'Depends on your input and arguments'}\n\n`; }; -const getArgsTable = (args: { [key: string]: ExpressionFunctionParameter }) => { +const getArgsTable = (args: { [key: string]: ExpressionFunctionParameter }) => { if (!args || Object.keys(args).length === 0) { return 'None'; } diff --git a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts index 28f9b08eff1c..4d53f96c71fd 100644 --- a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts +++ b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts @@ -7,11 +7,10 @@ import { Datatable, DatatableColumn } from 'src/plugins/expressions/public'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; -import { FormatColumnArgs, formatColumn } from './index'; +import { formatColumn } from './index'; describe('format_column', () => { - const fn: (input: Datatable, args: FormatColumnArgs) => Promise = - functionWrapper(formatColumn); + const fn = functionWrapper(formatColumn); let datatable: Datatable;