「AppArch」Interpreter 👉 New Platform (#39329)

* feat: 🎸 set-up NP data plugin

* refactor: 💡 move interpreter functions registry to NP

* refactor: 💡 move interpreter renderer registry to NP plugin

* refactor: 💡 move interpreter typesRegistry to NP

* refactor: 💡 move interpreter types to NP

* chore: 🤖 import typeRegistry from NP and change TS type folder

* refactor: 💡 move interpreter expression types to NP

* refactor: 💡 move rest of interpreter common folder to NP plugin

* fix: 🐛 fix TypeScript errors

* test: 💍 improve typings and test mocks

* refactor: 💡 make Interpreter internal registry impl private

* test: 💍 inline NP backdoor mock creation in test suites

* chore: 🤖 change @kbn/interpreter import paths to try fix errors

* fix: 🐛 improve core Plugin interfaces

* feat: 🎸 add stop() lifecycle to NP data plugins

* refactor: 💡 move interpreter into expressions service data NP

* refactor: 💡 inline Registry @kbn/interpreter class

* refactor: 💡 remove dependency on @kbn/interpreter in data pub

* refactor: 💡 move interpreter common dir into expressions dir

* fix: 🐛 use TS types in kibana_context

* feat: 🎸 add types suggested in PR review

* feat: 🎸 add semantic interpreter registration functions

* refactor: 💡 use require for all @kbn/interpreter imports

* test: 💍 add Karma test mocks, thx @spalger 🙏

* docs: ✏️ update Core docs

* test: 💍 add Sinon stubs for registries

* chore: 🤖 change import syntax in hopes CI will work

* chore: 🤖 set App Architecture as owners of data plugin

* docs: ✏️ add README

* chore: 🤖 change import in hopes to fix optimizer

* fix: 🐛 make stop() plugin life-cycle optional

* docs: ✏️ update Core API docs

* test: 💍 remove unnecessary Jest mock

* chore: 🤖 don't import from deeply inside a plugin

* refactor: 💡 try different interpreter import

* fix: 🐛 fix Karma mocking

* fix: 🐛 fix TypeScript type imports

* test: 💍 fix broken test
This commit is contained in:
Vadim Dalecky 2019-07-02 09:07:07 +02:00 committed by GitHub
parent 12ba1f1894
commit f18e7439d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 1041 additions and 406 deletions

1
.github/CODEOWNERS vendored
View file

@ -3,6 +3,7 @@
# For more info, see https://help.github.com/articles/about-codeowners/
# App Architecture
/src/plugins/data/ @elastic/kibana-app-arch
/src/plugins/kibana_utils/ @elastic/kibana-app-arch
# APM

View file

@ -9,14 +9,14 @@ The interface that should be returned by a `PluginInitializer`<!-- -->.
<b>Signature:</b>
```typescript
export interface Plugin<TSetup, TStart, TPluginsSetup extends Record<string, unknown> = {}, TPluginsStart extends Record<string, unknown> = {}>
export interface Plugin<TSetup = void, TStart = void, TPluginsSetup extends {} = {}, TPluginsStart extends {} = {}>
```
## Properties
## Methods
| Property | Type | Description |
| --- | --- | --- |
| [setup](./kibana-plugin-public.plugin.setup.md) | <code>(core: CoreSetup, plugins: TPluginsSetup) =&gt; TSetup &#124; Promise&lt;TSetup&gt;</code> | |
| [start](./kibana-plugin-public.plugin.start.md) | <code>(core: CoreStart, plugins: TPluginsStart) =&gt; TStart &#124; Promise&lt;TStart&gt;</code> | |
| [stop](./kibana-plugin-public.plugin.stop.md) | <code>() =&gt; void</code> | |
| Method | Description |
| --- | --- |
| [setup(core, plugins)](./kibana-plugin-public.plugin.setup.md) | |
| [start(core, plugins)](./kibana-plugin-public.plugin.start.md) | |
| [stop()](./kibana-plugin-public.plugin.stop.md) | |

View file

@ -2,10 +2,22 @@
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [Plugin](./kibana-plugin-public.plugin.md) &gt; [setup](./kibana-plugin-public.plugin.setup.md)
## Plugin.setup property
## Plugin.setup() method
<b>Signature:</b>
```typescript
setup: (core: CoreSetup, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| core | <code>CoreSetup</code> | |
| plugins | <code>TPluginsSetup</code> | |
<b>Returns:</b>
`TSetup | Promise<TSetup>`

View file

@ -2,10 +2,22 @@
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [Plugin](./kibana-plugin-public.plugin.md) &gt; [start](./kibana-plugin-public.plugin.start.md)
## Plugin.start property
## Plugin.start() method
<b>Signature:</b>
```typescript
start: (core: CoreStart, plugins: TPluginsStart) => TStart | Promise<TStart>;
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| core | <code>CoreStart</code> | |
| plugins | <code>TPluginsStart</code> | |
<b>Returns:</b>
`TStart | Promise<TStart>`

View file

@ -2,10 +2,14 @@
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [Plugin](./kibana-plugin-public.plugin.md) &gt; [stop](./kibana-plugin-public.plugin.stop.md)
## Plugin.stop property
## Plugin.stop() method
<b>Signature:</b>
```typescript
stop?: () => void;
stop?(): void;
```
<b>Returns:</b>
`void`

View file

@ -9,14 +9,14 @@ The interface that should be returned by a `PluginInitializer`<!-- -->.
<b>Signature:</b>
```typescript
export interface Plugin<TSetup, TStart, TPluginsSetup extends Record<PluginName, unknown> = {}, TPluginsStart extends Record<PluginName, unknown> = {}>
export interface Plugin<TSetup = void, TStart = void, TPluginsSetup extends {} = {}, TPluginsStart extends {} = {}>
```
## Properties
## Methods
| Property | Type | Description |
| --- | --- | --- |
| [setup](./kibana-plugin-server.plugin.setup.md) | <code>(core: CoreSetup, plugins: TPluginsSetup) =&gt; TSetup &#124; Promise&lt;TSetup&gt;</code> | |
| [start](./kibana-plugin-server.plugin.start.md) | <code>(core: CoreStart, plugins: TPluginsStart) =&gt; TStart &#124; Promise&lt;TStart&gt;</code> | |
| [stop](./kibana-plugin-server.plugin.stop.md) | <code>() =&gt; void</code> | |
| Method | Description |
| --- | --- |
| [setup(core, plugins)](./kibana-plugin-server.plugin.setup.md) | |
| [start(core, plugins)](./kibana-plugin-server.plugin.start.md) | |
| [stop()](./kibana-plugin-server.plugin.stop.md) | |

View file

@ -2,10 +2,22 @@
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Plugin](./kibana-plugin-server.plugin.md) &gt; [setup](./kibana-plugin-server.plugin.setup.md)
## Plugin.setup property
## Plugin.setup() method
<b>Signature:</b>
```typescript
setup: (core: CoreSetup, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| core | <code>CoreSetup</code> | |
| plugins | <code>TPluginsSetup</code> | |
<b>Returns:</b>
`TSetup | Promise<TSetup>`

View file

@ -2,10 +2,22 @@
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Plugin](./kibana-plugin-server.plugin.md) &gt; [start](./kibana-plugin-server.plugin.start.md)
## Plugin.start property
## Plugin.start() method
<b>Signature:</b>
```typescript
start: (core: CoreStart, plugins: TPluginsStart) => TStart | Promise<TStart>;
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| core | <code>CoreStart</code> | |
| plugins | <code>TPluginsStart</code> | |
<b>Returns:</b>
`TStart | Promise<TStart>`

View file

@ -2,10 +2,14 @@
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Plugin](./kibana-plugin-server.plugin.md) &gt; [stop](./kibana-plugin-server.plugin.stop.md)
## Plugin.stop property
## Plugin.stop() method
<b>Signature:</b>
```typescript
stop?: () => void;
stop?(): void;
```
<b>Returns:</b>
`void`

View file

@ -28,14 +28,14 @@ import { CoreStart, CoreSetup } from '..';
* @public
*/
export interface Plugin<
TSetup,
TStart,
TPluginsSetup extends Record<string, unknown> = {},
TPluginsStart extends Record<string, unknown> = {}
TSetup = void,
TStart = void,
TPluginsSetup extends {} = {},
TPluginsStart extends {} = {}
> {
setup: (core: CoreSetup, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
start: (core: CoreStart, plugins: TPluginsStart) => TStart | Promise<TStart>;
stop?: () => void;
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
stop?(): void;
}
/**

View file

@ -475,13 +475,13 @@ export interface OverlayStart {
}
// @public
export interface Plugin<TSetup, TStart, TPluginsSetup extends Record<string, unknown> = {}, TPluginsStart extends Record<string, unknown> = {}> {
export interface Plugin<TSetup = void, TStart = void, TPluginsSetup extends {} = {}, TPluginsStart extends {} = {}> {
// (undocumented)
setup: (core: CoreSetup, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
// (undocumented)
start: (core: CoreStart, plugins: TPluginsStart) => TStart | Promise<TStart>;
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
// (undocumented)
stop?: () => void;
stop?(): void;
}
// @public

View file

@ -136,14 +136,14 @@ export interface DiscoveredPluginInternal extends DiscoveredPlugin {
* @public
*/
export interface Plugin<
TSetup,
TStart,
TPluginsSetup extends Record<PluginName, unknown> = {},
TPluginsStart extends Record<PluginName, unknown> = {}
TSetup = void,
TStart = void,
TPluginsSetup extends {} = {},
TPluginsStart extends {} = {}
> {
setup: (core: CoreSetup, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
start: (core: CoreStart, plugins: TPluginsStart) => TStart | Promise<TStart>;
stop?: () => void;
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
stop?(): void;
}
/**

View file

@ -313,13 +313,13 @@ export interface OnPreAuthToolkit {
}
// @public
export interface Plugin<TSetup, TStart, TPluginsSetup extends Record<PluginName, unknown> = {}, TPluginsStart extends Record<PluginName, unknown> = {}> {
export interface Plugin<TSetup = void, TStart = void, TPluginsSetup extends {} = {}, TPluginsStart extends {} = {}> {
// (undocumented)
setup: (core: CoreSetup, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
// (undocumented)
start: (core: CoreStart, plugins: TPluginsStart) => TStart | Promise<TStart>;
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
// (undocumented)
stop?: () => void;
stop?(): void;
}
// @public

View file

@ -25,7 +25,4 @@ export { getQueryLog } from './lib/get_query_log';
// @ts-ignore
export { setupDirective } from './directive';
export interface Query {
query: string | { [key: string]: any };
language: string;
}
export { Query } from '../../../../../../plugins/data/common/query/types';

View file

@ -20,6 +20,8 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { inputControlVis } from './input_control_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
describe('interpreter/functions#input_control_vis', () => {
const fn = functionWrapper(inputControlVis);
const visConfig = {

View file

@ -34,4 +34,10 @@ export {
PointSeriesColumnName,
Render,
Style,
} from './types';
} from '../../../../plugins/data/common/expressions/expression_types';
export const API_ROUTE = '/api/interpreter';
export * from '../../../../plugins/data/common/expressions/serialize_provider';
export { Type } from '../../../../plugins/data/common/expressions/interpreter';
export {
interpreterProvider,
} from '../../../../plugins/data/common/expressions/interpreter_provider';

View file

@ -17,20 +17,31 @@
* under the License.
*/
/* eslint-disable max-classes-per-file */
// @ts-ignore
import { register, registryFactory } from '@kbn/interpreter/common';
import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common';
// @ts-ignore
import { routes } from './server/routes';
// @ts-ignore
import { typeSpecs as types } from '../../../plugins/data/common/expressions/expression_types';
import { Type } from './common';
import { Legacy } from '../../../../kibana';
// @ts-ignore
import { FunctionsRegistry } from './common/functions_registry';
// @ts-ignore
import { typeSpecs as types } from './common/types';
// @ts-ignore
import { TypesRegistry } from './common/types_registry';
export class TypesRegistry extends Registry<any, any> {
wrapper(obj: any) {
return new (Type as any)(obj);
}
}
export class FunctionsRegistry extends Registry<any, any> {
wrapper(obj: any) {
return new Fn(obj);
}
}
export const registries = {
types: new TypesRegistry(),

View file

@ -37,7 +37,7 @@ const courierRequestHandlerProvider = CourierRequestHandlerProvider;
const courierRequestHandler = courierRequestHandlerProvider().handler;
import { ExpressionFunction } from '../../types';
import { KibanaContext, KibanaDatatable } from '../../common/types';
import { KibanaContext, KibanaDatatable } from '../../common';
const name = 'esaggs';

View file

@ -18,7 +18,7 @@
*/
import { i18n } from '@kbn/i18n';
import { KibanaDatatable, Range } from '../../common/types';
import { KibanaDatatable, Range } from '../../types';
import { ExpressionFunction } from '../../types';
const name = 'range';

View file

@ -25,7 +25,7 @@ import { kfetch } from 'ui/kfetch';
import { ajaxStream } from 'ui/ajax_stream';
import { functions } from './functions';
import { visualization } from './renderers/visualization';
import { typeSpecs } from '../common/types';
import { typeSpecs } from '../../../../plugins/data/common/expressions/expression_types';
// Expose kbnInterpreter.register(specs) and kbnInterpreter.registries() globally so that plugins
// can register without a transpile step.

View file

@ -17,8 +17,7 @@
* under the License.
*/
import { interpreterProvider } from '../../common/interpreter/interpret';
import { serializeProvider } from '../../common/serialize';
import { interpreterProvider, serializeProvider } from '../../common';
import { createHandlers } from './create_handlers';
import { batchedFetch } from './batched_fetch';
import { FUNCTIONS_URL } from './consts';

View file

@ -20,11 +20,7 @@
import { FUNCTIONS_URL } from './consts';
import { initializeInterpreter } from './interpreter';
jest.mock('../../common/interpreter/interpret', () => ({
interpreterProvider: () => () => ({}),
}));
jest.mock('../../common/serialize', () => ({
jest.mock('../../common', () => ({
serializeProvider: () => ({ serialize: () => ({}) }),
}));

View file

@ -0,0 +1,43 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import sinon from 'sinon';
export const functionsRegistry = {};
export const renderersRegistry = {};
export const typesRegistry = {};
export const registries = {
browserFunctions: functionsRegistry,
renderers: renderersRegistry,
types: typesRegistry,
};
const resetRegistry = (registry: any) => {
registry.wrapper = sinon.stub();
registry.register = sinon.stub();
registry.toJS = sinon.stub();
registry.toArray = sinon.stub();
registry.get = sinon.stub();
registry.getProp = sinon.stub();
registry.reset = sinon.stub();
};
const resetAll = () => Object.values(registries).forEach(resetRegistry);
resetAll();
afterEach(resetAll);

View file

@ -17,14 +17,16 @@
* under the License.
*/
import { typesRegistry } from './type_registry';
import { functionsRegistry } from './functions_registry';
import { renderersRegistry } from './renderer_registry';
import { npSetup } from 'ui/new_platform';
const registries = {
export const functionsRegistry =
npSetup.plugins.data.expressions.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.functions;
export const renderersRegistry =
npSetup.plugins.data.expressions.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.renderers;
export const typesRegistry =
npSetup.plugins.data.expressions.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.types;
export const registries = {
browserFunctions: functionsRegistry,
renderers: renderersRegistry,
types: typesRegistry,
};
export { registries, typesRegistry, functionsRegistry, renderersRegistry };

View file

@ -18,8 +18,7 @@
*/
import Boom from 'boom';
import { serializeProvider } from '../../common/serialize';
import { API_ROUTE } from '../../common/constants';
import { serializeProvider, API_ROUTE } from '../../common';
import { createHandlers } from '../lib/create_handlers';
import Joi from 'joi';

View file

@ -17,126 +17,4 @@
* under the License.
*/
import { KnownTypeToString, TypeString, UnmappedTypeStrings } from './common';
/**
* This type represents all of the possible combinations of properties of an
* Argument in an Expression Function. The presence or absence of certain fields
* influence the shape and presence of others within each `arg` in the specification.
*/
export type ArgumentType<T> =
| SingleArgumentType<T>
| MultipleArgumentType<T>
| UnresolvedSingleArgumentType<T>
| UnresolvedMultipleArgumentType<T>;
/**
* Map the type within the the generic array to a string-based
* representation of the type.
*/
// prettier-ignore
type ArrayTypeToArgumentString<T> =
T extends Array<infer ElementType> ? TypeString<ElementType> :
T extends null ? 'null' :
never;
/**
* Map the return type of the function within the generic to a
* string-based representation of the return type.
*/
// prettier-ignore
type UnresolvedTypeToArgumentString<T> =
T extends (...args: any) => infer ElementType ? TypeString<ElementType> :
T extends null ? 'null' :
never;
/**
* Map the array-based return type of the function within the generic to a
* string-based representation of the return type.
*/
// prettier-ignore
type UnresolvedArrayTypeToArgumentString<T> =
T extends Array<(...args: any) => infer ElementType> ? TypeString<ElementType> :
T extends (...args: any) => infer ElementType ? ArrayTypeToArgumentString<ElementType> :
T extends null ? 'null' :
never;
/** A type containing properties common to all Function Arguments. */
interface BaseArgumentType<T> {
/** Alternate names for the Function valid for use in the Expression Editor */
aliases?: string[];
/** Help text for the Argument to be displayed in the Expression Editor */
help: string;
/** Default options for the Argument */
options?: T[];
/**
* Is this Argument required?
* @default false
*/
required?: boolean;
/**
* If false, the Argument is supplied as a function to be invoked in the
* implementation, rather than a value.
* @default true
*/
resolve?: boolean;
/** Names of types that are valid values of the Argument. */
types?: string[];
/** The optional default value of the Argument. */
default?: T | string;
/**
* If true, multiple values may be supplied to the Argument.
* @default false
*/
multi?: boolean;
}
/**
* The `types` array in a `FunctionSpec` should contain string
* representations of the `ArgumentsSpec` types:
*
* `someArgument: boolean | string` results in `types: ['boolean', 'string']`
*/
type SingleArgumentType<T> = BaseArgumentType<T> & {
multi?: false;
resolve?: true;
types?: Array<KnownTypeToString<T> | UnmappedTypeStrings>;
};
/**
* If the `multi` property on the argument is true, the `types` array should
* contain string representations of the `ArgumentsSpec` array types:
*
* `someArgument: boolean[] | string[]` results in: `types: ['boolean', 'string']`
*/
type MultipleArgumentType<T> = BaseArgumentType<T> & {
multi: true;
resolve?: true;
types?: Array<ArrayTypeToArgumentString<T> | UnmappedTypeStrings>;
};
/**
* If the `resolve` property on the arugument is false, the `types` array, if
* present, should contain string representations of the result of the argument
* function:
*
* `someArgument: () => string` results in `types: ['string']`
*/
type UnresolvedSingleArgumentType<T> = BaseArgumentType<T> & {
multi?: false;
resolve: false;
types?: Array<UnresolvedTypeToArgumentString<T> | UnmappedTypeStrings>;
};
/**
* If the `resolve` property on the arugument is false, the `types` array, if
* present, should contain string representations of the result of the argument
* function:
*
* `someArgument: () => string[]` results in `types: ['string']`
*/
type UnresolvedMultipleArgumentType<T> = BaseArgumentType<T> & {
multi: true;
resolve: false;
types?: Array<UnresolvedArrayTypeToArgumentString<T> | UnmappedTypeStrings>;
};
export * from '../../../../plugins/data/common/expressions/types/arguments';

View file

@ -17,44 +17,4 @@
* under the License.
*/
/**
* This can convert a type into a known Expression string representation of
* that type. For example, `TypeToString<Datatable>` will resolve to `'datatable'`.
* This allows Expression Functions to continue to specify their type in a
* simple string format.
*/
export type TypeToString<T> = KnownTypeToString<T> | UnmappedTypeStrings;
/**
* Map the type of the generic to a string-based representation of the type.
*
* If the provided generic is its own type interface, we use the value of
* the `type` key as a string literal type for it.
*/
// prettier-ignore
export type KnownTypeToString<T> =
T extends string ? 'string' :
T extends boolean ? 'boolean' :
T extends number ? 'number' :
T extends null ? 'null' :
T extends { type: string } ? T['type'] :
never;
/**
* If the type extends a Promise, we still need to return the string representation:
*
* `someArgument: Promise<boolean | string>` results in `types: ['boolean', 'string']`
*/
export type TypeString<T> = KnownTypeToString<UnwrapPromise<T>>;
/**
* Types used in Expressions that don't map to a primitive cleanly:
*
* `date` is typed as a number or string, and represents a date
*/
export type UnmappedTypeStrings = 'date' | 'filter';
/**
* Utility type: extracts returned type from a Promise.
*/
export type UnwrapPromise<T> = T extends Promise<infer P> ? P : T;
export * from '../../../../plugins/data/common/expressions/types/common';

View file

@ -17,31 +17,4 @@
* under the License.
*/
import { ArgumentType } from './arguments';
import { TypeToString, UnwrapPromise } from './common';
/**
* A generic type which represents an Expression Function definition.
*/
export interface ExpressionFunction<Name extends string, Context, Arguments, Return> {
/** Arguments for the Function */
args: { [key in keyof Arguments]: ArgumentType<Arguments[key]> };
aliases?: string[];
context?: {
types: Array<TypeToString<Context>>;
};
/** Help text displayed in the Expression editor */
help: string;
/** The name of the Function */
name: Name;
/** The type of the Function */
type?: TypeToString<UnwrapPromise<Return>>;
/** The implementation of the Function */
fn(context: Context, args: Arguments, handlers: FunctionHandlers): Return;
}
// TODO: Handlers can be passed to the `fn` property of the Function. At the moment, these Functions
// are not strongly defined.
interface FunctionHandlers {
[key: string]: (...args: any) => any;
}
export * from '../../../../plugins/data/common/expressions/types/functions';

View file

@ -17,14 +17,4 @@
* under the License.
*/
export { ArgumentType } from './arguments';
export {
TypeToString,
KnownTypeToString,
TypeString,
UnmappedTypeStrings,
UnwrapPromise,
} from './common';
export { ExpressionFunction } from './functions';
export { ExpressionType } from './types';
export * from '../common/types';
export * from '../../../../plugins/data/common';

View file

@ -17,17 +17,4 @@
* under the License.
*/
/**
* A generic type which represents a custom Expression Type Definition that's
* registered to the Interpreter.
*/
export interface ExpressionType<Name extends string, Type, SerializedType = undefined> {
name: Name;
validate?: (type: any) => void | Error;
serialize?: (type: Type) => SerializedType;
deserialize?: (type: SerializedType) => Type;
// TODO: Update typings for the `availableTypes` parameter once interfaces for this
// have been added elsewhere in the interpreter.
from?: Record<string, (ctx: any, availableTypes: Record<string, any>) => Type>;
to?: Record<string, (type: Type, availableTypes: Record<string, any>) => unknown>;
}
export * from '../../../../plugins/data/common/expressions/types/types';

View file

@ -20,6 +20,8 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { kibanaPie } from './pie_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
const mockResponseHandler = jest.fn().mockReturnValue(Promise.resolve({
hits: 1,
names: ['Count'],

View file

@ -20,6 +20,8 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { kibanaMarkdown } from './markdown_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
describe('interpreter/functions#markdown', () => {
const fn = functionWrapper(kibanaMarkdown);
const args = {

View file

@ -20,6 +20,8 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { metric } from './metric_vis_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
describe('interpreter/functions#metric', () => {
const fn = functionWrapper(metric);
const context = {

View file

@ -20,6 +20,8 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { regionmap } from './region_map_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
describe('interpreter/functions#regionmap', () => {
const fn = functionWrapper(regionmap);
const context = {

View file

@ -20,6 +20,8 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { kibanaTable } from './table_vis_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
const mockResponseHandler = jest.fn().mockReturnValue(Promise.resolve({
tables: [{ columns: [], rows: [] }],
}));

View file

@ -20,6 +20,8 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { tagcloud } from './tag_cloud_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
describe('interpreter/functions#tagcloud', () => {
const fn = functionWrapper(tagcloud);
const context = {

View file

@ -21,6 +21,7 @@ import { createReadStream } from 'fs';
import globby from 'globby';
import MultiStream from 'multistream';
import webpackMerge from 'webpack-merge';
import { fromRoot } from '../../../legacy/utils';
import { replacePlaceholder } from '../../../optimize/public_path_placeholder';
@ -94,7 +95,7 @@ export default (kibana) => {
uiBundles.addPostLoader({
test: /\.js$/,
exclude: /[\/\\](__tests__|node_modules|bower_components|webpackShims)[\/\\]/,
loader: 'istanbul-instrumenter-loader'
loader: 'istanbul-instrumenter-loader',
});
}
@ -102,6 +103,13 @@ export default (kibana) => {
id: 'tests',
modules: [...modules],
template: createTestEntryTemplate(uiSettingDefaults),
extendConfig(webpackConfig) {
return webpackMerge({
resolve: {
extensions: ['.karma_mock.js', '.karma_mock.tsx', '.karma_mock.ts']
}
}, webpackConfig);
}
});
kbnServer.server.route({

View file

@ -20,6 +20,7 @@
import { functionWrapper } from '../../interpreter/test_helpers';
import { tilemap } from './tilemap_fn';
jest.mock('ui/new_platform', () => require('../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor());
jest.mock('ui/vis/map/convert_to_geojson', () => ({
convertToGeoJson: jest.fn().mockReturnValue({
featureCollection: {

View file

@ -0,0 +1,68 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export const mockNewPlatformBackdoor = () => {
return {
__setup__: () => {},
__start__: () => {},
npSetup: {
core: {
i18n: {
Context: {},
},
chrome: {
recentlyAccessed: false,
},
},
plugins: {
data: {
expressions: {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
functions: {
register: () => {},
},
renderers: {
register: () => {},
},
types: {
register: () => {},
},
},
},
},
},
},
npStart: {
core: {
uiSettings: {
get: () => true,
},
chrome: {
recentlyAccessed: false,
},
i18n: {
Context: () => {},
},
},
plugins: {
data: {},
},
},
};
};

View file

@ -17,15 +17,24 @@
* under the License.
*/
import { InternalCoreSetup, InternalCoreStart } from '../../../../core/public';
import { Plugin as DataPlugin } from '../../../../plugins/data/public';
export interface PluginsSetup {
data: ReturnType<DataPlugin['setup']>;
}
export interface PluginsStart {
data: ReturnType<DataPlugin['start']>;
}
export const npSetup = {
core: (null as unknown) as InternalCoreSetup,
plugins: {} as Record<string, unknown>,
plugins: {} as PluginsSetup,
};
export const npStart = {
core: (null as unknown) as InternalCoreStart,
plugins: {} as Record<string, unknown>,
plugins: {} as PluginsStart,
};
/**
@ -34,15 +43,17 @@ export const npStart = {
*/
export function __reset__() {
npSetup.core = (null as unknown) as InternalCoreSetup;
npSetup.plugins = {} as any;
npStart.core = (null as unknown) as InternalCoreStart;
npStart.plugins = {} as any;
}
export function __setup__(coreSetup: InternalCoreSetup, plugins: Record<string, unknown>) {
export function __setup__(coreSetup: InternalCoreSetup, plugins: PluginsSetup) {
npSetup.core = coreSetup;
npSetup.plugins = plugins;
}
export function __start__(coreStart: InternalCoreStart, plugins: Record<string, unknown>) {
export function __start__(coreStart: InternalCoreStart, plugins: PluginsStart) {
npStart.core = coreStart;
npStart.plugins = plugins;
}

View file

@ -26,25 +26,9 @@ import { VisResponseData } from './types';
import { Inspector } from '../../inspector';
import { EmbeddedVisualizeHandler } from './embedded_visualize_handler';
jest.mock('ui/new_platform', () => ({
npStart: {
core: {
i18n: {
Context: {},
},
chrome: {
recentlyAccessed: false,
},
},
},
npSetup: {
core: {
uiSettings: {
get: () => true,
},
},
},
}));
jest.mock('ui/new_platform', () =>
require('../../../../ui/public/new_platform/index.test.mocks').mockNewPlatformBackdoor()
);
describe('EmbeddedVisualizeHandler', () => {
let handler: any;

View file

@ -32,12 +32,14 @@ export class UiBundle {
modules,
template,
controller,
extendConfig,
} = options;
this._id = id;
this._modules = modules;
this._template = template;
this._controller = controller;
this._extendConfig = extendConfig;
}
getId() {
@ -126,4 +128,12 @@ export class UiBundle {
outputPath: this.getOutputPath()
};
}
getExtendedConfig(webpackConfig) {
if (!this._extendConfig) {
return webpackConfig;
}
return this._extendConfig(webpackConfig);
}
}

View file

@ -96,6 +96,7 @@ export class UiBundlesController {
id,
modules,
template,
extendConfig,
} = bundleSpec;
if (this._filter.test(id)) {
@ -104,6 +105,7 @@ export class UiBundlesController {
modules,
template,
controller: this,
extendConfig,
}));
}
}
@ -223,4 +225,8 @@ export class UiBundlesController {
return this._bundles
.map(bundle => bundle.getId());
}
getExtendedConfig(webpackConfig) {
return this._bundles.reduce((acc, bundle) => bundle.getExtendedConfig(acc), webpackConfig);
}
}

View file

@ -472,14 +472,16 @@ export default class BaseOptimizer {
}
};
return webpackMerge(
commonConfig,
IS_KIBANA_DISTRIBUTABLE
? isDistributableConfig
: {},
this.uiBundles.isDevMode()
? webpackMerge(watchingConfig, supportEnzymeConfig)
: productionConfig
return this.uiBundles.getExtendedConfig(
webpackMerge(
commonConfig,
IS_KIBANA_DISTRIBUTABLE
? isDistributableConfig
: {},
this.uiBundles.isDevMode()
? webpackMerge(watchingConfig, supportEnzymeConfig)
: productionConfig
)
);
}

View file

@ -0,0 +1,9 @@
# data
`data` plugin provides common data access services.
- `expressions` &mdash; run pipeline functions and render results.
- `filter`
- `index_patterns`
- `query`
- `search`

View file

@ -17,7 +17,7 @@
* under the License.
*/
export const createError = err => ({
export const createError = (err: any) => ({
type: 'error',
error: {
stack: process.env.NODE_ENV === 'production' ? undefined : err.stack,

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { Datatable } from './datatable';
import { Render } from './render';

View file

@ -19,7 +19,7 @@
import { map, pick, zipObject } from 'lodash';
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { PointSeries } from './pointseries';
import { Render } from './render';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { Render } from './render';
const name = 'error';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
const name = 'filter';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { Render } from './render';
const name = 'image';

View file

@ -17,12 +17,16 @@
* under the License.
*/
import { Query } from 'src/legacy/core_plugins/data/public';
import { TimeRange } from 'ui/timefilter/time_history';
import { Filter } from '@kbn/es-query';
import { Query } from '../../query/types';
const name = 'kibana_context';
interface TimeRange {
from: string;
to: string;
}
export interface KibanaContext {
type: typeof name;
query?: Query;

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
const name = 'null';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { Datatable } from './datatable';
import { Render } from './render';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { Datatable } from './datatable';
import { Render } from './render';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType, Render } from '../../types';
import { ExpressionType, Render } from '../../../common/expressions/types';
const name = 'range';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
const name = 'render';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { Render } from './render';
const name = 'shape';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
import { Datatable } from './datatable';
import { Render } from './render';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ExpressionType } from '../../types';
import { ExpressionType } from '../types';
const name = 'style';

View file

@ -17,4 +17,4 @@
* under the License.
*/
export const API_ROUTE = '/api/interpreter';
export * from './types';

View file

@ -17,13 +17,18 @@
* under the License.
*/
// All types must be universal and be castable on the client or on the server
import { get } from 'lodash';
import { getType } from '@kbn/interpreter/common';
// TODO: Currently all casting functions must be syncronous.
function getType(node: any) {
if (node == null) return 'null';
if (typeof node === 'object') {
if (!node.type) throw new Error('Objects must have a type property');
return node.type;
}
return typeof node;
}
export function Type(config) {
export function Type(this: any, config: any) {
// Required
this.name = config.name;
@ -40,28 +45,27 @@ export function Type(config) {
this.serialize = config.serialize;
this.deserialize = config.deserialize;
const getToFn = type => get(config, ['to', type]) || get(config, ['to', '*']);
const getFromFn = type => get(config, ['from', type]) || get(config, ['from', '*']);
const getToFn = (type: any) => get(config, ['to', type]) || get(config, ['to', '*']);
const getFromFn = (type: any) => get(config, ['from', type]) || get(config, ['from', '*']);
this.castsTo = type => typeof getToFn(type) === 'function';
this.castsFrom = type => typeof getFromFn(type) === 'function';
this.castsTo = (type: any) => typeof getToFn(type) === 'function';
this.castsFrom = (type: any) => typeof getFromFn(type) === 'function';
this.to = (node, toTypeName, types) => {
this.to = (node: any, toTypeName: any, types: any) => {
const typeName = getType(node);
if (typeName !== this.name) {
throw new Error(`Can not cast object of type '${typeName}' using '${this.name}'`);
}
else if (!this.castsTo(toTypeName)) {
} else if (!this.castsTo(toTypeName)) {
throw new Error(`Can not cast '${typeName}' to '${toTypeName}'`);
}
return getToFn(toTypeName)(node, types);
return (getToFn(toTypeName) as any)(node, types);
};
this.from = (node, types) => {
this.from = (node: any, types: any) => {
const typeName = getType(node);
if (!this.castsFrom(typeName)) throw new Error(`Can not cast '${this.name}' from ${typeName}`);
return getFromFn(typeName)(node, types);
return (getFromFn(typeName) as any)(node, types);
};
}

View file

@ -17,19 +17,25 @@
* under the License.
*/
import clone from 'lodash.clone';
import { each, keys, last, mapValues, reduce, zipObject } from 'lodash';
import { getType, fromExpression, getByAlias, castProvider } from '@kbn/interpreter/common';
import { createError } from './create_error';
/* eslint-disable @typescript-eslint/no-var-requires */
import { clone, each, keys, last, mapValues, reduce, zipObject } from 'lodash';
export function interpreterProvider(config) {
// @ts-ignore
import { fromExpression, getType, getByAlias, castProvider } from '@kbn/interpreter/common';
import { createError } from './create_error';
import { ExpressionAST } from './types';
export { createError };
export function interpreterProvider(config: any) {
const { functions, types } = config;
const handlers = { ...config.handlers, types };
const cast = castProvider(types);
return interpret;
async function interpret(node, context = null) {
async function interpret(node: ExpressionAST, context = null) {
switch (getType(node)) {
case 'expression':
return invokeChain(node.chain, context);
@ -43,7 +49,7 @@ export function interpreterProvider(config) {
}
}
async function invokeChain(chainArr, context) {
async function invokeChain(chainArr: any, context: any): Promise<any> {
if (!chainArr.length) return Promise.resolve(context);
const chain = clone(chainArr);
@ -75,7 +81,7 @@ export function interpreterProvider(config) {
}
}
async function invokeFunction(fnDef, context, args) {
async function invokeFunction(fnDef: any, context: any, args: any): Promise<any> {
// Check function input.
const acceptableContext = cast(context, fnDef.context.types);
const fnOutput = await fnDef.fn(acceptableContext, args, handlers);
@ -105,23 +111,23 @@ export function interpreterProvider(config) {
}
// Processes the multi-valued AST argument values into arguments that can be passed to the function
async function resolveArgs(fnDef, context, argAsts) {
async function resolveArgs(fnDef: any, context: any, argAsts: any): Promise<any> {
const argDefs = fnDef.args;
// Use the non-alias name from the argument definition
const dealiasedArgAsts = reduce(
argAsts,
(argAsts, argAst, argName) => {
(acc, argAst, argName) => {
const argDef = getByAlias(argDefs, argName);
// TODO: Implement a system to allow for undeclared arguments
if (!argDef) {
throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`);
}
argAsts[argDef.name] = (argAsts[argDef.name] || []).concat(argAst);
return argAsts;
acc[argDef.name] = (acc[argDef.name] || []).concat(argAst);
return acc;
},
{}
{} as any
);
// Check for missing required arguments
@ -144,25 +150,25 @@ export function interpreterProvider(config) {
// Fill in default values from argument definition
const argAstsWithDefaults = reduce(
argDefs,
(argAsts, argDef, argName) => {
if (typeof argAsts[argName] === 'undefined' && typeof argDef.default !== 'undefined') {
argAsts[argName] = [fromExpression(argDef.default, 'argument')];
(acc: any, argDef: any, argName: any) => {
if (typeof acc[argName] === 'undefined' && typeof argDef.default !== 'undefined') {
acc[argName] = [(fromExpression as any)(argDef.default, 'argument')];
}
return argAsts;
return acc;
},
dealiasedArgAsts
);
// Create the functions to resolve the argument ASTs into values
// These are what are passed to the actual functions if you opt out of resolving
const resolveArgFns = mapValues(argAstsWithDefaults, (argAsts, argName) => {
return argAsts.map(argAst => {
const resolveArgFns = mapValues(argAstsWithDefaults, (asts, argName) => {
return asts.map((item: any) => {
return async (ctx = context) => {
const newContext = await interpret(argAst, ctx);
const newContext = await interpret(item, ctx);
// This is why when any sub-expression errors, the entire thing errors
if (getType(newContext) === 'error') throw newContext.error;
return cast(newContext, argDefs[argName].types);
return cast(newContext, argDefs[argName as any].types);
};
});
});
@ -174,7 +180,7 @@ export function interpreterProvider(config) {
argNames.map(argName => {
const interpretFns = resolveArgFns[argName];
if (!argDefs[argName].resolve) return interpretFns;
return Promise.all(interpretFns.map(fn => fn()));
return Promise.all(interpretFns.map((fn: any) => fn()));
})
);
@ -182,8 +188,8 @@ export function interpreterProvider(config) {
// Just return the last unless the argument definition allows multiple
const resolvedArgs = mapValues(resolvedMultiArgs, (argValues, argName) => {
if (argDefs[argName].multi) return argValues;
return last(argValues);
if (argDefs[argName as any].multi) return argValues;
return last(argValues as any);
});
// Return an object here because the arguments themselves might actually have a 'then'

View file

@ -18,19 +18,27 @@
*/
import { get, identity } from 'lodash';
import { getType } from '@kbn/interpreter/common';
export function serializeProvider(types) {
export function getType(node: any) {
if (node == null) return 'null';
if (typeof node === 'object') {
if (!node.type) throw new Error('Objects must have a type property');
return node.type;
}
return typeof node;
}
export function serializeProvider(types: any) {
return {
serialize: provider('serialize'),
deserialize: provider('deserialize'),
};
function provider(key) {
return context => {
function provider(key: any) {
return (context: any) => {
const type = getType(context);
const typeDef = types[type];
const fn = get(typeDef, key) || identity;
const fn: any = get(typeDef, key) || identity;
return fn(context);
};
}

View file

@ -0,0 +1,142 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { KnownTypeToString, TypeString, UnmappedTypeStrings } from './common';
/**
* This type represents all of the possible combinations of properties of an
* Argument in an Expression Function. The presence or absence of certain fields
* influence the shape and presence of others within each `arg` in the specification.
*/
export type ArgumentType<T> =
| SingleArgumentType<T>
| MultipleArgumentType<T>
| UnresolvedSingleArgumentType<T>
| UnresolvedMultipleArgumentType<T>;
/**
* Map the type within the the generic array to a string-based
* representation of the type.
*/
// prettier-ignore
type ArrayTypeToArgumentString<T> =
T extends Array<infer ElementType> ? TypeString<ElementType> :
T extends null ? 'null' :
never;
/**
* Map the return type of the function within the generic to a
* string-based representation of the return type.
*/
// prettier-ignore
type UnresolvedTypeToArgumentString<T> =
T extends (...args: any) => infer ElementType ? TypeString<ElementType> :
T extends null ? 'null' :
never;
/**
* Map the array-based return type of the function within the generic to a
* string-based representation of the return type.
*/
// prettier-ignore
type UnresolvedArrayTypeToArgumentString<T> =
T extends Array<(...args: any) => infer ElementType> ? TypeString<ElementType> :
T extends (...args: any) => infer ElementType ? ArrayTypeToArgumentString<ElementType> :
T extends null ? 'null' :
never;
/** A type containing properties common to all Function Arguments. */
interface BaseArgumentType<T> {
/** Alternate names for the Function valid for use in the Expression Editor */
aliases?: string[];
/** Help text for the Argument to be displayed in the Expression Editor */
help: string;
/** Default options for the Argument */
options?: T[];
/**
* Is this Argument required?
* @default false
*/
required?: boolean;
/**
* If false, the Argument is supplied as a function to be invoked in the
* implementation, rather than a value.
* @default true
*/
resolve?: boolean;
/** Names of types that are valid values of the Argument. */
types?: string[];
/** The optional default value of the Argument. */
default?: T | string;
/**
* If true, multiple values may be supplied to the Argument.
* @default false
*/
multi?: boolean;
}
/**
* The `types` array in a `FunctionSpec` should contain string
* representations of the `ArgumentsSpec` types:
*
* `someArgument: boolean | string` results in `types: ['boolean', 'string']`
*/
type SingleArgumentType<T> = BaseArgumentType<T> & {
multi?: false;
resolve?: true;
types?: Array<KnownTypeToString<T> | UnmappedTypeStrings>;
};
/**
* If the `multi` property on the argument is true, the `types` array should
* contain string representations of the `ArgumentsSpec` array types:
*
* `someArgument: boolean[] | string[]` results in: `types: ['boolean', 'string']`
*/
type MultipleArgumentType<T> = BaseArgumentType<T> & {
multi: true;
resolve?: true;
types?: Array<ArrayTypeToArgumentString<T> | UnmappedTypeStrings>;
};
/**
* If the `resolve` property on the arugument is false, the `types` array, if
* present, should contain string representations of the result of the argument
* function:
*
* `someArgument: () => string` results in `types: ['string']`
*/
type UnresolvedSingleArgumentType<T> = BaseArgumentType<T> & {
multi?: false;
resolve: false;
types?: Array<UnresolvedTypeToArgumentString<T> | UnmappedTypeStrings>;
};
/**
* If the `resolve` property on the arugument is false, the `types` array, if
* present, should contain string representations of the result of the argument
* function:
*
* `someArgument: () => string[]` results in `types: ['string']`
*/
type UnresolvedMultipleArgumentType<T> = BaseArgumentType<T> & {
multi: true;
resolve: false;
types?: Array<UnresolvedArrayTypeToArgumentString<T> | UnmappedTypeStrings>;
};

View file

@ -0,0 +1,60 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* This can convert a type into a known Expression string representation of
* that type. For example, `TypeToString<Datatable>` will resolve to `'datatable'`.
* This allows Expression Functions to continue to specify their type in a
* simple string format.
*/
export type TypeToString<T> = KnownTypeToString<T> | UnmappedTypeStrings;
/**
* Map the type of the generic to a string-based representation of the type.
*
* If the provided generic is its own type interface, we use the value of
* the `type` key as a string literal type for it.
*/
// prettier-ignore
export type KnownTypeToString<T> =
T extends string ? 'string' :
T extends boolean ? 'boolean' :
T extends number ? 'number' :
T extends null ? 'null' :
T extends { type: string } ? T['type'] :
never;
/**
* If the type extends a Promise, we still need to return the string representation:
*
* `someArgument: Promise<boolean | string>` results in `types: ['boolean', 'string']`
*/
export type TypeString<T> = KnownTypeToString<UnwrapPromise<T>>;
/**
* Types used in Expressions that don't map to a primitive cleanly:
*
* `date` is typed as a number or string, and represents a date
*/
export type UnmappedTypeStrings = 'date' | 'filter';
/**
* Utility type: extracts returned type from a Promise.
*/
export type UnwrapPromise<T> = T extends Promise<infer P> ? P : T;

View file

@ -0,0 +1,47 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ArgumentType } from './arguments';
import { TypeToString, UnwrapPromise } from './common';
/**
* A generic type which represents an Expression Function definition.
*/
export interface ExpressionFunction<Name extends string, Context, Arguments, Return> {
/** Arguments for the Function */
args: { [key in keyof Arguments]: ArgumentType<Arguments[key]> };
aliases?: string[];
context?: {
types: Array<TypeToString<Context>>;
};
/** Help text displayed in the Expression editor */
help: string;
/** The name of the Function */
name: Name;
/** The type of the Function */
type?: TypeToString<UnwrapPromise<Return>>;
/** The implementation of the Function */
fn(context: Context, args: Arguments, handlers: FunctionHandlers): Return;
}
// TODO: Handlers can be passed to the `fn` property of the Function. At the moment, these Functions
// are not strongly defined.
interface FunctionHandlers {
[key: string]: (...args: any) => any;
}

View file

@ -0,0 +1,45 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { ArgumentType } from './arguments';
export {
TypeToString,
KnownTypeToString,
TypeString,
UnmappedTypeStrings,
UnwrapPromise,
} from './common';
export { ExpressionFunction } from './functions';
export { ExpressionType } from './types';
export * from '../expression_types';
export type ExpressionArgAST = string | boolean | number | ExpressionAST;
export interface ExpressionFunctionAST {
type: 'function';
function: string;
arguments: {
[key: string]: ExpressionArgAST[];
};
}
export interface ExpressionAST {
type: 'expression';
chain: ExpressionFunctionAST[];
}

View file

@ -0,0 +1,33 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* A generic type which represents a custom Expression Type Definition that's
* registered to the Interpreter.
*/
export interface ExpressionType<Name extends string, Type, SerializedType = undefined> {
name: Name;
validate?: (type: any) => void | Error;
serialize?: (type: Type) => SerializedType;
deserialize?: (type: SerializedType) => Type;
// TODO: Update typings for the `availableTypes` parameter once interfaces for this
// have been added elsewhere in the interpreter.
from?: Record<string, (ctx: any, availableTypes: Record<string, any>) => Type>;
to?: Record<string, (type: Type, availableTypes: Record<string, any>) => unknown>;
}

View file

@ -17,6 +17,4 @@
* under the License.
*/
import { TypesRegistry } from '../../common/types_registry';
export const typesRegistry = new TypesRegistry();
export * from './expressions';

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { FunctionsRegistry } from '../../common/functions_registry';
export const functionsRegistry = new FunctionsRegistry();
export interface Query {
query: string | { [key: string]: any };
language: string;
}

View file

@ -0,0 +1,6 @@
{
"id": "data",
"version": "8",
"server": true,
"ui": true
}

View file

@ -0,0 +1,48 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './interpreter';
import { ExpressionType } from '../../common/expressions/types';
export class ExpressionsService {
private readonly functions = new FunctionsRegistry();
private readonly renderers = new RenderFunctionsRegistry();
private readonly types = new TypesRegistry();
public setup() {
const { functions, renderers, types } = this;
return {
registerFunction: (fn: any) => {
this.functions.register(fn);
},
registerRenderer: (renderer: any) => {
this.renderers.register(renderer);
},
registerType: (type: () => ExpressionType<any, any>) => {
this.types.register(type);
},
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
functions,
renderers,
types,
},
};
}
}

View file

@ -0,0 +1,173 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* @todo
* This whole file needs major refactoring. `Registry` class does not do anything
* useful. "Wrappers" like `RenderFunction` basically just set default props on the objects.
*/
/* eslint-disable max-classes-per-file */
import { clone, mapValues, includes } from 'lodash';
import { Type } from '../../common/expressions/interpreter';
import { ExpressionType } from '../../common/expressions/types';
export class Registry<ItemSpec, Item> {
_prop: string;
_indexed: any;
constructor(prop: string = 'name') {
if (typeof prop !== 'string') throw new Error('Registry property name must be a string');
this._prop = prop;
this._indexed = new Object();
}
wrapper(obj: ItemSpec) {
return obj;
}
register(fn: () => ItemSpec) {
if (typeof fn !== 'function') throw new Error(`Register requires an function`);
const obj = fn() as any;
if (typeof obj !== 'object' || !obj[this._prop]) {
throw new Error(`Registered functions must return an object with a ${this._prop} property`);
}
this._indexed[obj[this._prop].toLowerCase()] = this.wrapper(obj);
}
toJS(): Record<string, any> {
return Object.keys(this._indexed).reduce(
(acc, key) => {
acc[key] = this.get(key);
return acc;
},
{} as any
);
}
toArray(): Item[] {
return Object.keys(this._indexed).map(key => this.get(key)!);
}
get(name: string): Item | null {
if (name === undefined) {
return null;
}
const lowerCaseName = name.toLowerCase();
return this._indexed[lowerCaseName] ? clone(this._indexed[lowerCaseName]) : null;
}
getProp(): string {
return this._prop;
}
reset() {
this._indexed = new Object();
}
}
function RenderFunction(this: any, config: any) {
// This must match the name of the function that is used to create the `type: render` object
this.name = config.name;
// Use this to set a more friendly name
this.displayName = config.displayName || this.name;
// A sentence or few about what this element does
this.help = config.help;
// used to validate the data before calling the render function
this.validate = config.validate || function validate() {};
// tell the renderer if the dom node should be reused, it's recreated each time by default
this.reuseDomNode = Boolean(config.reuseDomNode);
// the function called to render the data
this.render =
config.render ||
function render(domNode: any, data: any, done: any) {
done();
};
}
export function Arg(this: any, config: any) {
if (config.name === '_') throw Error('Arg names must not be _. Use it in aliases instead.');
this.name = config.name;
this.required = config.required || false;
this.help = config.help || '';
this.types = config.types || [];
this.default = config.default;
this.aliases = config.aliases || [];
this.multi = config.multi == null ? false : config.multi;
this.resolve = config.resolve == null ? true : config.resolve;
this.options = config.options || [];
this.accepts = (type: any) => {
if (!this.types.length) return true;
return includes(config.types, type);
};
}
export function Fn(this: any, config: any) {
// Required
this.name = config.name; // Name of function
// Return type of function.
// This SHOULD be supplied. We use it for UI and autocomplete hinting,
// We may also use it for optimizations in the future.
this.type = config.type;
this.aliases = config.aliases || [];
// Function to run function (context, args)
this.fn = (...args: any) => Promise.resolve(config.fn(...args));
// Optional
this.help = config.help || ''; // A short help text
this.args = mapValues(
config.args || {},
(arg: any, name: any) => new (Arg as any)({ name, ...arg })
);
this.context = config.context || {};
this.accepts = (type: any) => {
if (!this.context.types) return true; // If you don't tell us about context, we'll assume you don't care what you get
return includes(this.context.types, type); // Otherwise, check it
};
}
export class RenderFunctionsRegistry extends Registry<any, any> {
wrapper(obj: any) {
return new (RenderFunction as any)(obj);
}
}
export class FunctionsRegistry extends Registry<any, any> {
wrapper(obj: any) {
return new (Fn as any)(obj);
}
}
export class TypesRegistry extends Registry<ExpressionType<any, any>, any> {
wrapper(obj: any) {
return new (Type as any)(obj);
}
}

View file

@ -17,10 +17,11 @@
* under the License.
*/
import { Fn, Registry } from '@kbn/interpreter/common';
import { PluginInitializerContext } from '../../../core/public';
import { DataPublicPlugin } from './plugin';
export class FunctionsRegistry extends Registry {
wrapper(obj) {
return new Fn(obj);
}
export function plugin(initializerContext: PluginInitializerContext) {
return new DataPublicPlugin(initializerContext);
}
export { DataPublicPlugin as Plugin };

View file

@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsService } from './expressions/expressions_service';
export class DataPublicPlugin implements Plugin<{}> {
expressions = new ExpressionsService();
constructor(initializerContext: PluginInitializerContext) {}
public setup(core: CoreSetup) {
const expressions = this.expressions.setup();
return {
expressions,
};
}
public start(core: CoreStart) {}
public stop() {}
}

View file

@ -17,11 +17,11 @@
* under the License.
*/
import { Registry } from '@kbn/interpreter/common';
import { Type } from './type';
import { PluginInitializerContext } from '../../../core/server';
import { DataServerPlugin } from './plugin';
export class TypesRegistry extends Registry {
wrapper(obj) {
return new Type(obj);
}
export function plugin(initializerContext: PluginInitializerContext) {
return new DataServerPlugin(initializerContext);
}
export { DataServerPlugin as Plugin };

View file

@ -17,6 +17,13 @@
* under the License.
*/
import { RenderFunctionsRegistry } from '../lib/render_functions_registry';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/server';
export const renderersRegistry = new RenderFunctionsRegistry();
export class DataServerPlugin implements Plugin {
constructor(initializerContext: PluginInitializerContext) {}
public setup(core: CoreSetup) {}
public start(core: CoreStart) {}
public stop() {}
}
export { DataServerPlugin as Plugin };

View file

@ -32,6 +32,8 @@ export class TestbedPlugin implements Plugin<TestbedPluginSetup, TestbedPluginSt
// eslint-disable-next-line no-console
console.log(`Testbed plugin started`);
}
public stop() {}
}
export type TestbedPluginSetup = ReturnType<TestbedPlugin['setup']>;

View file

@ -29,6 +29,7 @@ export class CorePluginAPlugin implements Plugin<CorePluginAPluginSetup, CorePlu
}
public start() {}
public stop() {}
}
export type CorePluginAPluginSetup = ReturnType<CorePluginAPlugin['setup']>;

View file

@ -37,6 +37,7 @@ export class CorePluginBPlugin
}
public start() {}
public stop() {}
}
export type CorePluginBPluginSetup = ReturnType<CorePluginBPlugin['setup']>;