diff --git a/Jenkinsfile b/Jenkinsfile index ec0bc2327321..c0afcac731ff 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,6 +17,8 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'oss-ciGroup4': getOssCiGroupWorker(4), 'oss-ciGroup5': getOssCiGroupWorker(5), 'oss-ciGroup6': getOssCiGroupWorker(6), + ]), + 'kibana-oss-agent2': withWorkers('kibana-oss-tests2', { buildOss() }, [ 'oss-ciGroup7': getOssCiGroupWorker(7), 'oss-ciGroup8': getOssCiGroupWorker(8), 'oss-ciGroup9': getOssCiGroupWorker(9), @@ -32,6 +34,8 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'xpack-ciGroup3': getXpackCiGroupWorker(3), 'xpack-ciGroup4': getXpackCiGroupWorker(4), 'xpack-ciGroup5': getXpackCiGroupWorker(5), + ]), + 'kibana-xpack-agent2': withWorkers('kibana-xpack-tests2', { buildXpack() }, [ 'xpack-ciGroup6': getXpackCiGroupWorker(6), 'xpack-ciGroup7': getXpackCiGroupWorker(7), 'xpack-ciGroup8': getXpackCiGroupWorker(8), diff --git a/docs/development/core/public/kibana-plugin-public.applicationsetup.registermountcontext.md b/docs/development/core/public/kibana-plugin-public.applicationsetup.registermountcontext.md index 0b5bd8eeb36e..f264ba500ed6 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationsetup.registermountcontext.md +++ b/docs/development/core/public/kibana-plugin-public.applicationsetup.registermountcontext.md @@ -9,7 +9,7 @@ Register a context provider for application mounting. Will only be available to Signature: ```typescript -registerMountContext(contextName: T, provider: IContextProvider): void; +registerMountContext(contextName: T, provider: IContextProvider): void; ``` ## Parameters @@ -17,7 +17,7 @@ registerMountContext(contextName: T, provider: | Parameter | Type | Description | | --- | --- | --- | | contextName | T | The key of [AppMountContext](./kibana-plugin-public.appmountcontext.md) this provider's return value should be attached to. | -| provider | IContextProvider<AppMountContext, T> | A [IContextProvider](./kibana-plugin-public.icontextprovider.md) function | +| provider | IContextProvider<App['mount'], T> | A [IContextProvider](./kibana-plugin-public.icontextprovider.md) function | Returns: diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.registermountcontext.md b/docs/development/core/public/kibana-plugin-public.applicationstart.registermountcontext.md index fc86aaf658b6..62821fcbb92b 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationstart.registermountcontext.md +++ b/docs/development/core/public/kibana-plugin-public.applicationstart.registermountcontext.md @@ -9,7 +9,7 @@ Register a context provider for application mounting. Will only be available to Signature: ```typescript -registerMountContext(contextName: T, provider: IContextProvider): void; +registerMountContext(contextName: T, provider: IContextProvider): void; ``` ## Parameters @@ -17,7 +17,7 @@ registerMountContext(contextName: T, provider: | Parameter | Type | Description | | --- | --- | --- | | contextName | T | The key of [AppMountContext](./kibana-plugin-public.appmountcontext.md) this provider's return value should be attached to. | -| provider | IContextProvider<AppMountContext, T> | A [IContextProvider](./kibana-plugin-public.icontextprovider.md) function | +| provider | IContextProvider<App['mount'], T> | A [IContextProvider](./kibana-plugin-public.icontextprovider.md) function | Returns: diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md index 63b3ead814f0..f4dee0f29af3 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md @@ -18,5 +18,8 @@ core: { notifications: NotificationsStart; overlays: OverlayStart; uiSettings: UiSettingsClientContract; + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; }; ``` diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.md index c6541e3eca39..97d143d518f6 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.md @@ -16,5 +16,5 @@ export interface AppMountContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
uiSettings: UiSettingsClientContract;
} | Core service APIs available to mounted applications. | +| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
uiSettings: UiSettingsClientContract;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
} | Core service APIs available to mounted applications. | diff --git a/docs/development/core/public/kibana-plugin-public.contextsetup.createcontextcontainer.md b/docs/development/core/public/kibana-plugin-public.contextsetup.createcontextcontainer.md index 2644596354e3..5334eee84257 100644 --- a/docs/development/core/public/kibana-plugin-public.contextsetup.createcontextcontainer.md +++ b/docs/development/core/public/kibana-plugin-public.contextsetup.createcontextcontainer.md @@ -9,9 +9,9 @@ Creates a new [IContextContainer](./kibana-plugin-public.icontextcontainer.md) f Signature: ```typescript -createContextContainer(): IContextContainer; +createContextContainer>(): IContextContainer; ``` Returns: -`IContextContainer` +`IContextContainer` diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.injectedmetadata.md b/docs/development/core/public/kibana-plugin-public.coresetup.injectedmetadata.md new file mode 100644 index 000000000000..f9c1a283e380 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.coresetup.injectedmetadata.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreSetup](./kibana-plugin-public.coresetup.md) > [injectedMetadata](./kibana-plugin-public.coresetup.injectedmetadata.md) + +## CoreSetup.injectedMetadata property + +> Warning: This API is now obsolete. +> +> + +exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. + +Signature: + +```typescript +injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; +``` diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.md b/docs/development/core/public/kibana-plugin-public.coresetup.md index 9b94e2db5283..f9335425fed4 100644 --- a/docs/development/core/public/kibana-plugin-public.coresetup.md +++ b/docs/development/core/public/kibana-plugin-public.coresetup.md @@ -20,6 +20,7 @@ export interface CoreSetup | [context](./kibana-plugin-public.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-public.contextsetup.md) | | [fatalErrors](./kibana-plugin-public.coresetup.fatalerrors.md) | FatalErrorsSetup | [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | | [http](./kibana-plugin-public.coresetup.http.md) | HttpSetup | [HttpSetup](./kibana-plugin-public.httpsetup.md) | +| [injectedMetadata](./kibana-plugin-public.coresetup.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. | | [notifications](./kibana-plugin-public.coresetup.notifications.md) | NotificationsSetup | [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | | [uiSettings](./kibana-plugin-public.coresetup.uisettings.md) | UiSettingsClientContract | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-public.corestart.injectedmetadata.md b/docs/development/core/public/kibana-plugin-public.corestart.injectedmetadata.md new file mode 100644 index 000000000000..9224b97bc430 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.corestart.injectedmetadata.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreStart](./kibana-plugin-public.corestart.md) > [injectedMetadata](./kibana-plugin-public.corestart.injectedmetadata.md) + +## CoreStart.injectedMetadata property + +> Warning: This API is now obsolete. +> +> + +exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. + +Signature: + +```typescript +injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; +``` diff --git a/docs/development/core/public/kibana-plugin-public.corestart.md b/docs/development/core/public/kibana-plugin-public.corestart.md index 5c1626958c4d..47eba78bf43e 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.md @@ -21,6 +21,7 @@ export interface CoreStart | [docLinks](./kibana-plugin-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | | [http](./kibana-plugin-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-public.httpstart.md) | | [i18n](./kibana-plugin-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-public.i18nstart.md) | +| [injectedMetadata](./kibana-plugin-public.corestart.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. | | [notifications](./kibana-plugin-public.corestart.notifications.md) | NotificationsStart | [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | [overlays](./kibana-plugin-public.corestart.overlays.md) | OverlayStart | [OverlayStart](./kibana-plugin-public.overlaystart.md) | | [savedObjects](./kibana-plugin-public.corestart.savedobjects.md) | SavedObjectsStart | [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | diff --git a/docs/development/core/public/kibana-plugin-public.handlercontexttype.md b/docs/development/core/public/kibana-plugin-public.handlercontexttype.md new file mode 100644 index 000000000000..b083449d2b70 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.handlercontexttype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) + +## HandlerContextType type + +Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. + +Signature: + +```typescript +export declare type HandlerContextType> = T extends HandlerFunction ? U : never; +``` diff --git a/docs/development/core/public/kibana-plugin-public.handlerfunction.md b/docs/development/core/public/kibana-plugin-public.handlerfunction.md new file mode 100644 index 000000000000..98c342c17691 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.handlerfunction.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HandlerFunction](./kibana-plugin-public.handlerfunction.md) + +## HandlerFunction type + +A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) + +Signature: + +```typescript +export declare type HandlerFunction = (context: T, ...args: any[]) => any; +``` diff --git a/docs/development/core/public/kibana-plugin-public.handlerparameters.md b/docs/development/core/public/kibana-plugin-public.handlerparameters.md new file mode 100644 index 000000000000..f46c4b649e94 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.handlerparameters.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HandlerParameters](./kibana-plugin-public.handlerparameters.md) + +## HandlerParameters type + +Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). + +Signature: + +```typescript +export declare type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; +``` diff --git a/docs/development/core/public/kibana-plugin-public.icontextcontainer.createhandler.md b/docs/development/core/public/kibana-plugin-public.icontextcontainer.createhandler.md index 2a995df45757..af3b5e3fc2eb 100644 --- a/docs/development/core/public/kibana-plugin-public.icontextcontainer.createhandler.md +++ b/docs/development/core/public/kibana-plugin-public.icontextcontainer.createhandler.md @@ -9,7 +9,7 @@ Create a new handler function pre-wired to context for the plugin. Signature: ```typescript -createHandler(pluginOpaqueId: PluginOpaqueId, handler: IContextHandler): (...rest: THandlerParameters) => THandlerReturn extends Promise ? THandlerReturn : Promise; +createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; ``` ## Parameters @@ -17,11 +17,11 @@ createHandler(pluginOpaqueId: PluginOpaqueId, handler: IContextHandlerPluginOpaqueId | The plugin opaque ID for the plugin that registers this handler. | -| handler | IContextHandler<TContext, THandlerReturn, THandlerParameters> | Handler function to pass context object to. | +| handler | THandler | Handler function to pass context object to. | Returns: -`(...rest: THandlerParameters) => THandlerReturn extends Promise ? THandlerReturn : Promise` +`(...rest: HandlerParameters) => ShallowPromise>` A function that takes `THandlerParameters`, calls `handler` with a new context, and returns a Promise of the `handler` return value. diff --git a/docs/development/core/public/kibana-plugin-public.icontextcontainer.md b/docs/development/core/public/kibana-plugin-public.icontextcontainer.md index 0bc7c8f3808d..f16c07b3d790 100644 --- a/docs/development/core/public/kibana-plugin-public.icontextcontainer.md +++ b/docs/development/core/public/kibana-plugin-public.icontextcontainer.md @@ -9,7 +9,7 @@ An object that handles registration of context providers and configuring handler Signature: ```typescript -export interface IContextContainer +export interface IContextContainer> ``` ## Methods diff --git a/docs/development/core/public/kibana-plugin-public.icontextcontainer.registercontext.md b/docs/development/core/public/kibana-plugin-public.icontextcontainer.registercontext.md index 2cf10a6ec841..775f95bd7aff 100644 --- a/docs/development/core/public/kibana-plugin-public.icontextcontainer.registercontext.md +++ b/docs/development/core/public/kibana-plugin-public.icontextcontainer.registercontext.md @@ -9,7 +9,7 @@ Register a new context provider. Signature: ```typescript -registerContext(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; ``` ## Parameters @@ -18,7 +18,7 @@ registerContext(pluginOpaqueId: PluginOpaqu | --- | --- | --- | | pluginOpaqueId | PluginOpaqueId | The plugin opaque ID for the plugin that registers this context. | | contextName | TContextName | The key of the TContext object this provider supplies the value for. | -| provider | IContextProvider<TContext, TContextName, THandlerParameters> | A [IContextProvider](./kibana-plugin-public.icontextprovider.md) to be called each time a new context is created. | +| provider | IContextProvider<THandler, TContextName> | A [IContextProvider](./kibana-plugin-public.icontextprovider.md) to be called each time a new context is created. | Returns: diff --git a/docs/development/core/public/kibana-plugin-public.icontexthandler.md b/docs/development/core/public/kibana-plugin-public.icontexthandler.md deleted file mode 100644 index 2251b1131c31..000000000000 --- a/docs/development/core/public/kibana-plugin-public.icontexthandler.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IContextHandler](./kibana-plugin-public.icontexthandler.md) - -## IContextHandler type - -A function registered by a plugin to perform some action. - -Signature: - -```typescript -export declare type IContextHandler = (context: TContext, ...rest: THandlerParameters) => TReturn; -``` - -## Remarks - -A new `TContext` will be built for each handler before invoking. - diff --git a/docs/development/core/public/kibana-plugin-public.icontextprovider.md b/docs/development/core/public/kibana-plugin-public.icontextprovider.md index a84917d6e144..40f0ee3782f6 100644 --- a/docs/development/core/public/kibana-plugin-public.icontextprovider.md +++ b/docs/development/core/public/kibana-plugin-public.icontextprovider.md @@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type Signature: ```typescript -export declare type IContextProvider, TContextName extends keyof TContext, TProviderParameters extends any[] = []> = (context: Partial, ...rest: TProviderParameters) => Promise | TContext[TContextName]; +export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; ``` ## Remarks diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index e2ef807a7501..7531cf9a0633 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -91,11 +91,13 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AppUnmount](./kibana-plugin-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. | | [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | | | [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | | +| [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | +| [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | +| [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | | [HttpBody](./kibana-plugin-public.httpbody.md) | | | [HttpHandler](./kibana-plugin-public.httphandler.md) | | | [HttpSetup](./kibana-plugin-public.httpsetup.md) | | | [HttpStart](./kibana-plugin-public.httpstart.md) | | -| [IContextHandler](./kibana-plugin-public.icontexthandler.md) | A function registered by a plugin to perform some action. | | [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | | [OverlayBannerMount](./kibana-plugin-public.overlaybannermount.md) | A function that will mount the banner inside the provided element. | | [OverlayBannerUnmount](./kibana-plugin-public.overlaybannerunmount.md) | A function that will unmount the banner from the element. | diff --git a/docs/development/core/server/kibana-plugin-server.basepath.get.md b/docs/development/core/server/kibana-plugin-server.basepath.get.md index 2b3b6c899e8d..04feca7ccc5a 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.get.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.get.md @@ -9,5 +9,5 @@ returns `basePath` value, specific for an incoming request. Signature: ```typescript -get: (request: KibanaRequest | LegacyRequest) => string; +get: (request: LegacyRequest | KibanaRequest) => string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.basepath.md b/docs/development/core/server/kibana-plugin-server.basepath.md index 45fb697b329f..bfa1ea02aec1 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.md @@ -16,11 +16,11 @@ export declare class BasePath | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | +| [get](./kibana-plugin-server.basepath.get.md) | | (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown>) => string | returns basePath value, specific for an incoming request. | | [prepend](./kibana-plugin-server.basepath.prepend.md) | | (path: string) => string | returns a new basePath value, prefixed with passed url. | | [remove](./kibana-plugin-server.basepath.remove.md) | | (path: string) => string | returns a new basePath value, cleaned up from passed url. | | [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | string | returns the server's basePathSee [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request | -| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | +| [set](./kibana-plugin-server.basepath.set.md) | | (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown>, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | ## Remarks diff --git a/docs/development/core/server/kibana-plugin-server.basepath.set.md b/docs/development/core/server/kibana-plugin-server.basepath.set.md index 1272a134ef5c..cec70ee853bf 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.set.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.set.md @@ -9,5 +9,5 @@ sets `basePath` value, specific for an incoming request. Signature: ```typescript -set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; +set: (request: LegacyRequest | KibanaRequest, requestSpecificBasePath: string) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.contextsetup.createcontextcontainer.md b/docs/development/core/server/kibana-plugin-server.contextsetup.createcontextcontainer.md index f44e6a3d7640..7096bfc43a52 100644 --- a/docs/development/core/server/kibana-plugin-server.contextsetup.createcontextcontainer.md +++ b/docs/development/core/server/kibana-plugin-server.contextsetup.createcontextcontainer.md @@ -9,9 +9,9 @@ Creates a new [IContextContainer](./kibana-plugin-server.icontextcontainer.md) f Signature: ```typescript -createContextContainer(): IContextContainer; +createContextContainer>(): IContextContainer; ``` Returns: -`IContextContainer` +`IContextContainer` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.http.md b/docs/development/core/server/kibana-plugin-server.coresetup.http.md index 254f2728abef..8474f4ef940d 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.http.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.http.md @@ -14,7 +14,7 @@ http: { registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; basePath: HttpServiceSetup['basePath']; isTlsEnabled: HttpServiceSetup['isTlsEnabled']; - registerRouteHandlerContext: (name: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; + registerRouteHandlerContext: (name: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; createRouter: () => IRouter; }; ``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index ed487a570f28..528557e91bd1 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -18,5 +18,5 @@ export interface CoreSetup | --- | --- | --- | | [context](./kibana-plugin-server.coresetup.context.md) | {
createContextContainer: ContextSetup['createContextContainer'];
} | | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | {
adminClient$: Observable<ClusterClient>;
dataClient$: Observable<ClusterClient>;
createClient: (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ClusterClient;
} | | -| [http](./kibana-plugin-server.coresetup.http.md) | {
createCookieSessionStorageFactory: HttpServiceSetup['createCookieSessionStorageFactory'];
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
basePath: HttpServiceSetup['basePath'];
isTlsEnabled: HttpServiceSetup['isTlsEnabled'];
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(name: T, provider: RequestHandlerContextProvider<RequestHandlerContext>) => RequestHandlerContextContainer<RequestHandlerContext>;
createRouter: () => IRouter;
} | | +| [http](./kibana-plugin-server.coresetup.http.md) | {
createCookieSessionStorageFactory: HttpServiceSetup['createCookieSessionStorageFactory'];
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
basePath: HttpServiceSetup['basePath'];
isTlsEnabled: HttpServiceSetup['isTlsEnabled'];
registerRouteHandlerContext: <T extends keyof RequestHandlerContext>(name: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer;
createRouter: () => IRouter;
} | | diff --git a/docs/development/core/server/kibana-plugin-server.handlercontexttype.md b/docs/development/core/server/kibana-plugin-server.handlercontexttype.md new file mode 100644 index 000000000000..e8f1f346e8b8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.handlercontexttype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HandlerContextType](./kibana-plugin-server.handlercontexttype.md) + +## HandlerContextType type + +Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md) to represent the type of the context. + +Signature: + +```typescript +export declare type HandlerContextType> = T extends HandlerFunction ? U : never; +``` diff --git a/docs/development/core/server/kibana-plugin-server.handlerfunction.md b/docs/development/core/server/kibana-plugin-server.handlerfunction.md new file mode 100644 index 000000000000..97acd37946fc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.handlerfunction.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HandlerFunction](./kibana-plugin-server.handlerfunction.md) + +## HandlerFunction type + +A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-server.icontextcontainer.md) + +Signature: + +```typescript +export declare type HandlerFunction = (context: T, ...args: any[]) => any; +``` diff --git a/docs/development/core/server/kibana-plugin-server.handlerparameters.md b/docs/development/core/server/kibana-plugin-server.handlerparameters.md new file mode 100644 index 000000000000..3dd7998a71a1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.handlerparameters.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HandlerParameters](./kibana-plugin-server.handlerparameters.md) + +## HandlerParameters type + +Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-server.handlercontexttype.md). + +Signature: + +```typescript +export declare type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index 92bf158ad331..eec63cf5c809 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -10,6 +10,6 @@ ```typescript export declare type HttpServiceSetup = Omit & { createRouter: (path: string, plugin?: PluginOpaqueId) => IRouter; - registerRouteHandlerContext: (pluginOpaqueId: PluginOpaqueId, contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; + registerRouteHandlerContext: (pluginOpaqueId: PluginOpaqueId, contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; }; ``` diff --git a/docs/development/core/server/kibana-plugin-server.icontextcontainer.createhandler.md b/docs/development/core/server/kibana-plugin-server.icontextcontainer.createhandler.md index c5549ab017e5..09a9e28d6d0f 100644 --- a/docs/development/core/server/kibana-plugin-server.icontextcontainer.createhandler.md +++ b/docs/development/core/server/kibana-plugin-server.icontextcontainer.createhandler.md @@ -9,7 +9,7 @@ Create a new handler function pre-wired to context for the plugin. Signature: ```typescript -createHandler(pluginOpaqueId: PluginOpaqueId, handler: IContextHandler): (...rest: THandlerParameters) => THandlerReturn extends Promise ? THandlerReturn : Promise; +createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; ``` ## Parameters @@ -17,11 +17,11 @@ createHandler(pluginOpaqueId: PluginOpaqueId, handler: IContextHandlerPluginOpaqueId | The plugin opaque ID for the plugin that registers this handler. | -| handler | IContextHandler<TContext, THandlerReturn, THandlerParameters> | Handler function to pass context object to. | +| handler | THandler | Handler function to pass context object to. | Returns: -`(...rest: THandlerParameters) => THandlerReturn extends Promise ? THandlerReturn : Promise` +`(...rest: HandlerParameters) => ShallowPromise>` A function that takes `THandlerParameters`, calls `handler` with a new context, and returns a Promise of the `handler` return value. diff --git a/docs/development/core/server/kibana-plugin-server.icontextcontainer.md b/docs/development/core/server/kibana-plugin-server.icontextcontainer.md index 1ab699be105b..114da31442ff 100644 --- a/docs/development/core/server/kibana-plugin-server.icontextcontainer.md +++ b/docs/development/core/server/kibana-plugin-server.icontextcontainer.md @@ -9,7 +9,7 @@ An object that handles registration of context providers and configuring handler Signature: ```typescript -export interface IContextContainer +export interface IContextContainer> ``` ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.icontextcontainer.registercontext.md b/docs/development/core/server/kibana-plugin-server.icontextcontainer.registercontext.md index 1a63f63dc91b..30d3fc154d1e 100644 --- a/docs/development/core/server/kibana-plugin-server.icontextcontainer.registercontext.md +++ b/docs/development/core/server/kibana-plugin-server.icontextcontainer.registercontext.md @@ -9,7 +9,7 @@ Register a new context provider. Signature: ```typescript -registerContext(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; ``` ## Parameters @@ -18,7 +18,7 @@ registerContext(pluginOpaqueId: PluginOpaqu | --- | --- | --- | | pluginOpaqueId | PluginOpaqueId | The plugin opaque ID for the plugin that registers this context. | | contextName | TContextName | The key of the TContext object this provider supplies the value for. | -| provider | IContextProvider<TContext, TContextName, THandlerParameters> | A [IContextProvider](./kibana-plugin-server.icontextprovider.md) to be called each time a new context is created. | +| provider | IContextProvider<THandler, TContextName> | A [IContextProvider](./kibana-plugin-server.icontextprovider.md) to be called each time a new context is created. | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.icontexthandler.md b/docs/development/core/server/kibana-plugin-server.icontexthandler.md deleted file mode 100644 index c1f5acc22734..000000000000 --- a/docs/development/core/server/kibana-plugin-server.icontexthandler.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IContextHandler](./kibana-plugin-server.icontexthandler.md) - -## IContextHandler type - -A function registered by a plugin to perform some action. - -Signature: - -```typescript -export declare type IContextHandler = (context: TContext, ...rest: THandlerParameters) => TReturn; -``` - -## Remarks - -A new `TContext` will be built for each handler before invoking. - diff --git a/docs/development/core/server/kibana-plugin-server.icontextprovider.md b/docs/development/core/server/kibana-plugin-server.icontextprovider.md index 250e6a2be3f6..39ace8b9bc57 100644 --- a/docs/development/core/server/kibana-plugin-server.icontextprovider.md +++ b/docs/development/core/server/kibana-plugin-server.icontextprovider.md @@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type Signature: ```typescript -export declare type IContextProvider, TContextName extends keyof TContext, TProviderParameters extends any[] = []> = (context: Partial, ...rest: TProviderParameters) => Promise | TContext[TContextName]; +export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index d943228bbea0..3c01e7aeef32 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -116,11 +116,13 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | | [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | | [GetAuthState](./kibana-plugin-server.getauthstate.md) | Get authentication state for a request. Returned by auth interceptor. | +| [HandlerContextType](./kibana-plugin-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md) to represent the type of the context. | +| [HandlerFunction](./kibana-plugin-server.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | +| [HandlerParameters](./kibana-plugin-server.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-server.handlercontexttype.md). | | [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | | [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | | | [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | -| [IContextHandler](./kibana-plugin-server.icontexthandler.md) | A function registered by a plugin to perform some action. | | [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | | [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | @@ -136,8 +138,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RequestHandler](./kibana-plugin-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) functions. | | [RequestHandlerContextContainer](./kibana-plugin-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | | [RequestHandlerContextProvider](./kibana-plugin-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | -| [RequestHandlerParams](./kibana-plugin-server.requesthandlerparams.md) | Parameters passed to the request handler function. | -| [RequestHandlerReturn](./kibana-plugin-server.requesthandlerreturn.md) | Expected outcome the request handler function. | | [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | | [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | | [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontextcontainer.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontextcontainer.md index afdb48859706..b76a9ce7d235 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontextcontainer.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontextcontainer.md @@ -9,5 +9,5 @@ An object that handles registration of http request context providers. Signature: ```typescript -export declare type RequestHandlerContextContainer = IContextContainer, RequestHandlerParams>; +export declare type RequestHandlerContextContainer = IContextContainer>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontextprovider.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontextprovider.md index 0d9cc6b70b80..ea7294b721aa 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontextprovider.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontextprovider.md @@ -9,5 +9,5 @@ Context provider for request handler. Extends request context object with provid Signature: ```typescript -export declare type RequestHandlerContextProvider = IContextProvider; +export declare type RequestHandlerContextProvider = IContextProvider, TContextName>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlerparams.md b/docs/development/core/server/kibana-plugin-server.requesthandlerparams.md deleted file mode 100644 index 7f466845b4d4..000000000000 --- a/docs/development/core/server/kibana-plugin-server.requesthandlerparams.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RequestHandlerParams](./kibana-plugin-server.requesthandlerparams.md) - -## RequestHandlerParams type - -Parameters passed to the request handler function. - -Signature: - -```typescript -export declare type RequestHandlerParams = [KibanaRequest, KibanaResponseFactory]; -``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlerreturn.md b/docs/development/core/server/kibana-plugin-server.requesthandlerreturn.md deleted file mode 100644 index 6c01e21b6ecb..000000000000 --- a/docs/development/core/server/kibana-plugin-server.requesthandlerreturn.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RequestHandlerReturn](./kibana-plugin-server.requesthandlerreturn.md) - -## RequestHandlerReturn type - -Expected outcome the request handler function. - -Signature: - -```typescript -export declare type RequestHandlerReturn = KibanaResponse; -``` diff --git a/docs/management/index-patterns.asciidoc b/docs/management/index-patterns.asciidoc index 25c07dc50248..8d9ef515108e 100644 --- a/docs/management/index-patterns.asciidoc +++ b/docs/management/index-patterns.asciidoc @@ -35,8 +35,11 @@ image:management/index-patterns/images/rollup-index-pattern.png["Menu with rollu {kib} makes it easy for you to create an index pattern by walking you through the process. Just start typing in the *Index pattern* field, and {kib} looks for -the names of {es} indices that match your input. If you want to include -system indices in your search, toggle the switch in the upper right. +the names of {es} indices that match your input. Make sure that the name of the +index pattern is unique. + +If you want to include system indices in your search, toggle the switch in the +upper right. [role="screenshot"] image:management/index-patterns/images/create-index-pattern.png["Create index pattern"] diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 7330c7e144b6..97fb891c95bd 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -21,7 +21,7 @@ To control how data is collected from your {es} nodes, you configure {ref}/monitoring-settings.html[`xpack.monitoring.collection` settings] in `elasticsearch.yml`. To control how monitoring data is collected from Logstash, you configure -{logstash-ref}/configuring-logstash.html#monitoring-settings[`xpack.monitoring` settings] +{logstash-ref}/monitoring-internal-collection.html#monitoring-settings[`xpack.monitoring` settings] in `logstash.yml`. For more information, see diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 5b3db22a39ea..091714cdd15e 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -271,7 +271,7 @@ identifies this Kibana instance. `server.port:`:: *Default: 5601* Kibana is served by a back end server. This setting specifies the port to use. -`server.rewriteBasePath:`:: *Default: true* Specifies whether Kibana should +`server.rewriteBasePath:`:: *Default: false* Specifies whether Kibana should rewrite requests that are prefixed with `server.basePath` or require that they are rewritten by your reverse proxy. diff --git a/package.json b/package.json index 8aff95748560..ef6aba59139f 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "dependencies": { "@babel/core": "^7.5.5", "@babel/register": "^7.5.5", - "@elastic/charts": "^12.0.2", + "@elastic/charts": "^12.1.0", "@elastic/datemath": "5.0.2", "@elastic/eui": "14.4.0", "@elastic/filesaver": "1.1.2", @@ -195,7 +195,6 @@ "markdown-it": "^8.4.1", "mini-css-extract-plugin": "0.8.0", "minimatch": "^3.0.4", - "mkdirp": "0.5.1", "moment": "^2.20.1", "moment-timezone": "^0.5.14", "mustache": "2.3.2", @@ -240,7 +239,7 @@ "style-loader": "0.23.1", "symbol-observable": "^1.2.0", "tar": "4.4.13", - "terser-webpack-plugin": "^1.4.1", + "terser-webpack-plugin": "^2.1.2", "thread-loader": "^2.1.3", "tinygradient": "0.4.3", "tinymath": "1.2.1", @@ -250,7 +249,7 @@ "tslib": "^1.9.3", "type-detect": "^4.0.8", "ui-select": "0.19.8", - "url-loader": "2.1.0", + "url-loader": "2.2.0", "uuid": "3.3.2", "val-loader": "^1.1.1", "validate-npm-package-name": "2.2.2", @@ -259,8 +258,8 @@ "vega-schema-url-parser": "1.0.0", "vega-tooltip": "^0.9.14", "vision": "^5.3.3", - "webpack": "4.39.2", - "webpack-merge": "4.2.1", + "webpack": "4.41.0", + "webpack-merge": "4.2.2", "whatwg-fetch": "^3.0.0", "yauzl": "2.10.0" }, @@ -322,7 +321,6 @@ "@types/lru-cache": "^5.1.0", "@types/markdown-it": "^0.0.7", "@types/minimatch": "^2.0.29", - "@types/mkdirp": "^0.5.2", "@types/mocha": "^5.2.7", "@types/moment-timezone": "^0.5.8", "@types/mustache": "^0.8.31", @@ -348,6 +346,7 @@ "@types/supertest": "^2.0.5", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", + "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "1.13.0", "@typescript-eslint/parser": "1.13.0", @@ -360,7 +359,7 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chokidar": "3.0.2", + "chokidar": "3.2.1", "chromedriver": "^77.0.0", "classnames": "2.2.6", "dedent": "^0.7.0", @@ -399,6 +398,7 @@ "gulp-babel": "^8.0.0", "gulp-sourcemaps": "2.6.5", "has-ansi": "^3.0.0", + "iedriver": "^3.14.1", "image-diff": "1.6.3", "intl-messageformat-parser": "^1.4.0", "is-path-inside": "^2.1.0", diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index c3e8f5601e47..4412ce81368a 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -22,6 +22,11 @@ module.exports = { from: 'expect.js', to: '@kbn/expect', }, + { + from: 'mkdirp', + to: false, + disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` + }, { from: 'x-pack', toRelative: 'x-pack', diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index 1064d719af83..5521d57c22e8 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "private": true, "dependencies": { - "@elastic/elasticsearch": "^7.3.0", + "@elastic/elasticsearch": "^7.4.0", "@kbn/dev-utils": "1.0.0", "abort-controller": "^2.0.3", "chalk": "^2.4.2", @@ -14,7 +14,6 @@ "execa": "^1.0.0", "getopts": "^2.2.4", "glob": "^7.1.2", - "mkdirp": "^0.5.1", "node-fetch": "^2.6.0", "simple-git": "^1.91.0", "tar-fs": "^1.16.3", diff --git a/packages/kbn-es/src/artifact.js b/packages/kbn-es/src/artifact.js index 3398663a2acf..19d95e82fe48 100644 --- a/packages/kbn-es/src/artifact.js +++ b/packages/kbn-es/src/artifact.js @@ -22,7 +22,6 @@ const AbortController = require('abort-controller'); const fs = require('fs'); const { promisify } = require('util'); const { pipeline, Transform } = require('stream'); -const mkdirp = require('mkdirp'); const chalk = require('chalk'); const { createHash } = require('crypto'); const path = require('path'); @@ -285,7 +284,8 @@ exports.Artifact = class Artifact { let first500Bytes = Buffer.alloc(0); let contentLength = 0; - mkdirp.sync(path.dirname(tmpPath)); + fs.mkdirSync(path.dirname(tmpPath), { recursive: true }); + await asyncPipeline( resp.body, new Transform({ diff --git a/packages/kbn-es/src/cli_commands/build_snapshots.js b/packages/kbn-es/src/cli_commands/build_snapshots.js index eca904c144af..7c2dcff4aa79 100644 --- a/packages/kbn-es/src/cli_commands/build_snapshots.js +++ b/packages/kbn-es/src/cli_commands/build_snapshots.js @@ -24,7 +24,6 @@ const { pipeline, Transform } = require('stream'); const Fs = require('fs'); const getopts = require('getopts'); -const mkdirp = require('mkdirp'); const del = require('del'); const { buildSnapshot, log } = require('../utils'); @@ -49,7 +48,7 @@ exports.run = async (defaults = {}) => { const outputDir = resolve(process.cwd(), options.output); del.sync(outputDir); - mkdirp.sync(outputDir); + Fs.mkdirSync(outputDir, { recursive: true }); for (const license of ['oss', 'trial']) { for (const platform of ['darwin', 'win32', 'linux']) { diff --git a/packages/kbn-es/src/utils/cache.js b/packages/kbn-es/src/utils/cache.js index 01f4b9f374c9..a8bc663ecf09 100644 --- a/packages/kbn-es/src/utils/cache.js +++ b/packages/kbn-es/src/utils/cache.js @@ -18,7 +18,6 @@ */ const fs = require('fs'); -const mkdirp = require('mkdirp'); const path = require('path'); exports.readMeta = function readMeta(file) { @@ -48,6 +47,6 @@ exports.writeMeta = function readMeta(file, details = {}) { ...details, }; - mkdirp.sync(path.dirname(file)); + fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(`${file}.meta`, JSON.stringify(meta, null, 2)); }; diff --git a/packages/kbn-es/src/utils/decompress.js b/packages/kbn-es/src/utils/decompress.js index a0e0d832ff73..b4299594c506 100644 --- a/packages/kbn-es/src/utils/decompress.js +++ b/packages/kbn-es/src/utils/decompress.js @@ -21,7 +21,6 @@ const fs = require('fs'); const path = require('path'); const yauzl = require('yauzl'); -const mkdirp = require('mkdirp'); const zlib = require('zlib'); const tarFs = require('tar-fs'); @@ -38,7 +37,7 @@ function decompressTarball(archive, dirPath) { } function decompressZip(input, output) { - mkdirp.sync(output); + fs.mkdirSync(output, { recursive: true }); return new Promise((resolve, reject) => { yauzl.open(input, { lazyEntries: true }, (err, zipfile) => { if (err) { @@ -63,7 +62,7 @@ function decompressZip(input, output) { const fileName = path.resolve(output, zipPath); if (/\/$/.test(entry.fileName)) { - mkdirp.sync(fileName); + fs.mkdirSync(fileName, { recursive: true }); zipfile.readEntry(); } else { // file entry diff --git a/packages/kbn-es/src/utils/decompress.test.js b/packages/kbn-es/src/utils/decompress.test.js index ff36e9521257..a267950abc7a 100644 --- a/packages/kbn-es/src/utils/decompress.test.js +++ b/packages/kbn-es/src/utils/decompress.test.js @@ -20,7 +20,6 @@ const { decompress } = require('./decompress'); const fs = require('fs'); const path = require('path'); -const mkdirp = require('mkdirp'); const del = require('del'); const os = require('os'); @@ -34,9 +33,9 @@ const zipSnapshot = path.resolve(dataFolder, 'snapshot.zip'); const tarGzSnapshot = path.resolve(dataFolder, 'snapshot.tar.gz'); beforeEach(() => { - mkdirp.sync(tmpFolder); - mkdirp.sync(dataFolder); - mkdirp.sync(esFolder); + fs.mkdirSync(tmpFolder, { recursive: true }); + fs.mkdirSync(dataFolder, { recursive: true }); + fs.mkdirSync(esFolder, { recursive: true }); fs.copyFileSync(path.resolve(fixturesFolder, 'snapshot.zip'), zipSnapshot); fs.copyFileSync(path.resolve(fixturesFolder, 'snapshot.tar.gz'), tarGzSnapshot); diff --git a/packages/kbn-es/src/utils/extract_config_files.js b/packages/kbn-es/src/utils/extract_config_files.js index 7ad75dcbbf34..a8a44a149e9b 100644 --- a/packages/kbn-es/src/utils/extract_config_files.js +++ b/packages/kbn-es/src/utils/extract_config_files.js @@ -19,7 +19,6 @@ const path = require('path'); const fs = require('fs'); -const mkdirp = require('mkdirp'); /** * Copies config references to an absolute path to @@ -62,7 +61,7 @@ function copyFileSync(src, dest) { const destPath = path.dirname(dest); if (!fs.existsSync(destPath)) { - mkdirp(destPath); + fs.mkdirSync(destPath, { recursive: true }); } fs.writeFileSync(dest, fs.readFileSync(src)); diff --git a/packages/kbn-eslint-import-resolver-kibana/package.json b/packages/kbn-eslint-import-resolver-kibana/package.json index 14a922492426..9fae27011767 100755 --- a/packages/kbn-eslint-import-resolver-kibana/package.json +++ b/packages/kbn-eslint-import-resolver-kibana/package.json @@ -16,6 +16,6 @@ "glob-all": "^3.1.0", "lru-cache": "^4.1.5", "resolve": "^1.7.1", - "webpack": "^4.39.2" + "webpack": "^4.41.0" } } diff --git a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js index c5f2aff9bfdc..8119d338ee53 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js +++ b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js @@ -31,6 +31,14 @@ function checkModuleNameNode(context, mappings, node) { let newSource; + if (mapping.to === false) { + context.report({ + message: mapping.disallowedMessage || `Importing "${mapping.from}" is not allowed`, + loc: node.loc, + }); + return; + } + // support for toRelative added to migrate away from X-Pack being bundled // within node modules. after that migration, this can be removed. if (mapping.toRelative) { @@ -66,11 +74,21 @@ module.exports = { type: 'string', }, to: { - type: 'string', + anyOf: [ + { + type: 'string', + }, + { + const: false, + }, + ], }, toRelative: { type: 'string', }, + disallowedMessage: { + type: 'string', + }, }, anyOf: [ { diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index a9b30c17e292..f2aa2e1ced9c 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -30,8 +30,8 @@ "sass-loader": "^7.3.1", "style-loader": "0.23.1", "supports-color": "^7.0.0", - "url-loader": "2.1.0", - "webpack": "4.39.2", - "webpack-cli": "^3.3.7" + "url-loader": "2.2.0", + "webpack": "4.41.0", + "webpack-cli": "^3.3.9" } } diff --git a/packages/kbn-plugin-helpers/lib/index.d.ts b/packages/kbn-plugin-helpers/lib/index.d.ts new file mode 100644 index 000000000000..1515bf6b84bf --- /dev/null +++ b/packages/kbn-plugin-helpers/lib/index.d.ts @@ -0,0 +1,26 @@ +/* + * 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 function babelRegister(): void; +export function resolveKibanaPath(path: string): string; +export function readFtrConfigFile(path: string): any; +export function run( + task: 'build' | 'start' | 'testAll' | 'testBrowser' | 'testServer' | 'postinstall', + options: any +): Promise; diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index 47af4e288afa..215963c0769b 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -21,7 +21,7 @@ "globby": "^8.0.1", "gulp-babel": "^8.0.0", "gulp-rename": "1.4.0", - "gulp-zip": "4.2.0", + "gulp-zip": "5.0.1", "inquirer": "^1.2.2", "minimatch": "^3.0.4", "node-sass": "^4.9.4", diff --git a/packages/kbn-plugin-helpers/tsconfig.json b/packages/kbn-plugin-helpers/tsconfig.json new file mode 100644 index 000000000000..f5559aa7290c --- /dev/null +++ b/packages/kbn-plugin-helpers/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["lib/index.d.ts"] +} diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index b054e21c856a..5d2f5c786f2c 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -2697,11 +2697,11 @@ async function linkProjectExecutables(projectsByName, projectGraph) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unlink", function() { return unlink; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "copyDirectory", function() { return copyDirectory; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "chmod", function() { return chmod; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readFile", function() { return readFile; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "chmod", function() { return chmod; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mkdirp", function() { return mkdirp; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unlink", function() { return unlink; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "copyDirectory", function() { return copyDirectory; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isSymlink", function() { return isSymlink; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isDirectory", function() { return isDirectory; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isFile", function() { return isFile; }); @@ -2710,14 +2710,12 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var cmd_shim__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cmd_shim__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(23); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var mkdirp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31); -/* harmony import */ var mkdirp__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(mkdirp__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var ncp__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(33); -/* harmony import */ var ncp__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(ncp__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_4__); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(29); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_5__); +/* harmony import */ var ncp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(33); +/* harmony import */ var ncp__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ncp__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(29); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_4__); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -2741,16 +2739,17 @@ __webpack_require__.r(__webpack_exports__); - -const lstat = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.lstat); -const readFile = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.readFile); -const symlink = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.symlink); -const chmod = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.chmod); -const cmdShim = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(cmd_shim__WEBPACK_IMPORTED_MODULE_0___default.a); -const mkdirp = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(mkdirp__WEBPACK_IMPORTED_MODULE_2___default.a); -const unlink = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.unlink); -const copyDirectory = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(ncp__WEBPACK_IMPORTED_MODULE_3__["ncp"]); - +const lstat = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.lstat); +const readFile = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.readFile); +const symlink = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.symlink); +const chmod = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.chmod); +const cmdShim = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(cmd_shim__WEBPACK_IMPORTED_MODULE_0___default.a); +const mkdir = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.mkdir); +const mkdirp = async path => await mkdir(path, { + recursive: true +}); +const unlink = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_1___default.a.unlink); +const copyDirectory = Object(util__WEBPACK_IMPORTED_MODULE_4__["promisify"])(ncp__WEBPACK_IMPORTED_MODULE_2__["ncp"]); async function statTest(path, block) { try { @@ -2808,7 +2807,7 @@ async function createSymlink(src, dest, type) { } } else { const posixType = type === 'exec' ? 'file' : type; - const relativeSource = Object(path__WEBPACK_IMPORTED_MODULE_4__["relative"])(Object(path__WEBPACK_IMPORTED_MODULE_4__["dirname"])(dest), src); + const relativeSource = Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(Object(path__WEBPACK_IMPORTED_MODULE_3__["dirname"])(dest), src); await forceCreate(relativeSource, dest, posixType); } } diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index a3d3baed3750..8480e74ceb3a 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -26,7 +26,6 @@ "@types/indent-string": "^3.0.0", "@types/lodash.clonedeepwith": "^4.5.3", "@types/log-symbols": "^2.0.0", - "@types/mkdirp": "^0.5.2", "@types/ncp": "^2.0.1", "@types/node": "^10.12.27", "@types/ora": "^1.3.5", @@ -50,10 +49,9 @@ "indent-string": "^3.2.0", "lodash.clonedeepwith": "^4.5.0", "log-symbols": "^2.2.0", - "mkdirp": "^0.5.1", "ncp": "^2.0.0", "ora": "^1.4.0", - "prettier": "^1.14.3", + "prettier": "^1.18.2", "read-pkg": "^5.2.0", "rxjs": "^6.2.1", "spawn-sync": "^1.0.15", @@ -63,8 +61,8 @@ "tempy": "^0.3.0", "typescript": "3.5.3", "unlazy-loader": "^0.1.3", - "webpack": "^4.39.2", - "webpack-cli": "^3.3.7", + "webpack": "^4.41.0", + "webpack-cli": "^3.3.9", "wrap-ansi": "^3.0.1", "write-pkg": "^4.0.0" }, diff --git a/packages/kbn-pm/src/utils/fs.ts b/packages/kbn-pm/src/utils/fs.ts index 8ccaca29f97e..9484c3a61e60 100644 --- a/packages/kbn-pm/src/utils/fs.ts +++ b/packages/kbn-pm/src/utils/fs.ts @@ -19,22 +19,20 @@ import cmdShimCb from 'cmd-shim'; import fs from 'fs'; -import mkdirpCb from 'mkdirp'; import { ncp } from 'ncp'; import { dirname, relative } from 'path'; import { promisify } from 'util'; const lstat = promisify(fs.lstat); -const readFile = promisify(fs.readFile); +export const readFile = promisify(fs.readFile); const symlink = promisify(fs.symlink); -const chmod = promisify(fs.chmod); +export const chmod = promisify(fs.chmod); const cmdShim = promisify(cmdShimCb); -const mkdirp = promisify(mkdirpCb); +const mkdir = promisify(fs.mkdir); +export const mkdirp = async (path: string) => await mkdir(path, { recursive: true }); export const unlink = promisify(fs.unlink); export const copyDirectory = promisify(ncp); -export { chmod, readFile, mkdirp }; - async function statTest(path: string, block: (stats: fs.Stats) => boolean) { try { return block(await lstat(path)); diff --git a/packages/kbn-spec-to-console/package.json b/packages/kbn-spec-to-console/package.json index 674a801e9286..2e5f897894a9 100644 --- a/packages/kbn-spec-to-console/package.json +++ b/packages/kbn-spec-to-console/package.json @@ -18,7 +18,7 @@ "homepage": "https://github.com/jbudz/spec-to-console#readme", "devDependencies": { "jest": "^24.9.0", - "prettier": "^1.14.3" + "prettier": "^1.18.2" }, "dependencies": { "commander": "^3.0.0", diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/es_types.js b/packages/kbn-test/index.d.ts similarity index 86% rename from src/legacy/core_plugins/vis_type_timeseries/common/es_types.js rename to packages/kbn-test/index.d.ts index 7e6a53753fae..aa55df9215c2 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/common/es_types.js +++ b/packages/kbn-test/index.d.ts @@ -17,10 +17,4 @@ * under the License. */ -export const ES_TYPES = { - NUMBER: 'number', - STRING: 'string', - KEYWORD: 'keyword', - TEXT: 'text', - DATE: 'date', -}; +export * from './src/index'; diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 6891f01bae2a..d02b2cf41d3f 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -21,7 +21,7 @@ "getopts": "^2.2.4", "glob": "^7.1.2", "rxjs": "^6.2.1", - "tar-fs": "^1.16.2", + "tar-fs": "^1.16.3", "tmp": "^0.1.0", "zlib": "^1.0.5" } diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index d9cf282d8f4b..52672d5f039f 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -134,7 +134,7 @@ export const schema = Joi.object() browser: Joi.object() .keys({ type: Joi.string() - .valid('chrome', 'firefox') + .valid('chrome', 'firefox', 'ie') .default('chrome'), logPollingMs: Joi.number().default(100), diff --git a/packages/kbn-test/src/index.js b/packages/kbn-test/src/index.ts similarity index 87% rename from packages/kbn-test/src/index.js rename to packages/kbn-test/src/index.ts index 8313d0c7334b..9167fd190dc8 100644 --- a/packages/kbn-test/src/index.js +++ b/packages/kbn-test/src/index.ts @@ -17,20 +17,28 @@ * under the License. */ +// @ts-ignore not typed yet export { runTestsCli, startServersCli } from './functional_tests/cli'; +// @ts-ignore not typed yet export { runTests, startServers } from './functional_tests/tasks'; +// @ts-ignore not typed yet export { OPTIMIZE_BUNDLE_DIR, KIBANA_ROOT } from './functional_tests/lib/paths'; +// @ts-ignore not typed yet export { esTestConfig, createEsTestCluster } from './es'; +// @ts-ignore not typed yet export { kbnTestConfig, kibanaServerTestUser, kibanaTestUser, adminTestUser } from './kbn'; +// @ts-ignore not typed yet export { setupUsers, DEFAULT_SUPERUSER_PASS } from './functional_tests/lib/auth'; +// @ts-ignore not typed yet export { readConfigFile } from './functional_test_runner/lib/config/read_config_file'; +// @ts-ignore not typed yet export { runFtrCli } from './functional_test_runner/cli'; export { diff --git a/packages/kbn-test/src/mocha/index.js b/packages/kbn-test/src/mocha/index.ts similarity index 88% rename from packages/kbn-test/src/mocha/index.js rename to packages/kbn-test/src/mocha/index.ts index f18282d4a8b1..99c0f6f4230b 100644 --- a/packages/kbn-test/src/mocha/index.js +++ b/packages/kbn-test/src/mocha/index.ts @@ -17,8 +17,13 @@ * under the License. */ +// @ts-ignore not typed yet export { createAutoJUnitReporter } from './auto_junit_reporter'; +// @ts-ignore not typed yet export { setupJUnitReportGeneration } from './junit_report_generation'; +// @ts-ignore not typed yet export { runMochaCli } from './run_mocha_cli'; +// @ts-ignore not typed yet export { recordLog, snapshotLogsForRunnable } from './log_cache'; +// @ts-ignore not typed yet export { escapeCdata } from './xml'; diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index 3463c738df5a..54df5c5f3349 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -18,10 +18,9 @@ */ import { resolve, dirname, relative } from 'path'; -import { writeFileSync } from 'fs'; +import { writeFileSync, mkdirSync } from 'fs'; import { inspect } from 'util'; -import mkdirp from 'mkdirp'; import xmlBuilder from 'xmlbuilder'; import { getSnapshotOfRunnableLogs } from './log_cache'; @@ -150,7 +149,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { spacebeforeslash: '', }); - mkdirp.sync(dirname(reportPath)); + mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); }); } diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index 825fddc0bdcc..fdb53de52687 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "include": [ "types/**/*", - "src/functional_test_runner/**/*", - "src/mocha/xml.ts" + "src/**/*", + "index.d.ts" ] } diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index bd9658934100..cb4d2e3cadf9 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -37,7 +37,7 @@ "babel-loader": "^8.0.6", "brace": "0.11.1", "chalk": "^2.4.2", - "chokidar": "3.0.2", + "chokidar": "3.2.1", "core-js": "^3.2.1", "css-loader": "^2.1.1", "expose-loader": "^0.7.5", @@ -68,8 +68,8 @@ "sass-loader": "^7.3.1", "sinon": "^7.4.2", "style-loader": "^0.23.1", - "webpack": "^4.39.2", - "webpack-dev-server": "^3.8.0", + "webpack": "^4.41.0", + "webpack-dev-server": "^3.8.2", "yeoman-generator": "1.1.1", "yo": "2.0.6" } diff --git a/renovate.json5 b/renovate.json5 index e5dd29aa6319..e8fd7b6ceda5 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -67,6 +67,17 @@ '(\\b|_)jest(\\b|_)', ], }, + { + groupSlug: '@elastic/charts', + groupName: '@elastic/charts related packages', + packageNames: [ + '@elastic/charts', + '@types/elastic__charts', + ], + reviewers: [ + 'markov00', + ], + }, { groupSlug: 'mocha', groupName: 'mocha related packages', @@ -248,6 +259,18 @@ '(\\b|_)storybook(\\b|_)', ], }, + { + groupSlug: 'typescript', + groupName: 'typescript related packages', + packagePatterns: [ + '(\\b|_)ts(\\b|_)', + '(\\b|_)typescript(\\b|_)', + ], + packageNames: [ + 'tslib', + '@types/tslib', + ], + }, { groupSlug: 'json-stable-stringify', groupName: 'json-stable-stringify related packages', @@ -464,14 +487,6 @@ '@types/minimatch', ], }, - { - groupSlug: 'mkdirp', - groupName: 'mkdirp related packages', - packageNames: [ - 'mkdirp', - '@types/mkdirp', - ], - }, { groupSlug: 'mustache', groupName: 'mustache related packages', @@ -592,6 +607,14 @@ '@types/uuid', ], }, + { + groupSlug: 'vinyl-fs', + groupName: 'vinyl-fs related packages', + packageNames: [ + 'vinyl-fs', + '@types/vinyl-fs', + ], + }, { groupSlug: 'zen-observable', groupName: 'zen-observable related packages', @@ -624,6 +647,14 @@ '@types/color', ], }, + { + groupSlug: 'fancy-log', + groupName: 'fancy-log related packages', + packageNames: [ + 'fancy-log', + '@types/fancy-log', + ], + }, { groupSlug: 'file-saver', groupName: 'file-saver related packages', @@ -632,6 +663,14 @@ '@types/file-saver', ], }, + { + groupSlug: 'getos', + groupName: 'getos related packages', + packageNames: [ + 'getos', + '@types/getos', + ], + }, { groupSlug: 'git-url-parse', groupName: 'git-url-parse related packages', diff --git a/src/cli/serve/integration_tests/reload_logging_config.test.js b/src/cli/serve/integration_tests/reload_logging_config.test.js index 206118d2d1be..8e8bdb15abc6 100644 --- a/src/cli/serve/integration_tests/reload_logging_config.test.js +++ b/src/cli/serve/integration_tests/reload_logging_config.test.js @@ -21,7 +21,6 @@ import { spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; import { safeDump } from 'js-yaml'; @@ -59,7 +58,7 @@ describe('Server logging configuration', function () { isJson = true; setLoggingJson(true); - mkdirp.sync(tempDir); + fs.mkdirSync(tempDir, { recursive: true }); }); afterEach(() => { diff --git a/src/cli_plugin/install/download.test.js b/src/cli_plugin/install/download.test.js index 97586f962737..9de6491f3c95 100644 --- a/src/cli_plugin/install/download.test.js +++ b/src/cli_plugin/install/download.test.js @@ -21,7 +21,7 @@ import sinon from 'sinon'; import nock from 'nock'; import glob from 'glob-all'; import rimraf from 'rimraf'; -import mkdirp from 'mkdirp'; +import Fs from 'fs'; import Logger from '../lib/logger'; import { UnsupportedProtocolError } from '../lib/errors'; import { download, _downloadSingle, _getFilePath, _checkFilePathDeprecation } from './download'; @@ -64,7 +64,7 @@ describe('kibana cli', function () { sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); rimraf.sync(testWorkingPath); - mkdirp.sync(testWorkingPath); + Fs.mkdirSync(testWorkingPath, { recursive: true }); }); afterEach(function () { diff --git a/src/cli_plugin/install/install.js b/src/cli_plugin/install/install.js index 57d2d485b9dd..10816591f7d7 100644 --- a/src/cli_plugin/install/install.js +++ b/src/cli_plugin/install/install.js @@ -17,8 +17,10 @@ * under the License. */ +import Fs from 'fs'; +import { promisify } from 'util'; + import { download } from './download'; -import Promise from 'bluebird'; import path from 'path'; import { cleanPrevious, cleanArtifacts } from './cleanup'; import { extract, getPackData } from './pack'; @@ -27,9 +29,8 @@ import { sync as rimrafSync } from 'rimraf'; import { errorIfXPackInstall } from '../lib/error_if_x_pack'; import { existingInstall, assertVersion } from './kibana'; import { prepareExternalProjectDependencies } from '@kbn/pm'; -import mkdirp from 'mkdirp'; -const mkdir = Promise.promisify(mkdirp); +const mkdir = promisify(Fs.mkdir); export default async function install(settings, logger) { try { @@ -37,7 +38,7 @@ export default async function install(settings, logger) { await cleanPrevious(settings, logger); - await mkdir(settings.workingPath); + await mkdir(settings.workingPath, { recursive: true }); await download(settings, logger); diff --git a/src/cli_plugin/install/kibana.test.js b/src/cli_plugin/install/kibana.test.js index 4a4ca6ebe303..64de96b34e80 100644 --- a/src/cli_plugin/install/kibana.test.js +++ b/src/cli_plugin/install/kibana.test.js @@ -27,7 +27,6 @@ import sinon from 'sinon'; import Logger from '../lib/logger'; import { join } from 'path'; import rimraf from 'rimraf'; -import mkdirp from 'mkdirp'; import fs from 'fs'; import { existingInstall, assertVersion } from './kibana'; @@ -55,7 +54,7 @@ describe('kibana cli', function () { beforeEach(function () { rimraf.sync(testWorkingPath); - mkdirp.sync(testWorkingPath); + fs.mkdirSync(testWorkingPath, { recursive: true }); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); }); diff --git a/src/cli_plugin/install/pack.test.js b/src/cli_plugin/install/pack.test.js index bc854f66a692..1bc2504397fb 100644 --- a/src/cli_plugin/install/pack.test.js +++ b/src/cli_plugin/install/pack.test.js @@ -17,10 +17,11 @@ * under the License. */ +import Fs from 'fs'; + import sinon from 'sinon'; import glob from 'glob-all'; import rimraf from 'rimraf'; -import mkdirp from 'mkdirp'; import Logger from '../lib/logger'; import { extract, getPackData } from './pack'; import { _downloadSingle } from './download'; @@ -58,7 +59,7 @@ describe('kibana cli', function () { logger = new Logger(settings); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); - mkdirp.sync(testWorkingPath); + Fs.mkdirSync(testWorkingPath, { recursive: true }); }); afterEach(function () { diff --git a/src/cli_plugin/install/zip.js b/src/cli_plugin/install/zip.js index 7f928275525d..1d5d49b6c63b 100644 --- a/src/cli_plugin/install/zip.js +++ b/src/cli_plugin/install/zip.js @@ -19,8 +19,7 @@ import yauzl from 'yauzl'; import path from 'path'; -import mkdirp from 'mkdirp'; -import { createWriteStream } from 'fs'; +import { createWriteStream, mkdir } from 'fs'; import { get } from 'lodash'; /** @@ -112,7 +111,7 @@ export function extractArchive(archive, targetDir, extractPath) { } if (_isDirectory(fileName)) { - mkdirp(fileName, function (err) { + mkdir(fileName, { recursive: true }, function (err) { if (err) { return reject(err); } @@ -127,7 +126,7 @@ export function extractArchive(archive, targetDir, extractPath) { } // ensure parent directory exists - mkdirp(path.dirname(fileName), function (err) { + mkdir(path.dirname(fileName), { recursive: true }, function (err) { if (err) { return reject(err); } diff --git a/src/cli_plugin/list/list.test.js b/src/cli_plugin/list/list.test.js index c72f6c330543..425a6bee5394 100644 --- a/src/cli_plugin/list/list.test.js +++ b/src/cli_plugin/list/list.test.js @@ -19,16 +19,15 @@ import sinon from 'sinon'; import rimraf from 'rimraf'; -import mkdirp from 'mkdirp'; import Logger from '../lib/logger'; import list from './list'; import { join } from 'path'; -import { writeFileSync, appendFileSync } from 'fs'; +import { writeFileSync, appendFileSync, mkdirSync } from 'fs'; function createPlugin(name, version, pluginBaseDir) { const pluginDir = join(pluginBaseDir, name); - mkdirp.sync(pluginDir); + mkdirSync(pluginDir, { recursive: true }); appendFileSync(join(pluginDir, 'package.json'), '{"version": "' + version + '"}'); } @@ -48,7 +47,7 @@ describe('kibana cli', function () { sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); rimraf.sync(pluginDir); - mkdirp.sync(pluginDir); + mkdirSync(pluginDir, { recursive: true }); }); afterEach(function () { @@ -97,7 +96,7 @@ describe('kibana cli', function () { it('list should throw an exception if a plugin does not have a package.json', function () { createPlugin('plugin1', '1.0.0', pluginDir); - mkdirp.sync(join(pluginDir, 'empty-plugin')); + mkdirSync(join(pluginDir, 'empty-plugin'), { recursive: true }); expect(function () { list(settings, logger); @@ -107,7 +106,7 @@ describe('kibana cli', function () { it('list should throw an exception if a plugin have an empty package.json', function () { createPlugin('plugin1', '1.0.0', pluginDir); const invalidPluginDir = join(pluginDir, 'invalid-plugin'); - mkdirp.sync(invalidPluginDir); + mkdirSync(invalidPluginDir, { recursive: true }); appendFileSync(join(invalidPluginDir, 'package.json'), ''); expect(function () { diff --git a/src/cli_plugin/remove/remove.test.js b/src/cli_plugin/remove/remove.test.js index 971881dd0b87..5d936d027852 100644 --- a/src/cli_plugin/remove/remove.test.js +++ b/src/cli_plugin/remove/remove.test.js @@ -20,11 +20,10 @@ import sinon from 'sinon'; import glob from 'glob-all'; import rimraf from 'rimraf'; -import mkdirp from 'mkdirp'; import Logger from '../lib/logger'; import remove from './remove'; import { join } from 'path'; -import { writeFileSync, existsSync } from 'fs'; +import { writeFileSync, existsSync, mkdirSync } from 'fs'; describe('kibana cli', function () { @@ -42,7 +41,7 @@ describe('kibana cli', function () { sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); rimraf.sync(pluginDir); - mkdirp.sync(pluginDir); + mkdirSync(pluginDir, { recursive: true }); }); afterEach(function () { @@ -72,7 +71,7 @@ describe('kibana cli', function () { it('remove x-pack if it exists', () => { settings.pluginPath = join(pluginDir, 'x-pack'); settings.plugin = 'x-pack'; - mkdirp.sync(join(pluginDir, 'x-pack')); + mkdirSync(join(pluginDir, 'x-pack'), { recursive: true }); expect(existsSync(settings.pluginPath)).toEqual(true); remove(settings, logger); expect(existsSync(settings.pluginPath)).toEqual(false); @@ -88,8 +87,8 @@ describe('kibana cli', function () { it('delete the specified folder.', function () { settings.pluginPath = join(pluginDir, 'foo'); - mkdirp.sync(join(pluginDir, 'foo')); - mkdirp.sync(join(pluginDir, 'bar')); + mkdirSync(join(pluginDir, 'foo'), { recursive: true }); + mkdirSync(join(pluginDir, 'bar'), { recursive: true }); remove(settings, logger); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index d1855a0370f0..935844baddf8 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -27,12 +27,9 @@ import { AppRouter } from './ui'; import { HttpStart } from '../http'; import { ContextSetup, IContextContainer } from '../context'; import { - AppMountContext, App, LegacyApp, AppMounter, - AppUnmount, - AppMountParameters, InternalApplicationSetup, InternalApplicationStart, } from './types'; @@ -64,11 +61,7 @@ export class ApplicationService { private readonly apps$ = new BehaviorSubject>(new Map()); private readonly legacyApps$ = new BehaviorSubject>(new Map()); private readonly capabilities = new CapabilitiesService(); - private mountContext?: IContextContainer< - AppMountContext, - AppUnmount | Promise, - [AppMountParameters] - >; + private mountContext?: IContextContainer; public setup({ context }: SetupDeps): InternalApplicationSetup { this.mountContext = context.createContextContainer(); @@ -98,7 +91,7 @@ export class ApplicationService { this.legacyApps$.next(new Map([...this.legacyApps$.value.entries(), [app.id, app]])); }, - registerMountContext: this.mountContext.registerContext, + registerMountContext: this.mountContext!.registerContext, }; } diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 018d7569ce41..5b1d4affe884 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -114,6 +114,15 @@ export interface AppMountContext { overlays: OverlayStart; /** {@link UiSettingsClient} */ uiSettings: UiSettingsClientContract; + /** + * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done + * use *only* to retrieve config values. There is no way to set injected values + * in the new platform. Use the legacy platform API instead. + * @deprecated + * */ + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; }; } @@ -193,7 +202,7 @@ export interface ApplicationSetup { */ registerMountContext( contextName: T, - provider: IContextProvider + provider: IContextProvider ): void; } @@ -224,7 +233,7 @@ export interface InternalApplicationSetup { registerMountContext( pluginOpaqueId: PluginOpaqueId, contextName: T, - provider: IContextProvider + provider: IContextProvider ): void; } @@ -261,7 +270,7 @@ export interface ApplicationStart { */ registerMountContext( contextName: T, - provider: IContextProvider + provider: IContextProvider ): void; } @@ -291,7 +300,7 @@ export interface InternalApplicationStart registerMountContext( pluginOpaqueId: PluginOpaqueId, contextName: T, - provider: IContextProvider + provider: IContextProvider ): void; // Internal APIs diff --git a/src/core/public/context/context_service.ts b/src/core/public/context/context_service.ts index 704524d83863..dadc509c9782 100644 --- a/src/core/public/context/context_service.ts +++ b/src/core/public/context/context_service.ts @@ -18,7 +18,7 @@ */ import { PluginOpaqueId } from '../../server'; -import { IContextContainer, ContextContainer } from '../../utils/context'; +import { IContextContainer, ContextContainer, HandlerFunction } from '../../utils/context'; import { CoreContext } from '../core_system'; interface StartDeps { @@ -31,15 +31,8 @@ export class ContextService { public setup({ pluginDependencies }: StartDeps): ContextSetup { return { - createContextContainer: < - TContext extends {}, - THandlerReturn, - THandlerParameters extends any[] = [] - >() => - new ContextContainer( - pluginDependencies, - this.core.coreId - ), + createContextContainer: >() => + new ContextContainer(pluginDependencies, this.core.coreId), }; } } @@ -111,9 +104,5 @@ export interface ContextSetup { /** * Creates a new {@link IContextContainer} for a service owner. */ - createContextContainer< - TContext extends {}, - THandlerReturn, - THandlerParmaters extends any[] = [] - >(): IContextContainer; + createContextContainer>(): IContextContainer; } diff --git a/src/core/public/context/index.ts b/src/core/public/context/index.ts index 28b2641b2a5a..f22c4168d754 100644 --- a/src/core/public/context/index.ts +++ b/src/core/public/context/index.ts @@ -18,4 +18,10 @@ */ export { ContextService, ContextSetup } from './context_service'; -export { IContextContainer, IContextProvider, IContextHandler } from '../../utils/context'; +export { + IContextContainer, + IContextProvider, + HandlerFunction, + HandlerContextType, + HandlerParameters, +} from '../../utils/context'; diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 7b9ed50f0959..7a87f97208a7 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -236,6 +236,7 @@ export class CoreSystem { notifications, overlays, uiSettings, + injectedMetadata: pick(injectedMetadata, ['getInjectedVar']), })); const core: InternalCoreStart = { diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 393a7076759e..9640f6f510c4 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -67,7 +67,14 @@ import { UiSettingsClient, UiSettingsState, UiSettingsClientContract } from './u import { ApplicationSetup, Capabilities, ApplicationStart } from './application'; import { DocLinksStart } from './doc_links'; import { SavedObjectsStart } from './saved_objects'; -import { IContextContainer, IContextProvider, ContextSetup, IContextHandler } from './context'; +import { + IContextContainer, + IContextProvider, + ContextSetup, + HandlerFunction, + HandlerContextType, + HandlerParameters, +} from './context'; export { CoreContext, CoreSystem } from './core_system'; export { RecursiveReadonly } from '../utils'; @@ -138,6 +145,15 @@ export interface CoreSetup { notifications: NotificationsSetup; /** {@link UiSettingsClient} */ uiSettings: UiSettingsClientContract; + /** + * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done + * use *only* to retrieve config values. There is no way to set injected values + * in the new platform. Use the legacy platform API instead. + * @deprecated + * */ + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; } /** @@ -168,6 +184,15 @@ export interface CoreStart { overlays: OverlayStart; /** {@link UiSettingsClient} */ uiSettings: UiSettingsClientContract; + /** + * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done + * use *only* to retrieve config values. There is no way to set injected values + * in the new platform. Use the legacy platform API instead. + * @deprecated + * */ + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; } /** @@ -217,7 +242,9 @@ export { ChromeRecentlyAccessedHistoryItem, ChromeStart, IContextContainer, - IContextHandler, + HandlerFunction, + HandlerContextType, + HandlerParameters, IContextProvider, ContextSetup, DocLinksStart, diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 7c99f69d6fd7..8ce163fd59e1 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -28,6 +28,7 @@ import { overlayServiceMock } from './overlays/overlay_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { savedObjectsMock } from './saved_objects/saved_objects_service.mock'; import { contextServiceMock } from './context/context_service.mock'; +import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; export { chromeServiceMock } from './chrome/chrome_service.mock'; export { docLinksServiceMock } from './doc_links/doc_links_service.mock'; @@ -48,6 +49,9 @@ function createCoreSetupMock() { http: httpServiceMock.createSetupContract(), notifications: notificationServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), + injectedMetadata: { + getInjectedVar: injectedMetadataServiceMock.createSetupContract().getInjectedVar, + }, }; return mock; @@ -64,6 +68,9 @@ function createCoreStartMock() { overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsMock.createStartContract(), + injectedMetadata: { + getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, + }, }; return mock; diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index f4e25d27447b..51bd118d280e 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -86,6 +86,9 @@ export function createPluginSetupContext< http: deps.http, notifications: deps.notifications, uiSettings: deps.uiSettings, + injectedMetadata: { + getInjectedVar: deps.injectedMetadata.getInjectedVar, + }, }; } @@ -125,5 +128,8 @@ export function createPluginStartContext< overlays: deps.overlays, uiSettings: deps.uiSettings, savedObjects: deps.savedObjects, + injectedMetadata: { + getInjectedVar: deps.injectedMetadata.getInjectedVar, + }, }; } diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index d6411554e5f8..358bf71ac918 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { omit } from 'lodash'; +import { omit, pick } from 'lodash'; import { MockedPluginInitializer, @@ -76,12 +76,12 @@ beforeEach(() => { context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), - injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + injectedMetadata: pick(injectedMetadataServiceMock.createStartContract(), 'getInjectedVar'), notifications: notificationServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), }; mockSetupContext = { - ...omit(mockSetupDeps, 'injectedMetadata'), + ...mockSetupDeps, application: expect.any(Object), }; mockStartDeps = { @@ -90,14 +90,14 @@ beforeEach(() => { http: httpServiceMock.createStartContract(), chrome: chromeServiceMock.createStartContract(), i18n: i18nServiceMock.createStartContract(), - injectedMetadata: injectedMetadataServiceMock.createStartContract(), + injectedMetadata: pick(injectedMetadataServiceMock.createStartContract(), 'getInjectedVar'), notifications: notificationServiceMock.createStartContract(), overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsMock.createStartContract(), }; mockStartContext = { - ...omit(mockStartDeps, 'injectedMetadata'), + ...mockStartDeps, application: expect.any(Object), chrome: omit(mockStartDeps.chrome, 'getComponent'), }; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 102e77b564a6..0156cbaebb94 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -9,6 +9,7 @@ import { MouseEventHandler } from 'react'; import { Observable } from 'rxjs'; import React from 'react'; import * as Rx from 'rxjs'; +import { ShallowPromise } from '@kbn/utility-types'; import { EuiGlobalToastListToast as Toast } from '@elastic/eui'; // @public @@ -31,7 +32,7 @@ export interface AppBase { // @public (undocumented) export interface ApplicationSetup { register(app: App): void; - registerMountContext(contextName: T, provider: IContextProvider): void; + registerMountContext(contextName: T, provider: IContextProvider): void; } // @public (undocumented) @@ -44,7 +45,7 @@ export interface ApplicationStart { path?: string; state?: any; }): void; - registerMountContext(contextName: T, provider: IContextProvider): void; + registerMountContext(contextName: T, provider: IContextProvider): void; } // @public @@ -58,6 +59,9 @@ export interface AppMountContext { notifications: NotificationsStart; overlays: OverlayStart; uiSettings: UiSettingsClientContract; + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; }; } @@ -213,7 +217,7 @@ export interface ChromeStart { // @public export interface ContextSetup { - createContextContainer(): IContextContainer; + createContextContainer>(): IContextContainer; } // @internal (undocumented) @@ -234,6 +238,10 @@ export interface CoreSetup { fatalErrors: FatalErrorsSetup; // (undocumented) http: HttpSetup; + // @deprecated + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; // (undocumented) notifications: NotificationsSetup; // (undocumented) @@ -252,6 +260,10 @@ export interface CoreStart { http: HttpStart; // (undocumented) i18n: I18nStart; + // @deprecated + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; // (undocumented) notifications: NotificationsStart; // (undocumented) @@ -389,6 +401,15 @@ export interface FatalErrorsSetup { get$: () => Rx.Observable; } +// @public +export type HandlerContextType> = T extends HandlerFunction ? U : never; + +// @public +export type HandlerFunction = (context: T, ...args: any[]) => any; + +// @public +export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; + // @public (undocumented) export type HttpBody = BodyInit | null | any; @@ -551,16 +572,13 @@ export interface I18nStart { } // @public -export interface IContextContainer { - createHandler(pluginOpaqueId: PluginOpaqueId, handler: IContextHandler): (...rest: THandlerParameters) => THandlerReturn extends Promise ? THandlerReturn : Promise; - registerContext(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +export interface IContextContainer> { + createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; + registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; } // @public -export type IContextHandler = (context: TContext, ...rest: THandlerParameters) => TReturn; - -// @public -export type IContextProvider, TContextName extends keyof TContext, TProviderParameters extends any[] = []> = (context: Partial, ...rest: TProviderParameters) => Promise | TContext[TContextName]; +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; // @public @deprecated export interface LegacyCoreSetup extends CoreSetup { diff --git a/src/core/server/context/context_service.ts b/src/core/server/context/context_service.ts index 80935840c553..1c5bd41a01f0 100644 --- a/src/core/server/context/context_service.ts +++ b/src/core/server/context/context_service.ts @@ -18,7 +18,7 @@ */ import { PluginOpaqueId } from '../../server'; -import { IContextContainer, ContextContainer } from '../../utils/context'; +import { IContextContainer, ContextContainer, HandlerFunction } from '../../utils/context'; import { CoreContext } from '../core_context'; interface SetupDeps { @@ -31,15 +31,8 @@ export class ContextService { public setup({ pluginDependencies }: SetupDeps): ContextSetup { return { - createContextContainer: < - TContext extends {}, - THandlerReturn, - THandlerParameters extends any[] = [] - >() => { - return new ContextContainer( - pluginDependencies, - this.core.coreId - ); + createContextContainer: >() => { + return new ContextContainer(pluginDependencies, this.core.coreId); }, }; } @@ -112,9 +105,5 @@ export interface ContextSetup { /** * Creates a new {@link IContextContainer} for a service owner. */ - createContextContainer< - TContext extends {}, - THandlerReturn, - THandlerParmaters extends any[] = [] - >(): IContextContainer; + createContextContainer>(): IContextContainer; } diff --git a/src/core/server/context/index.ts b/src/core/server/context/index.ts index 28b2641b2a5a..f22c4168d754 100644 --- a/src/core/server/context/index.ts +++ b/src/core/server/context/index.ts @@ -18,4 +18,10 @@ */ export { ContextService, ContextSetup } from './context_service'; -export { IContextContainer, IContextProvider, IContextHandler } from '../../utils/context'; +export { + IContextContainer, + IContextProvider, + HandlerFunction, + HandlerContextType, + HandlerParameters, +} from '../../utils/context'; diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 5814991a2dd2..0ac5ad927635 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -83,8 +83,8 @@ export type HttpServiceSetup = Omit & { registerRouteHandlerContext: ( pluginOpaqueId: PluginOpaqueId, contextName: T, - provider: RequestHandlerContextProvider - ) => RequestHandlerContextContainer; + provider: RequestHandlerContextProvider + ) => RequestHandlerContextContainer; }; /** @public */ @@ -103,7 +103,7 @@ export class HttpService implements CoreService; + private requestHandlerContext?: RequestHandlerContextContainer; constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger; @@ -150,7 +150,7 @@ export class HttpService implements CoreService( pluginOpaqueId: PluginOpaqueId, contextName: T, - provider: RequestHandlerContextProvider + provider: RequestHandlerContextProvider ) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider), }; diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index a391b2e2e5d4..cade4ea4d4f2 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -16,30 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { IContextProvider, IContextContainer } from '../context'; -import { KibanaRequest, KibanaResponseFactory, KibanaResponse } from './router'; - -/** - * Parameters passed to the request handler function. - * @public - */ -export type RequestHandlerParams = [KibanaRequest, KibanaResponseFactory]; -/** - * Expected outcome the request handler function. - * @public - */ -export type RequestHandlerReturn = KibanaResponse; +import { IContextProvider, IContextContainer } from '../context'; +import { RequestHandler } from './router'; +import { RequestHandlerContext } from '..'; /** * An object that handles registration of http request context providers. * @public */ -export type RequestHandlerContextContainer = IContextContainer< - TContext, - RequestHandlerReturn | Promise, - RequestHandlerParams ->; +export type RequestHandlerContextContainer = IContextContainer>; /** * Context provider for request handler. @@ -47,8 +33,6 @@ export type RequestHandlerContextContainer = IContextContainer< * * @public */ -export type RequestHandlerContextProvider = IContextProvider< - TContext, - keyof TContext, - RequestHandlerParams ->; +export type RequestHandlerContextProvider< + TContextName extends keyof RequestHandlerContext +> = IContextProvider, TContextName>; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index d3fe64ddc1e0..ca497e0f2d32 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -59,7 +59,13 @@ import { SavedObjectsServiceStart } from './saved_objects'; export { bootstrap } from './bootstrap'; export { ConfigPath, ConfigService } from './config'; -export { IContextContainer, IContextProvider, IContextHandler } from './context'; +export { + IContextContainer, + IContextProvider, + HandlerFunction, + HandlerContextType, + HandlerParameters, +} from './context'; export { CoreId } from './core_context'; export { CallAPIOptions, @@ -102,8 +108,6 @@ export { RequestHandler, RequestHandlerContextContainer, RequestHandlerContextProvider, - RequestHandlerParams, - RequestHandlerReturn, ResponseError, ResponseErrorAttributes, ResponseHeaders, @@ -212,8 +216,8 @@ export interface CoreSetup { isTlsEnabled: HttpServiceSetup['isTlsEnabled']; registerRouteHandlerContext: ( name: T, - provider: RequestHandlerContextProvider - ) => RequestHandlerContextContainer; + provider: RequestHandlerContextProvider + ) => RequestHandlerContextContainer; createRouter: () => IRouter; }; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ae839644fc2e..6451e2b9b715 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -21,6 +21,7 @@ import { Request } from 'hapi'; import { ResponseObject } from 'hapi'; import { ResponseToolkit } from 'hapi'; import { Server } from 'hapi'; +import { ShallowPromise } from '@kbn/utility-types'; import { Stream } from 'stream'; import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; @@ -61,11 +62,11 @@ export interface AuthToolkit { export class BasePath { // @internal constructor(serverBasePath?: string); - get: (request: KibanaRequest | LegacyRequest) => string; + get: (request: LegacyRequest | KibanaRequest) => string; prepend: (path: string) => string; remove: (path: string) => string; readonly serverBasePath: string; - set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; + set: (request: LegacyRequest | KibanaRequest, requestSpecificBasePath: string) => void; } // Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts @@ -109,7 +110,7 @@ export class ConfigService { // @public export interface ContextSetup { - createContextContainer(): IContextContainer; + createContextContainer>(): IContextContainer; } // @internal (undocumented) @@ -135,7 +136,7 @@ export interface CoreSetup { registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; basePath: HttpServiceSetup['basePath']; isTlsEnabled: HttpServiceSetup['isTlsEnabled']; - registerRouteHandlerContext: (name: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; + registerRouteHandlerContext: (name: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; createRouter: () => IRouter; }; } @@ -219,6 +220,15 @@ export type GetAuthState = (request: KibanaRequest | LegacyRequest) => { state: unknown; }; +// @public +export type HandlerContextType> = T extends HandlerFunction ? U : never; + +// @public +export type HandlerFunction = (context: T, ...args: any[]) => any; + +// @public +export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; + // @public export type Headers = { [header in KnownHeaders]?: string | string[] | undefined; @@ -258,7 +268,7 @@ export interface HttpServerSetup { // @public (undocumented) export type HttpServiceSetup = Omit & { createRouter: (path: string, plugin?: PluginOpaqueId) => IRouter; - registerRouteHandlerContext: (pluginOpaqueId: PluginOpaqueId, contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; + registerRouteHandlerContext: (pluginOpaqueId: PluginOpaqueId, contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; }; // @public (undocumented) @@ -270,16 +280,13 @@ export interface HttpServiceStart { export type IBasePath = Pick; // @public -export interface IContextContainer { - createHandler(pluginOpaqueId: PluginOpaqueId, handler: IContextHandler): (...rest: THandlerParameters) => THandlerReturn extends Promise ? THandlerReturn : Promise; - registerContext(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +export interface IContextContainer> { + createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; + registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; } // @public -export type IContextHandler = (context: TContext, ...rest: THandlerParameters) => TReturn; - -// @public -export type IContextProvider, TContextName extends keyof TContext, TProviderParameters extends any[] = []> = (context: Partial, ...rest: TProviderParameters) => Promise | TContext[TContextName]; +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; // @public export interface IKibanaSocket { @@ -602,16 +609,10 @@ export interface RequestHandlerContext { } // @public -export type RequestHandlerContextContainer = IContextContainer, RequestHandlerParams>; - -// @public -export type RequestHandlerContextProvider = IContextProvider; - -// @public -export type RequestHandlerParams = [KibanaRequest, KibanaResponseFactory]; +export type RequestHandlerContextContainer = IContextContainer>; // @public -export type RequestHandlerReturn = KibanaResponse; +export type RequestHandlerContextProvider = IContextProvider, TContextName>; // @public export type ResponseError = string | Error | { diff --git a/src/core/utils/context.mock.ts b/src/core/utils/context.mock.ts index 4d91c11542b2..de844f3f0f07 100644 --- a/src/core/utils/context.mock.ts +++ b/src/core/utils/context.mock.ts @@ -19,7 +19,7 @@ import { IContextContainer } from './context'; -export type ContextContainerMock = jest.Mocked>; +export type ContextContainerMock = jest.Mocked>; const createContextMock = () => { const contextMock: ContextContainerMock = { diff --git a/src/core/utils/context.test.ts b/src/core/utils/context.test.ts index 1249c14736fb..4bfeddc2e08c 100644 --- a/src/core/utils/context.test.ts +++ b/src/core/utils/context.test.ts @@ -44,7 +44,7 @@ const coreId = Symbol(); describe('ContextContainer', () => { it('does not allow the same context to be registered twice', () => { - const contextContainer = new ContextContainer(plugins, coreId); + const contextContainer = new ContextContainer<(context: MyContext) => string>(plugins, coreId); contextContainer.registerContext(coreId, 'ctxFromA', () => 'aString'); expect(() => @@ -56,7 +56,10 @@ describe('ContextContainer', () => { describe('registerContext', () => { it('throws error if called with an unknown symbol', async () => { - const contextContainer = new ContextContainer(plugins, coreId); + const contextContainer = new ContextContainer<(context: MyContext) => string>( + plugins, + coreId + ); await expect(() => contextContainer.registerContext(Symbol('unknown'), 'ctxFromA', jest.fn()) ).toThrowErrorMatchingInlineSnapshot( @@ -67,7 +70,10 @@ describe('ContextContainer', () => { describe('context building', () => { it('resolves dependencies', async () => { - const contextContainer = new ContextContainer(plugins, coreId); + const contextContainer = new ContextContainer<(context: MyContext) => string>( + plugins, + coreId + ); expect.assertions(8); contextContainer.registerContext(coreId, 'core1', context => { expect(context).toEqual({}); @@ -118,7 +124,10 @@ describe('ContextContainer', () => { it('exposes all core context to all providers regardless of registration order', async () => { expect.assertions(4); - const contextContainer = new ContextContainer(plugins, coreId); + const contextContainer = new ContextContainer<(context: MyContext) => string>( + plugins, + coreId + ); contextContainer .registerContext(pluginA, 'ctxFromA', context => { expect(context).toEqual({ core1: 'core', core2: 101 }); @@ -146,7 +155,10 @@ describe('ContextContainer', () => { it('exposes all core context to core providers', async () => { expect.assertions(4); - const contextContainer = new ContextContainer(plugins, coreId); + const contextContainer = new ContextContainer<(context: MyContext) => string>( + plugins, + coreId + ); contextContainer .registerContext(coreId, 'core1', context => { @@ -171,7 +183,10 @@ describe('ContextContainer', () => { }); it('does not expose plugin contexts to core handler', async () => { - const contextContainer = new ContextContainer(plugins, coreId); + const contextContainer = new ContextContainer<(context: MyContext) => string>( + plugins, + coreId + ); contextContainer .registerContext(coreId, 'core1', context => 'core') @@ -189,10 +204,9 @@ describe('ContextContainer', () => { it('passes additional arguments to providers', async () => { expect.assertions(6); - const contextContainer = new ContextContainer( - plugins, - coreId - ); + const contextContainer = new ContextContainer< + (context: MyContext, arg1: string, arg2: number) => string + >(plugins, coreId); contextContainer.registerContext(coreId, 'core1', (context, str, num) => { expect(str).toEqual('passed string'); @@ -228,7 +242,10 @@ describe('ContextContainer', () => { describe('createHandler', () => { it('throws error if called with an unknown symbol', async () => { - const contextContainer = new ContextContainer(plugins, coreId); + const contextContainer = new ContextContainer<(context: MyContext) => string>( + plugins, + coreId + ); await expect(() => contextContainer.createHandler(Symbol('unknown'), jest.fn()) ).toThrowErrorMatchingInlineSnapshot( @@ -237,8 +254,10 @@ describe('ContextContainer', () => { }); it('returns value from original handler', async () => { - const contextContainer = new ContextContainer(plugins, coreId); - + const contextContainer = new ContextContainer<(context: MyContext) => string>( + plugins, + coreId + ); const rawHandler1 = jest.fn(() => 'handler1'); const handler1 = contextContainer.createHandler(pluginA, rawHandler1); @@ -246,10 +265,9 @@ describe('ContextContainer', () => { }); it('passes additional arguments to handlers', async () => { - const contextContainer = new ContextContainer( - plugins, - coreId - ); + const contextContainer = new ContextContainer< + (context: MyContext, arg1: string, arg2: number) => string + >(plugins, coreId); const rawHandler1 = jest.fn(() => 'handler1'); const handler1 = contextContainer.createHandler(pluginA, rawHandler1); diff --git a/src/core/utils/context.ts b/src/core/utils/context.ts index 6d1732ea06b0..022c3e433003 100644 --- a/src/core/utils/context.ts +++ b/src/core/utils/context.ts @@ -18,6 +18,7 @@ */ import { flatten } from 'lodash'; +import { ShallowPromise } from '@kbn/utility-types'; import { pick } from '.'; import { CoreId, PluginOpaqueId } from '../server'; @@ -35,26 +36,44 @@ import { CoreId, PluginOpaqueId } from '../server'; * @public */ export type IContextProvider< - TContext extends Record, - TContextName extends keyof TContext, - TProviderParameters extends any[] = [] + THandler extends HandlerFunction, + TContextName extends keyof HandlerContextType > = ( - context: Partial, - ...rest: TProviderParameters -) => Promise | TContext[TContextName]; + context: Partial>, + ...rest: HandlerParameters +) => + | Promise[TContextName]> + | HandlerContextType[TContextName]; /** - * A function registered by a plugin to perform some action. + * A function that accepts a context object and an optional number of additional arguments. Used for the generic types + * in {@link IContextContainer} * - * @remarks - * A new `TContext` will be built for each handler before invoking. + * @public + */ +export type HandlerFunction = (context: T, ...args: any[]) => any; + +/** + * Extracts the type of the first argument of a {@link HandlerFunction} to represent the type of the context. * * @public */ -export type IContextHandler = ( - context: TContext, - ...rest: THandlerParameters -) => TReturn; +export type HandlerContextType> = T extends HandlerFunction + ? U + : never; + +/** + * Extracts the types of the additional arguments of a {@link HandlerFunction}, excluding the + * {@link HandlerContextType}. + * + * @public + */ +export type HandlerParameters> = T extends ( + context: any, + ...args: infer U +) => any + ? U + : never; /** * An object that handles registration of context providers and configuring handlers with context. @@ -123,13 +142,12 @@ export type IContextHandler { +export interface IContextContainer> { /** * Register a new context provider. * @@ -144,10 +162,10 @@ export interface IContextContainer< * @param provider - A {@link IContextProvider} to be called each time a new context is created. * @returns The {@link IContextContainer} for method chaining. */ - registerContext( + registerContext>( pluginOpaqueId: PluginOpaqueId, contextName: TContextName, - provider: IContextProvider + provider: IContextProvider ): this; /** @@ -160,31 +178,26 @@ export interface IContextContainer< */ createHandler( pluginOpaqueId: PluginOpaqueId, - handler: IContextHandler - ): ( - ...rest: THandlerParameters - ) => THandlerReturn extends Promise ? THandlerReturn : Promise; + handler: THandler + ): (...rest: HandlerParameters) => ShallowPromise>; } /** @internal */ -export class ContextContainer< - TContext extends Record, - THandlerReturn, - THandlerParameters extends any[] = [] -> implements IContextContainer { +export class ContextContainer> + implements IContextContainer { /** * Used to map contexts to their providers and associated plugin. In registration order which is tightly coupled to * plugin load order. */ private readonly contextProviders = new Map< - keyof TContext, + keyof HandlerContextType, { - provider: IContextProvider; + provider: IContextProvider>; source: symbol; } >(); /** Used to keep track of which plugins registered which contexts for dependency resolution. */ - private readonly contextNamesBySource: Map>; + private readonly contextNamesBySource: Map>>; /** * @param pluginDependencies - A map of plugins to an array of their dependencies. @@ -193,13 +206,15 @@ export class ContextContainer< private readonly pluginDependencies: ReadonlyMap, private readonly coreId: CoreId ) { - this.contextNamesBySource = new Map>([[coreId, []]]); + this.contextNamesBySource = new Map>>([ + [coreId, []], + ]); } - public registerContext = ( + public registerContext = >( source: symbol, contextName: TContextName, - provider: IContextProvider + provider: IContextProvider ): this => { if (this.contextProviders.has(contextName)) { throw new Error(`Context provider for ${contextName} has already been registered.`); @@ -217,27 +232,22 @@ export class ContextContainer< return this; }; - public createHandler = ( - source: symbol, - handler: IContextHandler - ) => { + public createHandler = (source: symbol, handler: THandler) => { if (source !== this.coreId && !this.pluginDependencies.has(source)) { throw new Error(`Cannot create handler for unknown plugin: ${source.toString()}`); } - return (async (...args: THandlerParameters) => { + return (async (...args: HandlerParameters) => { const context = await this.buildContext(source, ...args); return handler(context, ...args); - }) as ( - ...args: THandlerParameters - ) => THandlerReturn extends Promise ? THandlerReturn : Promise; + }) as (...args: HandlerParameters) => ShallowPromise>; }; private async buildContext( source: symbol, - ...contextArgs: THandlerParameters - ): Promise { - const contextsToBuild: ReadonlySet = new Set( + ...contextArgs: HandlerParameters + ): Promise> { + const contextsToBuild: ReadonlySet> = new Set( this.getContextNamesForSource(source) ); @@ -252,18 +262,20 @@ export class ContextContainer< // registered that provider. const exposedContext = pick(resolvedContext, [ ...this.getContextNamesForSource(providerSource), - ]); + ]) as Partial>; return { ...resolvedContext, - [contextName]: await provider(exposedContext as Partial, ...contextArgs), + [contextName]: await provider(exposedContext, ...contextArgs), }; }, - Promise.resolve({}) as Promise + Promise.resolve({}) as Promise> ); } - private getContextNamesForSource(source: symbol): ReadonlySet { + private getContextNamesForSource( + source: symbol + ): ReadonlySet> { if (source === this.coreId) { return this.getContextNamesForCore(); } else { diff --git a/src/dev/build/lib/fs.js b/src/dev/build/lib/fs.js index de0ae5fc72e9..1aeccdb76a4f 100644 --- a/src/dev/build/lib/fs.js +++ b/src/dev/build/lib/fs.js @@ -26,14 +26,13 @@ import { inspect } from 'util'; import vfs from 'vinyl-fs'; import { promisify } from 'bluebird'; -import mkdirpCb from 'mkdirp'; import del from 'del'; import deleteEmpty from 'delete-empty'; import { createPromiseFromStreams, createMapStream } from '../../../legacy/utils'; import tar from 'tar'; -const mkdirpAsync = promisify(mkdirpCb); +const mkdirAsync = promisify(fs.mkdir); const writeFileAsync = promisify(fs.writeFile); const readFileAsync = promisify(fs.readFile); const readdirAsync = promisify(fs.readdir); @@ -66,7 +65,7 @@ function longInspect(value) { export async function mkdirp(path) { assertAbsolute(path); - await mkdirpAsync(path); + await mkdirAsync(path, { recursive: true }); } export async function write(path, contents) { @@ -185,7 +184,7 @@ export async function untar(source, destination, extractOptions = {}) { assertAbsolute(source); assertAbsolute(destination); - await mkdirpAsync(destination); + await mkdirAsync(destination, { recursive: true }); await createPromiseFromStreams([ fs.createReadStream(source), diff --git a/src/dev/build/tasks/nodejs/download.js b/src/dev/build/tasks/nodejs/download.js index ee6da41042cd..48313c0911c2 100644 --- a/src/dev/build/tasks/nodejs/download.js +++ b/src/dev/build/tasks/nodejs/download.js @@ -23,7 +23,8 @@ import { dirname } from 'path'; import chalk from 'chalk'; import { createHash } from 'crypto'; import wreck from '@hapi/wreck'; -import mkdirp from 'mkdirp'; + +import { mkdirp } from '../../lib'; function tryUnlink(path) { try { @@ -43,7 +44,7 @@ export async function download(options) { } // mkdirp and open file outside of try/catch, we don't retry for those errors - mkdirp.sync(dirname(destination)); + await mkdirp(dirname(destination)); const fileHandle = openSync(destination, 'w'); let error; diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.js b/src/dev/build/tasks/nodejs/extract_node_builds_task.js index f8e767ac79a4..46c6433b27f9 100644 --- a/src/dev/build/tasks/nodejs/extract_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.js @@ -20,13 +20,11 @@ import { dirname, resolve } from 'path'; import fs from 'fs'; import { promisify } from 'util'; -import mkdirp from 'mkdirp'; -import { untar } from '../../lib'; +import { untar, mkdirp } from '../../lib'; import { getNodeDownloadInfo } from './node_download_info'; const statAsync = promisify(fs.stat); -const mkdirpAsync = promisify(mkdirp); const copyFileAsync = promisify(fs.copyFile); export const ExtractNodeBuildsTask = { @@ -50,7 +48,7 @@ export const ExtractNodeBuildsTask = { async copyWindows(source, destination) { // ensure source exists before creating destination directory await statAsync(source); - await mkdirpAsync(dirname(destination)); + await mkdirp(dirname(destination)); // for performance reasons, do a copy-on-write by using the fs.constants.COPYFILE_FICLONE flag return await copyFileAsync(source, destination, fs.constants.COPYFILE_FICLONE); }, diff --git a/src/dev/build/tasks/patch_native_modules_task.js b/src/dev/build/tasks/patch_native_modules_task.js index 7da51ac717c4..4d952f44626c 100644 --- a/src/dev/build/tasks/patch_native_modules_task.js +++ b/src/dev/build/tasks/patch_native_modules_task.js @@ -18,10 +18,9 @@ */ import { scanCopy, untar, deleteAll } from '../lib'; -import { createWriteStream } from 'fs'; +import { createWriteStream, mkdirSync } from 'fs'; import { binaryInfo } from '../../../../x-pack/legacy/plugins/code/tasks/nodegit_info'; import wreck from '@hapi/wreck'; -import mkdirp from 'mkdirp'; import { dirname, join, basename } from 'path'; import { createPromiseFromStreams } from '../../../legacy/utils/streams'; @@ -31,7 +30,7 @@ async function download(url, destination, log) { if (response.statusCode !== 200) { throw new Error(`Unexpected status code ${response.statusCode} when downloading ${url}`); } - mkdirp.sync(dirname(destination)); + mkdirSync(dirname(destination), { recursive: true }); await createPromiseFromStreams([response, createWriteStream(destination)]); log.debug('Downloaded ', url); } diff --git a/src/dev/jest/junit_reporter.js b/src/dev/jest/junit_reporter.js index 4fda2315dd54..8846beaed8b3 100644 --- a/src/dev/jest/junit_reporter.js +++ b/src/dev/jest/junit_reporter.js @@ -18,9 +18,8 @@ */ import { resolve, dirname, relative } from 'path'; -import { writeFileSync } from 'fs'; +import { writeFileSync, mkdirSync } from 'fs'; -import mkdirp from 'mkdirp'; import xmlBuilder from 'xmlbuilder'; import { escapeCdata } from '@kbn/test'; @@ -117,7 +116,7 @@ export default class JestJUnitReporter { spacebeforeslash: '', }); - mkdirp.sync(dirname(reportPath)); + mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); } } diff --git a/src/dev/notice/generate_notice_from_source.js b/src/dev/notice/generate_notice_from_source.ts similarity index 83% rename from src/dev/notice/generate_notice_from_source.js rename to src/dev/notice/generate_notice_from_source.ts index 75bc7c8cb2f1..6dd47db929f4 100644 --- a/src/dev/notice/generate_notice_from_source.js +++ b/src/dev/notice/generate_notice_from_source.ts @@ -18,25 +18,30 @@ */ import vfs from 'vinyl-fs'; +import { ToolingLog } from '@kbn/dev-utils'; const NOTICE_COMMENT_RE = /\/\*[\s\n\*]*@notice([\w\W]+?)\*\//g; const NEWLINE_RE = /\r?\n/g; +interface Options { + /** + * Name to print at the top of the notice + */ + productName: string; + /** + * absolute path to the repo to search for @notice comments + */ + directory: string; + log: ToolingLog; +} + /** * Generates the text for the NOTICE.txt file at the root of the * repo which details the licenses for code that is copied/vendored * into the repository. - * - * @param {Object} options - * @property {string} options.productName Name to print at the top of the notice - * @property {ToolingLog} options.log - * @property {string} options.directory absolute path to the repo to search for @notice comments - * @return {string} */ -export async function generateNoticeFromSource({ productName, directory, log }) { - const globs = [ - '**/*.{js,less,css,ts}', - ]; +export async function generateNoticeFromSource({ productName, directory, log }: Options) { + const globs = ['**/*.{js,less,css,ts}']; const options = { cwd: directory, @@ -46,7 +51,7 @@ export async function generateNoticeFromSource({ productName, directory, log }) 'packages/*/{node_modules,build,target,dist}/**', 'x-pack/{node_modules,build,target,dist,data}/**', 'x-pack/packages/*/{node_modules,build,target,dist}/**', - ] + ], }; log.debug('vfs.src globs', globs); @@ -54,10 +59,10 @@ export async function generateNoticeFromSource({ productName, directory, log }) log.info(`Searching ${directory} for multi-line comments starting with @notice`); const files = vfs.src(globs, options); - const noticeComments = []; + const noticeComments: string[] = []; await new Promise((resolve, reject) => { files - .on('data', (file) => { + .on('data', file => { log.verbose(`Checking for @notice comments in ${file.relative}`); const source = file.contents.toString('utf8'); @@ -75,19 +80,19 @@ export async function generateNoticeFromSource({ productName, directory, log }) let noticeText = ''; noticeText += `${productName}\n`; - noticeText += `Copyright 2012-${(new Date()).getUTCFullYear()} Elasticsearch B.V.\n`; + noticeText += `Copyright 2012-${new Date().getUTCFullYear()} Elasticsearch B.V.\n`; for (const comment of noticeComments.sort()) { noticeText += '\n---\n'; noticeText += comment .split(NEWLINE_RE) - .map(line => ( + .map(line => line // trim whitespace .trim() // trim leading * and a single space .replace(/(^\* ?)/, '') - )) + ) .join('\n') .trim(); noticeText += '\n'; diff --git a/src/dev/notice/index.js b/src/dev/notice/index.ts similarity index 97% rename from src/dev/notice/index.js rename to src/dev/notice/index.ts index 17fbd20c2206..9f3fd61a9831 100644 --- a/src/dev/notice/index.js +++ b/src/dev/notice/index.ts @@ -18,4 +18,5 @@ */ export { generateNoticeFromSource } from './generate_notice_from_source'; +// @ts-ignore not typed yet export { generateBuildNoticeText } from './generate_build_notice_text'; diff --git a/src/dev/register_git_hook/register_git_hook.js b/src/dev/register_git_hook/register_git_hook.js index bdb3e2d80603..a61922078e68 100644 --- a/src/dev/register_git_hook/register_git_hook.js +++ b/src/dev/register_git_hook/register_git_hook.js @@ -37,7 +37,7 @@ const writeFileAsync = promisify(writeFile); async function getPrecommitGitHookScriptPath(rootPath) { // Retrieves the correct location for the .git dir for // every git setup (including git worktree) - const gitDirPath = (await gitRevParseAsync(['--git-dir'])).trim(); + const gitDirPath = (await gitRevParseAsync(['--git-common-dir'])).trim(); return resolve(rootPath, gitDirPath, 'hooks/pre-commit'); } diff --git a/src/dev/renovate/config.ts b/src/dev/renovate/config.ts index a86c080f5271..1bf3af9fe524 100644 --- a/src/dev/renovate/config.ts +++ b/src/dev/renovate/config.ts @@ -86,6 +86,7 @@ export const RENOVATE_CONFIG = { labels: group.extraLabels && [...DEFAULT_LABELS, ...group.extraLabels], enabled: group.enabled === false ? false : undefined, allowedVersions: group.allowedVersions || undefined, + reviewers: group.reviewers || undefined, })), // internal/local packages diff --git a/src/dev/renovate/package_groups.ts b/src/dev/renovate/package_groups.ts index 85692e94eb91..100eb8764e08 100644 --- a/src/dev/renovate/package_groups.ts +++ b/src/dev/renovate/package_groups.ts @@ -56,6 +56,11 @@ interface PackageGroup { * https://renovatebot.com/docs/configuration-options/#allowedversions */ readonly allowedVersions?: string; + + /** + * An array of users to request reviews from + */ + readonly reviewers?: string[]; } export const RENOVATE_PACKAGE_GROUPS: PackageGroup[] = [ @@ -75,6 +80,12 @@ export const RENOVATE_PACKAGE_GROUPS: PackageGroup[] = [ packageWords: ['jest'], }, + { + name: '@elastic/charts', + packageNames: ['@elastic/charts'], + reviewers: ['markov00'], + }, + { name: 'mocha', packageWords: ['mocha'], @@ -166,6 +177,12 @@ export const RENOVATE_PACKAGE_GROUPS: PackageGroup[] = [ name: 'storybook', packageWords: ['storybook'], }, + + { + name: 'typescript', + packageWords: ['ts', 'typescript'], + packageNames: ['tslib'], + }, ]; /** diff --git a/src/es_archiver/actions/save.js b/src/es_archiver/actions/save.js index 610e85f3a89a..c515a376ecd7 100644 --- a/src/es_archiver/actions/save.js +++ b/src/es_archiver/actions/save.js @@ -18,10 +18,7 @@ */ import { resolve } from 'path'; -import { createWriteStream } from 'fs'; - -import { fromNode } from 'bluebird'; -import mkdirp from 'mkdirp'; +import { createWriteStream, mkdirSync } from 'fs'; import { createListStream, @@ -42,7 +39,7 @@ export async function saveAction({ name, indices, client, dataDir, log, raw }) { log.info('[%s] Creating archive of %j', name, indices); - await fromNode(cb => mkdirp(outputDir, cb)); + mkdirSync(outputDir, { recursive: true }); const progress = new Progress(); progress.activate(log); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 3d9dfe631912..bb041924215d 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -17,9 +17,9 @@ * under the License. */ -import Promise from 'bluebird'; -import { mkdirp as mkdirpNode } from 'mkdirp'; +import Fs from 'fs'; import { resolve } from 'path'; +import { promisify } from 'util'; import { migrations } from './migrations'; import manageUuid from './server/lib/manage_uuid'; @@ -41,7 +41,7 @@ import { makeKQLUsageCollector } from './server/lib/kql_usage_collector'; import { injectVars } from './inject_vars'; import { i18n } from '@kbn/i18n'; -const mkdirp = Promise.promisify(mkdirpNode); +const mkdirAsync = promisify(Fs.mkdir); export default function (kibana) { const kbnBaseUrl = '/app/kibana'; @@ -320,7 +320,7 @@ export default function (kibana) { try { // Create the data directory (recursively, if the a parent dir doesn't exist). // If it already exists, does nothing. - await mkdirp(server.config().get('path.data')); + await mkdirAsync(server.config().get('path.data'), { recursive: true }); } catch (err) { server.log(['error', 'init'], err); // Stop the server startup with a fatal error diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 741931af11c7..22f127d12c43 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -331,7 +331,8 @@ export class DashboardAppController { getDashboardTitle( dashboardStateManager.getTitle(), dashboardStateManager.getViewMode(), - dashboardStateManager.getIsDirty(timefilter) + dashboardStateManager.getIsDirty(timefilter), + dashboardStateManager.isNew() ); // Push breadcrumbs to new header navigation diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index c1ce5b764f2f..7c1fc771de34 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -229,6 +229,14 @@ export class DashboardStateManager { return this.appState.title; } + public isSaved() { + return !!this.savedDashboard.id; + } + + public isNew() { + return !this.isSaved(); + } + public getDescription() { return this.appState.description; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts index b7f9293539ab..d932116d08dc 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts @@ -27,22 +27,31 @@ import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; * end of the title. * @returns {string} A title to display to the user based on the above parameters. */ -export function getDashboardTitle(title: string, viewMode: ViewMode, isDirty: boolean): string { +export function getDashboardTitle( + title: string, + viewMode: ViewMode, + isDirty: boolean, + isNew: boolean +): string { const isEditMode = viewMode === ViewMode.EDIT; let displayTitle: string; + const newDashboardTitle = i18n.translate('kbn.dashboard.savedDashboard.newDashboardTitle', { + defaultMessage: 'New Dashboard', + }); + const dashboardTitle = isNew ? newDashboardTitle : title; if (isEditMode && isDirty) { displayTitle = i18n.translate('kbn.dashboard.strings.dashboardUnsavedEditTitle', { defaultMessage: 'Editing {title} (unsaved)', - values: { title }, + values: { title: dashboardTitle }, }); } else if (isEditMode) { displayTitle = i18n.translate('kbn.dashboard.strings.dashboardEditTitle', { defaultMessage: 'Editing {title}', - values: { title }, + values: { title: dashboardTitle }, }); } else { - displayTitle = title; + displayTitle = dashboardTitle; } return displayTitle; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js index 36a083ef5a39..fe9e7b18d500 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js @@ -18,7 +18,6 @@ */ import angular from 'angular'; -import { i18n } from '@kbn/i18n'; import { uiModules } from 'ui/modules'; import { createDashboardEditUrl } from '../dashboard_constants'; import { createLegacyClass } from 'ui/utils/legacy_class'; @@ -50,7 +49,7 @@ module.factory('SavedDashboard', function (Private) { // default values that will get assigned if the doc is new defaults: { - title: i18n.translate('kbn.dashboard.savedDashboard.newDashboardTitle', { defaultMessage: 'New Dashboard' }), + title: '', hits: 0, description: '', panelsJSON: '[]', diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js index abbf2dcaafc8..92558559845b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js @@ -35,7 +35,7 @@ import { EuiFormRow, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; import { METRIC_TYPES } from '../../../common/metric_types'; export const FilterRatioAgg = props => { @@ -56,7 +56,7 @@ export const FilterRatioAgg = props => { const model = { ...defaults, ...props.model }; const htmlId = htmlIdGenerator(); - const restrictFields = model.metric_agg === METRIC_TYPES.CARDINALITY ? [] : [ES_FIELD_TYPES.NUMBER]; + const restrictFields = model.metric_agg === METRIC_TYPES.CARDINALITY ? [] : [KBN_FIELD_TYPES.NUMBER]; return ( { const { series, panel, fields } = props; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js index 9cbb8ffb716f..4b8b356f2af4 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js @@ -33,14 +33,14 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; import { METRIC_TYPES } from '../../../common/metric_types'; export function StandardAgg(props) { const { model, panel, series, fields, uiRestrictions } = props; const handleChange = createChangeHandler(props.onChange, model); const handleSelectChange = createSelectHandler(handleChange); - const restrictFields = model.type === METRIC_TYPES.CARDINALITY ? [] : [ES_FIELD_TYPES.NUMBER]; + const restrictFields = model.type === METRIC_TYPES.CARDINALITY ? [] : [KBN_FIELD_TYPES.NUMBER]; const indexPattern = (series.override_index_pattern && series.series_index_pattern) || panel.index_pattern; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js index 8ccb37adeda4..c078bac8d7f9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js @@ -36,9 +36,9 @@ import { EuiSpacer, } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; -const RESTRICT_FIELDS = [ES_FIELD_TYPES.NUMBER]; +const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; const StandardDeviationAggUi = props => { const { series, panel, fields, intl } = props; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js index 65bec274021e..1439768b9564 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js @@ -35,7 +35,7 @@ import { EuiFormRow, } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; import { PANEL_TYPES } from '../../../common/panel_types'; const isFieldTypeEnabled = (fieldRestrictions, fieldType) => @@ -44,7 +44,7 @@ const isFieldTypeEnabled = (fieldRestrictions, fieldType) => const getAggWithOptions = (field = {}, fieldTypesRestriction) => { if (isFieldTypeEnabled(fieldTypesRestriction, field.type)) { switch (field.type) { - case ES_FIELD_TYPES.NUMBER: + case KBN_FIELD_TYPES.NUMBER: return [ { label: i18n.translate('visTypeTimeseries.topHit.aggWithOptions.averageLabel', { @@ -71,8 +71,7 @@ const getAggWithOptions = (field = {}, fieldTypesRestriction) => { value: 'sum', }, ]; - case ES_FIELD_TYPES.KEYWORD: - case ES_FIELD_TYPES.STRING: + case KBN_FIELD_TYPES.STRING: return [ { label: i18n.translate('visTypeTimeseries.topHit.aggWithOptions.concatenate', { @@ -102,7 +101,7 @@ const getOrderOptions = () => [ }, ]; -const ORDER_DATE_RESTRICT_FIELDS = [ES_FIELD_TYPES.DATE]; +const ORDER_DATE_RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; const TopHitAggUi = props => { const { fields, series, panel } = props; @@ -120,8 +119,8 @@ const TopHitAggUi = props => { PANEL_TYPES.METRIC, PANEL_TYPES.MARKDOWN, ].includes(panel.type) - ? [ES_FIELD_TYPES.NUMBER, ES_FIELD_TYPES.KEYWORD, ES_FIELD_TYPES.STRING] - : [ES_FIELD_TYPES.NUMBER]; + ? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING] + : [KBN_FIELD_TYPES.NUMBER]; const handleChange = createChangeHandler(props.onChange, model); const handleSelectChange = createSelectHandler(handleChange); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js index cf3f5c69c8b0..4357d4737fbe 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/annotations_editor.js @@ -21,7 +21,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import _ from 'lodash'; import { collectionActions } from './lib/collection_actions'; -import { ES_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { AddDeleteButtons } from './add_delete_buttons'; import { ColorPicker } from './color_picker'; import { FieldSelect } from './aggs/field_select'; @@ -57,7 +57,7 @@ function newAnnotation() { }; } -const RESTRICT_FIELDS = [ES_FIELD_TYPES.DATE]; +const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; export class AnnotationsEditor extends Component { constructor(props) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js index 61f67a1bde36..d954b0ab93b5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js @@ -33,7 +33,7 @@ import { FieldSelect } from './aggs/field_select'; import { createSelectHandler } from './lib/create_select_handler'; import { createTextHandler } from './lib/create_text_handler'; import { YesNo } from './yes_no'; -import { ES_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { FormValidationContext } from '../contexts/form_validation_context'; import { isGteInterval, @@ -47,7 +47,7 @@ import { PANEL_TYPES } from '../../common/panel_types'; import { isTimerangeModeEnabled } from '../lib/check_ui_restrictions'; import { VisDataContext } from '../contexts/vis_data_context'; -const RESTRICT_FIELDS = [ES_FIELD_TYPES.DATE]; +const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; const validateIntervalValue = intervalValue => { const isAutoOrGteInterval = isGteInterval(intervalValue) || isAutoInterval(intervalValue); diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 69bf95e57cab..b3e7078d8b5a 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -39,6 +39,8 @@ import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/ela import { CapabilitiesModifier } from './capabilities'; import { IndexPatternsServiceFactory } from './index_patterns'; import { Capabilities } from '../../core/public'; +import { IUiSettingsClient } from '../../legacy/ui/ui_settings/ui_settings_service'; +import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; export interface KibanaConfig { get(key: string): T; @@ -77,14 +79,15 @@ declare module 'hapi' { name: string, factoryFn: (request: Request) => Record ) => void; - uiSettingsServiceFactory: (options: any) => any; + uiSettingsServiceFactory: (options?: UiSettingsServiceFactoryOptions) => IUiSettingsClient; + logWithMetadata: (tags: string[], message: string, meta: Record) => void; } interface Request { getSavedObjectsClient(options?: SavedObjectsClientProviderOptions): SavedObjectsClientContract; getBasePath(): string; getDefaultRoute(): Promise; - getUiSettingsService(): any; + getUiSettingsService(): IUiSettingsClient; getCapabilities(): Promise; } diff --git a/src/legacy/server/sass/build.js b/src/legacy/server/sass/build.js index f33acbd1abd7..db1b26e10283 100644 --- a/src/legacy/server/sass/build.js +++ b/src/legacy/server/sass/build.js @@ -24,7 +24,6 @@ import sass from 'node-sass'; import autoprefixer from 'autoprefixer'; import postcss from 'postcss'; import postcssUrl from 'postcss-url'; -import mkdirp from 'mkdirp'; import chalk from 'chalk'; import isPathInside from 'is-path-inside'; import { PUBLIC_PATH_PLACEHOLDER } from '../../../optimize/public_path_placeholder'; @@ -33,7 +32,7 @@ const renderSass = promisify(sass.render); const writeFile = promisify(fs.writeFile); const exists = promisify(fs.exists); const copyFile = promisify(fs.copyFile); -const mkdirpAsync = promisify(mkdirp); +const mkdirAsync = promisify(fs.mkdir); const UI_ASSETS_DIR = resolve(__dirname, '../../ui/public/assets'); const DARK_THEME_IMPORTER = (url) => { @@ -165,7 +164,7 @@ export class Build { })); // write css - await mkdirpAsync(this.targetDir); + await mkdirAsync(this.targetDir, { recursive: true }); await writeFile(this.targetPath, prefixed.css); // copy non-shared urlAssets @@ -174,7 +173,7 @@ export class Build { return; } - await mkdirpAsync(dirname(asset.copyTo)); + await mkdirAsync(dirname(asset.copyTo), { recursive: true }); await copyFile(asset.path, asset.copyTo); })); diff --git a/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.js b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.js new file mode 100644 index 000000000000..cd726709b4b5 --- /dev/null +++ b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.js @@ -0,0 +1,25 @@ +/* + * 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 function filterDocvalueFields(docvalueFields, fields) { + return docvalueFields.filter(docValue => { + const docvalueFieldName = typeof docValue === 'string' ? docValue : docValue.field; + return fields.includes(docvalueFieldName); + }); +} diff --git a/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.js b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.js new file mode 100644 index 000000000000..b220361e33b3 --- /dev/null +++ b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.js @@ -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. + */ + +import { filterDocvalueFields } from './filter_docvalue_fields'; + +test('Should exclude docvalue_fields that are not contained in fields', () => { + const docvalueFields = [ + 'my_ip_field', + { field: 'my_keyword_field' }, + { field: 'my_date_field', 'format': 'epoch_millis' } + ]; + const out = filterDocvalueFields(docvalueFields, ['my_ip_field', 'my_keyword_field']); + expect(out).toEqual([ + 'my_ip_field', + { field: 'my_keyword_field' }, + ]); +}); diff --git a/src/legacy/ui/public/courier/search_source/search_source.js b/src/legacy/ui/public/courier/search_source/search_source.js index ceb10861dc9a..afa42a7d7c01 100644 --- a/src/legacy/ui/public/courier/search_source/search_source.js +++ b/src/legacy/ui/public/courier/search_source/search_source.js @@ -81,6 +81,7 @@ import { searchRequestQueue } from '../search_request_queue'; import { FetchSoonProvider } from '../fetch'; import { FieldWildcardProvider } from '../../field_wildcard'; import { getHighlightRequest } from '../../../../../plugins/data/common/field_formats'; +import { filterDocvalueFields } from './filter_docvalue_fields'; const FIELDS = [ 'type', @@ -551,12 +552,13 @@ export function SearchSourceProvider(Promise, Private, config) { flatData.body = flatData.body || {}; const computedFields = flatData.index.getComputedFields(); + flatData.body.stored_fields = computedFields.storedFields; flatData.body.script_fields = flatData.body.script_fields || {}; - flatData.body.docvalue_fields = flatData.body.docvalue_fields || []; - _.extend(flatData.body.script_fields, computedFields.scriptFields); - flatData.body.docvalue_fields = _.union(flatData.body.docvalue_fields, computedFields.docvalueFields); + + const defaultDocValueFields = computedFields.docvalueFields ? computedFields.docvalueFields : []; + flatData.body.docvalue_fields = flatData.body.docvalue_fields || defaultDocValueFields; if (flatData.body._source) { // exclude source fields for this index pattern specified by the user @@ -570,7 +572,7 @@ export function SearchSourceProvider(Promise, Private, config) { const fields = flatData.fields; if (fields) { // filter out the docvalue_fields, and script_fields to only include those that we are concerned with - flatData.body.docvalue_fields = _.intersection(flatData.body.docvalue_fields, fields); + flatData.body.docvalue_fields = filterDocvalueFields(flatData.body.docvalue_fields, fields); flatData.body.script_fields = _.pick(flatData.body.script_fields, fields); // request the remaining fields from both stored_fields and _source diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_labels.js b/src/legacy/ui/public/vislib/lib/axis/axis_labels.js index 5669b255e5db..5db747f8a76b 100644 --- a/src/legacy/ui/public/vislib/lib/axis/axis_labels.js +++ b/src/legacy/ui/public/vislib/lib/axis/axis_labels.js @@ -105,7 +105,7 @@ export class AxisLabels { selection.selectAll('.tick text') .text(function (d) { const par = d3.select(this.parentNode).node(); - const myPos = scaleStartPad + self.axisScale.scale(d); + const myPos = scaleStartPad + (config.isHorizontal() ? self.axisScale.scale(d) : maxSize - self.axisScale.scale(d)); const mySize = (config.isHorizontal() ? par.getBBox().width : par.getBBox().height) * padding; const halfSize = mySize / 2; diff --git a/src/legacy/ui/ui_bundles/ui_bundles_controller.js b/src/legacy/ui/ui_bundles/ui_bundles_controller.js index f0410f57c457..2e6436b370fb 100644 --- a/src/legacy/ui/ui_bundles/ui_bundles_controller.js +++ b/src/legacy/ui/ui_bundles/ui_bundles_controller.js @@ -20,11 +20,10 @@ import { resolve, relative, isAbsolute } from 'path'; import { createHash } from 'crypto'; import { promisify } from 'util'; -import { existsSync } from 'fs'; +import { existsSync, mkdir } from 'fs'; import del from 'del'; import { makeRe } from 'minimatch'; -import mkdirp from 'mkdirp'; import jsonStableStringify from 'json-stable-stringify'; import { IS_KIBANA_DISTRIBUTABLE, fromRoot } from '../../utils'; @@ -32,7 +31,7 @@ import { IS_KIBANA_DISTRIBUTABLE, fromRoot } from '../../utils'; import { UiBundle } from './ui_bundle'; import { appEntryTemplate } from './app_entry_template'; -const mkdirpAsync = promisify(mkdirp); +const mkdirAsync = promisify(mkdir); const REPO_ROOT = fromRoot(); function getWebpackAliases(pluginSpecs) { @@ -179,7 +178,7 @@ export class UiBundlesController { async resetBundleDir() { if (!existsSync(this._workingDir)) { // create a fresh working directory - await mkdirpAsync(this._workingDir); + await mkdirAsync(this._workingDir, { recursive: true }); } else { // delete all children of the working directory await del(this.resolvePath('*'), { diff --git a/src/legacy/ui/ui_settings/create_objects_client_stub.ts b/src/legacy/ui/ui_settings/create_objects_client_stub.ts index ebbedb761fae..ad19b5c8bc7c 100644 --- a/src/legacy/ui/ui_settings/create_objects_client_stub.ts +++ b/src/legacy/ui/ui_settings/create_objects_client_stub.ts @@ -26,6 +26,10 @@ export interface SavedObjectsClientStub { update: sinon.SinonStub; get: sinon.SinonStub; create: sinon.SinonStub; + bulkCreate: sinon.SinonStub; + bulkGet: sinon.SinonStub; + delete: sinon.SinonStub; + find: sinon.SinonStub; errors: typeof savedObjectsClientErrors; } @@ -35,6 +39,10 @@ export function createObjectsClientStub(esDocSource = {}): SavedObjectsClientStu get: sinon.stub().returns({ attributes: esDocSource }), create: sinon.stub(), errors: savedObjectsClientErrors, + bulkCreate: sinon.stub(), + bulkGet: sinon.stub(), + delete: sinon.stub(), + find: sinon.stub(), }; return savedObjectsClient; } diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index 9b9a2fad39ac..654c0fbb66c8 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -21,9 +21,7 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; import Chance from 'chance'; -// @ts-ignore import * as getUpgradeableConfigNS from './get_upgradeable_config'; -// @ts-ignore import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; const chance = new Chance(); @@ -45,7 +43,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { id: options.id, version: 'foo', })), - }; + } as any; // mute until we have savedObjects mocks async function run(options = {}) { const resp = await createOrUpgradeSavedConfig({ @@ -103,7 +101,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { [chance.word()]: chance.sentence(), }; - getUpgradeableConfig.returns({ id: prevVersion, attributes: savedAttributes }); + getUpgradeableConfig.resolves({ + id: prevVersion, + attributes: savedAttributes, + type: '', + references: [], + }); await run(); @@ -125,7 +128,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('should log a message for upgrades', async () => { const { getUpgradeableConfig, logWithMetadata, run } = setup(); - getUpgradeableConfig.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } }); + getUpgradeableConfig.resolves({ + id: prevVersion, + attributes: { buildNum: buildNum - 100 }, + type: '', + references: [], + }); await run(); sinon.assert.calledOnce(logWithMetadata); @@ -143,7 +151,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('does not log when upgrade fails', async () => { const { getUpgradeableConfig, logWithMetadata, run, savedObjectsClient } = setup(); - getUpgradeableConfig.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } }); + getUpgradeableConfig.resolves({ + id: prevVersion, + attributes: { buildNum: buildNum - 100 }, + type: '', + references: [], + }); savedObjectsClient.create.callsFake(async () => { throw new Error('foo'); diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts similarity index 55% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.js rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index c175e583ee91..0dc3d5f50e97 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.js +++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -18,37 +18,38 @@ */ import { defaults } from 'lodash'; +import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server'; +import { Legacy } from 'kibana'; import { getUpgradeableConfig } from './get_upgradeable_config'; -export async function createOrUpgradeSavedConfig(options) { - const { - savedObjectsClient, - version, - buildNum, - logWithMetadata, - onWriteError, - } = options; +interface Options { + savedObjectsClient: SavedObjectsClientContract; + version: string; + buildNum: number; + logWithMetadata: Legacy.Server['logWithMetadata']; + onWriteError?: ( + error: Error, + attributes: Record + ) => Record | undefined; +} +export async function createOrUpgradeSavedConfig( + options: Options +): Promise | undefined> { + const { savedObjectsClient, version, buildNum, logWithMetadata, onWriteError } = options; // try to find an older config we can upgrade const upgradeableConfig = await getUpgradeableConfig({ savedObjectsClient, - version + version, }); // default to the attributes of the upgradeableConfig if available - const attributes = defaults( - { buildNum }, - upgradeableConfig ? upgradeableConfig.attributes : {} - ); + const attributes = defaults({ buildNum }, upgradeableConfig ? upgradeableConfig.attributes : {}); try { // create the new SavedConfig - await savedObjectsClient.create( - 'config', - attributes, - { id: version } - ); + await savedObjectsClient.create('config', attributes, { id: version }); } catch (error) { if (onWriteError) { return onWriteError(error, attributes); @@ -58,9 +59,13 @@ export async function createOrUpgradeSavedConfig(options) { } if (upgradeableConfig) { - logWithMetadata(['plugin', 'elasticsearch'], `Upgrade config from ${upgradeableConfig.id} to ${version}`, { - prevVersion: upgradeableConfig.id, - newVersion: version - }); + logWithMetadata( + ['plugin', 'elasticsearch'], + `Upgrade config from ${upgradeableConfig.id} to ${version}`, + { + prevVersion: upgradeableConfig.id, + newVersion: version, + } + ); } } diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts similarity index 80% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.js rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts index 1108a0116758..350137a81a49 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.js +++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import { SavedObjectsClientContract } from 'src/core/server'; import { isConfigVersionUpgradeable } from './is_config_version_upgradeable'; /** @@ -26,18 +26,22 @@ import { isConfigVersionUpgradeable } from './is_config_version_upgradeable'; * @property {string} version * @return {Promise} */ -export async function getUpgradeableConfig({ savedObjectsClient, version }) { +export async function getUpgradeableConfig({ + savedObjectsClient, + version, +}: { + savedObjectsClient: SavedObjectsClientContract; + version: string; +}) { // attempt to find a config we can upgrade const { saved_objects: savedConfigs } = await savedObjectsClient.find({ type: 'config', page: 1, perPage: 1000, sortField: 'buildNum', - sortOrder: 'desc' + sortOrder: 'desc', }); // try to find a config that we can upgrade - return savedConfigs.find(savedConfig => ( - isConfigVersionUpgradeable(savedConfig.id, version) - )); + return savedConfigs.find(savedConfig => isConfigVersionUpgradeable(savedConfig.id, version)); } diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.ts similarity index 100% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.js rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.ts diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts index 7d5f4e970638..753e73058af2 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts +++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts @@ -24,7 +24,6 @@ import { SavedObjectsClientContract } from 'src/core/server'; import KbnServer from '../../../../server/kbn_server'; import { createTestServers } from '../../../../../test_utils/kbn_server'; -// @ts-ignore import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; describe('createOrUpgradeSavedConfig()', () => { diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts index 91231da96822..6bb2cb3b8785 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts +++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts @@ -19,7 +19,6 @@ import expect from '@kbn/expect'; -// @ts-ignore import { isConfigVersionUpgradeable } from './is_config_version_upgradeable'; // @ts-ignore import { pkg } from '../../../utils'; diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts similarity index 89% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.js rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts index beeba6717f24..8359f02ffee7 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.js +++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts @@ -20,14 +20,12 @@ import semver from 'semver'; const rcVersionRegex = /^(\d+\.\d+\.\d+)\-rc(\d+)$/i; -function extractRcNumber(version) { +function extractRcNumber(version: string): [string, number] { const match = version.match(rcVersionRegex); - return match - ? [match[1], parseInt(match[2], 10)] - : [version, Infinity]; + return match ? [match[1], parseInt(match[2], 10)] : [version, Infinity]; } -export function isConfigVersionUpgradeable(savedVersion, kibanaVersion) { +export function isConfigVersionUpgradeable(savedVersion: string, kibanaVersion: string): boolean { if ( typeof savedVersion !== 'string' || typeof kibanaVersion !== 'string' || diff --git a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts index f522f119a26c..f43c6436d1c3 100644 --- a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts +++ b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts @@ -23,9 +23,7 @@ import expect from '@kbn/expect'; // @ts-ignore import { Config } from '../../../server/config'; -// @ts-ignore import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory'; -// @ts-ignore import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request'; // @ts-ignore import { uiSettingsMixin } from '../ui_settings_mixin'; @@ -123,7 +121,8 @@ describe('uiSettingsMixin()', () => { foo: 'bar', }); sinon.assert.calledOnce(uiSettingsServiceFactoryStub); - sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server, { + sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server as any, { + // @ts-ignore foo doesn't exist on Hapi.Server foo: 'bar', overrides: { foo: 'bar', @@ -162,7 +161,12 @@ describe('uiSettingsMixin()', () => { sinon.assert.notCalled(getUiSettingsServiceForRequestStub); const request = {}; decorations.request.getUiSettingsService.call(request); - sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server, request); + sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server as any, request as any, { + overrides: { + foo: 'bar', + }, + getDefaults: sinon.match.func, + }); }); }); diff --git a/src/legacy/ui/ui_settings/routes/delete.js b/src/legacy/ui/ui_settings/routes/delete.ts similarity index 81% rename from src/legacy/ui/ui_settings/routes/delete.js rename to src/legacy/ui/ui_settings/routes/delete.ts index 78e07bbceab0..7825204e6b99 100644 --- a/src/legacy/ui/ui_settings/routes/delete.js +++ b/src/legacy/ui/ui_settings/routes/delete.ts @@ -16,21 +16,22 @@ * specific language governing permissions and limitations * under the License. */ +import { Legacy } from 'kibana'; -async function handleRequest(request) { +async function handleRequest(request: Legacy.Request) { const { key } = request.params; const uiSettings = request.getUiSettingsService(); await uiSettings.remove(key); return { - settings: await uiSettings.getUserProvided() + settings: await uiSettings.getUserProvided(), }; } export const deleteRoute = { path: '/api/kibana/settings/{key}', method: 'DELETE', - handler: async (request, h) => { - return h.response(await handleRequest(request)); - } + handler: async (request: Legacy.Request) => { + return await handleRequest(request); + }, }; diff --git a/src/legacy/ui/ui_settings/routes/get.js b/src/legacy/ui/ui_settings/routes/get.ts similarity index 84% rename from src/legacy/ui/ui_settings/routes/get.js rename to src/legacy/ui/ui_settings/routes/get.ts index 7e91bc46596b..3e165a12522b 100644 --- a/src/legacy/ui/ui_settings/routes/get.js +++ b/src/legacy/ui/ui_settings/routes/get.ts @@ -16,18 +16,19 @@ * specific language governing permissions and limitations * under the License. */ +import { Legacy } from 'kibana'; -async function handleRequest(request) { +async function handleRequest(request: Legacy.Request) { const uiSettings = request.getUiSettingsService(); return { - settings: await uiSettings.getUserProvided() + settings: await uiSettings.getUserProvided(), }; } export const getRoute = { path: '/api/kibana/settings', method: 'GET', - handler: function (request) { + handler(request: Legacy.Request) { return handleRequest(request); - } + }, }; diff --git a/src/legacy/ui/ui_settings/routes/index.js b/src/legacy/ui/ui_settings/routes/index.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/index.js rename to src/legacy/ui/ui_settings/routes/index.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts index 5b0fbf5a5f25..b076a2a86e16 100644 --- a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts +++ b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts @@ -23,6 +23,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import KbnServer from '../../../../../server/kbn_server'; import { createTestServers } from '../../../../../../test_utils/kbn_server'; import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch'; +import { IUiSettingsClient } from '../../../ui_settings_service'; let kbnServer: KbnServer; let servers: ReturnType; @@ -33,7 +34,7 @@ interface AllServices { kbnServer: KbnServer; savedObjectsClient: SavedObjectsClientContract; callCluster: CallCluster; - uiSettings: any; + uiSettings: IUiSettingsClient; deleteKibanaIndex: typeof deleteKibanaIndex; } diff --git a/src/legacy/ui/ui_settings/routes/set.js b/src/legacy/ui/ui_settings/routes/set.ts similarity index 70% rename from src/legacy/ui/ui_settings/routes/set.js rename to src/legacy/ui/ui_settings/routes/set.ts index e50c9bf08de3..1f1ab17a0daf 100644 --- a/src/legacy/ui/ui_settings/routes/set.js +++ b/src/legacy/ui/ui_settings/routes/set.ts @@ -16,18 +16,18 @@ * specific language governing permissions and limitations * under the License. */ - +import { Legacy } from 'kibana'; import Joi from 'joi'; -async function handleRequest(request) { +async function handleRequest(request: Legacy.Request) { const { key } = request.params; - const { value } = request.payload; + const { value } = request.payload as any; const uiSettings = request.getUiSettingsService(); await uiSettings.set(key, value); return { - settings: await uiSettings.getUserProvided() + settings: await uiSettings.getUserProvided(), }; } @@ -36,16 +36,20 @@ export const setRoute = { method: 'POST', config: { validate: { - params: Joi.object().keys({ - key: Joi.string().required(), - }).default(), + params: Joi.object() + .keys({ + key: Joi.string().required(), + }) + .default(), - payload: Joi.object().keys({ - value: Joi.any().required() - }).required() + payload: Joi.object() + .keys({ + value: Joi.any().required(), + }) + .required(), }, - handler(request) { + handler(request: Legacy.Request) { return handleRequest(request); - } - } + }, + }, }; diff --git a/src/legacy/ui/ui_settings/routes/set_many.js b/src/legacy/ui/ui_settings/routes/set_many.ts similarity index 73% rename from src/legacy/ui/ui_settings/routes/set_many.js rename to src/legacy/ui/ui_settings/routes/set_many.ts index 8e7882f48ef7..18b1046417fe 100644 --- a/src/legacy/ui/ui_settings/routes/set_many.js +++ b/src/legacy/ui/ui_settings/routes/set_many.ts @@ -16,17 +16,17 @@ * specific language governing permissions and limitations * under the License. */ - +import { Legacy } from 'kibana'; import Joi from 'joi'; -async function handleRequest(request) { - const { changes } = request.payload; +async function handleRequest(request: Legacy.Request) { + const { changes } = request.payload as any; const uiSettings = request.getUiSettingsService(); await uiSettings.setMany(changes); return { - settings: await uiSettings.getUserProvided() + settings: await uiSettings.getUserProvided(), }; } @@ -35,12 +35,16 @@ export const setManyRoute = { method: 'POST', config: { validate: { - payload: Joi.object().keys({ - changes: Joi.object().unknown(true).required() - }).required() + payload: Joi.object() + .keys({ + changes: Joi.object() + .unknown(true) + .required(), + }) + .required(), }, - handler(request) { + handler(request: Legacy.Request) { return handleRequest(request); - } - } + }, + }, }; diff --git a/src/legacy/ui/ui_settings/ui_settings_service.mock.ts b/src/legacy/ui/ui_settings/ui_settings_service.mock.ts new file mode 100644 index 000000000000..7c1a17ebd447 --- /dev/null +++ b/src/legacy/ui/ui_settings/ui_settings_service.mock.ts @@ -0,0 +1,40 @@ +/* + * 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 { IUiSettingsClient } from './ui_settings_service'; + +const createServiceMock = () => { + const mocked: jest.Mocked = { + getDefaults: jest.fn(), + get: jest.fn(), + getAll: jest.fn(), + getUserProvided: jest.fn(), + setMany: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + removeMany: jest.fn(), + isOverridden: jest.fn(), + }; + mocked.get.mockResolvedValue(false); + return mocked; +}; + +export const uiSettingsServiceMock = { + create: createServiceMock, +}; diff --git a/src/legacy/ui/ui_settings/ui_settings_service.test.ts b/src/legacy/ui/ui_settings/ui_settings_service.test.ts index bb407d7b3a91..f37076b27ad6 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.test.ts +++ b/src/legacy/ui/ui_settings/ui_settings_service.test.ts @@ -21,9 +21,7 @@ import expect from '@kbn/expect'; import Chance from 'chance'; import sinon from 'sinon'; -// @ts-ignore import { UiSettingsService } from './ui_settings_service'; -// @ts-ignore import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config'; import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub'; @@ -43,7 +41,7 @@ describe('ui settings', () => { const sandbox = sinon.createSandbox(); function setup(options: SetupOptions = {}) { - const { getDefaults, defaults = {}, overrides, esDocSource = {} } = options; + const { getDefaults, defaults = {}, overrides = {}, esDocSource = {} } = options; const savedObjectsClient = createObjectsClientStub(esDocSource); @@ -233,7 +231,7 @@ describe('ui settings', () => { }); try { - await uiSettings.setMany(['bar', 'foo']); + await uiSettings.setMany({ baz: 'baz', foo: 'foo' }); } catch (error) { expect(error.message).to.be('Unable to update "foo" because it is overridden'); } @@ -489,7 +487,7 @@ describe('ui settings', () => { it('pulls user configuration from ES', async () => { const esDocSource = {}; const { uiSettings, assertGetQuery } = setup({ esDocSource }); - await uiSettings.get(); + await uiSettings.get('any'); assertGetQuery(); }); diff --git a/src/legacy/ui/ui_settings/ui_settings_service.js b/src/legacy/ui/ui_settings/ui_settings_service.ts similarity index 52% rename from src/legacy/ui/ui_settings/ui_settings_service.js rename to src/legacy/ui/ui_settings/ui_settings_service.ts index 9f79ed2dbe16..57312140b16b 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.js +++ b/src/legacy/ui/ui_settings/ui_settings_service.ts @@ -16,28 +16,77 @@ * specific language governing permissions and limitations * under the License. */ - +import { Legacy } from 'kibana'; import { defaultsDeep } from 'lodash'; import Boom from 'boom'; +import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; +export interface UiSettingsServiceOptions { + type: string; + id: string; + buildNum: number; + savedObjectsClient: SavedObjectsClientContract; + overrides?: Record; + getDefaults?: () => Record; + logWithMetadata?: Legacy.Server['logWithMetadata']; +} + +interface ReadOptions { + ignore401Errors?: boolean; + autoCreateOrUpgradeIfMissing?: boolean; +} + +interface UserProvidedValue { + userValue?: SavedObjectAttribute; + isOverridden?: boolean; +} + +type UiSettingsRawValue = UiSettingsParams & UserProvidedValue; + +type UserProvided = Record; +type UiSettingsRaw = Record; + +type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + +interface UiSettingsParams { + name: string; + value: SavedObjectAttribute; + description: string; + category: string[]; + options?: string[]; + optionLabels?: Record; + requiresPageReload?: boolean; + readonly?: boolean; + type?: UiSettingsType; +} + +export interface IUiSettingsClient { + getDefaults: () => Promise>; + get: (key: string) => Promise; + getAll: () => Promise>; + getUserProvided: () => Promise; + setMany: (changes: Record) => Promise; + set: (key: string, value: T) => Promise; + remove: (key: string) => Promise; + removeMany: (keys: string[]) => Promise; + isOverridden: (key: string) => boolean; +} /** * Service that provides access to the UiSettings stored in elasticsearch. * @class UiSettingsService */ -export class UiSettingsService { - /** - * @constructor - * @param {Object} options - * @property {string} options.type type of SavedConfig object - * @property {string} options.id id of SavedConfig object - * @property {number} options.buildNum - * @property {SavedObjectsClient} options.savedObjectsClient - * @property {Function} [options.getDefaults] - * @property {Function} [options.log] - */ - constructor(options) { +export class UiSettingsService implements IUiSettingsClient { + private readonly _type: UiSettingsServiceOptions['type']; + private readonly _id: UiSettingsServiceOptions['id']; + private readonly _buildNum: UiSettingsServiceOptions['buildNum']; + private readonly _savedObjectsClient: UiSettingsServiceOptions['savedObjectsClient']; + private readonly _overrides: NonNullable; + private readonly _getDefaults: NonNullable; + private readonly _logWithMetadata: NonNullable; + + constructor(options: UiSettingsServiceOptions) { const { type, id, @@ -65,36 +114,38 @@ export class UiSettingsService { } // returns a Promise for the value of the requested setting - async get(key) { + async get(key: string): Promise { const all = await this.getAll(); return all[key]; } - async getAll() { + async getAll() { const raw = await this.getRaw(); - return Object.keys(raw) - .reduce((all, key) => { + return Object.keys(raw).reduce( + (all, key) => { const item = raw[key]; - const hasUserValue = 'userValue' in item; - all[key] = hasUserValue ? item.userValue : item.value; + all[key] = ('userValue' in item ? item.userValue : item.value) as T; return all; - }, {}); + }, + {} as Record + ); } - async getRaw() { + // NOTE: should be a private method + async getRaw(): Promise { const userProvided = await this.getUserProvided(); return defaultsDeep(userProvided, await this.getDefaults()); } - async getUserProvided(options) { - const userProvided = {}; + async getUserProvided(options: ReadOptions = {}): Promise { + const userProvided: UserProvided = {}; // write the userValue for each key stored in the saved object that is not overridden for (const [key, userValue] of Object.entries(await this._read(options))) { if (userValue !== null && !this.isOverridden(key)) { userProvided[key] = { - userValue + userValue, }; } } @@ -102,45 +153,51 @@ export class UiSettingsService { // write all overridden keys, dropping the userValue is override is null and // adding keys for overrides that are not in saved object for (const [key, userValue] of Object.entries(this._overrides)) { - userProvided[key] = userValue === null - ? { isOverridden: true } - : { isOverridden: true, userValue }; + userProvided[key] = + userValue === null ? { isOverridden: true } : { isOverridden: true, userValue }; } return userProvided; } - async setMany(changes) { + async setMany(changes: Record) { await this._write({ changes }); } - async set(key, value) { + async set(key: string, value: T) { await this.setMany({ [key]: value }); } - async remove(key) { + async remove(key: string) { await this.set(key, null); } - async removeMany(keys) { - const changes = {}; + async removeMany(keys: string[]) { + const changes: Record = {}; keys.forEach(key => { changes[key] = null; }); await this.setMany(changes); } - isOverridden(key) { + isOverridden(key: string) { return this._overrides.hasOwnProperty(key); } - assertUpdateAllowed(key) { + // NOTE: should be private method + assertUpdateAllowed(key: string) { if (this.isOverridden(key)) { throw Boom.badRequest(`Unable to update "${key}" because it is overridden`); } } - async _write({ changes, autoCreateOrUpgradeIfMissing = true }) { + private async _write({ + changes, + autoCreateOrUpgradeIfMissing = true, + }: { + changes: Record; + autoCreateOrUpgradeIfMissing?: boolean; + }) { for (const key of Object.keys(changes)) { this.assertUpdateAllowed(key); } @@ -162,72 +219,77 @@ export class UiSettingsService { await this._write({ changes, - autoCreateOrUpgradeIfMissing: false + autoCreateOrUpgradeIfMissing: false, }); } } - async _read(options = {}) { - const { - ignore401Errors = false, - autoCreateOrUpgradeIfMissing = true - } = options; - + private async _read({ + ignore401Errors = false, + autoCreateOrUpgradeIfMissing = true, + }: ReadOptions = {}): Promise> { const { isConflictError, isNotFoundError, isForbiddenError, - isEsUnavailableError, isNotAuthorizedError, } = this._savedObjectsClient.errors; - const isIgnorableError = error => ( - isForbiddenError(error) || - isEsUnavailableError(error) || - (ignore401Errors && isNotAuthorizedError(error)) - ); - try { const resp = await this._savedObjectsClient.get(this._type, this._id); return resp.attributes; } catch (error) { if (isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { - const failedUpgradeAttributes = await createOrUpgradeSavedConfig({ + const failedUpgradeAttributes = await createOrUpgradeSavedConfig({ savedObjectsClient: this._savedObjectsClient, version: this._id, buildNum: this._buildNum, logWithMetadata: this._logWithMetadata, - async onWriteError(error, attributes) { - if (isConflictError(error)) { + onWriteError(writeError, attributes) { + if (isConflictError(writeError)) { // trigger `!failedUpgradeAttributes` check below, since another // request caused the uiSettings object to be created so we can // just re-read - return false; + return; } - if (isNotAuthorizedError(error) || isForbiddenError(error)) { + if (isNotAuthorizedError(writeError) || isForbiddenError(writeError)) { return attributes; } - throw error; - } + throw writeError; + }, }); if (!failedUpgradeAttributes) { return await this._read({ - ...options, - autoCreateOrUpgradeIfMissing: false + ignore401Errors, + autoCreateOrUpgradeIfMissing: false, }); } return failedUpgradeAttributes; } - if (isIgnorableError(error)) { + if (this.isIgnorableError(error, ignore401Errors)) { return {}; } throw error; } } + + private isIgnorableError(error: Error, ignore401Errors: boolean) { + const { + isForbiddenError, + isEsUnavailableError, + isNotAuthorizedError, + } = this._savedObjectsClient.errors; + + return ( + isForbiddenError(error) || + isEsUnavailableError(error) || + (ignore401Errors && isNotAuthorizedError(error)) + ); + } } diff --git a/src/legacy/ui/ui_settings/ui_settings_service_factory.js b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts similarity index 59% rename from src/legacy/ui/ui_settings/ui_settings_service_factory.js rename to src/legacy/ui/ui_settings/ui_settings_service_factory.ts index f83a0d982555..9e1384494161 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service_factory.js +++ b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts @@ -16,29 +16,30 @@ * specific language governing permissions and limitations * under the License. */ +import { Legacy } from 'kibana'; +import { + IUiSettingsClient, + UiSettingsService, + UiSettingsServiceOptions, +} from './ui_settings_service'; -import { UiSettingsService } from './ui_settings_service'; - +export type UiSettingsServiceFactoryOptions = Pick< + UiSettingsServiceOptions, + 'savedObjectsClient' | 'getDefaults' | 'overrides' +>; /** * Create an instance of UiSettingsService that will use the - * passed `callCluster` function to communicate with elasticsearch + * passed `savedObjectsClient` to communicate with elasticsearch * - * @param {Hapi.Server} server - * @param {Object} options - * @property {AsyncFunction} options.callCluster function that accepts a method name and - * param object which causes a request via some elasticsearch client - * @property {AsyncFunction} [options.getDefaults] async function that returns defaults/details about - * the uiSettings. - * @return {UiSettingsService} + * @return {IUiSettingsClient} */ -export function uiSettingsServiceFactory(server, options) { +export function uiSettingsServiceFactory( + server: Legacy.Server, + options: UiSettingsServiceFactoryOptions +): IUiSettingsClient { const config = server.config(); - const { - savedObjectsClient, - getDefaults, - overrides, - } = options; + const { savedObjectsClient, getDefaults, overrides } = options; return new UiSettingsService({ type: 'config', @@ -47,6 +48,6 @@ export function uiSettingsServiceFactory(server, options) { savedObjectsClient, getDefaults, overrides, - logWithMetadata: (...args) => server.logWithMetadata(...args), + logWithMetadata: server.logWithMetadata, }); } diff --git a/src/legacy/ui/ui_settings/ui_settings_service_for_request.js b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts similarity index 74% rename from src/legacy/ui/ui_settings/ui_settings_service_for_request.js rename to src/legacy/ui/ui_settings/ui_settings_service_for_request.ts index 422c9cc14f83..e265ad5f1e11 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service_for_request.js +++ b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts @@ -17,8 +17,11 @@ * under the License. */ +import { Legacy } from 'kibana'; import { uiSettingsServiceFactory } from './ui_settings_service_factory'; +import { IUiSettingsClient, UiSettingsServiceOptions } from './ui_settings_service'; +type Options = Pick; /** * Get/create an instance of UiSettingsService bound to a specific request. * Each call is cached (keyed on the request object itself) and subsequent @@ -28,20 +31,20 @@ import { uiSettingsServiceFactory } from './ui_settings_service_factory'; * @param {Hapi.Server} server * @param {Hapi.Request} request * @param {Object} [options={}] - * @property {AsyncFunction} [options.getDefaults] async function that returns defaults/details about - * the uiSettings. - * @return {UiSettingsService} + + * @return {IUiSettingsClient} */ -export function getUiSettingsServiceForRequest(server, request, options = {}) { - const { - getDefaults, - overrides, - } = options; +export function getUiSettingsServiceForRequest( + server: Legacy.Server, + request: Legacy.Request, + options: Options +): IUiSettingsClient { + const { getDefaults, overrides } = options; const uiSettingsService = uiSettingsServiceFactory(server, { getDefaults, overrides, - savedObjectsClient: request.getSavedObjectsClient() + savedObjectsClient: request.getSavedObjectsClient(), }); return uiSettingsService; diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js index 46b85b46f082..5b9dd2d04a55 100644 --- a/src/optimize/dynamic_dll_plugin/dll_compiler.js +++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js @@ -22,14 +22,13 @@ import { notInNodeModulesOrWebpackShims, notInNodeModules, inDllPluginPublic } f import { fromRoot } from '../../legacy/utils'; import { PUBLIC_PATH_PLACEHOLDER } from '../public_path_placeholder'; import fs from 'fs'; -import mkdirp from 'mkdirp'; import webpack from 'webpack'; import { promisify } from 'util'; import path from 'path'; import rimraf from 'rimraf'; const readFileAsync = promisify(fs.readFile); -const mkdirpAsync = promisify(mkdirp); +const mkdirAsync = promisify(fs.mkdir); const existsAsync = promisify(fs.exists); const writeFileAsync = promisify(fs.writeFile); const rimrafAsync = promisify(rimraf); @@ -132,7 +131,7 @@ export class DllCompiler { const exists = await existsAsync(filePath); if (!exists) { - await mkdirpAsync(path.dirname(filePath)); + await mkdirAsync(path.dirname(filePath), { recursive: true }); } return exists; diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index cdf2d3bf61f5..642314d1fb7f 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -46,7 +46,11 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { it('should show the default request', async () => { // collapse the help pane because we only get the VISIBLE TEXT, not the part that is scrolled - await PageObjects.console.collapseHelp(); + // on IE11, the dialog that says 'Your browser does not meet the security requirements for Kibana.' + // blocks the close help button for several seconds so just retry until we can click it. + await retry.try(async () => { + await PageObjects.console.collapseHelp(); + }); await retry.try(async () => { const actualRequest = await PageObjects.console.getRequest(); log.debug(actualRequest); diff --git a/test/functional/apps/home/_navigation.js b/test/functional/apps/home/_navigation.js index 7582e45f74a3..12ce0e6afd03 100644 --- a/test/functional/apps/home/_navigation.js +++ b/test/functional/apps/home/_navigation.js @@ -26,6 +26,7 @@ export default function ({ getService, getPageObjects }) { const appsMenu = getService('appsMenu'); const esArchiver = getService('esArchiver'); const retry = getService('retry'); + const kibanaServer = getService('kibanaServer'); const fromTime = '2015-09-19 06:31:44.000'; const toTime = '2015-09-23 18:31:44.000'; @@ -33,8 +34,15 @@ export default function ({ getService, getPageObjects }) { before(async () => { await esArchiver.loadIfNeeded('makelogs'); - await browser.refresh(); - await PageObjects.header.awaitKibanaChrome(); + if (browser.isInternetExplorer) { + await kibanaServer.uiSettings.replace({ 'state:storeInSessionStorage': false }); + } + }); + + after(async () => { + if (browser.isInternetExplorer) { + await kibanaServer.uiSettings.replace({ 'state:storeInSessionStorage': true }); + } }); // FLAKY: https://github.com/elastic/kibana/issues/33468 diff --git a/test/functional/apps/visualize/_chart_types.js b/test/functional/apps/visualize/_chart_types.js index 6f868b6df464..79ea326ad982 100644 --- a/test/functional/apps/visualize/_chart_types.js +++ b/test/functional/apps/visualize/_chart_types.js @@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }) { describe('chart types', function () { before(function () { log.debug('navigateToApp visualize'); - return PageObjects.common.navigateToUrl('visualize', 'new'); + return PageObjects.visualize.navigateToNewVisualization(); }); it('should show the correct chart types', async function () { diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index 9143b931df97..5c6af1f24506 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -62,7 +62,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/46677 - describe.skip('gauge', () => { + describe('gauge', () => { beforeEach(async () => { await PageObjects.visualBuilder.resetPage(); await PageObjects.visualBuilder.clickGauge(); diff --git a/test/functional/config.ie.js b/test/functional/config.ie.js new file mode 100644 index 000000000000..cf41e78e5389 --- /dev/null +++ b/test/functional/config.ie.js @@ -0,0 +1,55 @@ +/* + * 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 default async function ({ readConfigFile }) { + const defaultConfig = await readConfigFile(require.resolve('./config')); + + return { + ...defaultConfig.getAll(), + + browser: { + type: 'ie', + }, + + junit: { + reportName: 'Internet Explorer UI Functional Tests' + }, + + uiSettings: { + defaults: { + 'accessibility:disableAnimations': true, + 'dateFormat:tz': 'UTC', + 'telemetry:optIn': false, + 'state:storeInSessionStorage': true, + 'notifications:lifetime:info': 10000, + }, + }, + + + kbnTestServer: { + ...defaultConfig.get('kbnTestServer'), + serverArgs: [ + ...defaultConfig.get('kbnTestServer.serverArgs'), + '--csp.strict=false', + ], + }, + + + }; +} diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js index b5b22f428535..054c3edd567e 100644 --- a/test/functional/page_objects/time_picker.js +++ b/test/functional/page_objects/time_picker.js @@ -25,7 +25,7 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { const find = getService('find'); const browser = getService('browser'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['header']); + const PageObjects = getPageObjects(['header', 'common']); class TimePickerPage { @@ -48,18 +48,6 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { await find.waitForElementStale(panelElement); } - async setAbsoluteStart(startTime) { - await this.showStartEndTimes(); - - await testSubjects.click('superDatePickerstartDatePopoverButton'); - const panel = await this.getTimePickerPanel(); - await testSubjects.click('superDatePickerAbsoluteTab'); - await this.inputValue('superDatePickerAbsoluteDateInput', startTime); - await testSubjects.click('superDatePickerstartDatePopoverButton'); - await this.waitPanelIsGone(panel); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - } - /** * @param {String} commonlyUsedOption 'superDatePickerCommonlyUsed_This_week' */ @@ -73,6 +61,13 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { const input = await testSubjects.find(dataTestsubj); await input.clearValue(); await input.type(value); + } else if (browser.isInternetExplorer) { + const input = await testSubjects.find(dataTestsubj); + const currentValue = await input.getAttribute('value'); + await input.type((browser.keys.ARROW_RIGHT).repeat(currentValue.length)); + await input.type((browser.keys.BACK_SPACE).repeat(currentValue.length)); + await input.type(value); + await input.click(); } else { await testSubjects.setValue(dataTestsubj, value); } @@ -90,14 +85,16 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { await testSubjects.click('superDatePickerendDatePopoverButton'); let panel = await this.getTimePickerPanel(); await testSubjects.click('superDatePickerAbsoluteTab'); + await testSubjects.click('superDatePickerAbsoluteDateInput'); await this.inputValue('superDatePickerAbsoluteDateInput', toTime); - + await PageObjects.common.sleep(500); // set from time await testSubjects.click('superDatePickerstartDatePopoverButton'); await this.waitPanelIsGone(panel); panel = await this.getTimePickerPanel(); await testSubjects.click('superDatePickerAbsoluteTab'); + await testSubjects.click('superDatePickerAbsoluteDateInput'); await this.inputValue('superDatePickerAbsoluteDateInput', fromTime); const superDatePickerApplyButtonExists = await testSubjects.exists('superDatePickerApplyTimeButton'); @@ -148,6 +145,7 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { if (isShowDatesButton) { await testSubjects.click('superDatePickerShowDatesButton'); } + await testSubjects.exists('superDatePickerstartDatePopoverButton'); } async getRefreshConfig(keepQuickSelectOpen = false) { diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index e40ce93dd540..c1d9446febf9 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -718,8 +718,9 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli log.debug('Click Save Visualization button'); await testSubjects.click('confirmSaveSavedObjectButton'); + // if we wait for this, the success toast message could be gone :-() // wait for save to complete before completion - await PageObjects.header.waitUntilLoadingHasFinished(); + // await PageObjects.header.waitUntilLoadingHasFinished(); } async saveVisualizationExpectSuccess(vizName, { saveAsNew = false } = {}) { @@ -834,7 +835,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli // by a bunch of 'L'ines from that point to the next. Those points are // the values we're going to use to calculate the data values we're testing. // So git rid of the one 'M' and split the rest on the 'L's. - const tempArray = data.replace('M', '').split('L'); + const tempArray = data.replace('M ', '').replace('M', '').replace(/ L /g, 'L').replace(/ /g, ',').split('L'); const chartSections = tempArray.length / 2; // log.debug('chartSections = ' + chartSections + ' height = ' + yAxisHeight + ' yAxisLabel = ' + yAxisLabel); const chartData = []; @@ -888,7 +889,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli // 1). get the range/pixel ratio const yAxisRatio = await this.getChartYAxisRatio(axis); // 3). get the visWrapper__chart elements - const svg = await find.byCssSelector('div.chart > svg'); + const svg = await find.byCssSelector('div.chart'); const $ = await svg.parseDomContent(); const chartData = $(`g > g.series > rect[data-label="${dataLabel}"]`).toArray().map(chart => { const barHeight = $(chart).attr('height'); diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 25f87b794edd..81efbc3cb05f 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -68,6 +68,8 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { public readonly isFirefox: boolean = browserType === Browsers.Firefox; + public readonly isInternetExplorer: boolean = browserType === Browsers.InternetExplorer; + /** * Is WebDriver instance W3C compatible */ @@ -181,7 +183,12 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { */ public async getCurrentUrl(): Promise { // strip _t=Date query param when url is read - const current = await driver.getCurrentUrl(); + let current: string; + if (this.isInternetExplorer) { + current = await driver.executeScript('return window.document.location.href'); + } else { + current = await driver.getCurrentUrl(); + } const currentWithoutTime = modifyUrl(current, parsed => { delete (parsed.query as any)._t; return void 0; diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index e04d9a4eb303..d2a03e43fe5d 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -75,7 +75,7 @@ export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderCont return; } - comboBoxElement.scrollIntoViewIfNecessary(); + await comboBoxElement.scrollIntoViewIfNecessary(); await this.setFilterValue(comboBoxElement, value); await this.openOptionsList(comboBoxElement); diff --git a/test/functional/services/failure_debugging.ts b/test/functional/services/failure_debugging.ts index bd8869ad0856..e2be69d77979 100644 --- a/test/functional/services/failure_debugging.ts +++ b/test/functional/services/failure_debugging.ts @@ -17,22 +17,19 @@ * under the License. */ -import mkdirp from 'mkdirp'; import { resolve } from 'path'; -import { writeFile } from 'fs'; +import { writeFile, mkdir } from 'fs'; +import { promisify } from 'util'; + import del from 'del'; -import Bluebird, { promisify } from 'bluebird'; import { FtrProviderContext } from '../ftr_provider_context'; interface Test { fullTitle(): string; } -type WriteFileAsync = (path: string | number | Buffer | URL, data: any) => Bluebird; -type MkDirAsync = (dirName: string) => Bluebird; - -const writeFileAsync = promisify(writeFile) as WriteFileAsync; -const mkdirAsync = promisify(mkdirp) as MkDirAsync; +const writeFileAsync = promisify(writeFile); +const mkdirAsync = promisify(mkdir); export async function FailureDebuggingProvider({ getService }: FtrProviderContext) { const screenshots = getService('screenshots'); @@ -51,7 +48,7 @@ export async function FailureDebuggingProvider({ getService }: FtrProviderContex } async function savePageHtml(name: string) { - await mkdirAsync(config.get('failureDebugging.htmlDirectory')); + await mkdirAsync(config.get('failureDebugging.htmlDirectory'), { recursive: true }); const htmlOutputFileName = resolve( config.get('failureDebugging.htmlDirectory'), `${name}.html` diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index 74fc25043863..ce61530246ee 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -63,7 +63,7 @@ export function InspectorProvider({ getService }: FtrProviderContext) { if (!isOpen) { await retry.try(async () => { await testSubjects.click('openInspectorButton'); - await testSubjects.find('inspectorPanel'); + await testSubjects.exists('inspectorPanel'); }); } } diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index ee35ca9e1de7..a628f085ea70 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -233,6 +233,9 @@ export class WebElementWrapper { * @default { withJS: false } */ async clearValue(options: ClearOptions = { withJS: false }) { + if (this.browserType === Browsers.InternetExplorer) { + return this.clearValueWithKeyboard(); + } await this.retryCall(async function clearValue(wrapper) { if (wrapper.browserType === Browsers.Chrome || options.withJS) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=2702 @@ -249,6 +252,16 @@ export class WebElementWrapper { * @default { charByChar: false } */ async clearValueWithKeyboard(options: TypeOptions = { charByChar: false }) { + if (this.browserType === Browsers.InternetExplorer) { + const value = await this.getAttribute('value'); + // For IE testing, the text field gets clicked in the middle so + // first go HOME and then DELETE all chars + await this.pressKeys(this.Keys.HOME); + for (let i = 0; i <= value.length; i++) { + await this.pressKeys(this.Keys.DELETE); + } + return; + } if (options.charByChar === true) { const value = await this.getAttribute('value'); for (let i = 0; i <= value.length; i++) { diff --git a/test/functional/services/remote/browsers.ts b/test/functional/services/remote/browsers.ts index 7fb07046a363..46d81f1737a5 100644 --- a/test/functional/services/remote/browsers.ts +++ b/test/functional/services/remote/browsers.ts @@ -20,4 +20,5 @@ export enum Browsers { Chrome = 'chrome', Firefox = 'firefox', + InternetExplorer = 'ie', } diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 6377d97dad28..8c78ed852caf 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -27,6 +27,8 @@ import { Builder, Capabilities, By, Key, logging, until } from 'selenium-webdriv import chrome from 'selenium-webdriver/chrome'; // @ts-ignore types not available import firefox from 'selenium-webdriver/firefox'; +// @ts-ignore types not available +import ie from 'selenium-webdriver/ie'; // @ts-ignore internal modules are not typed import { LegacyActionSequence } from 'selenium-webdriver/lib/actions'; // @ts-ignore internal modules are not typed @@ -34,6 +36,7 @@ import { Executor } from 'selenium-webdriver/lib/http'; // @ts-ignore internal modules are not typed import { getLogger } from 'selenium-webdriver/lib/logging'; +import { resolve, delimiter } from 'path'; import { preventParallelCalls } from './prevent_parallel_calls'; import { Browsers } from './browsers'; @@ -89,6 +92,7 @@ async function attemptToCreateCommand(log: ToolingLog, browserType: Browsers) { .withCapabilities(chromeCapabilities) .setChromeService(new chrome.ServiceBuilder(chromeDriver.path).enableVerboseLogging()) .build(); + case 'firefox': const firefoxOptions = new firefox.Options(); if (headlessBrowser === '1') { @@ -100,6 +104,30 @@ async function attemptToCreateCommand(log: ToolingLog, browserType: Browsers) { .setFirefoxOptions(firefoxOptions) .setFirefoxService(new firefox.ServiceBuilder(geckoDriver.path).enableVerboseLogging()) .build(); + + case 'ie': + // https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/ie_exports_Options.html + const driverPath = resolve( + __dirname, + '..\\..\\..\\..\\node_modules\\iedriver\\lib\\iedriver' + ); + process.env.PATH = driverPath + delimiter + process.env.PATH; + + const ieCapabilities = Capabilities.ie(); + ieCapabilities.set('se:ieOptions', { + 'ie.ensureCleanSession': true, + ignoreProtectedModeSettings: true, + ignoreZoomSetting: false, // requires us to have 100% zoom level + nativeEvents: true, // need this for values to stick but it requires 100% scaling and window focus + requireWindowFocus: true, + logLevel: 'TRACE', + }); + + return new Builder() + .forBrowser(browserType) + .withCapabilities(ieCapabilities) + .build(); + default: throw new Error(`${browserType} is not supported yet`); } diff --git a/test/functional/services/screenshots.ts b/test/functional/services/screenshots.ts index ac9292da16a9..ddafa211ece7 100644 --- a/test/functional/services/screenshots.ts +++ b/test/functional/services/screenshots.ts @@ -18,19 +18,16 @@ */ import { resolve, dirname } from 'path'; -import { writeFile, readFileSync } from 'fs'; -import Bluebird, { fromNode as fcb, promisify } from 'bluebird'; -import mkdirp from 'mkdirp'; +import { writeFile, readFileSync, mkdir } from 'fs'; +import { promisify } from 'util'; + import del from 'del'; import { comparePngs } from './lib/compare_pngs'; import { FtrProviderContext } from '../ftr_provider_context'; import { WebElementWrapper } from './lib/web_element_wrapper'; -type WriteFileAsync = (path: string | number | Buffer | URL, data: any) => Bluebird; -type MkDirAsync = (dirName: string) => Bluebird; - -const mkdirAsync = promisify(mkdirp) as MkDirAsync; -const writeFileAsync = promisify(writeFile) as WriteFileAsync; +const mkdirAsync = promisify(mkdir); +const writeFileAsync = promisify(writeFile); export async function ScreenshotsProvider({ getService }: FtrProviderContext) { const log = getService('log'); @@ -65,7 +62,7 @@ export async function ScreenshotsProvider({ getService }: FtrProviderContext) { await writeFileAsync(baselinePath, readFileSync(sessionPath)); return 0; } else { - await mkdirAsync(FAILURE_DIRECTORY); + await mkdirAsync(FAILURE_DIRECTORY, { recursive: true }); return await comparePngs(sessionPath, baselinePath, failurePath, SESSION_DIRECTORY, log); } } @@ -82,8 +79,8 @@ export async function ScreenshotsProvider({ getService }: FtrProviderContext) { try { log.info(`Taking screenshot "${path}"`); const screenshot = await (el ? el.takeScreenshot() : browser.takeScreenshot()); - await fcb(cb => mkdirp(dirname(path), cb)); - await fcb(cb => writeFile(path, screenshot, 'base64', cb)); + await mkdirAsync(dirname(path), { recursive: true }); + await writeFileAsync(path, screenshot, 'base64'); } catch (err) { log.error('SCREENSHOT FAILED'); log.error(err); diff --git a/test/functional/services/snapshots.ts b/test/functional/services/snapshots.ts index 96e14864bef8..84526878a7bb 100644 --- a/test/functional/services/snapshots.ts +++ b/test/functional/services/snapshots.ts @@ -17,17 +17,16 @@ * under the License. */ -import expect from '@kbn/expect'; import { dirname, resolve } from 'path'; -import { writeFile, readFileSync } from 'fs'; -import Bluebird, { fromNode as fcb, promisify } from 'bluebird'; -import mkdirp from 'mkdirp'; +import { writeFile, readFileSync, mkdir } from 'fs'; +import { promisify } from 'util'; + +import expect from '@kbn/expect'; import del from 'del'; import { FtrProviderContext } from '../ftr_provider_context'; -type WriteFileAsync = (path: string | number | Buffer | URL, data: any) => Bluebird; - -const writeFileAsync = promisify(writeFile) as WriteFileAsync; +const mkdirAsync = promisify(mkdir); +const writeFileAsync = promisify(writeFile); export async function SnapshotsProvider({ getService }: FtrProviderContext) { const log = getService('log'); @@ -77,8 +76,8 @@ export async function SnapshotsProvider({ getService }: FtrProviderContext) { private async _take(path: string, snapshot?: object) { try { - await fcb(cb => mkdirp(dirname(path), cb)); - await fcb(cb => writeFile(path, JSON.stringify(snapshot), 'utf8', cb)); + await mkdirAsync(dirname(path), { recursive: true }); + await writeFileAsync(path, JSON.stringify(snapshot), 'utf8'); } catch (err) { log.error('SNAPSHOT FAILED'); log.error(err); diff --git a/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx index f9285fbe4eaa..627fd05404b2 100644 --- a/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx +++ b/test/plugin_functional/plugins/core_plugin_b/public/plugin.tsx @@ -23,6 +23,7 @@ import { CorePluginAPluginSetup } from '../../core_plugin_a/public/plugin'; declare global { interface Window { corePluginB?: string; + hasAccessToInjectedMetadata?: boolean; } } @@ -34,6 +35,7 @@ export class CorePluginBPlugin implements Plugin { public setup(core: CoreSetup, deps: CorePluginBDeps) { window.corePluginB = `Plugin A said: ${deps.core_plugin_a.getGreeting()}`; + window.hasAccessToInjectedMetadata = 'getInjectedVar' in core.injectedMetadata; core.application.register({ id: 'bar', diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.js b/test/plugin_functional/test_suites/core_plugins/ui_plugins.js index 3b46b60368bb..df855f243d40 100644 --- a/test/plugin_functional/test_suites/core_plugins/ui_plugins.js +++ b/test/plugin_functional/test_suites/core_plugins/ui_plugins.js @@ -23,14 +23,26 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common']); const browser = getService('browser'); - describe('ui plugin loading', function describeIndexTests() { - before(async () => { - await PageObjects.common.navigateToApp('settings'); + describe('ui plugins', function () { + describe('loading', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToApp('settings'); + }); + + it('should attach string to window.corePluginB', async () => { + const corePluginB = await browser.execute('return window.corePluginB'); + expect(corePluginB).to.equal(`Plugin A said: Hello from Plugin A!`); + }); }); + describe('have injectedMetadata service provided', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToApp('bar'); + }); - it('should attach string to window.corePluginB', async () => { - const corePluginB = await browser.execute('return window.corePluginB'); - expect(corePluginB).to.equal(`Plugin A said: Hello from Plugin A!`); + it('should attach string to window.corePluginB', async () => { + const hasAccessToInjectedMetadata = await browser.execute('return window.hasAccessToInjectedMetadata'); + expect(hasAccessToInjectedMetadata).to.equal(true); + }); }); }); } diff --git a/utilities/visual_regression.js b/utilities/visual_regression.js index 216d218ebdc5..c06b058d9ddf 100644 --- a/utilities/visual_regression.js +++ b/utilities/visual_regression.js @@ -25,7 +25,6 @@ import Handlebars from 'handlebars'; import fs from 'fs'; import path from 'path'; import imageDiff from 'image-diff'; -import mkdirp from 'mkdirp'; import moment from 'moment'; import SimpleGit from 'simple-git'; @@ -77,8 +76,8 @@ async function compareScreenshots() { const SESSION_SCREENSHOTS_DIR = path.resolve(SCREENSHOTS_DIR, 'session'); // We don't need to create the baseline dir because it's committed. - mkdirp.sync(DIFF_SCREENSHOTS_DIR); - mkdirp.sync(SESSION_SCREENSHOTS_DIR); + fs.mkdirSync(DIFF_SCREENSHOTS_DIR, { recursive: true }); + fs.mkdirSync(SESSION_SCREENSHOTS_DIR, { recursive: true }); const files = await readDirAsync(SESSION_SCREENSHOTS_DIR); const screenshots = files.filter(file => file.indexOf('.png') !== -1); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 896c7ac7a259..db8e618ff113 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -7,7 +7,7 @@ "xpack.apm": "legacy/plugins/apm", "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", - "xpack.code": "legacy/plugins/code", + "xpack.code": ["legacy/plugins/code", "plugins/code"], "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", "xpack.features": "plugins/features", diff --git a/x-pack/gulpfile.js b/x-pack/gulpfile.js index edf8d3c59c3b..74e24692f59f 100644 --- a/x-pack/gulpfile.js +++ b/x-pack/gulpfile.js @@ -4,36 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -require('@kbn/plugin-helpers').babelRegister(); -require('dotenv').config({ silent: true }); +require('../src/setup_node_env'); -const path = require('path'); -const gulp = require('gulp'); -const mocha = require('gulp-mocha'); -const multiProcess = require('gulp-multi-process'); -const fancyLog = require('fancy-log'); -const pkg = require('./package.json'); +const { buildTask } = require('./tasks/build'); +const { devTask } = require('./tasks/dev'); +const { testTask, testBrowserTask, testBrowserDevTask, testServerTask } = require('./tasks/test'); +const { prepareTask } = require('./tasks/prepare'); -const buildDir = path.resolve(__dirname, 'build'); -const buildTarget = path.resolve(buildDir, 'plugin'); -const packageDir = path.resolve(buildDir, 'distributions'); -const coverageDir = path.resolve(__dirname, 'coverage'); - -const gulpHelpers = { - buildDir, - buildTarget, - coverageDir, - log: fancyLog, - mocha, - multiProcess, - packageDir, - pkg, +// export the tasks that are runnable from the CLI +module.exports = { + build: buildTask, + dev: devTask, + prepare: prepareTask, + test: testTask, + testserver: testServerTask, + testbrowser: testBrowserTask, + 'testbrowser-dev': testBrowserDevTask, }; - -require('./tasks/build')(gulp, gulpHelpers); -require('./tasks/clean')(gulp, gulpHelpers); -require('./tasks/dev')(gulp, gulpHelpers); -require('./tasks/prepare')(gulp, gulpHelpers); -require('./tasks/report')(gulp, gulpHelpers); -require('./tasks/test')(gulp, gulpHelpers); -require('./legacy/plugins/canvas/tasks')(gulp, gulpHelpers); diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index bba0051e31e0..856143c69395 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -110,6 +110,7 @@ describe('create()', () => { retryAt: null, state: {}, params: {}, + ownerId: null, }); savedObjectsClient.update.mockResolvedValueOnce({ id: '1', @@ -491,6 +492,7 @@ describe('create()', () => { retryAt: null, state: {}, params: {}, + ownerId: null, }); savedObjectsClient.update.mockResolvedValueOnce({ id: '1', @@ -568,6 +570,7 @@ describe('enable()', () => { taskType: '', startedAt: null, retryAt: null, + ownerId: null, }); await alertsClient.enable({ id: '1' }); @@ -643,6 +646,7 @@ describe('enable()', () => { taskType: '', startedAt: null, retryAt: null, + ownerId: null, }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ created: true, diff --git a/x-pack/legacy/plugins/alerting/server/lib/get_create_task_runner_function.test.ts b/x-pack/legacy/plugins/alerting/server/lib/get_create_task_runner_function.test.ts index 2490a187ee45..8875e964ef58 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/get_create_task_runner_function.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/get_create_task_runner_function.test.ts @@ -33,6 +33,7 @@ beforeAll(() => { params: { alertId: '1', }, + ownerId: null, }; }); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts index bd45cec316df..91809fbaede0 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -6,6 +6,7 @@ import { Legacy } from 'kibana'; import { setupRequest } from './setup_request'; +import { uiSettingsServiceMock } from '../../../../../../../src/legacy/ui/ui_settings/ui_settings_service.mock'; function getMockRequest() { const callWithRequestSpy = jest.fn(); @@ -125,8 +126,10 @@ describe('setupRequest', () => { it('should set `ignore_throttled=true` if `includeFrozen=false`', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); + const uiSettingsService = uiSettingsServiceMock.create(); // mock includeFrozen to return false - mockRequest.getUiSettingsService = () => ({ get: async () => false }); + uiSettingsService.get.mockResolvedValue(false); + mockRequest.getUiSettingsService = () => uiSettingsService; const { client } = await setupRequest(mockRequest); await client.search({}); const params = callWithRequestSpy.mock.calls[0][2]; @@ -136,8 +139,10 @@ describe('setupRequest', () => { it('should set `ignore_throttled=false` if `includeFrozen=true`', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); + const uiSettingsService = uiSettingsServiceMock.create(); // mock includeFrozen to return true - mockRequest.getUiSettingsService = () => ({ get: async () => true }); + uiSettingsService.get.mockResolvedValue(true); + mockRequest.getUiSettingsService = () => uiSettingsService; const { client } = await setupRequest(mockRequest); await client.search({}); const params = callWithRequestSpy.mock.calls[0][2]; diff --git a/x-pack/legacy/plugins/canvas/i18n/components.ts b/x-pack/legacy/plugins/canvas/i18n/components.ts index 941db91c4172..b8f75a2f8c13 100644 --- a/x-pack/legacy/plugins/canvas/i18n/components.ts +++ b/x-pack/legacy/plugins/canvas/i18n/components.ts @@ -172,6 +172,48 @@ export const ComponentStrings = { defaultMessage: 'Remove Color', }), }, + CustomElementModal: { + getCancelButtonLabel: () => + i18n.translate('xpack.canvas.customElementModal.cancelButtonLabel', { + defaultMessage: 'Cancel', + }), + getCharactersRemainingDescription: (numberOfRemainingCharacter: number) => + i18n.translate('xpack.canvas.customElementModal.remainingCharactersDescription', { + defaultMessage: '{numberOfRemainingCharacter} characters remaining', + values: { + numberOfRemainingCharacter, + }, + }), + getDescriptionInputLabel: () => + i18n.translate('xpack.canvas.customElementModal.descriptionInputLabel', { + defaultMessage: 'Description', + }), + getElementPreviewTitle: () => + i18n.translate('xpack.canvas.customElementModal.elementPreviewTitle', { + defaultMessage: 'Element preview', + }), + getImageFilePickerPlaceholder: () => + i18n.translate('xpack.canvas.customElementModal.imageFilePickerPlaceholder', { + defaultMessage: 'Select or drag and drop an image', + }), + getImageInputLabel: () => + i18n.translate('xpack.canvas.customElementModal.imageInputLabel', { + defaultMessage: 'Thumbnail image', + }), + getImageInputDescription: () => + i18n.translate('xpack.canvas.customElementModal.imageInputDescription', { + defaultMessage: + 'Take a screenshot of your element and upload it here. This can also be done after saving.', + }), + getNameInputLabel: () => + i18n.translate('xpack.canvas.customElementModal.nameInputLabel', { + defaultMessage: 'Name', + }), + getSaveButtonLabel: () => + i18n.translate('xpack.canvas.customElementModal.saveButtonLabel', { + defaultMessage: 'Save', + }), + }, DatasourceDatasourceComponent: { getChangeButtonLabel: () => i18n.translate('xpack.canvas.datasourceDatasourceComponent.changeButtonLabel', { @@ -215,14 +257,64 @@ export const ComponentStrings = { defaultMessage: 'Datasource preview', }), }, - HelpMenu: { - getHelpMenuDescription: () => - i18n.translate('xpack.canvas.helpMenu.description', { - defaultMessage: 'For {CANVAS} specific information', + + ElementConfig: { + getFailedLabel: () => + i18n.translate('xpack.canvas.elementConfig.failedLabel', { + defaultMessage: 'Failed', + description: + 'The label for the total number of elements in a workpad that have thrown an error or failed to load', + }), + getLoadedLabel: () => + i18n.translate('xpack.canvas.elementConfig.loadedLabel', { + defaultMessage: 'Loaded', + description: 'The label for the number of elements in a workpad that have loaded', + }), + getProgressLabel: () => + i18n.translate('xpack.canvas.elementConfig.progressLabel', { + defaultMessage: 'Progress', + description: 'The label for the percentage of elements that have finished loading', + }), + getTitle: () => + i18n.translate('xpack.canvas.elementConfig.title', { + defaultMessage: 'Elements', + description: + '"Elements" refers to the individual text, images, or visualizations that you can add to a Canvas workpad', + }), + getTotalLabel: () => + i18n.translate('xpack.canvas.elementConfig.totalLabel', { + defaultMessage: 'Total', + description: 'The label for the total number of elements in a workpad', + }), + }, + ElementSettings: { + getDataTabLabel: () => + i18n.translate('xpack.canvas.elementSettings.dataTabLabel', { + defaultMessage: 'Data', + description: + 'This tab contains the settings for the data (i.e. Elasticsearch query) used as ' + + 'the source for a Canvas element', + }), + getDisplayTabLabel: () => + i18n.translate('xpack.canvas.elementSettings.displayTabLabel', { + defaultMessage: 'Display', + description: 'This tab contains the settings for how data is displayed in a Canvas element', + }), + }, + GroupSettings: { + getSaveGroupDescription: () => + i18n.translate('xpack.canvas.groupSettings.saveGroupDescription', { + defaultMessage: 'Save this group as a new element to re-use it throughout your workpad.', + }), + getUngroupDescription: () => + i18n.translate('xpack.canvas.groupSettings.ungroupDescription', { + defaultMessage: 'Ungroup ({uKey}) to edit individual element settings.', values: { - CANVAS, + uKey: 'U', }, }), + }, + HelpMenu: { getDocumentationLinkLabel: () => i18n.translate('xpack.canvas.helpMenu.documentationLinkLabel', { defaultMessage: '{CANVAS} documentation', @@ -230,15 +322,22 @@ export const ComponentStrings = { CANVAS, }, }), + getHelpMenuDescription: () => + i18n.translate('xpack.canvas.helpMenu.description', { + defaultMessage: 'For {CANVAS} specific information', + values: { + CANVAS, + }, + }), getKeyboardShortcutsLinkLabel: () => i18n.translate('xpack.canvas.helpMenu.keyboardShortcutsLinkLabel', { defaultMessage: 'Keyboard Shortcuts', }), }, KeyboardShortcutsDoc: { - getTitle: () => - i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyoutHeaderTitle', { - defaultMessage: 'Keyboard Shortcuts', + getFlyoutCloseButtonAriaLabel: () => + i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyout.closeButtonAriaLabel', { + defaultMessage: 'Closes keyboard shortcuts reference', }), getShortcutSeparator: () => i18n.translate('xpack.canvas.keyboardShortcutsDoc.shortcutListSeparator', { @@ -246,9 +345,227 @@ export const ComponentStrings = { description: 'Separates which keyboard shortcuts can be used for a single action. Example: "{shortcut1} or {shortcut2} or {shortcut3}"', }), - getFlyoutCloseButtonAriaLabel: () => - i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyout.closeButtonAriaLabel', { - defaultMessage: 'Closes keyboard shortcuts reference', + getTitle: () => + i18n.translate('xpack.canvas.keyboardShortcutsDoc.flyoutHeaderTitle', { + defaultMessage: 'Keyboard Shortcuts', + }), + }, + MultiElementSettings: { + getMultipleElementsActionsDescription: () => + i18n.translate('xpack.canvas.groupSettings.multipleElementsActionsDescription', { + defaultMessage: + 'Deselect these elements to edit their individual settings, press ({gKey}) to group them, or save this selection as a new ' + + 'element to re-use it throughout your workpad.', + values: { + gKey: 'G', + }, + }), + getMultipleElementsDescription: () => + i18n.translate('xpack.canvas.groupSettings.multipleElementsDescription', { + defaultMessage: 'Multiple elements are currently selected.', + }), + }, + PageConfig: { + getBackgroundColorDescription: () => + i18n.translate('xpack.canvas.pageConfig.backgroundColorDescription', { + defaultMessage: 'Accepts HEX, RGB or HTML color names', + }), + getBackgroundColorLabel: () => + i18n.translate('xpack.canvas.pageConfig.backgroundColorLabel', { + defaultMessage: 'Background Color', + }), + getNoTransitionDropDownOptionLabel: () => + i18n.translate('xpack.canvas.pageConfig.transitions.noneDropDownOptionLabel', { + defaultMessage: 'None', + description: + 'This is the option the user should choose if they do not want any page transition (i.e. fade in, fade out, etc) to ' + + 'be applied to the current page.', + }), + getTitle: () => + i18n.translate('xpack.canvas.pageConfig.title', { + defaultMessage: 'Page', + }), + getTransitionLabel: () => + i18n.translate('xpack.canvas.pageConfig.transitionLabel', { + defaultMessage: 'Transition', + description: + 'This refers to the transition effect, such as fade in or rotate, applied to a page in presentation mode.', + }), + getTransitionPreviewLabel: () => + i18n.translate('xpack.canvas.pageConfig.transitionPreviewLabel', { + defaultMessage: 'Preview', + description: 'This is the label for a preview of the transition effect selected.', + }), + }, + SidebarContent: { + getGroupedElementSidebarTitle: () => + i18n.translate('xpack.canvas.sidebarContent.groupedElementSidebarTitle', { + defaultMessage: 'Grouped element', + description: + 'The title displayed when a grouped element is selected. "elements" refer to the different visualizations, images, ' + + 'text, etc that can be added in a Canvas workpad. These elements can be grouped into a larger "grouped element" ' + + 'that contains multiple individual elements.', + }), + getMultiElementSidebarTitle: () => + i18n.translate('xpack.canvas.sidebarContent.multiElementSidebarTitle', { + defaultMessage: 'Multiple elements', + description: + 'The title displayed when multiple elements are selected. "elements" refer to the different visualizations, images, ' + + 'text, etc that can be added in a Canvas workpad.', + }), + getSingleElementSidebarTitle: () => + i18n.translate('xpack.canvas.sidebarContent.singleElementSidebarTitle', { + defaultMessage: 'Selected element', + description: + 'The title displayed when a single element are selected. "element" refer to the different visualizations, images, ' + + 'text, etc that can be added in a Canvas workpad.', + }), + }, + SidebarHeader: { + getAlignmentMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.alignmentMenuItemLabel', { + defaultMessage: 'Alignment', + description: + 'This refers to the vertical (i.e. left, center, right) and horizontal (i.e. top, middle, bottom) ' + + 'alignment options of the selected elements', + }), + getBottomAlignMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.bottomAlignMenuItemLabel', { + defaultMessage: 'Bottom', + }), + getBringForwardAriaLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.bringForwardArialLabel', { + defaultMessage: 'Move element up one layer', + }), + getBringToFrontAriaLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.bringToFrontArialLabel', { + defaultMessage: 'Move element to top layer', + }), + getCenterAlignMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.centerAlignMenuItemLabel', { + defaultMessage: 'Center', + description: 'This refers to alignment centered horizontally.', + }), + getContextMenuTitle: () => + i18n.translate('xpack.canvas.sidebarHeader.contextMenuAriaLabel', { + defaultMessage: 'Element options', + }), + getCreateElementModalTitle: () => + i18n.translate('xpack.canvas.sidebarHeader.createElementModalTitle', { + defaultMessage: 'Create new element', + }), + getDistributionMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.distributionMenutItemLabel', { + defaultMessage: 'Distribution', + description: + 'This refers to the options to evenly spacing the selected elements horizontall or vertically.', + }), + getGroupMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.groupMenuItemLabel', { + defaultMessage: 'Group', + description: 'This refers to grouping multiple selected elements.', + }), + getHorizontalDistributionMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.horizontalDistributionMenutItemLabel', { + defaultMessage: 'Horizontal', + }), + getLeftAlignMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.leftAlignMenuItemLabel', { + defaultMessage: 'Left', + }), + getMiddleAlignMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.middleAlignMenuItemLabel', { + defaultMessage: 'Middle', + description: 'This refers to alignment centered vertically.', + }), + getOrderMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.orderMenuItemLabel', { + defaultMessage: 'Order', + description: 'Refers to the order of the elements displayed on the page from front to back', + }), + getRightAlignMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.rightAlignMenuItemLabel', { + defaultMessage: 'Right', + }), + getSaveElementMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.savedElementMenuItemLabel', { + defaultMessage: 'Save as new element', + }), + getSendBackwardAriaLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.sendBackwardArialLabel', { + defaultMessage: 'Move element down one layer', + }), + getSendToBackAriaLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.sendToBackArialLabel', { + defaultMessage: 'Move element to bottom layer', + }), + getTopAlignMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.topAlignMenuItemLabel', { + defaultMessage: 'Top', + }), + getUngroupMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.ungroupMenuItemLabel', { + defaultMessage: 'Ungroup', + description: 'This refers to ungrouping a grouped element', + }), + getVerticalDistributionMenuItemLabel: () => + i18n.translate('xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel', { + defaultMessage: 'Vertical', + }), + }, + WorkpadConfig: { + getApplyStylesheetButtonLabel: () => + i18n.translate('xpack.canvas.workpadConfig.applyStylesheetButtonLabel', { + defaultMessage: `Apply stylesheet`, + description: + '"stylesheet" refers to the collection of CSS style rules entered by the user.', + }), + getFlipDimensionAriaLabel: () => + i18n.translate('xpack.canvas.workpadConfig.swapDimensionsAriaLabel', { + defaultMessage: `Swap the page's width and height`, + }), + getFlipDimensionTooltip: () => + i18n.translate('xpack.canvas.workpadConfig.swapDimensionsTooltip', { + defaultMessage: 'Swap the width and height', + }), + getGlobalCSSLabel: () => + i18n.translate('xpack.canvas.workpadConfig.globalCSSLabel', { + defaultMessage: `Global CSS overrides`, + }), + getGlobalCSSTooltip: () => + i18n.translate('xpack.canvas.workpadConfig.globalCSSTooltip', { + defaultMessage: `Apply styles to all pages in this workpad`, + }), + getPageHeightLabel: () => + i18n.translate('xpack.canvas.workpadConfig.heightLabel', { + defaultMessage: 'Height', + }), + getPageSizeBadgeAriaLabel: (sizeName: string) => + i18n.translate('xpack.canvas.workpadConfig.pageSizeBadgeAriaLabel', { + defaultMessage: `Preset page size: {sizeName}`, + values: { + sizeName, + }, + }), + getPageSizeBadgeOnClickAriaLabel: (sizeName: string) => + i18n.translate('xpack.canvas.workpadConfig.pageSizeBadgeOnClickAriaLabel', { + defaultMessage: `Set page size to {sizeName}`, + values: { + sizeName, + }, + }), + getPageWidthLabel: () => + i18n.translate('xpack.canvas.workpadConfig.widthLabel', { + defaultMessage: 'Width', + }), + getTitle: () => + i18n.translate('xpack.canvas.workpadConfig.title', { + defaultMessage: 'Workpad', + }), + getUSLetterButtonLabel: () => + i18n.translate('xpack.canvas.workpadConfig.USLetterButtonLabel', { + defaultMessage: 'US Letter', + description: 'This is referring to the dimentions of U.S. standard letter paper.', }), }, PageManager: { @@ -737,6 +1054,13 @@ export const ComponentStrings = { }), }, WorkpadTemplates: { + getCloneTemplateLinkAriaLabel: (templateName: string) => + i18n.translate('xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel', { + defaultMessage: `Clone workpad template '{templateName}'`, + values: { + templateName, + }, + }), getTableDescriptionColumnTitle: () => i18n.translate('xpack.canvas.workpadTemplates.table.descriptionColumnTitle', { defaultMessage: 'Description', @@ -756,12 +1080,5 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.workpadTemplate.searchPlaceholder', { defaultMessage: 'Find template', }), - getCloneTemplateLinkAriaLabel: (templateName: string) => - i18n.translate('xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel', { - defaultMessage: `Clone workpad template '{templateName}'`, - values: { - templateName, - }, - }), }, }; diff --git a/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx b/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx index 5f6e0fc9e7bc..b5d08d98072a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx @@ -31,10 +31,13 @@ import { import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; import { encode } from '../../../common/lib/dataurl'; import { ElementCard } from '../element_card'; +import { ComponentStrings } from '../../../i18n'; const MAX_NAME_LENGTH = 40; const MAX_DESCRIPTION_LENGTH = 100; +const { CustomElementModal: strings } = ComponentStrings; + interface Props { /** * initial value of the name of the custom element @@ -126,8 +129,8 @@ export class CustomElementModal extends PureComponent { { /> { -

- Take a screenshot of your element and upload it here. This can also be done after - saving. -

+

{strings.getImageInputDescription()}

{ grow={1} > -

Element preview

+

{strings.getElementPreviewTitle()}

@@ -187,7 +189,7 @@ export class CustomElementModal extends PureComponent { - Cancel + {strings.getCancelButtonLabel()} { onSave(name, description, image); }} > - Save + {strings.getSaveButtonLabel()} diff --git a/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js b/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js index e5ae93365580..76007994e56b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js @@ -7,6 +7,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiStat, EuiTitle } from '@elastic/eui'; import PropTypes from 'prop-types'; import React, { Fragment } from 'react'; +import { ComponentStrings } from '../../../i18n'; + +const { ElementConfig: strings } = ComponentStrings; export const ElementConfig = ({ elementStats }) => { if (!elementStats) { @@ -19,21 +22,21 @@ export const ElementConfig = ({ elementStats }) => { return ( -

Elements

+

{strings.getTitle()}

- + - + - + - +
diff --git a/x-pack/legacy/plugins/canvas/public/components/page_config/index.js b/x-pack/legacy/plugins/canvas/public/components/page_config/index.js index fb216af66763..a0692584d498 100644 --- a/x-pack/legacy/plugins/canvas/public/components/page_config/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/page_config/index.js @@ -9,8 +9,11 @@ import { get } from 'lodash'; import { transitionsRegistry } from '../../lib/transitions_registry'; import { getSelectedPageIndex, getPages } from '../../state/selectors/workpad'; import { stylePage, setPageTransition } from '../../state/actions/pages'; +import { ComponentStrings } from '../../../i18n'; import { PageConfig as Component } from './page_config'; +const { PageConfig: strings } = ComponentStrings; + const mapStateToProps = state => { const pageIndex = getSelectedPageIndex(state); const page = getPages(state)[pageIndex]; @@ -28,7 +31,7 @@ const mergeProps = (stateProps, dispatchProps) => { }, background: get(stateProps, 'page.style.background'), transition: transitionsRegistry.get(get(stateProps, 'page.transition.name')), - transitions: [{ value: '', text: 'None' }].concat( + transitions: [{ value: '', text: strings.getNoTransitionDropDownOptionLabel() }].concat( transitionsRegistry.toArray().map(({ name, displayName }) => ({ value: name, text: displayName, diff --git a/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js b/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js index 5c6e91621d55..2586b4ec61f0 100644 --- a/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js +++ b/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js @@ -8,6 +8,9 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { EuiCard, EuiFormRow, EuiTitle, EuiSpacer, EuiSelect } from '@elastic/eui'; import { WorkpadColorPicker } from '../workpad_color_picker'; +import { ComponentStrings } from '../../../i18n'; + +const { PageConfig: strings } = ComponentStrings; export const PageConfig = ({ pageIndex, @@ -20,13 +23,13 @@ export const PageConfig = ({ return ( -

Page

+

{strings.getTitle()}

@@ -35,7 +38,7 @@ export const PageConfig = ({ page, we use the second page's transition) */} {pageIndex > 0 ? ( - + {transition ? ( - + = ({ element }) => { const tabs = [ { id: 'edit', - name: 'Display', + name: strings.getDisplayTabLabel(), content: (
@@ -36,7 +39,7 @@ export const ElementSettings: FunctionComponent = ({ element }) => { }, { id: 'data', - name: 'Data', + name: strings.getDataTabLabel(), content: (
diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx index a514ef15b661..b46465d9ec77 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx @@ -6,10 +6,13 @@ import React, { FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; +import { ComponentStrings } from '../../../i18n'; + +const { GroupSettings: strings } = ComponentStrings; export const GroupSettings: FunctionComponent = () => ( -

Ungroup (U) to edit individual element settings.

-

Save this group as a new element to re-use it throughout your workpad.

+

{strings.getUngroupDescription()}

+

{strings.getSaveGroupDescription()}

); diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx index a3922aaac864..2de3a805c95e 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx @@ -6,13 +6,13 @@ import React, { FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; +import { ComponentStrings } from '../../../i18n'; + +const { MultiElementSettings: strings } = ComponentStrings; export const MultiElementSettings: FunctionComponent = () => ( -

Multiple elements are currently selected.

-

- Deselect these elements to edit their individual settings, press (G) to group them, or save - this selection as a new element to re-use it throughout your workpad. -

+

{strings.getMultipleElementsDescription()}

+

{strings.getMultipleElementsActionsDescription()}

); diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_content.js b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_content.js index fe53203684f5..825ea8a4f249 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_content.js +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_content.js @@ -11,11 +11,14 @@ import { EuiSpacer } from '@elastic/eui'; import { getSelectedToplevelNodes, getSelectedElementId } from '../../state/selectors/workpad'; import { SidebarHeader } from '../sidebar_header'; import { globalStateUpdater } from '../workpad_page/integration_utils'; +import { ComponentStrings } from '../../../i18n'; import { MultiElementSettings } from './multi_element_settings'; import { GroupSettings } from './group_settings'; import { GlobalConfig } from './global_config'; import { ElementSettings } from './element_settings'; +const { SidebarContent: strings } = ComponentStrings; + const mapStateToProps = state => ({ selectedToplevelNodes: getSelectedToplevelNodes(state), selectedElementId: getSelectedElementId(state), @@ -42,7 +45,10 @@ const withGlobalState = (commit, updateGlobalState) => (type, payload) => { const MultiElementSidebar = ({ commit, updateGlobalState }) => ( - + @@ -51,7 +57,7 @@ const MultiElementSidebar = ({ commit, updateGlobalState }) => ( const GroupedElementSidebar = ({ commit, updateGlobalState }) => ( @@ -62,7 +68,7 @@ const GroupedElementSidebar = ({ commit, updateGlobalState }) => ( const SingleElementSidebar = ({ selectedElementId }) => ( - + ); diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx index 5a76414019a2..3a0e191b9f4b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx @@ -20,9 +20,13 @@ import { import { Popover } from '../popover'; import { CustomElementModal } from '../custom_element_modal'; import { ToolTipShortcut } from '../tool_tip_shortcut/'; +import { ComponentStrings, ShortcutStrings } from '../../../i18n'; const topBorderClassName = 'canvasContextMenu--topBorder'; +const { SidebarHeader: strings } = ComponentStrings; +const shortcutHelp = ShortcutStrings.getShortcutHelp(); + interface Props { /** * title to display in the header @@ -139,7 +143,7 @@ const contextMenuButton = (handleClick: (event: MouseEvent) => void) => ( color="text" iconType="boxesVertical" onClick={handleClick} - aria-label="Element options" + aria-label={strings.getContextMenuTitle()} /> ); @@ -202,7 +206,7 @@ export class SidebarHeader extends Component { position="bottom" content={ - Bring to front + {shortcutHelp.BRING_TO_FRONT} } @@ -211,7 +215,7 @@ export class SidebarHeader extends Component { color="text" iconType="sortUp" onClick={bringToFront} - aria-label="Move element to top layer" + aria-label={strings.getBringToFrontAriaLabel()} /> @@ -220,7 +224,7 @@ export class SidebarHeader extends Component { position="bottom" content={ - Bring forward + {shortcutHelp.BRING_FORWARD} } @@ -229,7 +233,7 @@ export class SidebarHeader extends Component { color="text" iconType="arrowUp" onClick={bringForward} - aria-label="Move element up one layer" + aria-label={strings.getBringForwardAriaLabel()} /> @@ -238,7 +242,7 @@ export class SidebarHeader extends Component { position="bottom" content={ - Send backward + {shortcutHelp.SEND_BACKWARD} } @@ -247,7 +251,7 @@ export class SidebarHeader extends Component { color="text" iconType="arrowDown" onClick={sendBackward} - aria-label="Move element down one layer" + aria-label={strings.getSendBackwardAriaLabel()} /> @@ -256,7 +260,7 @@ export class SidebarHeader extends Component { position="bottom" content={ - Send to back + {shortcutHelp.SEND_TO_BACK} } @@ -265,7 +269,7 @@ export class SidebarHeader extends Component { color="text" iconType="sortDown" onClick={sendToBack} - aria-label="Move element to bottom layer" + aria-label={strings.getSendToBackAriaLabel()} /> @@ -277,28 +281,28 @@ export class SidebarHeader extends Component { const { bringToFront, bringForward, sendBackward, sendToBack } = this.props; return { - menuItem: { name: 'Order', className: topBorderClassName, panel: 1 }, + menuItem: { name: strings.getOrderMenuItemLabel(), className: topBorderClassName, panel: 1 }, panel: { id: 1, - title: 'Order', + title: strings.getOrderMenuItemLabel(), items: [ { - name: 'Bring to front', // TODO: check against current element position and disable if already top layer + name: shortcutHelp.BRING_TO_FRONT, // TODO: check against current element position and disable if already top layer icon: 'sortUp', onClick: bringToFront, }, { - name: 'Bring forward', // TODO: same as above + name: shortcutHelp.BRING_TO_FRONT, // TODO: same as above icon: 'arrowUp', onClick: bringForward, }, { - name: 'Send backward', // TODO: check against current element position and disable if already bottom layer + name: shortcutHelp.SEND_BACKWARD, // TODO: check against current element position and disable if already bottom layer icon: 'arrowDown', onClick: sendBackward, }, { - name: 'Send to back', // TODO: same as above + name: shortcutHelp.SEND_TO_BACK, // TODO: same as above icon: 'sortDown', onClick: sendToBack, }, @@ -311,38 +315,42 @@ export class SidebarHeader extends Component { const { alignLeft, alignCenter, alignRight, alignTop, alignMiddle, alignBottom } = this.props; return { - menuItem: { name: 'Align elements', className: 'canvasContextMenu', panel: 2 }, + menuItem: { + name: strings.getAlignmentMenuItemLabel(), + className: 'canvasContextMenu', + panel: 2, + }, panel: { id: 2, - title: 'Alignment', + title: strings.getAlignmentMenuItemLabel(), items: [ { - name: 'Left', + name: strings.getLeftAlignMenuItemLabel(), icon: 'editorItemAlignLeft', onClick: close(alignLeft), }, { - name: 'Center', + name: strings.getCenterAlignMenuItemLabel(), icon: 'editorItemAlignCenter', onClick: close(alignCenter), }, { - name: 'Right', + name: strings.getRightAlignMenuItemLabel(), icon: 'editorItemAlignRight', onClick: close(alignRight), }, { - name: 'Top', + name: strings.getTopAlignMenuItemLabel(), icon: 'editorItemAlignTop', onClick: close(alignTop), }, { - name: 'Middle', + name: strings.getMiddleAlignMenuItemLabel(), icon: 'editorItemAlignMiddle', onClick: close(alignMiddle), }, { - name: 'Bottom', + name: strings.getBottomAlignMenuItemLabel(), icon: 'editorItemAlignBottom', onClick: close(alignBottom), }, @@ -355,18 +363,22 @@ export class SidebarHeader extends Component { const { distributeHorizontally, distributeVertically } = this.props; return { - menuItem: { name: 'Distribute elements', className: 'canvasContextMenu', panel: 3 }, + menuItem: { + name: strings.getDistributionMenuItemLabel(), + className: 'canvasContextMenu', + panel: 3, + }, panel: { id: 3, - title: 'Distribution', + title: strings.getDistributionMenuItemLabel(), items: [ { - name: 'Horizontal', + name: strings.getHorizontalDistributionMenuItemLabel(), icon: 'editorDistributeHorizontal', onClick: close(distributeHorizontally), }, { - name: 'Vertical', + name: strings.getVerticalDistributionMenuItemLabel(), icon: 'editorDistributeVertical', onClick: close(distributeVertically), }, @@ -382,7 +394,7 @@ export class SidebarHeader extends Component { return groupIsSelected ? [ { - name: 'Ungroup', + name: strings.getUngroupMenuItemLabel(), className: topBorderClassName, onClick: close(ungroupNodes), }, @@ -390,7 +402,7 @@ export class SidebarHeader extends Component { : selectedNodes.length > 1 ? [ { - name: 'Group', + name: strings.getGroupMenuItemLabel(), className: topBorderClassName, onClick: close(groupNodes), }, @@ -416,27 +428,27 @@ export class SidebarHeader extends Component { const items: EuiContextMenuPanelItemDescriptor[] = [ { - name: 'Cut', + name: shortcutHelp.CUT, icon: 'cut', onClick: close(cutNodes), }, { - name: 'Copy', + name: shortcutHelp.COPY, icon: 'copy', onClick: copyNodes, }, { - name: 'Paste', // TODO: can this be disabled if clipboard is empty? + name: shortcutHelp.PASTE, // TODO: can this be disabled if clipboard is empty? icon: 'copyClipboard', onClick: close(pasteNodes), }, { - name: 'Delete', + name: shortcutHelp.DELETE, icon: 'trash', onClick: close(deleteNodes), }, { - name: 'Clone', + name: shortcutHelp.CLONE, onClick: close(cloneNodes), }, ...this._getGroupMenuItems(close), @@ -445,7 +457,7 @@ export class SidebarHeader extends Component { const panels: EuiContextMenuPanelDescriptor[] = [ { id: 0, - title: 'Element options', + title: strings.getContextMenuTitle(), items, }, ]; @@ -468,7 +480,7 @@ export class SidebarHeader extends Component { } items.push({ - name: 'Save as new element', + name: strings.getSaveElementMenuItemLabel(), icon: 'indexOpen', className: topBorderClassName, onClick: this._showModal, @@ -483,7 +495,7 @@ export class SidebarHeader extends Component { className="canvasContextMenu" button={contextMenuButton} panelPaddingSize="none" - tooltip="Element options" + tooltip={strings.getContextMenuTitle()} tooltipPosition="bottom" > {({ closePopover }: { closePopover: () => void }) => ( @@ -519,12 +531,12 @@ export class SidebarHeader extends Component { {showLayerControls ? this._renderLayoutControls() : null} - + @@ -535,7 +547,7 @@ export class SidebarHeader extends Component { {isModalVisible ? ( diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js b/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js index f1073f896e12..4eb8a336d14b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js @@ -23,6 +23,9 @@ import { EuiButton, } from '@elastic/eui'; import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants'; +import { ComponentStrings } from '../../../i18n'; + +const { WorkpadConfig: strings } = ComponentStrings; export class WorkpadConfig extends PureComponent { static propTypes = { @@ -57,7 +60,7 @@ export class WorkpadConfig extends PureComponent { size: { height: 842, width: 590 }, }, { - name: 'US Letter', + name: strings.getUSLetterButtonLabel(), size: { height: 792, width: 612 }, }, ]; @@ -65,7 +68,7 @@ export class WorkpadConfig extends PureComponent { return (
-

Workpad

+

{strings.getTitle()}

@@ -78,7 +81,7 @@ export class WorkpadConfig extends PureComponent { - + setSize({ width: Number(e.target.value), height: size.height })} @@ -88,18 +91,18 @@ export class WorkpadConfig extends PureComponent { - + - + setSize({ height: Number(e.target.value), width: size.width })} @@ -117,8 +120,8 @@ export class WorkpadConfig extends PureComponent { key={`page-size-badge-${i}`} color="hollow" onClick={() => setSize(badge.size)} - aria-label={`Preset Page Size: ${badge.name}`} - onClickAriaLabel={`Set page size to ${badge.name}`} + aria-label={strings.getPageSizeBadgeAriaLabel(badge.name)} + onClickAriaLabel={strings.getPageSizeBadgeOnClickAriaLabel(badge.name)} > {badge.name} @@ -132,19 +135,19 @@ export class WorkpadConfig extends PureComponent { className="canvasArg__accordion" buttonContent={ - Global CSS overrides + {strings.getGlobalCSSLabel()} } >
this.setState({ css: e.target.value })} @@ -152,7 +155,7 @@ export class WorkpadConfig extends PureComponent { /> setWorkpadCSS(css || DEFAULT_WORKPAD_CSS)}> - Apply stylesheet + {strings.getApplyStylesheetButtonLabel()}
diff --git a/x-pack/legacy/plugins/canvas/tasks/helpers/babelhook.js b/x-pack/legacy/plugins/canvas/tasks/helpers/babelhook.js deleted file mode 100644 index dea18db918fc..000000000000 --- a/x-pack/legacy/plugins/canvas/tasks/helpers/babelhook.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const { resolve } = require('path'); -const register = require('@babel/register'); -const options = { - babelrc: false, - presets: [require.resolve('@kbn/babel-preset/node_preset')], - sourceMaps: false, - plugins: [ - [ - 'mock-imports', - [ - { - pattern: 'ui/chrome', - location: resolve(__dirname, '..', 'mocks', 'uiChrome'), - }, - { - pattern: 'ui/notify', - location: resolve(__dirname, '..', 'mocks', 'uiNotify'), - }, - { - pattern: 'ui/storage', - location: resolve(__dirname, '..', 'mocks', 'uiStorage'), - }, - { - pattern: 'ui/url/absolute_to_parsed_url', - location: resolve(__dirname, '..', 'mocks', 'absoluteToParsedUrl'), - }, - { - // ugly hack so that importing non-js files works, required for the function docs - pattern: '.(less|png|svg)$', - location: resolve(__dirname, '..', 'mocks', 'noop'), - }, - { - pattern: 'plugins/canvas/apps', - location: resolve(__dirname, '..', 'mocks', 'noop'), - }, - { - pattern: '/state/store', - location: resolve(__dirname, '..', 'mocks', 'stateStore'), - }, - ], - ], - ], -}; - -register(options); diff --git a/x-pack/legacy/plugins/canvas/tasks/helpers/dom_setup.js b/x-pack/legacy/plugins/canvas/tasks/helpers/dom_setup.js deleted file mode 100644 index bbc8cc52d6a6..000000000000 --- a/x-pack/legacy/plugins/canvas/tasks/helpers/dom_setup.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { JSDOM } from 'jsdom'; -import { APP_ROUTE } from '../../common/lib/constants'; -import chrome from '../mocks/uiChrome'; - -const basePath = chrome.getBasePath(); -const basename = `${basePath}${APP_ROUTE}`; - -const { window } = new JSDOM('', { - url: `http://localhost:5601/${basename}`, - pretendToBeVisual: true, -}); - -global.window = window; -global.document = window.document; -global.navigator = window.navigator; -global.requestAnimationFrame = window.requestAnimationFrame; -global.HTMLElement = window.HTMLElement; diff --git a/x-pack/legacy/plugins/canvas/tasks/index.js b/x-pack/legacy/plugins/canvas/tasks/index.js deleted file mode 100644 index 48ff27587783..000000000000 --- a/x-pack/legacy/plugins/canvas/tasks/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import test from './test'; - -export default function canvasTasks(gulp, gulpHelpers) { - test(gulp, gulpHelpers); -} diff --git a/x-pack/legacy/plugins/canvas/tasks/mocks/absoluteToParsedUrl.js b/x-pack/legacy/plugins/canvas/tasks/mocks/absoluteToParsedUrl.js deleted file mode 100644 index d73885ef0cc2..000000000000 --- a/x-pack/legacy/plugins/canvas/tasks/mocks/absoluteToParsedUrl.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const absoluteToParsedUrl = () => { - getAbsoluteUrl: () => - 'http://localhost:5601/kbp/app/canvas#/workpad/workpad-24d56dad-ae70-42b8-9ef1-c5350ecd426c/page/1'; -}; // noop diff --git a/x-pack/legacy/plugins/canvas/tasks/mocks/stateStore.js b/x-pack/legacy/plugins/canvas/tasks/mocks/stateStore.js deleted file mode 100644 index 9d3df08fffe0..000000000000 --- a/x-pack/legacy/plugins/canvas/tasks/mocks/stateStore.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export function getState() { - return { - assets: { - yay: { value: 'here is your image' }, - }, - }; -} diff --git a/x-pack/legacy/plugins/canvas/tasks/test.js b/x-pack/legacy/plugins/canvas/tasks/test.js deleted file mode 100644 index 9857e9c774e0..000000000000 --- a/x-pack/legacy/plugins/canvas/tasks/test.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve, join } from 'path'; - -export default function testTasks(gulp, { mocha }) { - const canvasRoot = resolve(__dirname, '..'); - - function runMocha(globs, { withEnzyme = false, withDOM = false } = {}) { - const requires = [join(canvasRoot, 'tasks/helpers/babelhook')]; - - if (withDOM) { - requires.push(join(canvasRoot, 'tasks/helpers/dom_setup')); - } - if (withEnzyme) { - requires.push(join(canvasRoot, 'tasks/helpers/enzyme_setup')); - } - - return gulp.src(globs, { read: false }).pipe( - mocha({ - ui: 'bdd', - require: requires, - }) - ); - } - - const getTestGlobs = rootPath => [ - join(canvasRoot, `${rootPath}/**/__tests__/**/*.js`), - join(canvasRoot, `!${rootPath}/**/__tests__/fixtures/**/*.js`), - ]; - - const getRootGlobs = rootPath => [join(canvasRoot, `${rootPath}/**/*.js`)]; - - gulp.task('canvas:test:common', () => { - return runMocha(getTestGlobs('common'), { withDOM: true }); - }); - - gulp.task('canvas:test:server', () => { - return runMocha(getTestGlobs('server')); - }); - - gulp.task('canvas:test:browser', () => { - return runMocha(getTestGlobs('public'), { withEnzyme: true, withDOM: true }); - }); - - gulp.task('canvas:test:plugins', () => { - return runMocha(getTestGlobs('canvas_plugin_src')); - }); - - gulp.task('canvas:test', [ - 'canvas:test:plugins', - 'canvas:test:common', - 'canvas:test:server', - 'canvas:test:browser', - ]); - - gulp.task('canvas:test:dev', () => { - gulp.watch(getRootGlobs('common'), ['canvas:test:common']); - gulp.watch(getRootGlobs('server'), ['canvas:test:server']); - gulp.watch(getRootGlobs('public'), ['canvas:test:browser']); - gulp.watch(getRootGlobs('canvas_plugin_src'), ['canvas:test:plugins']); - }); -} diff --git a/x-pack/legacy/plugins/code/index.ts b/x-pack/legacy/plugins/code/index.ts index 9e99a0c65485..eb902367047e 100644 --- a/x-pack/legacy/plugins/code/index.ts +++ b/x-pack/legacy/plugins/code/index.ts @@ -7,14 +7,11 @@ import { RequestQuery, ResponseToolkit, RouteOptions, ServerRoute } from 'hapi'; import JoiNamespace from 'joi'; import { Legacy } from 'kibana'; -import moment from 'moment'; import { resolve } from 'path'; +import { CoreSetup } from 'src/core/server'; -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { APP_TITLE } from './common/constants'; import { codePlugin } from './server'; -import { DEFAULT_WATERMARK_LOW_PERCENTAGE } from './server/disk_watermark'; -import { LanguageServers, LanguageServersDeveloping } from './server/lsp/language_servers'; export type RequestFacade = Legacy.Request; export type RequestQueryFacade = RequestQuery; @@ -25,7 +22,7 @@ export type ServerRouteFacade = ServerRoute; export const code = (kibana: any) => new kibana.Plugin({ - require: ['kibana', 'elasticsearch', 'xpack_main'], + require: ['kibana', 'elasticsearch'], id: 'code', configPrefix: 'xpack.code', publicDir: resolve(__dirname, 'public'), @@ -41,103 +38,38 @@ export const code = (kibana: any) => const config = server.config(); return { codeUiEnabled: config.get('xpack.code.ui.enabled'), + codeIntegrationsEnabled: config.get('xpack.code.integrations.enabled'), }; }, hacks: ['plugins/code/hacks/toggle_app_link_in_nav'], }, config(Joi: typeof JoiNamespace) { - const langSwitches: any = {}; - LanguageServers.forEach(lang => { - langSwitches[lang.name] = Joi.object({ - enabled: Joi.boolean().default(true), - }); - }); - LanguageServersDeveloping.forEach(lang => { - langSwitches[lang.name] = Joi.object({ - enabled: Joi.boolean().default(false), - }); - }); return Joi.object({ + // Still keep this config item here for the injectDefaultVars + // in line 40 here. ui: Joi.object({ enabled: Joi.boolean().default(true), }).default(), - enabled: Joi.boolean().default(true), - queueIndex: Joi.string().default('.code_internal-worker-queue'), - // 1 hour by default. - queueTimeoutMs: Joi.number().default(moment.duration(1, 'hour').asMilliseconds()), - // The frequency which update scheduler executes. 1 minute by default. - updateFrequencyMs: Joi.number().default(moment.duration(1, 'minute').asMilliseconds()), - // The frequency which index scheduler executes. 1 day by default. - indexFrequencyMs: Joi.number().default(moment.duration(1, 'day').asMilliseconds()), - // The frequency which each repo tries to update. 5 minutes by default. - updateRepoFrequencyMs: Joi.number().default(moment.duration(5, 'minute').asMilliseconds()), - // The frequency which each repo tries to index. 1 day by default. - indexRepoFrequencyMs: Joi.number().default(moment.duration(1, 'day').asMilliseconds()), - // whether we want to show more logs - verbose: Joi.boolean().default(false), - lsp: Joi.object({ - ...langSwitches, - // timeout of a request - requestTimeoutMs: Joi.number().default(moment.duration(10, 'second').asMilliseconds()), - // if we want the language server run in seperately - detach: Joi.boolean().default(false), - // enable oom_score_adj on linux - oomScoreAdj: Joi.boolean().default(true), - }).default(), - repos: Joi.array().default([]), - security: Joi.object({ - enableMavenImport: Joi.boolean().default(true), - enableGradleImport: Joi.boolean().default(false), - installGoDependency: Joi.boolean().default(false), - installNodeDependency: Joi.boolean().default(true), - gitHostWhitelist: Joi.array() - .items(Joi.string()) - .default([ - 'github.com', - 'gitlab.com', - 'bitbucket.org', - 'gitbox.apache.org', - 'eclipse.org', - ]), - gitProtocolWhitelist: Joi.array() - .items(Joi.string()) - .default(['https', 'git', 'ssh']), - enableGitCertCheck: Joi.boolean().default(true), - }).default(), - disk: Joi.object({ - thresholdEnabled: Joi.bool().default(true), - watermarkLow: Joi.string().default(`${DEFAULT_WATERMARK_LOW_PERCENTAGE}%`), - }).default(), - maxWorkspace: Joi.number().default(5), // max workspace folder for each language server - enableGlobalReference: Joi.boolean().default(false), // Global reference as optional feature for now - enableCommitIndexing: Joi.boolean().default(false), - codeNodeUrl: Joi.string(), - clustering: Joi.object({ - enabled: Joi.bool().default(false), - codeNodes: Joi.array() - .items( - Joi.object({ - id: Joi.string(), - address: Joi.string(), - }) - ) - .default([]), + integrations: Joi.object({ + enabled: Joi.boolean().default(false), }).default(), + enabled: Joi.boolean().default(true), }).default(); }, - init(server: ServerFacade, options: any) { - if (!options.ui.enabled) { + async init(server: ServerFacade) { + // @ts-ignore + const initializerContext = server.newPlatform.setup.plugins.code; + if (!initializerContext.legacy.config.ui.enabled) { return; } - const initializerContext = {} as PluginInitializerContext; const coreSetup = ({ http: { server }, } as any) as CoreSetup; // Set up with the new platform plugin lifecycle API. const plugin = codePlugin(initializerContext); - plugin.setup(coreSetup, options); + plugin.setup(coreSetup); // @ts-ignore const kbnServer = this.kbnServer; diff --git a/x-pack/legacy/plugins/code/kibana.json b/x-pack/legacy/plugins/code/kibana.json index 03fd3694df53..5536f1e5957c 100644 --- a/x-pack/legacy/plugins/code/kibana.json +++ b/x-pack/legacy/plugins/code/kibana.json @@ -3,8 +3,5 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": [ - "elasticsearch", - "xpack_main" - ] + "requiredPlugins": ["elasticsearch"] } diff --git a/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx b/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx index 56f518fc82aa..3bca381d741d 100644 --- a/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx +++ b/x-pack/legacy/plugins/code/public/components/admin_page/project_tab.tsx @@ -6,23 +6,13 @@ import { EuiButton, - EuiButtonEmpty, - EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiForm, EuiFormRow, EuiGlobalToastList, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, EuiSuperSelect, EuiText, - EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -39,6 +29,7 @@ import { ToastType } from '../../reducers/repository_management'; import { isImportRepositoryURLInvalid } from '../../utils/url'; import { ProjectItem } from './project_item'; import { ProjectSettings } from './project_settings'; +import { ImportModal } from '../integrations/import_modal'; enum SortOptionsValue { AlphabeticalAsc = 'alphabetical_asc', @@ -169,70 +160,21 @@ class CodeProjectTab extends React.PureComponent { } }; - public updateIsInvalid = () => { - this.setState({ isInvalid: isImportRepositoryURLInvalid(this.state.repoURL) }); - }; - public renderImportModal = () => { - return ( - - - - - - - - - -

- -

-
- - - - - -
- - - - - - - - -
-
- ); + const { isInvalid, repoURL, showImportProjectModal } = this.state; + + if (showImportProjectModal) { + return ( + + ); + } }; public setSortOption = (value: string) => { @@ -242,7 +184,6 @@ class CodeProjectTab extends React.PureComponent { public render() { const { projects, status, toastMessage, showToast, toastType } = this.props; const projectsCount = projects.length; - const modal = this.state.showImportProjectModal && this.renderImportModal(); const sortedProjects = projects.sort(sortFunctionsFactory(status)[this.state.sortOption]); const repoList = sortedProjects.map((repo: Repository) => ( @@ -321,7 +262,7 @@ class CodeProjectTab extends React.PureComponent { {repoList} - {modal} + {this.renderImportModal()} {settings}
); diff --git a/x-pack/legacy/plugins/code/public/components/app.tsx b/x-pack/legacy/plugins/code/public/components/app.tsx index 54363213bcad..e87e9dd88e6a 100644 --- a/x-pack/legacy/plugins/code/public/components/app.tsx +++ b/x-pack/legacy/plugins/code/public/components/app.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { HashRouter as Router, Redirect, Switch } from 'react-router-dom'; +import chrome from 'ui/chrome'; import { connect } from 'react-redux'; import { RootState } from '../reducers'; import { Admin } from './admin_page/admin'; @@ -17,6 +18,7 @@ import { NotFound } from './main/not_found'; import { Route } from './route'; import * as ROUTES from './routes'; import { Search } from './search_page/search'; +import { Integrations } from './integrations'; const RooComponent = (props: { setupOk?: boolean }) => { if (props.setupOk) { @@ -33,6 +35,8 @@ const Root = connect(mapStateToProps)(RooComponent); const Empty = () => null; +const integrationsEnabled = chrome.getInjected('codeIntegrationsEnabled'); + export const App = () => { return ( @@ -45,6 +49,9 @@ export const App = () => { + {integrationsEnabled && ( + + )} diff --git a/x-pack/legacy/plugins/code/public/components/codeblock/codeblock.tsx b/x-pack/legacy/plugins/code/public/components/codeblock/codeblock.tsx index 910129eb1a3c..89c9e9137158 100644 --- a/x-pack/legacy/plugins/code/public/components/codeblock/codeblock.tsx +++ b/x-pack/legacy/plugins/code/public/components/codeblock/codeblock.tsx @@ -5,50 +5,72 @@ */ import { EuiPanel } from '@elastic/eui'; -import { editor, IPosition, IRange } from 'monaco-editor'; +import { editor, IRange } from 'monaco-editor'; import React from 'react'; import { ResizeChecker } from '../shared/resize_checker'; import { monaco } from '../../monaco/monaco'; import { registerEditor } from '../../monaco/single_selection_helper'; -interface Props { - code: string; - fileComponent?: React.ReactNode; - startLine?: number; - language?: string; - highlightRanges?: IRange[]; - onClick?: (event: IPosition) => void; +export interface Position { + lineNumber: string; + column: number; +} + +export interface Props { + content: string; + header: React.ReactNode; + language: string; + highlightRanges: IRange[]; + onClick: (event: Position) => void; folding: boolean; - lineNumbersFunc: (line: number) => string; + /** + * Returns the line number to display for a given line. + * @param lineIndex The index of the given line (0-indexed) + */ + lineNumber: (lineIndex: number) => string; + className?: string; } export class CodeBlock extends React.PureComponent { + static defaultProps = { + header: undefined, + folding: false, + highlightRanges: [], + language: 'text', + lineNumber: String, + onClick: () => {}, + }; + private el: HTMLDivElement | null = null; private ed?: editor.IStandaloneCodeEditor; private resizeChecker?: ResizeChecker; private currentHighlightDecorations: string[] = []; public async componentDidMount() { + const { content, highlightRanges, language, onClick } = this.props; + if (this.el) { - await this.tryLoadFile(this.props.code, this.props.language || 'text'); + await this.tryLoadFile(content, language); this.ed!.onMouseDown((e: editor.IEditorMouseEvent) => { if ( - this.props.onClick && + onClick && (e.target.type === monaco.editor.MouseTargetType.GUTTER_LINE_NUMBERS || e.target.type === monaco.editor.MouseTargetType.CONTENT_TEXT) ) { const position = e.target.position || { lineNumber: 0, column: 0 }; - const lineNumber = (this.props.startLine || 0) + position.lineNumber; - this.props.onClick({ + const lineNumber = this.lineNumber(position.lineNumber); + + onClick({ lineNumber, column: position.column, }); } }); registerEditor(this.ed!); - if (this.props.highlightRanges) { - const decorations = this.props.highlightRanges.map((range: IRange) => { + + if (highlightRanges.length) { + const decorations = highlightRanges.map((range: IRange) => { return { range, options: { @@ -66,6 +88,7 @@ export class CodeBlock extends React.PureComponent { }); } } + private async tryLoadFile(code: string, language: string) { try { await monaco.editor.colorize(code, language, {}); @@ -79,7 +102,7 @@ export class CodeBlock extends React.PureComponent { this.ed = monaco.editor.create(this.el!, { value: code, language, - lineNumbers: this.lineNumbersFunc.bind(this), + lineNumbers: this.lineNumber, readOnly: true, folding: this.props.folding, minimap: { @@ -103,17 +126,16 @@ export class CodeBlock extends React.PureComponent { } public componentDidUpdate(prevProps: Readonly) { - if ( - prevProps.code !== this.props.code || - prevProps.highlightRanges !== this.props.highlightRanges - ) { + const { content, highlightRanges } = this.props; + + if (prevProps.content !== content || prevProps.highlightRanges !== highlightRanges) { if (this.ed) { const model = this.ed.getModel(); if (model) { - model.setValue(this.props.code); + model.setValue(content); - if (this.props.highlightRanges) { - const decorations = this.props.highlightRanges!.map((range: IRange) => { + if (highlightRanges.length) { + const decorations = highlightRanges!.map((range: IRange) => { return { range, options: { @@ -138,19 +160,20 @@ export class CodeBlock extends React.PureComponent { } public render() { - const linesCount = this.props.code.split('\n').length; + const { className, header } = this.props; + const height = this.lines.length * 18; + return ( - - {this.props.fileComponent} -
(this.el = r)} style={{ height: linesCount * 18 }} /> + + {header} +
(this.el = r)} style={{ height }} /> ); } - private lineNumbersFunc = (line: number) => { - if (this.props.lineNumbersFunc) { - return this.props.lineNumbersFunc(line); - } - return `${(this.props.startLine || 0) + line}`; - }; + private lineNumber = (lineIndex: number) => this.props.lineNumber(lineIndex - 1); + + private get lines(): string[] { + return this.props.content.split('\n'); + } } diff --git a/x-pack/legacy/plugins/code/public/components/editor/references_panel.scss b/x-pack/legacy/plugins/code/public/components/editor/references_panel.scss index fc3df8bb79a4..13d1c45e6166 100644 --- a/x-pack/legacy/plugins/code/public/components/editor/references_panel.scss +++ b/x-pack/legacy/plugins/code/public/components/editor/references_panel.scss @@ -31,3 +31,6 @@ border-radius: $euiSizeXS $euiSizeXS 0 0; } +.referencesPanel__code-block { + margin-bottom: $euiSizeXL; +} diff --git a/x-pack/legacy/plugins/code/public/components/editor/references_panel.tsx b/x-pack/legacy/plugins/code/public/components/editor/references_panel.tsx index 219c5e837155..785238eb5fab 100644 --- a/x-pack/legacy/plugins/code/public/components/editor/references_panel.tsx +++ b/x-pack/legacy/plugins/code/public/components/editor/references_panel.tsx @@ -14,13 +14,12 @@ import { EuiTitle, } from '@elastic/eui'; import classname from 'classnames'; -import { IPosition } from 'monaco-editor'; import queryString from 'querystring'; import React from 'react'; import { parseSchema } from '../../../common/uri_util'; import { GroupedFileResults, GroupedRepoResults } from '../../actions'; import { history } from '../../utils/url'; -import { CodeBlock } from '../codeblock/codeblock'; +import { CodeBlock, Position } from '../codeblock/codeblock'; interface Props { isLoading: boolean; @@ -114,12 +113,9 @@ export class ReferencesPanel extends React.Component { private renderReference(file: GroupedFileResults) { const key = `${file.uri}`; - const lineNumberFn = (l: number) => { - return file.lineNumbers[l - 1]; - }; - const fileComponent = ( + const header = ( - + {file.file} @@ -128,23 +124,22 @@ export class ReferencesPanel extends React.Component { return ( file.lineNumbers[i]} highlightRanges={file.highlights} - fileComponent={fileComponent} - onClick={this.onCodeClick.bind(this, file.lineNumbers, file.uri)} + onClick={this.onCodeClick(file.uri)} /> ); } - private onCodeClick(lineNumbers: string[], url: string, pos: IPosition) { - const line = parseInt(lineNumbers[pos.lineNumber - 1], 10); - history.push(this.computeUrl(url, line)); - } + private onCodeClick = (url: string) => (position: Position) => { + const lineNum = parseInt(position.lineNumber, 10); + history.push(this.computeUrl(url, lineNum)); + }; private computeUrl(url: string, line?: number) { const { uri } = parseSchema(url)!; @@ -158,6 +153,7 @@ export class ReferencesPanel extends React.Component { tab: 'references', refUrl: this.props.refUrl, }); + return line !== undefined ? `${uri}!L${line}:0?${query}` : `${uri}?${query}`; } } diff --git a/x-pack/legacy/plugins/code/public/components/integrations/code_integrator.tsx b/x-pack/legacy/plugins/code/public/components/integrations/code_integrator.tsx new file mode 100644 index 000000000000..7df7a976be69 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/code_integrator.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { EuiButtonEmpty, EuiPopover, EuiText } from '@elastic/eui'; + +import { RepoSelector } from './repo_selector'; + +interface Props { + onRepoSelect: (repo: string) => void; + onImportSuccess: (repo: string) => void; + repos: string[]; +} + +export const CodeIntegrator = ({ onRepoSelect, onImportSuccess, repos }: Props) => { + const [showSelector, setShowSelector] = useState(false); + + const handleClick = () => setShowSelector(true); + + const handleSelect = (codeId: string) => { + onRepoSelect(codeId); + setShowSelector(false); + // TODO: show success + }; + + const link = ( + + View in Code + + ); + + return ( + setShowSelector(false)} + > + +

No repository mapping found

+

+ We can't find the mapping between service and the source code. Select the repository or + import a new one. +

+
+ +
+ ); +}; diff --git a/x-pack/legacy/plugins/code/public/components/integrations/data.ts b/x-pack/legacy/plugins/code/public/components/integrations/data.ts new file mode 100644 index 000000000000..83137dff158e --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/data.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface Snippet { + uri: string; + filePath: string; + language?: string; + compositeContent: { + content: string; + lineMapping: string[]; + }; +} + +export type Results = Record; + +export const results: Results = { + 'ringside.ts#L18': { + uri: 'github.com/rylnd/ringside', + filePath: 'src/ringside.ts', + language: 'typescript', + compositeContent: { + content: + "\nimport { fitsInside, fitsOutside } from './fitting';\n\nexport interface RingsideInterface {\n positions(): FittedPosition[];\n}\n\nclass Ringside implements RingsideInterface {\n readonly innerBounds: FullRect;\n readonly outerBounds: FullRect;\n\n}\n\nexport default Ringside;\n", + lineMapping: [ + '..', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '..', + '67', + '68', + '69', + '70', + ], + }, + }, + 'ringside.story.tsx#L12': { + uri: 'github.com/rylnd/ringside', + filePath: 'stories/ringside.story.tsx', + language: 'typescript', + compositeContent: { + content: + "\nimport { interpolateRainbow } from 'd3-scale-chromatic';\n\nimport { Ringside } from '../src';\nimport { XAlignment, YAlignment, XBasis, YBasis } from '../src/types';\n\nlet ringside: Ringside;\n\nconst enumKeys: (e: any) => string[] = e =>\n\n\nconst color = position => {\n const combos = ringside.positions().map(p => JSON.stringify(p));\n const hash = combos.indexOf(JSON.stringify(position)) / combos.length;\n\n\n};\n\nconst Stories = storiesOf('Ringside', module).addDecorator(withKnobs);\n\nStories.add('Ringside', () => {\n", + lineMapping: [ + '..', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', + '..', + '14', + '15', + '16', + '17', + '18', + '..', + '20', + '21', + '22', + '23', + '24', + '..', + ], + }, + }, + + 'ringside.story.tsx#L8': { + uri: 'github.com/rylnd/ringside', + filePath: 'stories/ringside.story.tsx', + language: 'typescript', + compositeContent: { + content: + "import { Ringside } from '../src';\n\ndescribe('Ringside', () => {\n let inner;\n let outer;\n let height;\n let width;\n let ringside: Ringside;\n\n beforeEach(() => {\n\n width = 50;\n\n ringside = new Ringside(inner, outer, height, width);\n });\n\n", + lineMapping: [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '..', + '14', + '15', + '16', + '17', + '18', + '..', + ], + }, + }, + + 'ringside.story.tsx#L14': { + uri: 'github.com/rylnd/ringside', + filePath: 'stories/ringside.story.tsx', + language: 'typescript', + compositeContent: { + content: + "import { Ringside } from '../src';\n\ndescribe('Ringside', () => {\n let inner;\n let outer;\n let height;\n let width;\n let ringside: Ringside;\n\n beforeEach(() => {\n\n width = 50;\n\n ringside = new Ringside(inner, outer, height, width);\n });\n\n", + lineMapping: [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '..', + '14', + '15', + '16', + '17', + '18', + '..', + ], + }, + }, +}; + +export interface Frame { + fileName: string; + lineNumber: number; + functionName?: string; +} + +export const frames: Frame[] = [ + { fileName: 'ringside.ts', lineNumber: 18 }, + { fileName: 'node_modules/library_code.js', lineNumber: 100 }, + { fileName: 'ringside.story.tsx', lineNumber: 8 }, + { fileName: 'node_modules/other_stuff.js', lineNumber: 58 }, + { fileName: 'node_modules/other/other.js', lineNumber: 3 }, + { fileName: 'ringside.story.tsx', lineNumber: 12 }, + { fileName: 'ringside.story.tsx', lineNumber: 14 }, +]; + +export const repos = [ + 'https://github.com/a/repo', + 'https://github.com/another/repo', + 'https://github.com/also/a_repo', +]; diff --git a/x-pack/legacy/plugins/code/public/components/integrations/frame_header.tsx b/x-pack/legacy/plugins/code/public/components/integrations/frame_header.tsx new file mode 100644 index 000000000000..c63faf75603d --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/frame_header.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiButtonIcon, EuiFlexGroup, EuiLink, EuiText, EuiTextColor } from '@elastic/eui'; + +export const FrameHeader = ({ + fileName, + lineNumber, + onClick, +}: { + fileName: string; + lineNumber: number | string; + onClick: () => void; +}) => ( + + + {fileName} + at + line {lineNumber} + + + Last updated: 14 mins ago + + + +); diff --git a/x-pack/legacy/plugins/canvas/tasks/mocks/noop.js b/x-pack/legacy/plugins/code/public/components/integrations/helpers.ts old mode 100755 new mode 100644 similarity index 58% rename from x-pack/legacy/plugins/canvas/tasks/mocks/noop.js rename to x-pack/legacy/plugins/code/public/components/integrations/helpers.ts index 8d6abb810be9..6b55a5e2a0f4 --- a/x-pack/legacy/plugins/canvas/tasks/mocks/noop.js +++ b/x-pack/legacy/plugins/code/public/components/integrations/helpers.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function() {} +// TODO(rylnd): make this an actual external link +export const externalFileURI: (repoUri: string, filePath: string) => string = (uri, path) => + `/${uri}/blob/HEAD/${path}`; diff --git a/x-pack/legacy/plugins/code/public/components/integrations/import_modal.tsx b/x-pack/legacy/plugins/code/public/components/integrations/import_modal.tsx new file mode 100644 index 000000000000..de526d4046e2 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/import_modal.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ChangeEvent } from 'react'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +interface Props { + isInvalid: boolean; + isLoading: boolean; + onChange: (e: ChangeEvent) => void; + onClose: () => void; + onSubmit: () => void; + value: string; +} + +export const ImportModal = ({ + isInvalid, + isLoading, + onChange, + onClose, + onSubmit, + value, +}: Props) => ( + + + + + + + + + +

+ +

+
+ + + + + +
+ + + + + + + + +
+
+); diff --git a/x-pack/legacy/plugins/code/public/components/integrations/index.tsx b/x-pack/legacy/plugins/code/public/components/integrations/index.tsx new file mode 100644 index 000000000000..25eeb6fbe1a2 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/index.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiText } from '@elastic/eui'; + +import { CodeBlock } from '../codeblock/codeblock'; +import { history } from '../../utils/url'; +import { FrameHeader } from './frame_header'; +import { RepoTitle } from './repo_title'; +import { CodeIntegrator } from './code_integrator'; +import { externalFileURI } from './helpers'; +import { frames, Frame, repos, results } from './data'; + +const associateToService = (frame: Frame) => (repo: string) => + alert(`repo ${repo} associated with service ${JSON.stringify(frame)}`); + +const handleImport = (repo: string) => alert(`import done: ${repo}`); + +export const Integrations = () => ( +
+ {frames.map(frame => { + const { fileName, lineNumber } = frame; + const key = `${fileName}#L${lineNumber}`; + const snippet = results[key]; + + if (snippet) { + const { compositeContent, filePath, language, uri } = snippet; + const { content, lineMapping } = compositeContent; + const fileUrl = externalFileURI(uri, filePath); + + return ( +
+ + history.push(fileUrl)} + /> + } + language={language} + lineNumber={i => lineMapping[i]} + /> +
+ ); + } + + return ( +
+ + + {fileName} + at + line {lineNumber} + + + +
+ ); + })} +
+); diff --git a/x-pack/legacy/plugins/code/public/components/integrations/integrations.scss b/x-pack/legacy/plugins/code/public/components/integrations/integrations.scss new file mode 100644 index 000000000000..98330c406082 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/integrations.scss @@ -0,0 +1,43 @@ +.codeIntegrations__container { + padding: $euiSize; +} + +.codeIntegrations__frame { + margin: $euiSizeS 0; +} + +.codeIntegrations__code { + @include euiCodeFont; +} + +.codeIntegrations__link--external { + margin-left: $euiSizeS; +} + +.codeIntegrations__preposition { + margin: 0 $euiSizeS; + color: $euiColorMediumShade; +} + +.codeIntegrations__button-icon { + padding: $euiSizeXS; + background-color: $euiColorLightestShade; + border: 1px solid $euiColorLightShade; +} + +.codeIntegrations__snippet-info { + margin-bottom: $euiSizeS; +} + +.codeIntegrations__snippet-title { + margin-bottom: $euiSizeS; +} + +.codeIntegrations__text--bold { + font-weight: $euiFontWeightBold; +} + +.codeIntegrations__popover { + margin-bottom: 1rem; + width: 300px; +} diff --git a/x-pack/legacy/plugins/code/public/components/integrations/repo_selector.tsx b/x-pack/legacy/plugins/code/public/components/integrations/repo_selector.tsx new file mode 100644 index 000000000000..8aeba2b600ed --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/repo_selector.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { EuiButton, EuiSelect } from '@elastic/eui'; +import { ImportModal } from './import_modal'; +import { isImportRepositoryURLInvalid } from '../../utils/url'; + +interface Props { + onSelect: (repo: string) => void; + onImport: (repo: string) => void; + repos: string[]; +} + +const placeHolderOption = { value: 'select_new', text: 'Select' }; +const importNewOption = { value: 'import_new', text: 'Import new' }; +const importStub: (repo: string) => Promise = repo => + new Promise(resolve => setTimeout(() => resolve(repo), 5000)); + +export const RepoSelector = ({ onImport, onSelect, repos: _repos }: Props) => { + const [selectedValue, setSelectedValue] = useState(placeHolderOption.value); + const [newRepo, setNewRepo] = useState(''); + const [isInvalid, setIsInvalid] = useState(false); + const [showModal, setShowModal] = useState(false); + + const repos = newRepo ? [..._repos, newRepo] : _repos; + const selectedRepo = repos.find(repo => repo === selectedValue); + + const options = [ + placeHolderOption, + ...repos.map(repo => ({ value: repo, text: repo })), + importNewOption, + ]; + + const handleNewRepoChange = ({ target: { value } }: React.ChangeEvent) => { + setIsInvalid(isImportRepositoryURLInvalid(value)); + setNewRepo(value); + }; + + const handleChange = ({ target: { value } }: React.ChangeEvent) => { + setSelectedValue(value); + + if (value === 'import_new') { + setShowModal(true); + } + }; + + const handleSave = () => selectedRepo && onSelect(selectedRepo); + + const handleImportSubmit = () => { + setSelectedValue(newRepo); + importStub(newRepo).then(onImport); + setShowModal(false); + }; + + const handleClose = () => { + setSelectedValue(placeHolderOption.value); + setShowModal(false); + }; + + return ( + <> + + + Save Mapping + + {showModal && ( + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/code/public/components/integrations/repo_title.tsx b/x-pack/legacy/plugins/code/public/components/integrations/repo_title.tsx new file mode 100644 index 000000000000..f3577d5f6426 --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/integrations/repo_title.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiText } from '@elastic/eui'; +import { RepositoryUtils } from '../../../common/repository_utils'; + +export const RepoTitle = ({ uri }: { uri: string }) => { + const org = RepositoryUtils.orgNameFromUri(uri); + const name = RepositoryUtils.repoNameFromUri(uri); + + return ( + + {org}/ + {name} + + ); +}; diff --git a/x-pack/legacy/plugins/code/public/components/routes.ts b/x-pack/legacy/plugins/code/public/components/routes.ts index 0d4ccf4e5e82..d741ca189685 100644 --- a/x-pack/legacy/plugins/code/public/components/routes.ts +++ b/x-pack/legacy/plugins/code/public/components/routes.ts @@ -15,3 +15,4 @@ export const REPO = `/:resource/:org/:repo`; export const MAIN_ROOT = `/:resource/:org/:repo/${pathTypes}/:revision`; export const ADMIN = '/admin'; export const SEARCH = '/search'; +export const INTEGRATIONS = '/integrations'; diff --git a/x-pack/legacy/plugins/code/public/components/search_page/code_result.tsx b/x-pack/legacy/plugins/code/public/components/search_page/code_result.tsx index 632c221f0680..72aacce97867 100644 --- a/x-pack/legacy/plugins/code/public/components/search_page/code_result.tsx +++ b/x-pack/legacy/plugins/code/public/components/search_page/code_result.tsx @@ -6,13 +6,12 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { IPosition } from 'monaco-editor'; import React from 'react'; import { Link } from 'react-router-dom'; import { RepositoryUtils } from '../../../common/repository_utils'; import { history } from '../../utils/url'; -import { CodeBlock } from '../codeblock/codeblock'; +import { CodeBlock, Position } from '../codeblock/codeblock'; interface Props { query: string; @@ -22,15 +21,14 @@ interface Props { export class CodeResult extends React.PureComponent { public render() { const { results, query } = this.props; + return results.map(item => { - const { uri, filePath, hits, compositeContent } = item; + const { compositeContent, filePath, hits, language, uri } = item; const { content, lineMapping, ranges } = compositeContent; - const repoLinkUrl = `/${uri}/tree/HEAD/`; - const fileLinkUrl = `/${uri}/blob/HEAD/${filePath}`; const key = `${uri}-${filePath}-${query}`; - const lineMappingFunc = (l: number) => { - return lineMapping[l - 1]; - }; + const repoLinkUrl = `/${uri}/tree/HEAD/`; + const fileLinkUrl = `/${uri}/blob/HEAD/${filePath}`; // TODO(rylnd) move these to link helpers + return (
@@ -75,23 +73,23 @@ export class CodeResult extends React.PureComponent { lineMapping[i]} + onClick={this.onCodeClick(fileLinkUrl)} />
); }); } - private onCodeClick(lineNumbers: string[], fileUrl: string, pos: IPosition) { - const line = parseInt(lineNumbers[pos.lineNumber - 1], 10); - if (!isNaN(line)) { - history.push(`${fileUrl}!L${line}:0`); + private onCodeClick = (url: string) => (position: Position) => { + const lineNumber = parseInt(position.lineNumber, 10); + + if (!isNaN(lineNumber)) { + history.push(`${url}!L${lineNumber}:0`); } - } + }; } diff --git a/x-pack/legacy/plugins/code/public/components/search_page/search.scss b/x-pack/legacy/plugins/code/public/components/search_page/search.scss new file mode 100644 index 000000000000..febad588af3c --- /dev/null +++ b/x-pack/legacy/plugins/code/public/components/search_page/search.scss @@ -0,0 +1,3 @@ +.codeResult__code-block { + margin-bottom: $euiSizeXL; +} diff --git a/x-pack/legacy/plugins/code/public/index.scss b/x-pack/legacy/plugins/code/public/index.scss index aecaa46272de..43768f7758ea 100644 --- a/x-pack/legacy/plugins/code/public/index.scss +++ b/x-pack/legacy/plugins/code/public/index.scss @@ -4,6 +4,8 @@ @import "./monaco/override_monaco_styles.scss"; @import "./components/diff_page/diff.scss"; @import "./components/main/main.scss"; +@import "./components/search_page/search.scss"; +@import "./components/integrations/integrations.scss"; // TODO: Cleanup everything above this line diff --git a/x-pack/legacy/plugins/code/public/monaco/override_monaco_styles.scss b/x-pack/legacy/plugins/code/public/monaco/override_monaco_styles.scss index 3e03c5693d43..b6f39a34e865 100644 --- a/x-pack/legacy/plugins/code/public/monaco/override_monaco_styles.scss +++ b/x-pack/legacy/plugins/code/public/monaco/override_monaco_styles.scss @@ -9,6 +9,7 @@ .monaco-editor.mac .margin-view-overlays .line-numbers { cursor: pointer; + color: $euiColorMediumShade; background-color: $euiColorLightestShade; } diff --git a/x-pack/legacy/plugins/code/scripts/mocha.js b/x-pack/legacy/plugins/code/scripts/mocha.js index 8c66d6090fd2..dd543f32e5d4 100644 --- a/x-pack/legacy/plugins/code/scripts/mocha.js +++ b/x-pack/legacy/plugins/code/scripts/mocha.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -require('./_helpers').runXPackScript('mocha', ['legacy/plugins/code/server/__tests__/*.{ts,tsx}']); +require('./_helpers').runKibanaScript('mocha', ['x-pack/legacy/plugins/code/server/__tests__/*.{ts,tsx}']); diff --git a/x-pack/legacy/plugins/code/server/__tests__/git_operations.ts b/x-pack/legacy/plugins/code/server/__tests__/git_operations.ts index 247aabdf3913..7cd9175efb9f 100644 --- a/x-pack/legacy/plugins/code/server/__tests__/git_operations.ts +++ b/x-pack/legacy/plugins/code/server/__tests__/git_operations.ts @@ -9,7 +9,6 @@ import Git from '@elastic/nodegit'; import assert from 'assert'; import { execSync } from 'child_process'; import fs from 'fs'; -import * as mkdirp from 'mkdirp'; import path from 'path'; import rimraf from 'rimraf'; import { GitOperations } from '../git_operations'; @@ -19,7 +18,7 @@ describe('git_operations', () => { it('get default branch from a non master repo', async () => { const repoUri = 'github.com/foo/bar'; const repoDir = path.join(serverOptions.repoPath, repoUri); - mkdirp.sync(repoDir); + fs.mkdirSync(repoDir, { recursive: true }); // create a non-master using git commands const shell = ` @@ -47,7 +46,7 @@ describe('git_operations', () => { }); async function prepareProject(repoPath: string) { - mkdirp.sync(repoPath); + fs.mkdirSync(repoPath, { recursive: true }); const workDir = path.join(serverOptions.workspacePath, repoUri); const repo = await Git.Repository.init(workDir, 0); const content = ''; diff --git a/x-pack/legacy/plugins/code/server/__tests__/lsp_service.ts b/x-pack/legacy/plugins/code/server/__tests__/lsp_service.ts index efc3d75398e8..1b85c077171c 100644 --- a/x-pack/legacy/plugins/code/server/__tests__/lsp_service.ts +++ b/x-pack/legacy/plugins/code/server/__tests__/lsp_service.ts @@ -6,7 +6,6 @@ import Git from '@elastic/nodegit'; import fs from 'fs'; -import mkdirp from 'mkdirp'; import path from 'path'; import rimraf from 'rimraf'; import sinon from 'sinon'; @@ -43,7 +42,7 @@ describe('lsp_service tests', () => { let firstCommitSha = ''; let secondCommitSha = ''; async function prepareProject(repoPath: string) { - mkdirp.sync(repoPath); + fs.mkdirSync(repoPath, { recursive: true }); const repo = await Git.Repository.init(repoPath, 0); const helloContent = "console.log('hello world');"; fs.writeFileSync(path.join(repo.workdir(), filename), helloContent, 'utf8'); diff --git a/x-pack/legacy/plugins/code/server/__tests__/workspace_handler.ts b/x-pack/legacy/plugins/code/server/__tests__/workspace_handler.ts index ac3ecc698c33..e738974b00e9 100644 --- a/x-pack/legacy/plugins/code/server/__tests__/workspace_handler.ts +++ b/x-pack/legacy/plugins/code/server/__tests__/workspace_handler.ts @@ -8,7 +8,6 @@ import path from 'path'; import Git from '@elastic/nodegit'; import assert from 'assert'; -import mkdirp from 'mkdirp'; import * as os from 'os'; import rimraf from 'rimraf'; import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; @@ -51,7 +50,7 @@ describe('workspace_handler tests', () => { file = 'src/controllers/user.ts' ) { const fullPath = path.join(workspacePath, repo, '__randomString', revision, file); - mkdirp.sync(path.dirname(fullPath)); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, ''); const strInUrl = fullPath .split(path.sep) @@ -119,7 +118,7 @@ describe('workspace_handler tests', () => { }); async function prepareProject(repoPath: string) { - mkdirp.sync(repoPath); + fs.mkdirSync(repoPath, { recursive: true }); const repo = await Git.Repository.init(repoPath, 0); const content = 'console.log("test")'; const subFolder = 'src'; @@ -173,8 +172,8 @@ describe('workspace_handler tests', () => { // @ts-ignore before(() => { - mkdirp.sync(workspaceDir); - mkdirp.sync(repoDir); + fs.mkdirSync(workspaceDir, { recursive: true }); + fs.mkdirSync(repoDir, { recursive: true }); }); // @ts-ignore diff --git a/x-pack/legacy/plugins/code/server/distributed/apis/workspace_api.ts b/x-pack/legacy/plugins/code/server/distributed/apis/workspace_api.ts index 19fa9fc937d9..e370efe0f37f 100644 --- a/x-pack/legacy/plugins/code/server/distributed/apis/workspace_api.ts +++ b/x-pack/legacy/plugins/code/server/distributed/apis/workspace_api.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; import { ServiceHandlerFor } from '../service_definition'; import { WorkspaceHandler } from '../../lsp/workspace_handler'; import { RepoConfig } from '../../../model'; import { WorkspaceCommand } from '../../lsp/workspace_command'; -import { Logger } from '../../log'; +import { LoggerFactory as CodeLoggerFactory } from '../../utils/log_factory'; export const WorkspaceDefinition = { initCmd: { @@ -19,7 +18,7 @@ export const WorkspaceDefinition = { }; export const getWorkspaceHandler = ( - server: Server, + loggerFactory: CodeLoggerFactory, workspaceHandler: WorkspaceHandler ): ServiceHandlerFor => ({ async initCmd({ repoUri, revision, repoConfig, force }) { @@ -28,9 +27,13 @@ export const getWorkspaceHandler = ( repoUri, revision ); - const log = new Logger(server, ['workspace', repoUri]); - const workspaceCmd = new WorkspaceCommand(repoConfig, workspaceDir, workspaceRevision, log); + const workspaceCmd = new WorkspaceCommand( + repoConfig, + workspaceDir, + workspaceRevision, + loggerFactory.getLogger(['workspace', repoUri]) + ); await workspaceCmd.runInit(force); return {}; } catch (e) { diff --git a/x-pack/legacy/plugins/code/server/distributed/code_services.test.ts b/x-pack/legacy/plugins/code/server/distributed/code_services.test.ts index eaefdaf5448e..5f5319730c25 100644 --- a/x-pack/legacy/plugins/code/server/distributed/code_services.test.ts +++ b/x-pack/legacy/plugins/code/server/distributed/code_services.test.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { Request, Server } from 'hapi'; import { createTestHapiServer } from '../test_utils'; import { LocalHandlerAdapter } from './local_handler_adapter'; @@ -13,9 +14,10 @@ import { DEFAULT_SERVICE_OPTION } from './service_handler_adapter'; import { NonCodeNodeAdapter } from './multinode/non_code_node_adapter'; import { CodeServices } from './code_services'; import { Logger } from '../log'; +import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; +const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); let hapiServer: Server = createTestHapiServer(); -const log = new Logger(hapiServer); let server: CodeServerRouter = new CodeServerRouter(hapiServer); beforeEach(async () => { diff --git a/x-pack/legacy/plugins/code/server/index.ts b/x-pack/legacy/plugins/code/server/index.ts index 510abaef68a0..61ed0c0c5a58 100644 --- a/x-pack/legacy/plugins/code/server/index.ts +++ b/x-pack/legacy/plugins/code/server/index.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; - import * as constants from '../common/constants'; import { CodePlugin } from './plugin'; +import { PluginSetupContract } from '../../../../plugins/code/server/index'; -export const codePlugin = (initializerContext: PluginInitializerContext) => +export const codePlugin = (initializerContext: PluginSetupContract) => new CodePlugin(initializerContext); + export { constants }; diff --git a/x-pack/legacy/plugins/code/server/init_local.ts b/x-pack/legacy/plugins/code/server/init_local.ts index f39726c5e55e..cd26ccfd644f 100644 --- a/x-pack/legacy/plugins/code/server/init_local.ts +++ b/x-pack/legacy/plugins/code/server/init_local.ts @@ -5,12 +5,13 @@ */ import { Server } from 'hapi'; + +import { LoggerFactory } from 'src/core/server'; import { ServerOptions } from './server_options'; import { CodeServices } from './distributed/code_services'; import { EsClient } from './lib/esqueue'; import { RepositoryConfigController } from './repository_config_controller'; import { GitOperations } from './git_operations'; -import { Logger } from './log'; import { getGitServiceHandler, getLspServiceHandler, @@ -29,7 +30,7 @@ import { ServerLoggerFactory } from './utils/server_logger_factory'; export function initLocalService( server: Server, - log: Logger, + loggerFactory: LoggerFactory, serverOptions: ServerOptions, codeServices: CodeServices, esClient: EsClient, @@ -43,6 +44,7 @@ export function initLocalService( GitServiceDefinitionOption ); + const serverLoggerFactory = new ServerLoggerFactory(loggerFactory, serverOptions.verbose); const installManager = new InstallManager(server, serverOptions); const lspService = new LspService( '127.0.0.1', @@ -50,11 +52,11 @@ export function initLocalService( gitOps, esClient, installManager, - new ServerLoggerFactory(server), + serverLoggerFactory, repoConfigController ); server.events.on('stop', async () => { - log.debug('shutdown lsp process'); + loggerFactory.get().debug('shutdown lsp process'); await lspService.shutdown(); await gitOps.cleanAllRepo(); }); @@ -65,7 +67,7 @@ export function initLocalService( ); codeServices.registerHandler( WorkspaceDefinition, - getWorkspaceHandler(server, lspService.workspaceHandler) + getWorkspaceHandler(serverLoggerFactory, lspService.workspaceHandler) ); codeServices.registerHandler(SetupDefinition, setupServiceHandler); diff --git a/x-pack/legacy/plugins/code/server/init_queue.ts b/x-pack/legacy/plugins/code/server/init_queue.ts index 444eb589d530..12e8eeba1576 100644 --- a/x-pack/legacy/plugins/code/server/init_queue.ts +++ b/x-pack/legacy/plugins/code/server/init_queue.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; import { EsClient, Esqueue } from './lib/esqueue'; import { Logger } from './log'; +import { ServerOptions } from './server_options'; -export function initQueue(server: Server, log: Logger, esClient: EsClient) { - const queueIndex: string = server.config().get('xpack.code.queueIndex'); - const queueTimeoutMs: number = server.config().get('xpack.code.queueTimeoutMs'); +export function initQueue(serverOptions: ServerOptions, log: Logger, esClient: EsClient) { + const queueIndex: string = serverOptions.queueIndex; + const queueTimeoutMs: number = serverOptions.queueTimeoutMs; const queue = new Esqueue(queueIndex, { client: esClient, timeout: queueTimeoutMs, diff --git a/x-pack/legacy/plugins/code/server/log.ts b/x-pack/legacy/plugins/code/server/log.ts index 961065e59f4e..cb4a5e89aff3 100644 --- a/x-pack/legacy/plugins/code/server/log.ts +++ b/x-pack/legacy/plugins/code/server/log.ts @@ -7,19 +7,22 @@ import { inspect } from 'util'; import { Logger as VsLogger } from 'vscode-jsonrpc'; -import { ServerFacade } from '..'; +import { Logger as KibanaLogger, LoggerFactory } from 'src/core/server'; export class Logger implements VsLogger { - private readonly verbose: boolean = false; - constructor(private server: ServerFacade, private baseTags: string[] = ['code']) { - if (server) { - this.verbose = this.server.config().get('xpack.code.verbose'); - } + private logger: KibanaLogger; + + constructor( + private readonly loggerFactory: LoggerFactory, + private readonly verbose: boolean = false, + private readonly baseTags: string[] = [] + ) { + this.logger = this.loggerFactory.get(...this.baseTags); } // Return a new logger with new tags public addTags(tags: string[]): Logger { - return new Logger(this.server, this.baseTags.concat(tags)); + return new Logger(this.loggerFactory, this.verbose, this.baseTags.concat(tags)); } public info(msg: string | any) { @@ -28,7 +31,7 @@ export class Logger implements VsLogger { colors: process.stdout.isTTY, }); } - this.server.log([...this.baseTags, 'info'], msg); + this.logger.info(msg); } public error(msg: string | any) { @@ -42,11 +45,11 @@ export class Logger implements VsLogger { }); } - this.server.log([...this.baseTags, 'error'], msg); + this.logger.error(msg); } - public log(message: string): void { - this.info(message); + public log(msg: string): void { + this.logger.info(msg); } public debug(msg: string | any) { @@ -56,9 +59,9 @@ export class Logger implements VsLogger { }); } if (this.verbose) { - this.server.log([...this.baseTags, 'info'], msg); + this.logger.info(msg); } else { - this.server.log([...this.baseTags, 'debug'], msg); + this.logger.debug(msg); } } @@ -73,7 +76,7 @@ export class Logger implements VsLogger { }); } - this.server.log([...this.baseTags, 'warning'], msg); + this.logger.warn(msg); } // Log subprocess stdout @@ -84,9 +87,9 @@ export class Logger implements VsLogger { }); } if (this.verbose) { - this.server.log([...this.baseTags, 'info', 'stdout'], msg); + this.logger.info(msg); } else { - this.server.log([...this.baseTags, 'debug', 'stdout'], msg); + this.logger.debug(msg); } } @@ -98,9 +101,9 @@ export class Logger implements VsLogger { }); } if (this.verbose) { - this.server.log([...this.baseTags, 'error', 'stderr'], msg); + this.logger.error(msg); } else { - this.server.log([...this.baseTags, 'debug', 'stderr'], msg); + this.logger.debug(msg); } } } diff --git a/x-pack/legacy/plugins/code/server/lsp/controller.test.ts b/x-pack/legacy/plugins/code/server/lsp/controller.test.ts index d1b62fdc2784..212381b36ca6 100644 --- a/x-pack/legacy/plugins/code/server/lsp/controller.test.ts +++ b/x-pack/legacy/plugins/code/server/lsp/controller.test.ts @@ -5,7 +5,6 @@ */ import fs from 'fs'; -import mkdirp from 'mkdirp'; import * as os from 'os'; import path from 'path'; import rimraf from 'rimraf'; @@ -29,7 +28,21 @@ const workspaceDir = path.join(baseDir, 'workspace'); // @ts-ignore const options: ServerOptions = sinon.createStubInstance(ServerOptions); // @ts-ignore -options.lsp = { detach: false }; +options.lsp = { + TypeScript: { + enabled: true, + }, + Java: { + enabled: true, + }, + Go: { + enabled: true, + }, + Ctags: { + enabled: true, + }, + detach: false, +}; // @ts-ignore options.maxWorkspace = 2; const server = createTestHapiServer(); @@ -69,7 +82,7 @@ JAVA.launcher = LauncherStub; let controller: typeof LanguageServerController; beforeAll(() => { - mkdirp.sync(workspaceDir); + fs.mkdirSync(workspaceDir, { recursive: true }); }); beforeEach(async () => { sinon.reset(); @@ -101,7 +114,7 @@ afterAll(() => { function mockRequest(repo: string, file: string) { const repoPath = path.join(workspaceDir, repo); - mkdirp.sync(repoPath); + fs.mkdirSync(repoPath, { recursive: true }); return { method: 'request', params: [], diff --git a/x-pack/legacy/plugins/code/server/lsp/controller.ts b/x-pack/legacy/plugins/code/server/lsp/controller.ts index 662689f17104..4dd2ae3ffa5b 100644 --- a/x-pack/legacy/plugins/code/server/lsp/controller.ts +++ b/x-pack/legacy/plugins/code/server/lsp/controller.ts @@ -57,7 +57,7 @@ export class LanguageServerController implements ILanguageServerHandler { readonly repoConfigController: RepositoryConfigController ) { this.log = loggerFactory.getLogger([]); - this.languageServers = enabledLanguageServers(installManager.server).map(def => ({ + this.languageServers = enabledLanguageServers(options).map(def => ({ definition: def, builtinWorkspaceFolders: def.builtinWorkspaceFolders, languages: def.languages, diff --git a/x-pack/legacy/plugins/code/server/lsp/language_servers.ts b/x-pack/legacy/plugins/code/server/lsp/language_servers.ts index 0c2179bfebee..40b6a41d68c5 100644 --- a/x-pack/legacy/plugins/code/server/lsp/language_servers.ts +++ b/x-pack/legacy/plugins/code/server/lsp/language_servers.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ServerFacade } from '../..'; import { InstallationType } from '../../common/installation'; import { CTAGS_SUPPORT_LANGS, LanguageServer } from '../../common/language_server'; +import { ServerOptions } from '../server_options'; import { CtagsLauncher } from './ctags_launcher'; import { GoServerLauncher } from './go_launcher'; import { JavaLauncher } from './java_launcher'; @@ -72,12 +72,13 @@ export const CTAGS: LanguageServerDefinition = { export const LanguageServers: LanguageServerDefinition[] = [TYPESCRIPT, JAVA, GO, CTAGS]; export const LanguageServersDeveloping: LanguageServerDefinition[] = []; -export function enabledLanguageServers(server: ServerFacade) { - const devMode: boolean = server.config().get('env.dev'); +export function enabledLanguageServers(serverOptions: ServerOptions) { + const devMode: boolean = serverOptions.devMode; function isEnabled(lang: LanguageServerDefinition, defaultEnabled: boolean) { const name = lang.name; - const enabled = server.config().get(`xpack.code.lsp.${name}.enabled`); + // @ts-ignore + const enabled = serverOptions.lsp[name] && serverOptions.lsp[name].enabled; return enabled === undefined ? defaultEnabled : enabled; } const results = LanguageServers.filter(lang => isEnabled(lang, true)); diff --git a/x-pack/legacy/plugins/code/server/lsp/request_expander.test.ts b/x-pack/legacy/plugins/code/server/lsp/request_expander.test.ts index c89f6f4ec485..cf3999ec244f 100644 --- a/x-pack/legacy/plugins/code/server/lsp/request_expander.test.ts +++ b/x-pack/legacy/plugins/code/server/lsp/request_expander.test.ts @@ -5,7 +5,6 @@ */ import fs from 'fs'; -import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; import sinon from 'sinon'; import { pathToFileURL } from 'url'; @@ -22,7 +21,7 @@ const options: ServerOptions = { beforeEach(async () => { sinon.reset(); if (!fs.existsSync(options.workspacePath)) { - mkdirp.sync(options.workspacePath); + fs.mkdirSync(options.workspacePath, { recursive: true }); } }); @@ -80,8 +79,8 @@ test('be able to open multiple workspace', async () => { params: [], workspacePath: '/tmp/test/workspace/2', }; - mkdirp.sync(request1.workspacePath); - mkdirp.sync(request2.workspacePath); + fs.mkdirSync(request1.workspacePath, { recursive: true }); + fs.mkdirSync(request2.workspacePath, { recursive: true }); await expander.handleRequest(request1); await expander.handleRequest(request2); expect(proxyStub.initialize.called); @@ -125,8 +124,8 @@ test('be able to swap workspace', async () => { params: [], workspacePath: '/tmp/test/workspace/2', }; - mkdirp.sync(request1.workspacePath); - mkdirp.sync(request2.workspacePath); + fs.mkdirSync(request1.workspacePath, { recursive: true }); + fs.mkdirSync(request2.workspacePath, { recursive: true }); await expander.handleRequest(request1); await expander.handleRequest(request2); expect(proxyStub.initialize.called); @@ -166,7 +165,7 @@ test('requests should be cancelled if workspace is unloaded', async () => { params: [], workspacePath: workspace1, }; - mkdirp.sync(workspace1); + fs.mkdirSync(workspace1, { recursive: true }); const promise1 = expander.handleRequest(request); const promise2 = expander.handleRequest(request); setTimeout(() => expander.unloadWorkspace(workspace1), 1); diff --git a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.test.ts b/x-pack/legacy/plugins/code/server/lsp/workspace_handler.test.ts index 6a814982c22f..d959eda2aa6d 100644 --- a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.test.ts +++ b/x-pack/legacy/plugins/code/server/lsp/workspace_handler.test.ts @@ -6,7 +6,6 @@ import fs from 'fs'; import path from 'path'; -import mkdirp from 'mkdirp'; import * as os from 'os'; import rimraf from 'rimraf'; import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; @@ -48,7 +47,7 @@ function makeAFile( file = 'src/controllers/user.ts' ) { const fullPath = path.join(workspacePath, repo, '__randomString', revision, file); - mkdirp.sync(path.dirname(fullPath)); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, ''); const strInUrl = fullPath .split(path.sep) @@ -139,8 +138,8 @@ test('should throw a error if url is invalid', async () => { }); beforeAll(() => { - mkdirp.sync(workspaceDir); - mkdirp.sync(repoDir); + fs.mkdirSync(workspaceDir, { recursive: true }); + fs.mkdirSync(repoDir, { recursive: true }); }); afterAll(() => { diff --git a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.ts b/x-pack/legacy/plugins/code/server/lsp/workspace_handler.ts index d8dcb12837e6..535e60ae0dad 100644 --- a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.ts +++ b/x-pack/legacy/plugins/code/server/lsp/workspace_handler.ts @@ -17,8 +17,8 @@ import Boom from 'boom'; import del from 'del'; import fs from 'fs'; import { delay } from 'lodash'; -import mkdirp from 'mkdirp'; import path from 'path'; +import { promisify } from 'util'; import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; import { Hover, Location, TextDocumentPositionParams } from 'vscode-languageserver'; @@ -35,6 +35,8 @@ import { LoggerFactory } from '../utils/log_factory'; export const MAX_RESULT_COUNT = 20; +const mkdirAsync = promisify(fs.mkdir); + export class WorkspaceHandler { private revisionMap: { [uri: string]: string } = {}; private log: Logger; @@ -396,15 +398,7 @@ export class WorkspaceHandler { this.log.info(`Create workspace ${workspaceDir} from url ${bareRepo.path()}`); const parentDir = path.dirname(workspaceDir); // on windows, git clone will failed if parent folder is not exists; - await new Promise((resolve, reject) => - mkdirp(parentDir, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }) - ); + await mkdirAsync(parentDir, { recursive: true }); const workTreeName = this.workspaceWorktreeBranchName(revision); await this.pruneWorktree(bareRepo, workTreeName); // Create the worktree and open it as Repository. diff --git a/x-pack/legacy/plugins/code/server/plugin.ts b/x-pack/legacy/plugins/code/server/plugin.ts index bae89cc5f167..802a851a6124 100644 --- a/x-pack/legacy/plugins/code/server/plugin.ts +++ b/x-pack/legacy/plugins/code/server/plugin.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import crypto from 'crypto'; import * as _ from 'lodash'; -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { CoreSetup } from 'src/core/server'; -import { XPackMainPlugin } from '../../xpack_main/xpack_main'; import { GitOperations } from './git_operations'; import { RepositoryIndexInitializerFactory, tryMigrateIndices } from './indexer'; import { Esqueue } from './lib/esqueue'; @@ -56,6 +54,7 @@ import { initWorkers } from './init_workers'; import { ClusterNodeAdapter } from './distributed/cluster/cluster_node_adapter'; import { NodeRepositoriesService } from './distributed/cluster/node_repositories_service'; import { initCodeUsageCollector } from './usage_collector'; +import { PluginSetupContract } from '../../../../plugins/code/server/index'; export class CodePlugin { private isCodeNode = false; @@ -70,50 +69,15 @@ export class CodePlugin { private codeServices: CodeServices | null = null; private nodeService: NodeRepositoriesService | null = null; - constructor(initializerContext: PluginInitializerContext) { + constructor(private readonly initContext: PluginSetupContract) { this.log = {} as Logger; this.serverOptions = {} as ServerOptions; } - // TODO: options is not a valid param for the setup() api - // of the new platform. Will need to pass through the configs - // correctly in the new platform. - public setup(core: CoreSetup, options: any) { + public setup(core: CoreSetup) { const { server } = core.http as any; - - this.log = new Logger(server); - this.serverOptions = new ServerOptions(options, server.config()); - - const xpackMainPlugin: XPackMainPlugin = server.plugins.xpack_main; - xpackMainPlugin.registerFeature({ - id: 'code', - name: i18n.translate('xpack.code.featureRegistry.codeFeatureName', { - defaultMessage: 'Code', - }), - icon: 'codeApp', - navLinkId: 'code', - app: ['code', 'kibana'], - catalogue: [], // TODO add catalogue here - privileges: { - all: { - excludeFromBasePrivileges: true, - api: ['code_user', 'code_admin'], - savedObject: { - all: [], - read: ['config'], - }, - ui: ['show', 'user', 'admin'], - }, - read: { - api: ['code_user'], - savedObject: { - all: [], - read: ['config'], - }, - ui: ['show', 'user'], - }, - }, - }); + this.serverOptions = new ServerOptions(this.initContext.legacy.config, server.config()); + this.log = new Logger(this.initContext.legacy.logger, this.serverOptions.verbose); } // TODO: CodeStart will not have the register route api. @@ -165,11 +129,11 @@ export class CodePlugin { const codeServices = new CodeServices(clusterNodeAdapter); - this.queue = initQueue(server, this.log, esClient); + this.queue = initQueue(this.serverOptions, this.log, esClient); const { gitOps, lspService } = initLocalService( server, - this.log, + this.initContext.legacy.logger, this.serverOptions, codeServices, esClient, @@ -213,11 +177,11 @@ export class CodePlugin { this.log ); - this.queue = initQueue(server, this.log, esClient); + this.queue = initQueue(this.serverOptions, this.log, esClient); const { gitOps, lspService } = initLocalService( server, - this.log, + this.initContext.legacy.logger, this.serverOptions, codeServices, esClient, @@ -293,7 +257,8 @@ export class CodePlugin { codeServices, repoIndexInitializerFactory, repoConfigController, - this.serverOptions + this.serverOptions, + this.log ); repositorySearchRoute(codeServerRouter, this.log); if (this.serverOptions.enableCommitIndexing) { @@ -304,8 +269,8 @@ export class CodePlugin { fileRoute(codeServerRouter, codeServices); workspaceRoute(codeServerRouter, this.serverOptions, codeServices); symbolByQnameRoute(codeServerRouter, this.log); - installRoute(codeServerRouter, codeServices); - lspRoute(codeServerRouter, codeServices, this.serverOptions); + installRoute(codeServerRouter, codeServices, this.serverOptions); + lspRoute(codeServerRouter, codeServices, this.serverOptions, this.log); setupRoute(codeServerRouter, codeServices); statusRoute(codeServerRouter, codeServices); } @@ -342,7 +307,7 @@ export class CodePlugin { private initDevMode(server: any) { // @ts-ignore - const devMode: boolean = server.config().get('env.dev'); + const devMode: boolean = this.serverOptions.devMode; server.injectUiAppVars('code', () => ({ enableLangserversDeveloping: devMode, })); diff --git a/x-pack/legacy/plugins/code/server/repository_service.ts b/x-pack/legacy/plugins/code/server/repository_service.ts index 213590285c07..1369802cb594 100644 --- a/x-pack/legacy/plugins/code/server/repository_service.ts +++ b/x-pack/legacy/plugins/code/server/repository_service.ts @@ -7,7 +7,7 @@ import Git, { RemoteCallbacks } from '@elastic/nodegit'; import del from 'del'; import fs from 'fs'; -import mkdirp from 'mkdirp'; +import { promisify } from 'util'; import moment from 'moment'; import path from 'path'; @@ -34,6 +34,8 @@ const NODEGIT_CALLBACK_RETURN_VALUE_ERROR = -7; const GIT_INDEXER_PROGRESS_CALLBACK_RETURN_VALUE_ERROR_MSG = `indexer progress callback returned ${GIT_FETCH_PROGRESS_CANCEL}`; const SSH_AUTH_ERROR = new Error('Failed to authenticate SSH session'); +const mkdirAsync = promisify(fs.mkdir); + function isCancelled(error: any) { return ( error && @@ -77,15 +79,7 @@ export class RepositoryService { } else { const parentDir = path.dirname(localPath); // on windows, git clone will failed if parent folder is not exists; - await new Promise((resolve, reject) => - mkdirp(parentDir, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }) - ); + await mkdirAsync(parentDir, { recursive: true }); } // Go head with the actual clone. if (repo.protocol === 'ssh') { diff --git a/x-pack/legacy/plugins/code/server/routes/install.ts b/x-pack/legacy/plugins/code/server/routes/install.ts index 87f5f049fc0f..338f305cba85 100644 --- a/x-pack/legacy/plugins/code/server/routes/install.ts +++ b/x-pack/legacy/plugins/code/server/routes/install.ts @@ -12,8 +12,13 @@ import { CodeServerRouter } from '../security'; import { CodeServices } from '../distributed/code_services'; import { LspServiceDefinition } from '../distributed/apis'; import { Endpoint } from '../distributed/resource_locator'; +import { ServerOptions } from '../server_options'; -export function installRoute(router: CodeServerRouter, codeServices: CodeServices) { +export function installRoute( + router: CodeServerRouter, + codeServices: CodeServices, + options: ServerOptions +) { const lspService = codeServices.serviceFor(LspServiceDefinition); const kibanaVersion = router.server.config().get('pkg.version') as string; const status = async (endpoint: Endpoint, def: LanguageServerDefinition) => ({ @@ -32,9 +37,7 @@ export function installRoute(router: CodeServerRouter, codeServices: CodeService path: '/api/code/install', async handler(req: RequestFacade) { const endpoint = await codeServices.locate(req, ''); - return await Promise.all( - enabledLanguageServers(router.server).map(def => status(endpoint, def)) - ); + return await Promise.all(enabledLanguageServers(options).map(def => status(endpoint, def))); }, method: 'GET', }); @@ -43,7 +46,7 @@ export function installRoute(router: CodeServerRouter, codeServices: CodeService path: '/api/code/install/{name}', async handler(req: RequestFacade) { const name = req.params.name; - const def = enabledLanguageServers(router.server).find(d => d.name === name); + const def = enabledLanguageServers(options).find(d => d.name === name); const endpoint = await codeServices.locate(req, ''); if (def) { return await status(endpoint, def); diff --git a/x-pack/legacy/plugins/code/server/routes/lsp.ts b/x-pack/legacy/plugins/code/server/routes/lsp.ts index b8dcf0e5cd77..6aa2ca072cae 100644 --- a/x-pack/legacy/plugins/code/server/routes/lsp.ts +++ b/x-pack/legacy/plugins/code/server/routes/lsp.ts @@ -7,8 +7,8 @@ import Boom from 'boom'; import { ResponseError } from 'vscode-jsonrpc'; import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; - import { SymbolLocator } from '@elastic/lsp-extension'; + import { LanguageServerStartFailed, ServerNotInitialized, @@ -32,9 +32,9 @@ const LANG_SERVER_ERROR = 'language server error'; export function lspRoute( server: CodeServerRouter, codeServices: CodeServices, - serverOptions: ServerOptions + serverOptions: ServerOptions, + log: Logger ) { - const log = new Logger(server.server); const lspService = codeServices.serviceFor(LspServiceDefinition); const gitService = codeServices.serviceFor(GitServiceDefinition); diff --git a/x-pack/legacy/plugins/code/server/routes/repository.ts b/x-pack/legacy/plugins/code/server/routes/repository.ts index a2f7fe9a433d..f40a56217807 100644 --- a/x-pack/legacy/plugins/code/server/routes/repository.ts +++ b/x-pack/legacy/plugins/code/server/routes/repository.ts @@ -25,7 +25,8 @@ export function repositoryRoute( codeServices: CodeServices, repoIndexInitializerFactory: RepositoryIndexInitializerFactory, repoConfigController: RepositoryConfigController, - options: ServerOptions + options: ServerOptions, + log: Logger ) { const repositoryService = codeServices.serviceFor(RepositoryServiceDefinition); // Clone a git repository @@ -35,7 +36,6 @@ export function repositoryRoute( method: 'POST', async handler(req: RequestFacade, h: ResponseToolkitFacade) { const repoUrl: string = (req.payload as any).url; - const log = new Logger(req.server); // Reject the request if the url is an invalid git url. try { @@ -104,7 +104,6 @@ export function repositoryRoute( method: 'DELETE', async handler(req: RequestFacade, h: ResponseToolkitFacade) { const repoUri: string = req.params.uri as string; - const log = new Logger(req.server); const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); try { // Check if the repository already exists. If not, an error will be thrown. @@ -146,7 +145,6 @@ export function repositoryRoute( method: 'GET', async handler(req: RequestFacade) { const repoUri = req.params.uri as string; - const log = new Logger(req.server); try { const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); return await repoObjectClient.getRepository(repoUri); @@ -164,7 +162,6 @@ export function repositoryRoute( method: 'GET', async handler(req: RequestFacade) { const repoUri = req.params.uri as string; - const log = new Logger(req.server); try { const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); let gitStatus = null; @@ -206,7 +203,6 @@ export function repositoryRoute( path: '/api/code/repos', method: 'GET', async handler(req: RequestFacade) { - const log = new Logger(req.server); try { const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); return await repoObjectClient.getAllRepositories(); @@ -228,7 +224,6 @@ export function repositoryRoute( requireAdmin: true, async handler(req: RequestFacade) { const repoUri = req.params.uri as string; - const log = new Logger(req.server); const reindex: boolean = (req.payload as any).reindex; try { const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); @@ -259,7 +254,6 @@ export function repositoryRoute( async handler(req: RequestFacade) { const config: RepositoryConfig = req.payload as RepositoryConfig; const repoUri: RepositoryUri = config.uri; - const log = new Logger(req.server); const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); try { diff --git a/x-pack/legacy/plugins/code/server/server_options.ts b/x-pack/legacy/plugins/code/server/server_options.ts index a2e744a48be4..50c175f7b2ef 100644 --- a/x-pack/legacy/plugins/code/server/server_options.ts +++ b/x-pack/legacy/plugins/code/server/server_options.ts @@ -30,6 +30,8 @@ export interface DiskOptions { } export class ServerOptions { + public readonly devMode: boolean = this.config.get('env.dev'); + public readonly workspacePath = resolve(this.config.get('path.data'), 'code/workspace'); public readonly repoPath = resolve(this.config.get('path.data'), 'code/repos'); @@ -76,8 +78,13 @@ export class ServerOptions { public readonly clusterEnabled: boolean = this.options.clustering.enabled; + public readonly verbose: boolean = this.options.verbose; + public readonly codeNodes: CodeNode[] = this.options.clustering.codeNodes; + public readonly queueIndex: string = this.options.queueIndex; + public readonly queueTimeoutMs: number = this.options.queueTimeoutMs; + constructor(private options: any, private config: any) {} /** diff --git a/x-pack/legacy/plugins/code/server/utils/console_logger.ts b/x-pack/legacy/plugins/code/server/utils/console_logger.ts index 1a0292e6f2b5..afc8677f494c 100644 --- a/x-pack/legacy/plugins/code/server/utils/console_logger.ts +++ b/x-pack/legacy/plugins/code/server/utils/console_logger.ts @@ -9,8 +9,11 @@ import { Logger } from '../log'; export class ConsoleLogger extends Logger { constructor() { - // @ts-ignore - super(undefined); + super({ + get: (...contextParts: string[]) => { + return console as any; + }, + }); } public info(msg: string | any) { diff --git a/x-pack/legacy/plugins/code/server/utils/console_logger_factory.ts b/x-pack/legacy/plugins/code/server/utils/console_logger_factory.ts index 6dc1f211902e..01588f38a858 100644 --- a/x-pack/legacy/plugins/code/server/utils/console_logger_factory.ts +++ b/x-pack/legacy/plugins/code/server/utils/console_logger_factory.ts @@ -9,7 +9,7 @@ import { ConsoleLogger } from './console_logger'; import { LoggerFactory } from './log_factory'; export class ConsoleLoggerFactory implements LoggerFactory { - public getLogger(tags: string[]): Logger { + public getLogger(tags: string[] = []): Logger { return new ConsoleLogger(); } } diff --git a/x-pack/legacy/plugins/code/server/utils/server_logger_factory.ts b/x-pack/legacy/plugins/code/server/utils/server_logger_factory.ts index 62a7d197e419..e9687da443f9 100644 --- a/x-pack/legacy/plugins/code/server/utils/server_logger_factory.ts +++ b/x-pack/legacy/plugins/code/server/utils/server_logger_factory.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { LoggerFactory } from 'src/core/server'; import { Logger } from '../log'; -import { LoggerFactory } from './log_factory'; -import { ServerFacade } from '../..'; +import { LoggerFactory as CodeLoggerFactory } from './log_factory'; -export class ServerLoggerFactory implements LoggerFactory { - constructor(private readonly server: ServerFacade) {} +export class ServerLoggerFactory implements CodeLoggerFactory { + constructor(private readonly loggerFactory: LoggerFactory, private readonly verbose: boolean) {} - public getLogger(tags: string[]): Logger { - return new Logger(this.server, tags); + public getLogger(tags: string[] = []): Logger { + return new Logger(this.loggerFactory, this.verbose, tags); } } diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/index.html b/x-pack/legacy/plugins/graph/public/angular/templates/index.html index 07b57ee32254..9e9356f30642 100644 --- a/x-pack/legacy/plugins/graph/public/angular/templates/index.html +++ b/x-pack/legacy/plugins/graph/public/angular/templates/index.html @@ -9,20 +9,20 @@ -
+
{ - // TODO this should be wrapped into canWipeWorkspace, - // but the check is too simple right now. Change this - // once actual state-diffing is in place. - $scope.$evalAsync(() => { - kbnUrl.changePath(getHomePath()); - }); - } - }); - } - - const store = createGraphStore(); - - $scope.title = 'Graph'; - $scope.spymode = 'request'; - - $scope.iconChoices = iconChoices; - $scope.drillDownIconChoices = urlTemplateIconChoices; - $scope.colors = colorChoices; - $scope.iconChoicesByClass = iconChoicesByClass; - - $scope.outlinkEncoders = outlinkEncoders; - - $scope.fields = []; - $scope.canEditDrillDownUrls = chrome.getInjected('canEditDrillDownUrls'); - - $scope.graphSavePolicy = chrome.getInjected('graphSavePolicy'); - $scope.allSavingDisabled = $scope.graphSavePolicy === 'none'; - $scope.searchTerm = ''; - - $scope.reduxDispatch = (action) => { - store.dispatch(action); - - // patch updated icons and fields on the nodes in the workspace state - // this workaround is necessary because the nodes are still managed by - // angular - once they are moved over to redux, this can be handled in - // the reducer - if (action.type === 'x-pack/graph/fields/UPDATE_FIELD_PROPERTIES' && - action.payload.fieldProperties.color && $scope.workspace) { - $scope.workspace.nodes.forEach(function (node) { - if (node.data.field === action.payload.fieldName) { - node.color = action.payload.fieldProperties.color; + // Replacement function for graphClientWorkspace's comms so + // that it works with Kibana. + function callNodeProxy(indexName, query, responseHandler) { + const request = { + index: indexName, + query: query + }; + $scope.loading = true; + return $http.post('../api/graph/graphExplore', request) + .then(function (resp) { + if (resp.data.resp.timed_out) { + toastNotifications.addWarning( + i18n.translate('xpack.graph.exploreGraph.timedOutWarningText', { + defaultMessage: 'Exploration timed out', + }) + ); } + responseHandler(resp.data.resp); + }) + .catch(handleHttpError) + .finally(() => { + $scope.loading = false; }); - } + } - if (action.type === 'x-pack/graph/fields/UPDATE_FIELD_PROPERTIES' && - action.payload.fieldProperties.icon && $scope.workspace) { - $scope.workspace.nodes.forEach(function (node) { - if (node.data.field === action.payload.fieldName) { - node.icon = action.payload.fieldProperties.icon; - } + + //Helper function for the graphClientWorkspace to perform a query + const callSearchNodeProxy = function (indexName, query, responseHandler) { + const request = { + index: indexName, + body: query + }; + $scope.loading = true; + $http.post('../api/graph/searchProxy', request) + .then(function (resp) { + responseHandler(resp.data.resp); + }) + .catch(handleHttpError) + .finally(() => { + $scope.loading = false; }); - } }; + $scope.indexPatternProvider = createCachedIndexPatternProvider($route.current.locals.GetIndexPatternProvider.get); + + const store = createGraphStore({ + basePath: chrome.getBasePath(), + indexPatternProvider: $scope.indexPatternProvider, + indexPatterns: $route.current.locals.indexPatterns, + createWorkspace: (indexPattern, exploreControls) => { + const options = { + indexName: indexPattern, + vertex_fields: [], + // Here we have the opportunity to look up labels for nodes... + nodeLabeller: function () { + // console.log(newNodes); + }, + changeHandler: function () { + //Allows DOM to update with graph layout changes. + $scope.$apply(); + }, + graphExploreProxy: callNodeProxy, + searchProxy: callSearchNodeProxy, + exploreControls, + }; + $scope.workspace = gws.createWorkspace(options); + }, + setLiveResponseFields: (fields) => { + $scope.liveResponseFields = fields; + }, + getWorkspace: () => { + return $scope.workspace; + }, + getSavedWorkspace: () => { + return $route.current.locals.savedWorkspace; + }, + notifications: npStart.core.notifications, + showSaveModal, + savePolicy: chrome.getInjected('graphSavePolicy'), + changeUrl: (newUrl) => { + $scope.$evalAsync(() => { + kbnUrl.change(newUrl, {}); + }); + }, + notifyAngular: () => { + $scope.$digest(); + }, + chrome, + }); $scope.store = new Storage(window.localStorage); $scope.coreStart = npStart.core; $scope.autocompleteStart = npStart.plugins.data.autocomplete; $scope.loading = false; - const updateScope = () => { - const newState = store.getState(); - $scope.reduxState = newState; - $scope.allFields = fieldsSelector(newState); - $scope.selectedFields = selectedFieldsSelector(newState); - $scope.liveResponseFields = liveResponseFieldsSelector(newState); - if ($scope.workspace) { - $scope.workspace.options.vertex_fields = $scope.selectedFields; - } - }; - store.subscribe(updateScope); - updateScope(); + $scope.spymode = 'request'; - //So scope properties can be used consistently with ng-model - $scope.grr = $scope; + const allSavingDisabled = chrome.getInjected('graphSavePolicy') === 'none'; - $scope.toggleDrillDownIcon = function (urlTemplate, icon) { - urlTemplate.icon === icon ? urlTemplate.icon = null : urlTemplate.icon = icon; - }; + $scope.reduxStore = store; $scope.nodeClick = function (n, $event) { @@ -350,14 +351,14 @@ app.controller('graphuiPlugin', function ( } }; - function canWipeWorkspace(yesFn, noFn) { - if ($scope.selectedFields.length === 0 && $scope.workspace === null) { - yesFn(); + function canWipeWorkspace(callback) { + if (!hasFieldsSelector(store.getState())) { + callback(); return; } const confirmModalOptions = { - onConfirm: yesFn, - onCancel: noFn || (() => {}), + onConfirm: callback, + onCancel: (() => {}), confirmButtonText: i18n.translate('xpack.graph.clearWorkspace.confirmButtonLabel', { defaultMessage: 'Continue', }), @@ -369,30 +370,7 @@ app.controller('graphuiPlugin', function ( defaultMessage: 'Once you discard changes made to a workspace, there is no getting them back.', }), confirmModalOptions); } - - $scope.uiSelectIndex = function (proposedIndex) { - canWipeWorkspace(function () { - $scope.indexSelected(proposedIndex); - }); - }; - - $scope.indexSelected = function (selectedIndex) { - $scope.clearWorkspace(); - $scope.allFields = []; - $scope.selectedFields = []; - $scope.basicModeSelectedSingleField = null; - $scope.selectedField = null; - $scope.selectedFieldConfig = null; - - return $route.current.locals.GetIndexPatternProvider.get(selectedIndex.id) - .then(handleSuccess) - .then(function (indexPattern) { - $scope.selectedIndex = indexPattern; - store.dispatch(loadFields(mapFields(indexPattern))); - $scope.$digest(); - }, handleError); - }; - + $scope.confirmWipeWorkspace = canWipeWorkspace; $scope.clickEdge = function (edge) { if (edge.inferred) { @@ -402,62 +380,20 @@ app.controller('graphuiPlugin', function ( } }; - // Replacement function for graphClientWorkspace's comms so - // that it works with Kibana. - function callNodeProxy(indexName, query, responseHandler) { - const request = { - index: indexName, - query: query - }; - $scope.loading = true; - return $http.post('../api/graph/graphExplore', request) - .then(function (resp) { - if (resp.data.resp.timed_out) { - toastNotifications.addWarning( - i18n.translate('xpack.graph.exploreGraph.timedOutWarningText', { - defaultMessage: 'Exploration timed out', - }) - ); - } - responseHandler(resp.data.resp); - }) - .catch(handleHttpError) - .finally(() => { - $scope.loading = false; - }); - } - - - //Helper function for the graphClientWorkspace to perform a query - const callSearchNodeProxy = function (indexName, query, responseHandler) { - const request = { - index: indexName, - body: query - }; - $scope.loading = true; - $http.post('../api/graph/searchProxy', request) - .then(function (resp) { - responseHandler(resp.data.resp); - }) - .catch(handleHttpError) - .finally(() => { - $scope.loading = false; - }); - }; $scope.fillWorkspace = async () => { try { const fields = selectedFieldsSelector(store.getState()); const topTermNodes = await fetchTopNodes( npStart.core.http.post, - $scope.selectedIndex.title, + datasourceSelector(store.getState()).current.title, fields ); - initWorkspaceIfRequired(); $scope.workspace.mergeGraph({ nodes: topTermNodes, edges: [] }); + $scope.workspaceInitialized = true; $scope.workspace.fillInGraph(fields.length * 10); } catch (e) { toastNotifications.addDanger({ @@ -470,7 +406,7 @@ app.controller('graphuiPlugin', function ( }; $scope.submit = function (searchTerm) { - initWorkspaceIfRequired(); + $scope.workspaceInitialized = true; const numHops = 2; if (searchTerm.startsWith('{')) { try { @@ -509,121 +445,12 @@ app.controller('graphuiPlugin', function ( return $scope.selectedSelectedVertex === node; }; - $scope.saveUrlTemplate = function (index, urlTemplate) { - const newTemplatesList = [...$scope.urlTemplates]; - if (index !== -1) { - newTemplatesList[index] = urlTemplate; - } else { - newTemplatesList.push(urlTemplate); - } - - $scope.urlTemplates = newTemplatesList; - }; - - $scope.removeUrlTemplate = function (urlTemplate) { - const newTemplatesList = [...$scope.urlTemplates]; - const i = newTemplatesList.indexOf(urlTemplate); - newTemplatesList.splice(i, 1); - $scope.urlTemplates = newTemplatesList; - }; - $scope.openUrlTemplate = function (template) { const url = template.url; const newUrl = url.replace(urlTemplateRegex, template.encoder.encode($scope.workspace)); window.open(newUrl, '_blank'); }; - - //============================ - - $scope.resetWorkspace = function () { - $scope.clearWorkspace(); - $scope.selectedIndex = null; - $scope.proposedIndex = null; - $scope.detail = null; - $scope.selectedSelectedVertex = null; - $scope.selectedField = null; - $scope.description = null; - $scope.allFields = []; - $scope.urlTemplates = []; - - $scope.fieldNamesFilterString = null; - $scope.filteredFields = []; - - $scope.selectedFields = []; - $scope.liveResponseFields = []; - - $scope.exploreControls = { - useSignificance: true, - sampleSize: 2000, - timeoutMillis: 5000, - sampleDiversityField: null, - maxValuesPerDoc: 1, - minDocCount: 3 - }; - }; - - - function initWorkspaceIfRequired() { - if ($scope.workspace) { - return; - } - const options = { - indexName: $scope.selectedIndex.title, - vertex_fields: $scope.selectedFields, - // Here we have the opportunity to look up labels for nodes... - nodeLabeller: function () { - // console.log(newNodes); - }, - changeHandler: function () { - //Allows DOM to update with graph layout changes. - $scope.$apply(); - }, - graphExploreProxy: callNodeProxy, - searchProxy: callSearchNodeProxy, - exploreControls: $scope.exploreControls - }; - $scope.workspace = gws.createWorkspace(options); - $scope.detail = null; - - // filter out default url templates because they will get re-added - $scope.urlTemplates = $scope.urlTemplates.filter(template => !template.isDefault); - - if ($scope.urlTemplates.length === 0) { - // url templates specified by users can include the `{{gquery}}` tag and - // will have the elasticsearch query for the graph nodes injected there - const tag = '{{gquery}}'; - - const kUrl = new KibanaParsedUrl({ - appId: 'kibana', - basePath: chrome.getBasePath(), - appPath: '/discover' - }); - - kUrl.addQueryParameter('_a', rison.encode({ - columns: ['_source'], - index: $scope.selectedIndex.id, - interval: 'auto', - query: { language: 'kuery', query: tag }, - sort: ['_score', 'desc'] - })); - - const discoverUrl = kUrl.getRootRelativePath() - // replace the URI encoded version of the tag with the unescaped version - // so it can be found with String.replace, regexp, etc. - .replace(encodeURIComponent(tag), tag); - - $scope.urlTemplates.push({ - url: discoverUrl, - description: i18n.translate('xpack.graph.settings.drillDowns.defaultUrlTemplateTitle', { - defaultMessage: 'Raw documents', - }), - encoder: $scope.outlinkEncoders[0], - isDefault: true - }); - } - } - $scope.aceLoaded = (editor) => { editor.$blockScrolling = Infinity; }; @@ -668,39 +495,6 @@ app.controller('graphuiPlugin', function ( $scope.detail = { mergeCandidates }; }; - //initialize all the state - $scope.resetWorkspace(); - - const managementUrl = npStart.core.chrome.navLinks.get('kibana:management').url; - const url = `${managementUrl}/kibana/index_patterns`; - - if ($route.current.locals.indexPatterns.length === 0) { - toastNotifications.addWarning({ - title: i18n.translate('xpack.graph.noDataSourceNotificationMessageTitle', { - defaultMessage: 'No data source', - }), - text: ( -

- - - - ) - }} - /> -

- ), - }); - } - - // ===== Menubar configuration ========= $scope.topNavMenu = []; $scope.topNavMenu.push({ @@ -737,7 +531,7 @@ app.controller('graphuiPlugin', function ( defaultMessage: 'Save workspace', }), tooltip: () => { - if ($scope.allSavingDisabled) { + if (allSavingDisabled) { return i18n.translate('xpack.graph.topNavMenu.saveWorkspace.disabledTooltip', { defaultMessage: 'No changes to saved workspaces are permitted by the current save policy', }); @@ -748,15 +542,12 @@ app.controller('graphuiPlugin', function ( } }, disableButton: function () { - return $scope.allSavingDisabled || $scope.selectedFields.length === 0; + return allSavingDisabled || !hasFieldsSelector(store.getState()); }, run: () => { - openSaveModal({ - savePolicy: $scope.graphSavePolicy, - hasData: $scope.workspace && ($scope.workspace.nodes.length > 0 || $scope.workspace.blacklistedNodes.length > 0), - workspace: $scope.savedWorkspace, - saveWorkspace: $scope.saveWorkspace, - showSaveModal + store.dispatch({ + type: 'x-pack/graph/SAVE_WORKSPACE', + payload: $route.current.locals.savedWorkspace, }); }, testId: 'graphSaveButton', @@ -780,10 +571,9 @@ app.controller('graphuiPlugin', function ( }, }); - let currentSettingsFlyout; $scope.topNavMenu.push({ key: 'settings', - disableButton: function () { return $scope.selectedIndex === null; }, + disableButton: function () { return datasourceSelector(store.getState()).type === 'none'; }, label: i18n.translate('xpack.graph.topNavMenu.settingsLabel', { defaultMessage: 'Settings', }), @@ -791,40 +581,25 @@ app.controller('graphuiPlugin', function ( defaultMessage: 'Settings', }), run: () => { - if (currentSettingsFlyout) { - currentSettingsFlyout.close(); - return; - } const settingsObservable = asAngularSyncedObservable(() => ({ - advancedSettings: { ...$scope.exploreControls }, - updateAdvancedSettings: (updatedSettings) => { - $scope.exploreControls = updatedSettings; - if ($scope.workspace) { - $scope.workspace.options.exploreControls = updatedSettings; - } - }, blacklistedNodes: $scope.workspace ? [...$scope.workspace.blacklistedNodes] : undefined, unblacklistNode: $scope.workspace ? $scope.workspace.unblacklist : undefined, - urlTemplates: [...$scope.urlTemplates], - removeUrlTemplate: $scope.removeUrlTemplate, - saveUrlTemplate: $scope.saveUrlTemplate, - allFields: [...$scope.allFields], - canEditDrillDownUrls: $scope.canEditDrillDownUrls + canEditDrillDownUrls: chrome.getInjected('canEditDrillDownUrls') }), $scope.$digest.bind($scope)); - currentSettingsFlyout = npStart.core.overlays.openFlyout(, { - size: 'm', - closeButtonAriaLabel: i18n.translate('xpack.graph.settings.closeLabel', { defaultMessage: 'Close' }), - 'data-test-subj': 'graphSettingsFlyout', - ownFocus: true, - className: 'gphSettingsFlyout', - maxWidth: 520, - }); - currentSettingsFlyout.onClose.then(() => { currentSettingsFlyout = null; }); + npStart.core.overlays.openFlyout( + + + , { + size: 'm', + closeButtonAriaLabel: i18n.translate('xpack.graph.settings.closeLabel', { defaultMessage: 'Close' }), + 'data-test-subj': 'graphSettingsFlyout', + ownFocus: true, + className: 'gphSettingsFlyout', + maxWidth: 520, + }); }, }); - updateBreadcrumbs(); - $scope.menus = { showSettings: false, }; @@ -836,99 +611,42 @@ app.controller('graphuiPlugin', function ( }; // Deal with situation of request to open saved workspace - if ($route.current.locals.savedWorkspace) { - $scope.savedWorkspace = $route.current.locals.savedWorkspace; - const selectedIndex = lookupIndexPattern($scope.savedWorkspace, $route.current.locals.indexPatterns); - if(!selectedIndex) { - toastNotifications.addDanger( - i18n.translate('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', { - defaultMessage: 'Index pattern not found', - }) - ); - return; - } - $route.current.locals.GetIndexPatternProvider.get(selectedIndex.id).then(indexPattern => { - $scope.selectedIndex = indexPattern; - initWorkspaceIfRequired(); - const { - urlTemplates, - advancedSettings, - allFields, - } = savedWorkspaceToAppState($scope.savedWorkspace, indexPattern, $scope.workspace); - - // wire up stuff to angular - store.dispatch(loadFields(allFields)); - $scope.exploreControls = advancedSettings; - $scope.workspace.options.exploreControls = advancedSettings; - $scope.urlTemplates = urlTemplates; - $scope.workspace.runLayout(); - // Allow URLs to include a user-defined text query - if ($route.current.params.query) { - $scope.initialQuery = $route.current.params.query; - $scope.submit($route.current.params.query); - } - - $scope.$digest(); + if ($route.current.locals.savedWorkspace.id) { + store.dispatch({ + type: 'x-pack/graph/LOAD_WORKSPACE', + payload: $route.current.locals.savedWorkspace, }); } else { - $route.current.locals.SavedWorkspacesProvider.get().then(function (newWorkspace) { - $scope.savedWorkspace = newWorkspace; - }); - } + const managementUrl = npStart.core.chrome.navLinks.get('kibana:management').url; + const url = `${managementUrl}/kibana/index_patterns`; - $scope.saveWorkspace = function (saveOptions, userHasConfirmedSaveWorkspaceData) { - if ($scope.allSavingDisabled) { - // It should not be possible to navigate to this function if allSavingDisabled is set - // but adding check here as a safeguard. - toastNotifications.addWarning( - i18n.translate('xpack.graph.saveWorkspace.disabledWarning', { defaultMessage: 'Saving is disabled' }) - ); - return; + if ($route.current.locals.indexPatterns.length === 0) { + toastNotifications.addWarning({ + title: i18n.translate('xpack.graph.noDataSourceNotificationMessageTitle', { + defaultMessage: 'No data source', + }), + text: ( +

+ + + + ) + }} + /> +

+ ), + }); } - initWorkspaceIfRequired(); - const canSaveData = $scope.graphSavePolicy === 'configAndData' || - ($scope.graphSavePolicy === 'configAndDataWithConsent' && userHasConfirmedSaveWorkspaceData); - - appStateToSavedWorkspace( - $scope.savedWorkspace, - { - workspace: $scope.workspace, - urlTemplates: $scope.urlTemplates, - advancedSettings: $scope.exploreControls, - selectedIndex: $scope.selectedIndex, - selectedFields: $scope.selectedFields - }, - canSaveData - ); - - return $scope.savedWorkspace.save(saveOptions).then(function (id) { - if (id) { - const title = i18n.translate('xpack.graph.saveWorkspace.successNotificationTitle', { - defaultMessage: 'Saved "{workspaceTitle}"', - values: { workspaceTitle: $scope.savedWorkspace.title }, - }); - let text; - if (!canSaveData && $scope.workspace.nodes.length > 0) { - text = i18n.translate('xpack.graph.saveWorkspace.successNotification.noDataSavedText', { - defaultMessage: 'The configuration was saved, but the data was not saved', - }); - } - - toastNotifications.addSuccess({ - title, - text, - 'data-test-subj': 'saveGraphSuccess', - }); - if ($scope.savedWorkspace.id !== $route.current.params.id) { - kbnUrl.change(getEditPath($scope.savedWorkspace)); - } - } - return { id }; - }, fatalError); - - }; - - + } + $scope.savedWorkspace = $route.current.locals.savedWorkspace; }); -//End controller + diff --git a/x-pack/legacy/plugins/graph/public/components/app.tsx b/x-pack/legacy/plugins/graph/public/components/app.tsx index 894c6b9ef45a..efc8c1bfcc00 100644 --- a/x-pack/legacy/plugins/graph/public/components/app.tsx +++ b/x-pack/legacy/plugins/graph/public/components/app.tsx @@ -5,29 +5,31 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { Provider } from 'react-redux'; import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { Storage } from 'ui/storage'; import { CoreStart } from 'kibana/public'; import { AutocompletePublicPluginStart } from 'src/plugins/data/public'; -import { FieldManagerProps, FieldManager } from './field_manager'; +import { FieldManager } from './field_manager'; import { SearchBarProps, SearchBar } from './search_bar'; +import { GraphStore } from '../state_management'; import { GuidancePanel } from './guidance_panel'; -import { selectedFieldsSelector } from '../state_management'; -import { openSourceModal } from '../services/source_modal'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -export interface GraphAppProps extends FieldManagerProps, SearchBarProps { +export interface GraphAppProps extends SearchBarProps { coreStart: CoreStart; autocompleteStart: AutocompletePublicPluginStart; store: Storage; - onFillWorkspace: () => void; + reduxStore: GraphStore; isInitialized: boolean; + onFillWorkspace: () => void; } export function GraphApp(props: GraphAppProps) { const [pickerOpen, setPickerOpen] = useState(false); + const { coreStart, autocompleteStart, store, reduxStore, ...searchBarProps } = props; return ( @@ -39,29 +41,28 @@ export function GraphApp(props: GraphAppProps) { ...props.coreStart, }} > -
- - - - - - - - -
- {!props.isInitialized && ( - 0} - onFillWorkspace={props.onFillWorkspace} - onOpenFieldPicker={() => { - setPickerOpen(true); - }} - onOpenDatasourcePicker={() => { - openSourceModal(props.coreStart, props.onIndexPatternSelected); - }} - /> - )} + + <> +
+ + + + + + + + +
+ {!props.isInitialized && ( + { + setPickerOpen(true); + }} + /> + )} + +
); diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx index b9de9b9c628a..519e41e84605 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx @@ -30,9 +30,10 @@ import { WorkspaceField } from '../../types'; import { iconChoices } from '../../helpers/style_choices'; import { LegacyIcon } from '../legacy_icon'; import { FieldIcon } from './field_icon'; +import { UpdateableFieldProperties } from './field_manager'; + import { isEqual } from '../helpers'; -type UpdateableFieldProperties = 'hopSize' | 'lastValidHopSize' | 'color' | 'icon'; export interface FieldPickerProps { field: WorkspaceField; allFields: WorkspaceField[]; diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx index fb715e759c62..e9d3f3738ea6 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx @@ -5,23 +5,25 @@ */ import React, { ReactElement } from 'react'; -import { EuiColorPicker, EuiSelectable, EuiContextMenu, EuiPopover, EuiButton } from '@elastic/eui'; +import { EuiColorPicker, EuiSelectable, EuiContextMenu, EuiButton } from '@elastic/eui'; import { FieldPicker } from './field_picker'; import { FieldEditor } from './field_editor'; -import { GraphStore, createGraphStore, loadFields } from '../../state_management'; +import { GraphStore, loadFields } from '../../state_management'; import { getSuitableIcon } from '../../helpers/style_choices'; import { shallow, ShallowWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { FieldManager } from './field_manager'; +import { Provider } from 'react-redux'; +import { createMockGraphStore } from '../../state_management/mocks'; describe('field_manager', () => { let store: GraphStore; let instance: ShallowWrapper; + let getInstance: () => ShallowWrapper; let dispatchSpy: jest.Mock; - let openSpy: jest.Mock; beforeEach(() => { - store = createGraphStore(); + store = createMockGraphStore({ includeSagas: false }).store; store.dispatch( loadFields([ { @@ -53,35 +55,31 @@ describe('field_manager', () => { ); dispatchSpy = jest.fn(store.dispatch); - openSpy = jest.fn(); + store.dispatch = dispatchSpy; instance = shallow( - + + {}} /> + ); - }); - function update() { - instance.setProps({ - state: store.getState(), - dispatch: dispatchSpy, - }); - } + getInstance = () => + instance + .find(FieldManager) + .dive() + .dive(); + }); it('should list editors for all selected fields', () => { - expect(instance.find(FieldEditor).length).toEqual(2); + expect(getInstance().find(FieldEditor).length).toEqual(2); expect( - instance + getInstance() .find(FieldEditor) .at(0) .prop('field').name ).toEqual('field1'); expect( - instance + getInstance() .find(FieldEditor) .at(1) .prop('field').name @@ -89,29 +87,21 @@ describe('field_manager', () => { }); it('should select fields from picker', () => { - act(() => { - (instance + expect( + getInstance() .find(FieldPicker) .dive() - .find(EuiPopover) - .prop('button')! as ReactElement).props.onClick(); - }); - - expect(openSpy).toHaveBeenCalled(); - - instance.setProps({ pickerOpen: true }); - - const fieldPicker = instance.find(FieldPicker).dive(); - - expect( - fieldPicker .find(EuiSelectable) .prop('options') .map((option: { label: string }) => option.label) ).toEqual(['field1', 'field2', 'field3']); act(() => { - fieldPicker.find(EuiSelectable).prop('onChange')([{ checked: 'on', label: 'field3' }]); + getInstance() + .find(FieldPicker) + .dive() + .find(EuiSelectable) + .prop('onChange')([{ checked: 'on', label: 'field3' }]); }); expect(dispatchSpy).toHaveBeenCalledWith({ @@ -119,13 +109,12 @@ describe('field_manager', () => { payload: 'field3', }); - update(); - expect(instance.find(FieldEditor).length).toEqual(3); + expect(getInstance().find(FieldEditor).length).toEqual(3); }); it('should deselect field', () => { act(() => { - instance + getInstance() .find(FieldEditor) .at(0) .dive() @@ -138,12 +127,11 @@ describe('field_manager', () => { payload: 'field1', }); - update(); - expect(instance.find(FieldEditor).length).toEqual(1); + expect(getInstance().find(FieldEditor).length).toEqual(1); }); it('should disable field', () => { - const toggleItem = instance + const toggleItem = getInstance() .find(FieldEditor) .at(0) .dive() @@ -165,10 +153,8 @@ describe('field_manager', () => { }, }); - update(); - expect( - instance + getInstance() .find(FieldEditor) .at(0) .dive() @@ -178,7 +164,7 @@ describe('field_manager', () => { }); it('should enable field', () => { - const toggleItem = instance + const toggleItem = getInstance() .find(FieldEditor) .at(1) .dive() @@ -200,10 +186,8 @@ describe('field_manager', () => { }, }); - update(); - expect( - instance + getInstance() .find(FieldEditor) .at(1) .dive() @@ -213,7 +197,7 @@ describe('field_manager', () => { }); it('should change color', () => { - const fieldEditor = instance + const fieldEditor = getInstance() .find(FieldEditor) .at(1) .dive(); diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.tsx index e44ad248e279..89b325d737bd 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.tsx @@ -7,55 +7,62 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; import { FieldPicker } from './field_picker'; import { FieldEditor } from './field_editor'; import { selectedFieldsSelector, fieldsSelector, + fieldMapSelector, updateFieldProperties, selectField, deselectField, - GraphDispatch, GraphState, - fieldMapSelector, } from '../../state_management'; +import { WorkspaceField } from '../../types'; + +export type UpdateableFieldProperties = 'hopSize' | 'lastValidHopSize' | 'color' | 'icon'; -export interface FieldManagerProps { - state: GraphState; - dispatch: GraphDispatch; +export function FieldManagerComponent(props: { + allFields: WorkspaceField[]; + fieldMap: Record; + selectedFields: WorkspaceField[]; + updateFieldProperties: (props: { + fieldName: string; + fieldProperties: Partial>; + }) => void; + selectField: (fieldName: string) => void; + deselectField: (fieldName: string) => void; pickerOpen: boolean; setPickerOpen: (open: boolean) => void; -} - -export function FieldManager({ state, dispatch, pickerOpen, setPickerOpen }: FieldManagerProps) { - const fieldMap = fieldMapSelector(state); - const allFields = fieldsSelector(state); - const selectedFields = selectedFieldsSelector(state); - - const actionCreators = bindActionCreators( - { - updateFieldProperties, - selectField, - deselectField, - }, - dispatch - ); - +}) { return ( - - {selectedFields.map(field => ( + + {props.selectedFields.map(field => ( - + ))} - + ); } + +export const FieldManager = connect( + (state: GraphState) => ({ + fieldMap: fieldMapSelector(state), + allFields: fieldsSelector(state), + selectedFields: selectedFieldsSelector(state), + }), + dispatch => + bindActionCreators( + { + updateFieldProperties, + selectField, + deselectField, + }, + dispatch + ) +)(FieldManagerComponent); diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 62d8bbb03bc3..840280a75415 100644 --- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -9,13 +9,25 @@ import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiLink } from ' import { i18n } from '@kbn/i18n'; import classNames from 'classnames'; import { FormattedMessage } from '@kbn/i18n/react'; +import { connect } from 'react-redux'; +import { IDataPluginServices } from 'src/legacy/core_plugins/data/public/types'; +import { + GraphState, + hasDatasourceSelector, + hasFieldsSelector, + requestDatasource, +} from '../../state_management'; +import { IndexPatternSavedObject } from '../../types'; +import { openSourceModal } from '../../services/source_modal'; + +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; export interface GuidancePanelProps { onFillWorkspace: () => void; onOpenFieldPicker: () => void; - onOpenDatasourcePicker: () => void; hasDatasource: boolean; hasFields: boolean; + onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => void; } function ListItem({ @@ -47,15 +59,23 @@ function ListItem({ ); } -export function GuidancePanel(props: GuidancePanelProps) { +function GuidancePanelComponent(props: GuidancePanelProps) { const { onFillWorkspace, onOpenFieldPicker, - onOpenDatasourcePicker, + onIndexPatternSelected, hasDatasource, hasFields, } = props; + const kibana = useKibana(); + const { overlays, savedObjects, uiSettings } = kibana.services; + if (!overlays) return null; + + const onOpenDatasourcePicker = () => { + openSourceModal({ overlays, savedObjects, uiSettings }, onIndexPatternSelected); + }; + return ( @@ -141,3 +161,23 @@ export function GuidancePanel(props: GuidancePanelProps) { ); } + +export const GuidancePanel = connect( + (state: GraphState) => { + return { + hasDatasource: hasDatasourceSelector(state), + hasFields: hasFieldsSelector(state), + }; + }, + dispatch => ({ + onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => { + dispatch( + requestDatasource({ + type: 'indexpattern', + id: indexPattern.id, + title: indexPattern.attributes.title, + }) + ); + }, + }) +)(GuidancePanelComponent); diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index a2a8e850c6e2..760b2aba5ba1 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchBar, SearchBarProps } from './search_bar'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { SearchBar, OuterSearchBarProps } from './search_bar'; import React, { ReactElement } from 'react'; import { CoreStart } from 'src/core/public'; import { act } from 'react-dom/test-utils'; -import { IndexPattern, QueryBarInput } from 'src/legacy/core_plugins/data/public'; +import { QueryBarInput, IndexPattern } from 'src/legacy/core_plugins/data/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; @@ -16,11 +17,19 @@ import { I18nProvider } from '@kbn/i18n/react'; jest.mock('ui/new_platform'); import { openSourceModal } from '../services/source_modal'; -import { mount } from 'enzyme'; +import { GraphStore, setDatasource } from '../state_management'; +import { ReactWrapper } from 'enzyme'; +import { createMockGraphStore } from '../state_management/mocks'; +import { Provider } from 'react-redux'; jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); +jest.mock('../../../../../../src/legacy/core_plugins/data/public', () => ({ + QueryBarInput: () => null, +})); -function wrapSearchBarInContext(testProps: SearchBarProps) { +const waitForIndexPatternFetch = () => new Promise(r => setTimeout(r)); + +function wrapSearchBarInContext(testProps: OuterSearchBarProps) { const services = { uiSettings: { get: (key: string) => { @@ -46,17 +55,47 @@ function wrapSearchBarInContext(testProps: SearchBarProps) { } describe('search_bar', () => { - it('should render search bar and submit queryies', () => { - const querySubmit = jest.fn(); - const instance = mount( - wrapSearchBarInContext({ - isLoading: false, - onIndexPatternSelected: () => {}, - onQuerySubmit: querySubmit, - currentIndexPattern: { title: 'Testpattern' } as IndexPattern, - coreStart: {} as CoreStart, + const defaultProps = { + isLoading: false, + onQuerySubmit: jest.fn(), + indexPatternProvider: { + get: jest.fn(() => Promise.resolve(({ fields: [] } as unknown) as IndexPattern)), + }, + confirmWipeWorkspace: (callback: () => void) => { + callback(); + }, + }; + let instance: ReactWrapper; + let store: GraphStore; + + beforeEach(() => { + store = createMockGraphStore({ includeSagas: false }).store; + store.dispatch( + setDatasource({ + type: 'indexpattern', + id: '123', + title: 'test-index', }) ); + }); + + function mountSearchBar() { + jest.clearAllMocks(); + const wrappedSearchBar = wrapSearchBarInContext({ ...defaultProps }); + instance = mountWithIntl({wrappedSearchBar}); + } + + it('should render search bar and fetch index pattern', () => { + mountSearchBar(); + + expect(defaultProps.indexPatternProvider.get).toHaveBeenCalledWith('123'); + }); + + it('should render search bar and submit queries', async () => { + mountSearchBar(); + + await waitForIndexPatternFetch(); + act(() => { instance.find(QueryBarInput).prop('onChange')!({ language: 'lucene', query: 'testQuery' }); }); @@ -65,20 +104,14 @@ describe('search_bar', () => { instance.find('form').simulate('submit', { preventDefault: () => {} }); }); - expect(querySubmit).toHaveBeenCalledWith('testQuery'); + expect(defaultProps.onQuerySubmit).toHaveBeenCalledWith('testQuery'); }); - it('should translate kql query into JSON dsl', () => { - const querySubmit = jest.fn(); - const instance = mount( - wrapSearchBarInContext({ - isLoading: false, - onIndexPatternSelected: () => {}, - onQuerySubmit: querySubmit, - currentIndexPattern: { title: 'Testpattern', fields: [{ name: 'test' }] } as IndexPattern, - coreStart: {} as CoreStart, - }) - ); + it('should translate kql query into JSON dsl', async () => { + mountSearchBar(); + + await waitForIndexPatternFetch(); + act(() => { instance.find(QueryBarInput).prop('onChange')!({ language: 'kuery', query: 'test: abc' }); }); @@ -87,24 +120,14 @@ describe('search_bar', () => { instance.find('form').simulate('submit', { preventDefault: () => {} }); }); - const parsedQuery = JSON.parse(querySubmit.mock.calls[0][0]); + const parsedQuery = JSON.parse(defaultProps.onQuerySubmit.mock.calls[0][0]); expect(parsedQuery).toEqual({ bool: { should: [{ match: { test: 'abc' } }], minimum_should_match: 1 }, }); }); it('should open index pattern picker', () => { - const indexPatternSelected = jest.fn(); - - const instance = mount( - wrapSearchBarInContext({ - isLoading: false, - onIndexPatternSelected: indexPatternSelected, - onQuerySubmit: () => {}, - currentIndexPattern: { title: 'Testpattern' } as IndexPattern, - coreStart: {} as CoreStart, - }) - ); + mountSearchBar(); // pick the button component out of the tree because // it's part of a popover and thus not covered by enzyme diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx index 18eca326776f..ae0d32cd5f68 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx @@ -5,28 +5,39 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; +import { connect } from 'react-redux'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { IDataPluginServices } from 'src/legacy/core_plugins/data/public/types'; -import { CoreStart } from 'kibana/public'; +import { IndexPatternSavedObject, IndexPatternProvider } from '../types'; import { QueryBarInput, Query, IndexPattern, } from '../../../../../../src/legacy/core_plugins/data/public'; -import { IndexPatternSavedObject } from '../types/app_state'; import { openSourceModal } from '../services/source_modal'; +import { + GraphState, + datasourceSelector, + requestDatasource, + IndexpatternDatasource, +} from '../state_management'; + import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -export interface SearchBarProps { - coreStart: CoreStart; +export interface OuterSearchBarProps { isLoading: boolean; - currentIndexPattern?: IndexPattern; initialQuery?: string; - onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => void; onQuerySubmit: (query: string) => void; + confirmWipeWorkspace: (onConfirm: () => void) => void; + indexPatternProvider: IndexPatternProvider; +} + +export interface SearchBarProps extends OuterSearchBarProps { + currentDatasource?: IndexpatternDatasource; + onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => void; } function queryToString(query: Query, indexPattern: IndexPattern) { @@ -45,17 +56,34 @@ function queryToString(query: Query, indexPattern: IndexPattern) { return JSON.stringify(query.query); } -export function SearchBar(props: SearchBarProps) { +export function SearchBarComponent(props: SearchBarProps) { const { - currentIndexPattern, + currentDatasource, onQuerySubmit, isLoading, onIndexPatternSelected, initialQuery, + indexPatternProvider, + confirmWipeWorkspace, } = props; const [query, setQuery] = useState({ language: 'kuery', query: initialQuery || '' }); + const [currentIndexPattern, setCurrentIndexPattern] = useState( + undefined + ); + + useEffect(() => { + async function fetchPattern() { + if (currentDatasource) { + setCurrentIndexPattern(await indexPatternProvider.get(currentDatasource.id)); + } else { + setCurrentIndexPattern(undefined); + } + } + fetchPattern(); + }, [currentDatasource]); + const kibana = useKibana(); - const { overlays } = kibana.services; + const { overlays, savedObjects, uiSettings } = kibana.services; if (!overlays) return null; return ( @@ -88,7 +116,12 @@ export function SearchBar(props: SearchBarProps) { className="gphSearchBar__datasourceButton" data-test-subj="graphDatasourceButton" onClick={() => { - openSourceModal(props.coreStart, onIndexPatternSelected); + confirmWipeWorkspace(() => + openSourceModal( + { overlays, savedObjects, uiSettings }, + onIndexPatternSelected + ) + ); }} > {currentIndexPattern @@ -113,3 +146,24 @@ export function SearchBar(props: SearchBarProps) { ); } + +export const SearchBar = connect( + (state: GraphState) => { + const datasource = datasourceSelector(state); + return { + currentDatasource: + datasource.current.type === 'indexpattern' ? datasource.current : undefined, + }; + }, + dispatch => ({ + onIndexPatternSelected: (indexPattern: IndexPatternSavedObject) => { + dispatch( + requestDatasource({ + type: 'indexpattern', + id: indexPattern.id, + title: indexPattern.attributes.title, + }) + ); + }, + }) +)(SearchBarComponent); diff --git a/x-pack/legacy/plugins/graph/public/components/settings/advanced_settings_form.tsx b/x-pack/legacy/plugins/graph/public/components/settings/advanced_settings_form.tsx index f963d53c639c..e6a99e909c69 100644 --- a/x-pack/legacy/plugins/graph/public/components/settings/advanced_settings_form.tsx +++ b/x-pack/legacy/plugins/graph/public/components/settings/advanced_settings_form.tsx @@ -23,11 +23,11 @@ type NumberKeys = Exclude< export function AdvancedSettingsForm({ advancedSettings, - updateAdvancedSettings, + updateSettings, allFields, -}: Pick) { +}: Pick) { function updateSetting(key: K, value: AdvancedSettings[K]) { - updateAdvancedSettings({ ...advancedSettings, [key]: value }); + updateSettings({ ...advancedSettings, [key]: value }); } function getNumberUpdater>(key: K) { diff --git a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx index a0c95a894ee8..782b26a55824 100644 --- a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx @@ -8,21 +8,44 @@ import React from 'react'; import { EuiTab, EuiListGroupItem, EuiButton, EuiAccordion, EuiFieldText } from '@elastic/eui'; import * as Rx from 'rxjs'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { Settings, SettingsProps } from './settings'; +import { Settings, AngularProps } from './settings'; import { act } from 'react-testing-library'; import { ReactWrapper } from 'enzyme'; import { UrlTemplateForm } from './url_template_form'; +import { + GraphStore, + updateSettings, + loadFields, + saveTemplate, + removeTemplate, +} from '../../state_management'; +import { createMockGraphStore } from '../../state_management/mocks'; +import { Provider } from 'react-redux'; +import { UrlTemplate } from '../../types'; describe('settings', () => { - const props: jest.Mocked = { - advancedSettings: { - maxValuesPerDoc: 5, - minDocCount: 10, - sampleSize: 12, - useSignificance: true, - timeoutMillis: 10000, + let store: GraphStore; + let dispatchSpy: jest.Mock; + + const initialTemplate: UrlTemplate = { + description: 'template', + encoder: { + description: 'test encoder description', + encode: jest.fn(), + id: 'test', + title: 'test encoder', + type: 'esq', + }, + url: 'http://example.org', + icon: { + class: 'test', + code: '1', + label: 'test', }, - updateAdvancedSettings: jest.fn(), + isDefault: false, + }; + + const angularProps: jest.Mocked = { blacklistedNodes: [ { x: 0, @@ -60,59 +83,63 @@ describe('settings', () => { }, ], unblacklistNode: jest.fn(), - urlTemplates: [ - { - description: 'template', - encoder: { - description: 'test encoder description', - encode: jest.fn(), - id: 'test', - title: 'test encoder', - type: 'esq', - }, - url: 'http://example.org', - icon: { - class: 'test', - code: '1', - label: 'test', - }, - }, - ], - removeUrlTemplate: jest.fn(), - saveUrlTemplate: jest.fn(), - allFields: [ - { - selected: false, - color: 'black', - name: 'B', - type: 'string', - icon: { - class: 'test', - code: '1', - label: 'test', - }, - }, - { - selected: false, - color: 'red', - name: 'C', - type: 'string', - icon: { - class: 'test', - code: '1', - label: 'test', - }, - }, - ], canEditDrillDownUrls: true, }; - let subject: Rx.BehaviorSubject>; + let subject: Rx.BehaviorSubject>; let instance: ReactWrapper; beforeEach(() => { - subject = new Rx.BehaviorSubject(props); - instance = mountWithIntl(); + store = createMockGraphStore({ includeSagas: false }).store; + store.dispatch( + updateSettings({ + maxValuesPerDoc: 5, + minDocCount: 10, + sampleSize: 12, + useSignificance: true, + timeoutMillis: 10000, + }) + ); + store.dispatch( + loadFields([ + { + selected: false, + color: 'black', + name: 'B', + type: 'string', + icon: { + class: 'test', + code: '1', + label: 'test', + }, + }, + { + selected: false, + color: 'red', + name: 'C', + type: 'string', + icon: { + class: 'test', + code: '1', + label: 'test', + }, + }, + ]) + ); + store.dispatch( + saveTemplate({ + index: -1, + template: initialTemplate, + }) + ); + dispatchSpy = jest.fn(store.dispatch); + store.dispatch = dispatchSpy; + subject = new Rx.BehaviorSubject(angularProps); + instance = mountWithIntl( + + + + ); }); function toTab(tab: string) { @@ -139,25 +166,14 @@ describe('settings', () => { HTMLInputElement >); - expect(props.updateAdvancedSettings).toHaveBeenCalledWith( - expect.objectContaining({ sampleSize: 13 }) - ); - }); - - it('should update on new data', () => { - act(() => { - subject.next({ - ...props, - advancedSettings: { - ...props.advancedSettings, + expect(dispatchSpy).toHaveBeenCalledWith( + updateSettings( + expect.objectContaining({ + timeoutMillis: 10000, sampleSize: 13, - }, - }); - }); - - instance.update(); - - expect(input('Sample size').prop('value')).toEqual(13); + }) + ) + ); }); }); @@ -173,13 +189,46 @@ describe('settings', () => { ]); }); + it('should update on new data', () => { + act(() => { + subject.next({ + ...angularProps, + blacklistedNodes: [ + { + x: 0, + y: 0, + scaledSize: 10, + parent: null, + color: 'black', + data: { + field: 'A', + term: '1', + }, + label: 'blacklisted node 3', + icon: { + class: 'test', + code: '1', + label: 'test', + }, + }, + ], + }); + }); + + instance.update(); + + expect(instance.find(EuiListGroupItem).map(item => item.prop('label'))).toEqual([ + 'blacklisted node 3', + ]); + }); + it('should delete node', () => { instance .find(EuiListGroupItem) .at(0) .prop('extraAction')!.onClick!({} as any); - expect(props.unblacklistNode).toHaveBeenCalledWith(props.blacklistedNodes![0]); + expect(angularProps.unblacklistNode).toHaveBeenCalledWith(angularProps.blacklistedNodes![0]); }); it('should delete all nodes', () => { @@ -188,8 +237,8 @@ describe('settings', () => { .find(EuiButton) .simulate('click'); - expect(props.unblacklistNode).toHaveBeenCalledWith(props.blacklistedNodes![0]); - expect(props.unblacklistNode).toHaveBeenCalledWith(props.blacklistedNodes![1]); + expect(angularProps.unblacklistNode).toHaveBeenCalledWith(angularProps.blacklistedNodes![0]); + expect(angularProps.unblacklistNode).toHaveBeenCalledWith(angularProps.blacklistedNodes![1]); }); }); @@ -226,7 +275,7 @@ describe('settings', () => { templateForm(0) .find('EuiButtonEmpty[data-test-subj="graphRemoveUrlTemplate"]') .simulate('click'); - expect(props.removeUrlTemplate).toHaveBeenCalledWith(props.urlTemplates[0]); + expect(dispatchSpy).toHaveBeenCalledWith(removeTemplate(initialTemplate)); }); it('should update url template', () => { @@ -236,10 +285,9 @@ describe('settings', () => { .find('form') .simulate('submit'); }); - expect(props.saveUrlTemplate).toHaveBeenCalledWith(0, { - ...props.urlTemplates[0], - description: 'Updated title', - }); + expect(dispatchSpy).toHaveBeenCalledWith( + saveTemplate({ index: 0, template: { ...initialTemplate, description: 'Updated title' } }) + ); }); it('should add url template', async () => { @@ -256,9 +304,11 @@ describe('settings', () => { .find('form') .simulate('submit'); }); - expect(props.saveUrlTemplate).toHaveBeenCalledWith( - -1, - expect.objectContaining({ description: 'Title', url: 'test-url' }) + expect(dispatchSpy).toHaveBeenCalledWith( + saveTemplate({ + index: -1, + template: expect.objectContaining({ description: 'Title', url: 'test-url' }), + }) ); }); }); diff --git a/x-pack/legacy/plugins/graph/public/components/settings/settings.tsx b/x-pack/legacy/plugins/graph/public/components/settings/settings.tsx index 4cb4b473f737..4dab0ece5b52 100644 --- a/x-pack/legacy/plugins/graph/public/components/settings/settings.tsx +++ b/x-pack/legacy/plugins/graph/public/components/settings/settings.tsx @@ -8,10 +8,21 @@ import { i18n } from '@kbn/i18n'; import React, { useState, useEffect } from 'react'; import { EuiFlyoutHeader, EuiTitle, EuiTabs, EuiFlyoutBody, EuiTab } from '@elastic/eui'; import * as Rx from 'rxjs'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { AdvancedSettingsForm } from './advanced_settings_form'; import { BlacklistForm } from './blacklist_form'; import { UrlTemplateList } from './url_template_list'; import { WorkspaceNode, AdvancedSettings, UrlTemplate, WorkspaceField } from '../../types'; +import { + GraphState, + settingsSelector, + templatesSelector, + fieldsSelector, + updateSettings, + saveTemplate, + removeTemplate, +} from '../../state_management'; const tabs = [ { @@ -35,31 +46,46 @@ const tabs = [ }, ]; -export interface SettingsProps { +/** + * These props are wired in the angular scope and are passed in via observable + * to catch update outside updates + */ +export interface AngularProps { + blacklistedNodes: WorkspaceNode[]; + unblacklistNode: (node: WorkspaceNode) => void; + canEditDrillDownUrls: boolean; +} + +export interface StateProps { advancedSettings: AdvancedSettings; - updateAdvancedSettings: (advancedSettings: AdvancedSettings) => void; - blacklistedNodes?: WorkspaceNode[]; - unblacklistNode?: (node: WorkspaceNode) => void; urlTemplates: UrlTemplate[]; - removeUrlTemplate: (urlTemplate: UrlTemplate) => void; - saveUrlTemplate: (index: number, urlTemplate: UrlTemplate) => void; allFields: WorkspaceField[]; - canEditDrillDownUrls: boolean; +} + +export interface DispatchProps { + updateSettings: (advancedSettings: AdvancedSettings) => void; + removeTemplate: (urlTemplate: UrlTemplate) => void; + saveTemplate: (props: { index: number; template: UrlTemplate }) => void; } interface AsObservable

{ observable: Readonly>; } -export function Settings({ observable }: AsObservable) { - const [currentProps, setCurrentProps] = useState(undefined); +export interface SettingsProps extends AngularProps, StateProps, DispatchProps {} + +export function SettingsComponent({ + observable, + ...props +}: AsObservable & StateProps & DispatchProps) { + const [angularProps, setAngularProps] = useState(undefined); const [activeTab, setActiveTab] = useState(0); useEffect(() => { - observable.subscribe(setCurrentProps); + observable.subscribe(setAngularProps); }, [observable]); - if (!currentProps) return null; + if (!angularProps) return null; const ActiveTabContent = tabs[activeTab].component; return ( @@ -70,7 +96,7 @@ export function Settings({ observable }: AsObservable) { {tabs - .filter(({ id }) => id !== 'drillDowns' || currentProps.canEditDrillDownUrls) + .filter(({ id }) => id !== 'drillDowns' || angularProps.canEditDrillDownUrls) .map(({ title }, index) => ( ) { - + ); } + +export const Settings = connect, GraphState>( + (state: GraphState) => ({ + advancedSettings: settingsSelector(state), + urlTemplates: templatesSelector(state), + allFields: fieldsSelector(state), + }), + dispatch => + bindActionCreators( + { + updateSettings, + saveTemplate, + removeTemplate, + }, + dispatch + ) +)(SettingsComponent); diff --git a/x-pack/legacy/plugins/graph/public/components/settings/url_template_list.tsx b/x-pack/legacy/plugins/graph/public/components/settings/url_template_list.tsx index 2e41f78bb940..1946c18b4142 100644 --- a/x-pack/legacy/plugins/graph/public/components/settings/url_template_list.tsx +++ b/x-pack/legacy/plugins/graph/public/components/settings/url_template_list.tsx @@ -14,10 +14,10 @@ import { useListKeys } from './use_list_keys'; const generateId = htmlIdGenerator(); export function UrlTemplateList({ - removeUrlTemplate, - saveUrlTemplate, + removeTemplate, + saveTemplate, urlTemplates, -}: Pick) { +}: Pick) { const [uncommittedForms, setUncommittedForms] = useState([]); const getListKey = useListKeys(urlTemplates); @@ -40,10 +40,10 @@ export function UrlTemplateList({ id={getListKey(template)} initialTemplate={template} onSubmit={newTemplate => { - saveUrlTemplate(index, newTemplate); + saveTemplate({ index, template: newTemplate }); }} onRemove={() => { - removeUrlTemplate(template); + removeTemplate(template); }} /> ))} @@ -53,7 +53,7 @@ export function UrlTemplateList({ id={`accordion-new-${id}`} key={id} onSubmit={newTemplate => { - saveUrlTemplate(-1, newTemplate); + saveTemplate({ index: -1, template: newTemplate }); removeUncommittedForm(id); }} onRemove={removeUncommittedForm.bind(undefined, id)} diff --git a/x-pack/legacy/plugins/graph/public/helpers/url_template.ts b/x-pack/legacy/plugins/graph/public/helpers/url_template.ts index bb85d2e8c583..f982d0443c39 100644 --- a/x-pack/legacy/plugins/graph/public/helpers/url_template.ts +++ b/x-pack/legacy/plugins/graph/public/helpers/url_template.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export const urlTemplatePlaceholder = '{{gquery}}'; export const urlTemplateRegex = /\{\{gquery\}\}/g; const defaultKibanaQuery = /,query:\(language:kuery,query:'.*?'\)/g; @@ -34,5 +35,8 @@ export function isKibanaUrl(url: string) { * @param url The url to turn into an url template */ export function replaceKibanaUrlParam(url: string) { - return url.replace(defaultKibanaQuery, ',query:(language:kuery,query:{{gquery}})'); + return url.replace( + defaultKibanaQuery, + `,query:(language:kuery,query:{{${urlTemplatePlaceholder}}})` + ); } diff --git a/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts b/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts new file mode 100644 index 000000000000..e4e02f860db1 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { IndexPatternProvider } from '../types'; + +export function createCachedIndexPatternProvider( + indexPatternGetter: (id: string) => Promise +): IndexPatternProvider { + const cache = new Map(); + + return { + get: async (id: string) => { + if (!cache.has(id)) { + cache.set(id, await indexPatternGetter(id)); + } + return Promise.resolve(cache.get(id)!); + }, + }; +} diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts index 4575b596d65d..6aa9fd671ffc 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GraphWorkspaceSavedObject } from '../../types'; +import { GraphWorkspaceSavedObject, Workspace } from '../../types'; import { savedWorkspaceToAppState } from './deserialize'; import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { createWorkspace } from '../../angular/graph_client_workspace'; @@ -12,6 +12,7 @@ import { outlinkEncoders } from '../../helpers/outlink_encoders'; describe('deserialize', () => { let savedWorkspace: GraphWorkspaceSavedObject; + let workspace: Workspace; beforeEach(() => { savedWorkspace = { @@ -110,6 +111,7 @@ describe('deserialize', () => { }, }), } as GraphWorkspaceSavedObject; + workspace = createWorkspace({}); }); function callSavedWorkspaceToAppState() { @@ -122,7 +124,7 @@ describe('deserialize', () => { { name: 'field3', type: 'string' }, ], } as IndexPattern, - createWorkspace({}) + workspace ); } @@ -133,7 +135,7 @@ describe('deserialize', () => { }); it('should deserialize fields', () => { - const { allFields, selectedFields } = callSavedWorkspaceToAppState(); + const { allFields } = callSavedWorkspaceToAppState(); expect(allFields).toMatchInlineSnapshot(` Array [ @@ -175,9 +177,6 @@ describe('deserialize', () => { }, ] `); - - expect(selectedFields.length).toEqual(2); - selectedFields.forEach(field => expect(allFields.includes(field)).toEqual(true)); }); it('should deserialize url templates', () => { @@ -188,7 +187,7 @@ describe('deserialize', () => { }); it('should deserialize nodes and edges', () => { - const { workspace } = callSavedWorkspaceToAppState(); + callSavedWorkspaceToAppState(); expect(workspace.blacklistedNodes.length).toEqual(1); expect(workspace.nodes.length).toEqual(5); diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts index 39c7ef841dcb..b1879ec92c13 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts @@ -6,7 +6,6 @@ import { IndexPattern } from 'src/legacy/core_plugins/data/public/index_patterns/index_patterns'; import { - AppState, SerializedNode, UrlTemplate, SerializedUrlTemplate, @@ -188,10 +187,11 @@ export function savedWorkspaceToAppState( savedWorkspace: GraphWorkspaceSavedObject, indexPattern: IndexPattern, workspaceInstance: Workspace -): Pick< - AppState, - 'urlTemplates' | 'advancedSettings' | 'workspace' | 'allFields' | 'selectedFields' -> { +): { + urlTemplates: UrlTemplate[]; + advancedSettings: AdvancedSettings; + allFields: WorkspaceField[]; +} { const persistedWorkspaceState: SerializedWorkspaceState = JSON.parse(savedWorkspace.wsState); // ================== url templates ============================= @@ -205,9 +205,11 @@ export function savedWorkspaceToAppState( persistedWorkspaceState.selectedFields ); const selectedFields = allFields.filter(field => field.selected); + workspaceInstance.options.vertex_fields = selectedFields; // ================== advanced settings ============================= const advancedSettings = Object.assign( + {}, defaultAdvancedSettings, persistedWorkspaceState.exploreControls ); @@ -220,6 +222,8 @@ export function savedWorkspaceToAppState( ); } + workspaceInstance.options.exploreControls = advancedSettings; + // ================== nodes and edges ============================= const graph = getNodesAndEdges(persistedWorkspaceState, allFields); workspaceInstance.mergeGraph(graph); @@ -232,8 +236,6 @@ export function savedWorkspaceToAppState( return { urlTemplates, advancedSettings, - workspace: workspaceInstance, allFields, - selectedFields, }; } diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts b/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts index b3450cce05c0..95f55bcc87eb 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts @@ -5,12 +5,25 @@ */ import { appStateToSavedWorkspace } from './serialize'; -import { GraphWorkspaceSavedObject, Workspace, WorkspaceEdge, AppState } from '../../types'; +import { + GraphWorkspaceSavedObject, + Workspace, + WorkspaceEdge, + UrlTemplate, + AdvancedSettings, + WorkspaceField, +} from '../../types'; import { outlinkEncoders } from '../../helpers/outlink_encoders'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { IndexpatternDatasource } from '../../state_management'; describe('serialize', () => { - let appState: AppState; + let appState: { + workspace: Workspace; + urlTemplates: UrlTemplate[]; + advancedSettings: AdvancedSettings; + selectedIndex: IndexpatternDatasource; + selectedFields: WorkspaceField[]; + }; beforeEach(() => { appState = { @@ -21,29 +34,6 @@ describe('serialize', () => { maxValuesPerDoc: 1, minDocCount: 3, }, - allFields: [ - { - color: 'black', - icon: { class: 'a', code: '', label: '' }, - name: 'field1', - selected: true, - type: 'string', - }, - { - color: 'black', - icon: { class: 'b', code: '', label: '' }, - name: 'field2', - selected: true, - type: 'string', - }, - { - color: 'black', - icon: { class: 'c', code: '', label: '' }, - name: 'field3', - selected: false, - type: 'string', - }, - ], selectedFields: [ { color: 'black', @@ -61,8 +51,10 @@ describe('serialize', () => { }, ], selectedIndex: { + type: 'indexpattern', + id: '123', title: 'Testindexpattern', - } as IndexPattern, + }, urlTemplates: [ { description: 'Template', diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.ts b/x-pack/legacy/plugins/graph/public/services/persistence/serialize.ts index 21583781cb14..3a94136d26e7 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/serialize.ts @@ -5,7 +5,6 @@ */ import { - AppState, SerializedNode, WorkspaceNode, WorkspaceEdge, @@ -15,7 +14,10 @@ import { WorkspaceField, GraphWorkspaceSavedObject, SerializedWorkspaceState, + Workspace, + AdvancedSettings, } from '../../types'; +import { IndexpatternDatasource } from '../../state_management'; function serializeNode( { data, scaledSize, parent, x, y, label, color }: WorkspaceNode, @@ -80,7 +82,19 @@ function serializeField({ export function appStateToSavedWorkspace( currentSavedWorkspace: GraphWorkspaceSavedObject, - { workspace, urlTemplates, advancedSettings, selectedIndex, selectedFields }: AppState, + { + workspace, + urlTemplates, + advancedSettings, + selectedIndex, + selectedFields, + }: { + workspace: Workspace; + urlTemplates: UrlTemplate[]; + advancedSettings: AdvancedSettings; + selectedIndex: IndexpatternDatasource; + selectedFields: WorkspaceField[]; + }, canSaveData: boolean ) { const blacklist: SerializedNode[] = canSaveData diff --git a/x-pack/legacy/plugins/graph/public/services/save_modal.tsx b/x-pack/legacy/plugins/graph/public/services/save_modal.tsx index bb453bd95df5..5930d2283b7c 100644 --- a/x-pack/legacy/plugins/graph/public/services/save_modal.tsx +++ b/x-pack/legacy/plugins/graph/public/services/save_modal.tsx @@ -9,6 +9,15 @@ import { SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; import { GraphWorkspaceSavedObject, GraphSavePolicy } from '../types'; import { SaveModal, OnSaveGraphProps } from '../components/save_modal'; +export type SaveWorkspaceHandler = ( + saveOptions: { + confirmOverwrite: boolean; + isTitleDuplicateConfirmed: boolean; + onTitleDuplicate: () => void; + }, + dataConsent: boolean +) => Promise; + export function openSaveModal({ savePolicy, hasData, @@ -19,14 +28,7 @@ export function openSaveModal({ savePolicy: GraphSavePolicy; hasData: boolean; workspace: GraphWorkspaceSavedObject; - saveWorkspace: ( - saveOptions: { - confirmOverwrite: boolean; - isTitleDuplicateConfirmed: boolean; - onTitleDuplicate: () => void; - }, - dataConsent: boolean - ) => Promise; + saveWorkspace: SaveWorkspaceHandler; showSaveModal: (el: React.ReactNode) => void; }) { const currentTitle = workspace.title; diff --git a/x-pack/legacy/plugins/graph/public/services/url.ts b/x-pack/legacy/plugins/graph/public/services/url.ts index 97a30e26c25f..ff14faf3e350 100644 --- a/x-pack/legacy/plugins/graph/public/services/url.ts +++ b/x-pack/legacy/plugins/graph/public/services/url.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { Chrome } from 'ui/chrome'; import { GraphWorkspaceSavedObject } from '../types'; +import { MetaDataState } from '../state_management'; export function getHomePath() { return '/home'; @@ -30,12 +31,12 @@ export type SetBreadcrumbOptions = } | { chrome: Chrome; - savedWorkspace?: GraphWorkspaceSavedObject; + metaData: MetaDataState; navigateTo: (path: string) => void; }; export function setBreadcrumbs(options: SetBreadcrumbOptions) { - if ('savedWorkspace' in options) { + if ('metaData' in options) { options.chrome.breadcrumbs.set([ { text: i18n.translate('xpack.graph.home.breadcrumb', { @@ -47,11 +48,7 @@ export function setBreadcrumbs(options: SetBreadcrumbOptions) { 'data-test-subj': 'graphHomeBreadcrumb', }, { - text: options.savedWorkspace - ? options.savedWorkspace.title - : i18n.translate('xpack.graph.newWorkspaceTitle', { - defaultMessage: 'Unsaved workspace', - }), + text: options.metaData.title, 'data-test-subj': 'graphCurrentWorkspaceBreadcrumb', }, ]); diff --git a/x-pack/legacy/plugins/graph/public/state_management/advanced_settings.ts b/x-pack/legacy/plugins/graph/public/state_management/advanced_settings.ts new file mode 100644 index 000000000000..44950f8a45f8 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/state_management/advanced_settings.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import actionCreatorFactory, { Action } from 'typescript-fsa'; +import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; +import { takeLatest } from 'redux-saga/effects'; +import { GraphState, GraphStoreDependencies } from './store'; +import { AdvancedSettings } from '../types'; +import { reset } from './global'; + +const actionCreator = actionCreatorFactory('x-pack/graph/advancedSettings'); + +export type AdvancedSettingsState = AdvancedSettings; + +export const updateSettings = actionCreator('UPDATE_SETTINGS'); + +const initialSettings: AdvancedSettingsState = { + useSignificance: true, + sampleSize: 2000, + timeoutMillis: 5000, + sampleDiversityField: undefined, + maxValuesPerDoc: 1, + minDocCount: 3, +}; + +export const advancedSettingsReducer = reducerWithInitialState(initialSettings) + .case(reset, () => initialSettings) + .case(updateSettings, (_oldSettings, newSettings) => newSettings) + .build(); + +export const settingsSelector = (state: GraphState) => state.advancedSettings; + +/** + * Saga making sure the advanced settings are always synced up to the workspace instance. + * + * Won't be necessary once the workspace is moved to redux + */ +export const syncSettingsSaga = ({ getWorkspace, notifyAngular }: GraphStoreDependencies) => { + function* syncSettings(action: Action): IterableIterator { + const workspace = getWorkspace(); + if (!workspace) { + return; + } + workspace.options.exploreControls = action.payload; + notifyAngular(); + } + + return function*() { + yield takeLatest(updateSettings.match, syncSettings); + }; +}; diff --git a/x-pack/legacy/plugins/graph/public/state_management/datasource.ts b/x-pack/legacy/plugins/graph/public/state_management/datasource.ts new file mode 100644 index 000000000000..801dd315df30 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/state_management/datasource.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import actionCreatorFactory, { Action } from 'typescript-fsa'; +import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; +import { takeLatest, put, call, select } from 'redux-saga/effects'; +import { i18n } from '@kbn/i18n'; +import { IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { createSelector } from 'reselect'; +import { GraphState, GraphStoreDependencies } from './store'; +import { reset } from './global'; +import { loadFields } from './fields'; +import { mapFields } from '../services/persistence'; +import { settingsSelector } from './advanced_settings'; + +const actionCreator = actionCreatorFactory('x-pack/graph/datasource'); + +export interface NoDatasource { + type: 'none'; +} +export interface IndexpatternDatasource { + type: 'indexpattern'; + id: string; + title: string; +} + +export interface DatasourceState { + current: NoDatasource | IndexpatternDatasource; + loading: boolean; +} + +/** + * Sets the current datasource. This will not trigger a load of fields + */ +export const setDatasource = actionCreator('SET_DATASOURCE'); + +/** + * Sets the current datasource. This will trigger a load of fields and overwrite the current + * fields configuration + */ +export const requestDatasource = actionCreator('SET_DATASOURCE_REQUEST'); + +/** + * Datasource loading finished successfully. + */ +export const datasourceLoaded = actionCreator('SET_DATASOURCE_SUCCESS'); + +const initialDatasource: DatasourceState = { + current: { type: 'none' }, + loading: false, +}; + +export const datasourceReducer = reducerWithInitialState(initialDatasource) + .case(reset, () => initialDatasource) + .case(setDatasource, (_oldDatasource, newDatasource) => ({ + current: newDatasource, + loading: false, + })) + .case(requestDatasource, (_oldDatasource, newDatasource) => ({ + current: newDatasource, + loading: true, + })) + .case(datasourceLoaded, datasource => ({ + ...datasource, + loading: false, + })) + .build(); + +export const datasourceSelector = (state: GraphState) => state.datasource; +export const hasDatasourceSelector = createSelector( + datasourceSelector, + datasource => datasource.current.type !== 'none' +); + +/** + * Saga loading field information when the datasource is switched. This will overwrite current settings + * in fields. + * + * TODO: Carry over fields than can be carried over because they also exist in the target index pattern + */ +export const datasourceSaga = ({ + indexPatternProvider, + notifications, + createWorkspace, +}: GraphStoreDependencies) => { + function* fetchFields(action: Action) { + try { + const indexPattern: IndexPattern = yield call(indexPatternProvider.get, action.payload.id); + yield put(loadFields(mapFields(indexPattern))); + yield put(datasourceLoaded()); + const advancedSettings = settingsSelector(yield select()); + createWorkspace(indexPattern.title, advancedSettings); + } catch (e) { + // in case of errors, reset the datasource and show notification + yield put(setDatasource({ type: 'none' })); + notifications.toasts.addDanger( + i18n.translate('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', { + defaultMessage: 'Index pattern not found', + }) + ); + } + } + + return function*() { + yield takeLatest(requestDatasource.match, fetchFields); + }; +}; diff --git a/x-pack/legacy/plugins/graph/public/state_management/fields.ts b/x-pack/legacy/plugins/graph/public/state_management/fields.ts index 4708d17734ad..62bb62c1f612 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/fields.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/fields.ts @@ -4,11 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import actionCreatorFactory from 'typescript-fsa'; +import actionCreatorFactory, { Action } from 'typescript-fsa'; import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; import { createSelector } from 'reselect'; +import { select, takeLatest, takeEvery } from 'redux-saga/effects'; import { WorkspaceField } from '../types'; -import { GraphState } from './store'; +import { GraphState, GraphStoreDependencies } from './store'; +import { reset } from './global'; +import { setDatasource } from './datasource'; +import { matchesOne, InferActionType } from './helpers'; const actionCreator = actionCreatorFactory('x-pack/graph/fields'); @@ -25,6 +29,8 @@ export type FieldsState = Record; const initialFields: FieldsState = {}; export const fieldsReducer = reducerWithInitialState(initialFields) + .case(reset, () => initialFields) + .case(setDatasource, () => initialFields) .case(loadFields, (_currentFields, newFields) => { const newFieldMap: Record = {}; newFields.forEach(field => { @@ -57,3 +63,86 @@ export const liveResponseFieldsSelector = createSelector( selectedFieldsSelector, fields => fields.filter(field => field.hopSize && field.hopSize > 0) ); +export const hasFieldsSelector = createSelector( + selectedFieldsSelector, + fields => fields.length > 0 +); + +/** + * Saga making notifying angular when fields are selected to re-calculate the state of the save button. + * + * Won't be necessary once the workspace is moved to redux + */ +export const updateSaveButtonSaga = ({ notifyAngular }: GraphStoreDependencies) => { + function* notify(): IterableIterator { + notifyAngular(); + } + return function*() { + yield takeLatest(matchesOne(selectField, deselectField), notify); + }; +}; + +/** + * Saga making sure the fields in the store are always synced with the fields + * known to the workspace. + * + * Won't be necessary once the workspace is moved to redux + */ +export const syncFieldsSaga = ({ getWorkspace, setLiveResponseFields }: GraphStoreDependencies) => { + function* syncFields() { + const workspace = getWorkspace(); + if (!workspace) { + return; + } + + const currentState = yield select(); + workspace.options.vertex_fields = selectedFieldsSelector(currentState); + setLiveResponseFields(liveResponseFieldsSelector(currentState)); + } + return function*() { + yield takeEvery( + matchesOne(loadFields, selectField, deselectField, updateFieldProperties), + syncFields + ); + }; +}; + +/** + * Saga making sure the field styles (icons and colors) are applied to nodes currently active + * in the workspace. + * + * Won't be necessary once the workspace is moved to redux + */ +export const syncNodeStyleSaga = ({ getWorkspace, notifyAngular }: GraphStoreDependencies) => { + function* syncNodeStyle(action: Action>) { + const workspace = getWorkspace(); + if (!workspace) { + return; + } + const newColor = action.payload.fieldProperties.color; + if (newColor) { + workspace.nodes.forEach(function(node) { + if (node.data.field === action.payload.fieldName) { + node.color = newColor; + } + }); + } + const newIcon = action.payload.fieldProperties.icon; + + if (newIcon) { + workspace.nodes.forEach(function(node) { + if (node.data.field === action.payload.fieldName) { + node.icon = newIcon; + } + }); + } + notifyAngular(); + + const selectedFields = selectedFieldsSelector(yield select()); + workspace.options.vertex_fields = selectedFields; + } + + return function*() { + yield takeLatest(updateFieldProperties.match, syncNodeStyle); + }; +}; diff --git a/x-pack/legacy/plugins/canvas/tasks/mocks/uiStorage.js b/x-pack/legacy/plugins/graph/public/state_management/global.ts similarity index 60% rename from x-pack/legacy/plugins/canvas/tasks/mocks/uiStorage.js rename to x-pack/legacy/plugins/graph/public/state_management/global.ts index ff10d2d4bf68..cfd1493a7e35 100644 --- a/x-pack/legacy/plugins/canvas/tasks/mocks/uiStorage.js +++ b/x-pack/legacy/plugins/graph/public/state_management/global.ts @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export class Storage { - get(key) { - return this[key]; - } +import actionCreatorFactory from 'typescript-fsa'; +const actionCreator = actionCreatorFactory('x-pack/graph'); - set(key, value) { - this[key] = value; - } -} +export const reset = actionCreator('RESET'); diff --git a/x-pack/legacy/plugins/graph/public/state_management/helpers.ts b/x-pack/legacy/plugins/graph/public/state_management/helpers.ts new file mode 100644 index 000000000000..215691d45448 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/state_management/helpers.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ActionCreator, AnyAction } from 'typescript-fsa'; + +/** + * Infers the type of an action out of a given action type. + * This makes it easier to distribute the action types because they come + * along with the creators: `type MyAction = InferActionType`. + * + * This isn't expected to be used in a lot of places - if it is, naming the individual + * action types might make more sense. + */ +export type InferActionType = X extends ActionCreator ? T : never; + +/** + * Helper to create a matcher that matches all passed in action creators. + * + * This is helpful to create a saga that takes multiple actions: + * `yield takeEvery(matchesOne(actionCreator1, actionCreator2), handler);` + * + * @param actionCreators The action creators to create a unified matcher for + */ +export const matchesOne = (...actionCreators: Array>) => (action: AnyAction) => + actionCreators.some(actionCreator => actionCreator.match(action)); diff --git a/x-pack/legacy/plugins/graph/public/state_management/index.ts b/x-pack/legacy/plugins/graph/public/state_management/index.ts index 546af1673ea9..e577fed63a50 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/index.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/index.ts @@ -5,4 +5,11 @@ */ export * from './fields'; +export * from './url_templates'; +export * from './advanced_settings'; +export * from './datasource'; +export * from './meta_data'; +export * from './persistence'; + +export * from './global'; export * from './store'; diff --git a/x-pack/legacy/plugins/graph/public/state_management/meta_data.ts b/x-pack/legacy/plugins/graph/public/state_management/meta_data.ts new file mode 100644 index 000000000000..7d4ad4814081 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/state_management/meta_data.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import actionCreatorFactory from 'typescript-fsa'; +import { reducerWithInitialState } from 'typescript-fsa-reducers'; +import { select, takeLatest, call } from 'redux-saga/effects'; +import { i18n } from '@kbn/i18n'; +import { GraphState, GraphStoreDependencies } from './store'; +import { reset } from './global'; +import { setBreadcrumbs } from '../services/url'; + +const actionCreator = actionCreatorFactory('x-pack/graph/metaData'); + +export interface MetaDataState { + title: string; + description: string; + savedObjectId?: string; +} + +export const updateMetaData = actionCreator>('UPDATE_META_DATA'); + +const initialMetaData: MetaDataState = { + title: i18n.translate('xpack.graph.newWorkspaceTitle', { + defaultMessage: 'Unsaved workspace', + }), + description: '', +}; + +export const metaDataReducer = reducerWithInitialState(initialMetaData) + .case(reset, () => initialMetaData) + .case(updateMetaData, (oldMetaData, newMetaData) => ({ ...oldMetaData, ...newMetaData })) + .build(); + +export const metaDataSelector = (state: GraphState) => state.metaData; + +/** + * Saga updating the breadcrumb when the shown workspace changes. + */ +export const syncBreadcrumbSaga = ({ chrome, changeUrl }: GraphStoreDependencies) => { + function* syncBreadcrumb() { + const metaData = metaDataSelector(yield select()); + setBreadcrumbs({ + chrome, + metaData, + navigateTo: (path: string) => { + // TODO this should be wrapped into canWipeWorkspace, + // but the check is too simple right now. Change this + // once actual state-diffing is in place. + changeUrl(path); + }, + }); + } + return function*() { + // initial sync + yield call(syncBreadcrumb); + yield takeLatest(updateMetaData.match, syncBreadcrumb); + }; +}; diff --git a/x-pack/legacy/plugins/graph/public/state_management/mocks.ts b/x-pack/legacy/plugins/graph/public/state_management/mocks.ts new file mode 100644 index 000000000000..9d22fd521a96 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/state_management/mocks.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Chrome } from 'ui/chrome'; +import { IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { NotificationsStart } from 'kibana/public'; +import createSagaMiddleware from 'redux-saga'; +import { createStore, applyMiddleware } from 'redux'; +import { GraphStoreDependencies, createRootReducer, registerSagas } from './store'; +import { Workspace, GraphWorkspaceSavedObject, IndexPatternSavedObject } from '../types'; + +jest.mock('ui/new_platform'); + +export function createMockGraphStore({ includeSagas }: { includeSagas: boolean }) { + const mockedDeps: jest.Mocked = { + basePath: '', + changeUrl: jest.fn(), + chrome: ({ + breadcrumbs: { + set: jest.fn(), + }, + } as unknown) as Chrome, + createWorkspace: jest.fn(), + getWorkspace: jest.fn(() => (({} as unknown) as Workspace)), + getSavedWorkspace: jest.fn(() => (({} as unknown) as GraphWorkspaceSavedObject)), + indexPatternProvider: { + get: jest.fn(() => Promise.resolve(({} as unknown) as IndexPattern)), + }, + indexPatterns: [ + ({ id: '123', attributes: { title: 'test-pattern' } } as unknown) as IndexPatternSavedObject, + ], + notifications: ({ + toasts: { + addDanger: jest.fn(), + addSuccess: jest.fn(), + }, + } as unknown) as NotificationsStart, + notifyAngular: jest.fn(), + savePolicy: 'configAndDataWithConsent', + showSaveModal: jest.fn(), + setLiveResponseFields: jest.fn(), + }; + const sagaMiddleware = createSagaMiddleware(); + + const rootReducer = createRootReducer(''); + + const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)); + + if (includeSagas) { + registerSagas(sagaMiddleware, mockedDeps); + } + + return { store, mockedDeps }; +} diff --git a/x-pack/legacy/plugins/graph/public/state_management/persistence.ts b/x-pack/legacy/plugins/graph/public/state_management/persistence.ts new file mode 100644 index 000000000000..0b8130a766cd --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/state_management/persistence.ts @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import actionCreatorFactory, { Action } from 'typescript-fsa'; +import { i18n } from '@kbn/i18n'; +import { takeLatest, call, put, select, cps } from 'redux-saga/effects'; +import { GraphWorkspaceSavedObject, Workspace } from '../types'; +import { GraphStoreDependencies, GraphState } from '.'; +import { setDatasource, datasourceSelector, IndexpatternDatasource } from './datasource'; +import { loadFields, selectedFieldsSelector } from './fields'; +import { updateSettings, settingsSelector } from './advanced_settings'; +import { loadTemplates, templatesSelector } from './url_templates'; +import { + lookupIndexPattern, + savedWorkspaceToAppState, + appStateToSavedWorkspace, +} from '../services/persistence'; +import { updateMetaData, metaDataSelector } from './meta_data'; +import { openSaveModal, SaveWorkspaceHandler } from '../services/save_modal'; +import { getEditPath } from '../services/url'; +const actionCreator = actionCreatorFactory('x-pack/graph'); + +export const loadSavedWorkspace = actionCreator('LOAD_WORKSPACE'); +export const saveWorkspace = actionCreator('SAVE_WORKSPACE'); + +/** + * Saga handling loading of a saved workspace. + * + * It will load the index pattern associated with the saved object and deserialize all properties + * into the store. Existing state will be overwritten. + */ +export const loadingSaga = ({ + createWorkspace, + getWorkspace, + indexPatterns, + notifications, + indexPatternProvider, +}: GraphStoreDependencies) => { + function* deserializeWorkspace(action: Action) { + const selectedIndex = lookupIndexPattern(action.payload, indexPatterns); + if (!selectedIndex) { + notifications.toasts.addDanger( + i18n.translate('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', { + defaultMessage: 'Index pattern not found', + }) + ); + return; + } + + const indexPattern = yield call(indexPatternProvider.get, selectedIndex.id); + const initialSettings = settingsSelector(yield select()); + + createWorkspace(selectedIndex.attributes.title, initialSettings); + + const { urlTemplates, advancedSettings, allFields } = savedWorkspaceToAppState( + action.payload, + indexPattern, + // workspace won't be null because it's created in the same call stack + getWorkspace()! + ); + + // put everything in the store + yield put( + updateMetaData({ + title: action.payload.title, + description: action.payload.description, + savedObjectId: action.payload.id, + }) + ); + yield put( + setDatasource({ + type: 'indexpattern', + id: selectedIndex.id, + title: selectedIndex.attributes.title, + }) + ); + yield put(loadFields(allFields)); + yield put(updateSettings(advancedSettings)); + yield put(loadTemplates(urlTemplates)); + + // workspace won't be null because it's created in the same call stack + getWorkspace()!.runLayout(); + } + + return function*() { + yield takeLatest(loadSavedWorkspace.match, deserializeWorkspace); + }; +}; + +/** + * Saga handling saving of current state. + * + * It will serialize everything and save it using the saved objects client + */ +export const savingSaga = (deps: GraphStoreDependencies) => { + function* persistWorkspace() { + const savedWorkspace = deps.getSavedWorkspace(); + const state: GraphState = yield select(); + const workspace = deps.getWorkspace(); + const selectedDatasource = datasourceSelector(state).current; + if (!workspace || selectedDatasource.type === 'none') { + return; + } + + const savedObjectId = yield cps(showModal, { + deps, + workspace, + savedWorkspace, + state, + selectedDatasource, + }); + if (savedObjectId) { + yield put(updateMetaData({ savedObjectId })); + } + } + + return function*() { + yield takeLatest(saveWorkspace.match, persistWorkspace); + }; +}; + +function showModal( + { + deps, + workspace, + savedWorkspace, + state, + selectedDatasource, + }: { + deps: GraphStoreDependencies; + workspace: Workspace; + savedWorkspace: GraphWorkspaceSavedObject; + state: GraphState; + selectedDatasource: IndexpatternDatasource; + }, + savingCallback: (error: unknown, id?: string) => void +) { + const saveWorkspaceHandler: SaveWorkspaceHandler = async ( + saveOptions, + userHasConfirmedSaveWorkspaceData + ) => { + const canSaveData = + deps.savePolicy === 'configAndData' || + (deps.savePolicy === 'configAndDataWithConsent' && userHasConfirmedSaveWorkspaceData); + appStateToSavedWorkspace( + savedWorkspace, + { + workspace, + urlTemplates: templatesSelector(state), + advancedSettings: settingsSelector(state), + selectedIndex: selectedDatasource, + selectedFields: selectedFieldsSelector(state), + }, + canSaveData + ); + try { + const id = await savedWorkspace.save(saveOptions); + if (id) { + const title = i18n.translate('xpack.graph.saveWorkspace.successNotificationTitle', { + defaultMessage: 'Saved "{workspaceTitle}"', + values: { workspaceTitle: savedWorkspace.title }, + }); + let text; + if (!canSaveData && workspace.nodes.length > 0) { + text = i18n.translate('xpack.graph.saveWorkspace.successNotification.noDataSavedText', { + defaultMessage: 'The configuration was saved, but the data was not saved', + }); + } + deps.notifications.toasts.addSuccess({ + title, + text, + 'data-test-subj': 'saveGraphSuccess', + }); + if (savedWorkspace.id !== metaDataSelector(state).savedObjectId) { + deps.changeUrl(getEditPath(savedWorkspace)); + } + } + savingCallback(null, id); + return { id }; + } catch (error) { + deps.notifications.toasts.addDanger( + i18n.translate('xpack.graph.saveWorkspace.savingErrorMessage', { + defaultMessage: 'Failed to save workspace: {message}', + values: { + message: error, + }, + }) + ); + return { error }; + } + }; + + openSaveModal({ + savePolicy: deps.savePolicy, + hasData: workspace.nodes.length > 0 || workspace.blacklistedNodes.length > 0, + workspace: savedWorkspace, + showSaveModal: deps.showSaveModal, + saveWorkspace: saveWorkspaceHandler, + }); +} diff --git a/x-pack/legacy/plugins/graph/public/state_management/store.ts b/x-pack/legacy/plugins/graph/public/state_management/store.ts index f5317e9c5b6a..4161a3c48593 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/store.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/store.ts @@ -4,16 +4,95 @@ * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers, createStore, Store, AnyAction, Dispatch } from 'redux'; -import { fieldsReducer, FieldsState } from './fields'; +import createSagaMiddleware, { SagaMiddleware } from 'redux-saga'; +import { combineReducers, createStore, Store, AnyAction, Dispatch, applyMiddleware } from 'redux'; +import { CoreStart } from 'src/core/public'; +import { Chrome } from 'ui/chrome'; +import { + fieldsReducer, + FieldsState, + syncNodeStyleSaga, + syncFieldsSaga, + updateSaveButtonSaga, +} from './fields'; +import { UrlTemplatesState, urlTemplatesReducer } from './url_templates'; +import { + AdvancedSettingsState, + advancedSettingsReducer, + syncSettingsSaga, +} from './advanced_settings'; +import { DatasourceState, datasourceReducer, datasourceSaga } from './datasource'; +import { + IndexPatternProvider, + Workspace, + IndexPatternSavedObject, + GraphSavePolicy, + GraphWorkspaceSavedObject, + AdvancedSettings, + WorkspaceField, +} from '../types'; +import { loadingSaga, savingSaga } from './persistence'; +import { metaDataReducer, MetaDataState, syncBreadcrumbSaga } from './meta_data'; export interface GraphState { fields: FieldsState; + urlTemplates: UrlTemplatesState; + advancedSettings: AdvancedSettingsState; + datasource: DatasourceState; + metaData: MetaDataState; } -const rootReducer = combineReducers({ fields: fieldsReducer }); +export interface GraphStoreDependencies { + basePath: string; + indexPatternProvider: IndexPatternProvider; + indexPatterns: IndexPatternSavedObject[]; + createWorkspace: (index: string, advancedSettings: AdvancedSettings) => void; + getWorkspace: () => Workspace | null; + getSavedWorkspace: () => GraphWorkspaceSavedObject; + notifications: CoreStart['notifications']; + showSaveModal: (el: React.ReactNode) => void; + savePolicy: GraphSavePolicy; + changeUrl: (newUrl: string) => void; + notifyAngular: () => void; + setLiveResponseFields: (fields: WorkspaceField[]) => void; + chrome: Chrome; +} + +export function createRootReducer(basePath: string) { + return combineReducers({ + fields: fieldsReducer, + urlTemplates: urlTemplatesReducer(basePath), + advancedSettings: advancedSettingsReducer, + datasource: datasourceReducer, + metaData: metaDataReducer, + }); +} + +export function registerSagas( + sagaMiddleware: SagaMiddleware, + deps: GraphStoreDependencies +) { + sagaMiddleware.run(datasourceSaga(deps)); + sagaMiddleware.run(loadingSaga(deps)); + sagaMiddleware.run(savingSaga(deps)); + sagaMiddleware.run(syncFieldsSaga(deps)); + sagaMiddleware.run(syncNodeStyleSaga(deps)); + sagaMiddleware.run(syncSettingsSaga(deps)); + sagaMiddleware.run(updateSaveButtonSaga(deps)); + sagaMiddleware.run(syncBreadcrumbSaga(deps)); +} + +export const createGraphStore = (deps: GraphStoreDependencies) => { + const sagaMiddleware = createSagaMiddleware(); + + const rootReducer = createRootReducer(deps.basePath); + + const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)); + + registerSagas(sagaMiddleware, deps); -export const createGraphStore = () => createStore(rootReducer); + return store; +}; export type GraphStore = Store; export type GraphDispatch = Dispatch; diff --git a/x-pack/legacy/plugins/graph/public/state_management/url_templates.ts b/x-pack/legacy/plugins/graph/public/state_management/url_templates.ts new file mode 100644 index 000000000000..3bb97c343ba3 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/state_management/url_templates.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import actionCreatorFactory from 'typescript-fsa'; +import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; +import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; +import { i18n } from '@kbn/i18n'; +import rison from 'rison-node'; +import { GraphState } from './store'; +import { UrlTemplate } from '../types'; +import { reset } from './global'; +import { setDatasource, IndexpatternDatasource, requestDatasource } from './datasource'; +import { outlinkEncoders } from '../helpers/outlink_encoders'; +import { urlTemplatePlaceholder } from '../helpers/url_template'; + +const actionCreator = actionCreatorFactory('x-pack/graph/urlTemplates'); + +export const loadTemplates = actionCreator('LOAD_TEMPLATES'); +export const saveTemplate = actionCreator<{ index: number; template: UrlTemplate }>( + 'SAVE_TEMPLATE' +); +export const removeTemplate = actionCreator('REMOVE_TEMPLATE'); + +export type UrlTemplatesState = UrlTemplate[]; + +const initialTemplates: UrlTemplatesState = []; + +function generateDefaultTemplate( + datasource: IndexpatternDatasource, + basePath: string +): UrlTemplate { + const kUrl = new KibanaParsedUrl({ + appId: 'kibana', + basePath, + appPath: '/discover', + }); + + kUrl.addQueryParameter( + '_a', + rison.encode({ + columns: ['_source'], + index: datasource.title, + interval: 'auto', + query: { language: 'kuery', query: urlTemplatePlaceholder }, + sort: ['_score', 'desc'], + }) + ); + + // replace the URI encoded version of the tag with the unescaped version + // so it can be found with String.replace, regexp, etc. + const discoverUrl = kUrl + .getRootRelativePath() + .replace(encodeURIComponent(urlTemplatePlaceholder), urlTemplatePlaceholder); + + return { + url: discoverUrl, + description: i18n.translate('xpack.graph.settings.drillDowns.defaultUrlTemplateTitle', { + defaultMessage: 'Raw documents', + }), + encoder: outlinkEncoders[0], + isDefault: true, + icon: null, + }; +} + +export const urlTemplatesReducer = (basePath: string) => + reducerWithInitialState(initialTemplates) + .case(reset, () => initialTemplates) + .cases([requestDatasource, setDatasource], (templates, datasource) => { + if (datasource.type === 'none') { + return initialTemplates; + } + const customTemplates = templates.filter(template => !template.isDefault); + return [...customTemplates, generateDefaultTemplate(datasource, basePath)]; + }) + .case(loadTemplates, (_currentTemplates, newTemplates) => newTemplates) + .case(saveTemplate, (templates, { index: indexToUpdate, template: updatedTemplate }) => { + // set default flag to false as soon as template is overwritten. + const newTemplate = { ...updatedTemplate, isDefault: false }; + return indexToUpdate === -1 + ? [...templates, newTemplate] + : templates.map((template, index) => (index === indexToUpdate ? newTemplate : template)); + }) + .case(removeTemplate, (templates, templateToDelete) => + templates.filter(template => template !== templateToDelete) + ) + .build(); + +export const templatesSelector = (state: GraphState) => state.urlTemplates; diff --git a/x-pack/legacy/plugins/graph/public/types/app_state.ts b/x-pack/legacy/plugins/graph/public/types/app_state.ts index f72c4057f4c2..24b1876bb713 100644 --- a/x-pack/legacy/plugins/graph/public/types/app_state.ts +++ b/x-pack/legacy/plugins/graph/public/types/app_state.ts @@ -6,7 +6,6 @@ import { SimpleSavedObject } from 'src/core/public'; import { IndexPattern } from 'src/legacy/core_plugins/data/public'; -import { Workspace } from './workspace_state'; import { FontawesomeIcon } from '../helpers/style_choices'; import { OutlinkEncoder } from '../helpers/outlink_encoders'; @@ -39,11 +38,6 @@ export interface AdvancedSettings { export type IndexPatternSavedObject = SimpleSavedObject<{ title: string }>; -export interface AppState { - urlTemplates: UrlTemplate[]; - advancedSettings: AdvancedSettings; - workspace: Workspace; - allFields: WorkspaceField[]; - selectedFields: WorkspaceField[]; - selectedIndex: IndexPattern; +export interface IndexPatternProvider { + get(id: string): Promise; } diff --git a/x-pack/legacy/plugins/graph/public/types/workspace_state.ts b/x-pack/legacy/plugins/graph/public/types/workspace_state.ts index fab093535cb6..c23ab49de496 100644 --- a/x-pack/legacy/plugins/graph/public/types/workspace_state.ts +++ b/x-pack/legacy/plugins/graph/public/types/workspace_state.ts @@ -69,6 +69,7 @@ export interface GraphData { } export interface Workspace { + options: WorkspaceOptions; nodesMap: Record; nodes: WorkspaceNode[]; edges: WorkspaceEdge[]; @@ -89,6 +90,9 @@ export interface Workspace { * @param newData */ mergeGraph(newData: GraphData): void; + + runLayout(): void; + stopLayout(): void; } export type ExploreRequest = any; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_frame_layout.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_frame_layout.scss index e3b91ee0674c..35c28595a59c 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_frame_layout.scss +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_frame_layout.scss @@ -42,8 +42,7 @@ @include euiScrollBar; min-width: $lnsPanelMinWidth + $euiSize; overflow-x: hidden; - overflow-y: auto; + overflow-y: scroll; padding-top: $euiSize; - padding-right: $euiSize; max-height: 100%; } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss index 30f6136c8362..b8f5f95df8f0 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss @@ -10,7 +10,8 @@ .lnsWorkspacePanelWrapper__pageContentHeader { padding: $euiSizeS; border-bottom: $euiBorderThin; - margin-bottom: 0; + // override EuiPage + margin-bottom: 0 !important; // sass-lint:disable-line no-important } .lnsWorkspacePanelWrapper__pageContentBody { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_dimension_panel.scss b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_dimension_panel.scss index 4ab4877a6feb..ddb37505f998 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_dimension_panel.scss +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_dimension_panel.scss @@ -1,9 +1,9 @@ .lnsIndexPatternDimensionPanel { @include euiFontSizeS; - background: $euiColorEmptyShade; + background-color: $euiColorEmptyShade; border-radius: $euiBorderRadius; display: flex; align-items: center; margin-top: $euiSizeXS; - padding: $euiSizeS; + overflow: hidden; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_popover.scss b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_popover.scss index ee25a501e5f8..8f26ab91e0f1 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_popover.scss +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/_popover.scss @@ -13,6 +13,8 @@ width: 100%; display: flex; align-items: center; + padding: $euiSizeS; + min-height: $euiSizeXXL; } .lnsPopoverEditor__left, @@ -22,7 +24,7 @@ .lnsPopoverEditor__left { padding-top: 0; - background-color: $euiColorLightestShade; + background-color: $euiPageBackgroundColor; } .lnsPopoverEditor__right { @@ -30,14 +32,20 @@ } .lnsPopoverEditor__operation { - padding: $euiSizeXS; - font-size: .875rem; + @include euiFontSizeS; + color: $euiColorPrimary; + + // TODO: Fix in EUI or don't use EuiSideNav + .euiSideNavItemButton__label { + color: inherit; + } } .lnsPopoverEditor__operation--selected { - background-color: $euiColorLightShade; + font-weight: bold; + color: $euiTextColor; } .lnsPopoverEditor__operation--incompatible { - opacity: .7; + color: $euiColorMediumShade; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx index b0d011412867..0cd7329b4613 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx @@ -140,6 +140,7 @@ export function FieldSelect({ ) : ( <> - setPopoverOpen(!isPopoverOpen)} - />{' '} - + size="xs" + > - + ) } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx index af0612be8dc2..804ea478e57f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx @@ -20,6 +20,7 @@ import { EuiPopoverTitle, EuiIconTip, } from '@elastic/eui'; +import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { Chart, Axis, @@ -210,6 +211,9 @@ export function FieldItem(props: FieldItemProps) { function FieldItemPopoverContents(props: State & FieldItemProps) { const { histogram, topValues, indexPattern, field, dateRange, core, sampledValues } = props; + const IS_DARK_THEME = core.uiSettings.get('theme:darkMode'); + const chartTheme = IS_DARK_THEME ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme; + if (props.isLoading) { return ; } else if ( @@ -371,7 +375,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { - + { expect(instance.find(EuiRange).prop('value')).toEqual(2); }); - it('should render disabled switch and no level of detail control for auto interval', () => { + it('should render disabled switch and no time intervals control for auto interval', () => { const instance = shallow( {intervalIsRestricted ? ( @@ -242,7 +242,7 @@ export const dateHistogramOperation: OperationDefinition )} diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap index f9a5a73eda27..fdc16f5aec6b 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap @@ -9,6 +9,99 @@ exports[`xy_expression XYChart component it renders area 1`] = ` rotation={0} showLegend={false} showLegendDisplayValue={false} + theme={ + Object { + "areaSeriesStyle": Object { + "area": Object { + "opacity": 0.3, + }, + "line": Object { + "strokeWidth": 2, + }, + "point": Object { + "fill": "rgba(255, 255, 255, 1)", + "radius": 3, + "strokeWidth": 2, + "visible": false, + }, + }, + "axes": Object { + "axisLineStyle": Object { + "stroke": "rgba(239, 241, 244, 1)", + }, + "axisTitleStyle": Object { + "fill": "rgba(52, 55, 65, 1)", + "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + "fontSize": 12, + "padding": 10, + }, + "tickLabelStyle": Object { + "fill": "rgba(105, 112, 125, 1)", + "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + "fontSize": 10, + "padding": 8, + }, + "tickLineStyle": Object { + "stroke": "rgba(0,0,0,0)", + "strokeWidth": 0, + }, + }, + "barSeriesStyle": Object { + "displayValue": Object { + "fill": "rgba(105, 112, 125, 1)", + "fontFamily": "'Inter UI', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + "fontSize": 8, + }, + }, + "chartMargins": Object { + "bottom": 0, + "left": 0, + "right": 0, + "top": 0, + }, + "colors": Object { + "defaultVizColor": "#3185FC", + "vizColors": Array [ + "#1EA593", + "#2B70F7", + "#CE0060", + "#38007E", + "#FCA5D3", + "#F37020", + "#E49E29", + "#B0916F", + "#7B000B", + "#34130C", + ], + }, + "crosshair": Object { + "band": Object { + "fill": "rgba(245, 247, 250, 1)", + }, + "line": Object { + "dash": Array [ + 4, + 4, + ], + "stroke": "rgba(105, 112, 125, 1)", + "strokeWidth": 1, + }, + }, + "lineSeriesStyle": Object { + "line": Object { + "strokeWidth": 2, + }, + "point": Object { + "fill": "rgba(255, 255, 255, 1)", + "radius": 3, + "strokeWidth": 2, + }, + }, + } + } /> 0 ? getIconForSeriesType(layers[0].seriesType) : 'bar'; return ( -

- -

+ +

1 || data.tables[layers[0].layerId].columns.length > 2; const shouldRotate = isHorizontalChart(layers); return ( diff --git a/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js index 10410f4d79e5..767237ecdf38 100644 --- a/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js +++ b/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js @@ -197,6 +197,7 @@ export class GeometryFilterForm extends Component { fill onClick={this._onSubmit} isDisabled={!this.state.geometryLabel || !this.state.geoFieldTag} + isLoading={this.props.isLoading} > {this.props.buttonLabel} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js index 16bf188c4377..10913bcdbb96 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js @@ -16,8 +16,46 @@ import { GeometryFilterForm } from '../../components/geometry_filter_form'; export class FeatureGeometryFilterForm extends Component { - _createFilter = ({ geometryLabel, indexPatternId, geoFieldName, geoFieldType, relation }) => { + state = { + isLoading: false, + } + + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } + + _loadPreIndexedShape = async () => { + this.setState({ + isLoading: true, + }); + + let preIndexedShape; + try { + preIndexedShape = await this.props.loadPreIndexedShape(); + } catch (err) { + // ignore error, just fall back to using geometry if preIndexedShape can not be fetched + } + + if (this._isMounted) { + this.setState({ isLoading: false }); + } + + return preIndexedShape; + } + + _createFilter = async ({ geometryLabel, indexPatternId, geoFieldName, geoFieldType, relation }) => { + const preIndexedShape = await this._loadPreIndexedShape(); + if (!this._isMounted) { + // do not create filter if component is unmounted + return; + } + const filter = createSpatialFilterWithGeometry({ + preIndexedShape, geometry: this.props.geometry, geometryLabel, indexPatternId, @@ -65,6 +103,7 @@ export class FeatureGeometryFilterForm extends Component { onSubmit={this._createFilter} isFilterGeometryClosed={this.props.geometry.type !== GEO_JSON_TYPE.LINE_STRING && this.props.geometry.type !== GEO_JSON_TYPE.MULTI_LINE_STRING} + isLoading={this.state.isLoading} /> ); } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/feature_tooltip.js b/x-pack/legacy/plugins/maps/public/connected_components/map/feature_tooltip.js index 4075ae54a369..6c68bd6be4c3 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/feature_tooltip.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/feature_tooltip.js @@ -338,6 +338,15 @@ export class FeatureTooltip extends React.Component { ); } + _loadCurrentFeaturePreIndexedShape = () => { + const filteredFeatures = this._filterFeatures(); + const currentFeature = filteredFeatures[this.state.pageNumber]; + return this.props.loadPreIndexedShape({ + layerId: currentFeature.layerId, + featureId: currentFeature.id + }); + } + render() { const filteredFeatures = this._filterFeatures(); const currentFeature = filteredFeatures[this.state.pageNumber]; @@ -355,6 +364,7 @@ export class FeatureTooltip extends React.Component { geometry={currentFeatureGeometry} geoFields={filteredGeoFields} addFilters={this.props.addFilters} + loadPreIndexedShape={this._loadCurrentFeaturePreIndexedShape} /> ); } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tool_control.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tool_control.test.js.snap index 1dfc4d681e56..8c09b7a160ec 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tool_control.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tool_control.test.js.snap @@ -111,6 +111,7 @@ exports[`TooltipControl render tooltipState is provided should render tooltip po isLocked={false} loadFeatureGeometry={[Function]} loadFeatureProperties={[Function]} + loadPreIndexedShape={[Function]} /> diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js index 165d73222a99..d788f93f9156 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js @@ -251,6 +251,7 @@ export class TooltipControl extends React.Component { if (!tooltipLayer) { return null; } + const targetFeature = tooltipLayer.getFeatureById(featureId); if (!targetFeature) { return null; @@ -264,6 +265,7 @@ export class TooltipControl extends React.Component { if (!tooltipLayer) { return []; } + const targetFeature = tooltipLayer.getFeatureById(featureId); if (!targetFeature) { return []; @@ -271,6 +273,20 @@ export class TooltipControl extends React.Component { return await tooltipLayer.getPropertiesForTooltip(targetFeature.properties); }; + _loadPreIndexedShape = async ({ layerId, featureId }) => { + const tooltipLayer = this._findLayerById(layerId); + if (!tooltipLayer) { + return null; + } + + const targetFeature = tooltipLayer.getFeatureById(featureId); + if (!targetFeature) { + return null; + } + + return await tooltipLayer.getSource().getPreIndexedShape(targetFeature.properties); + }; + _findLayerById = (layerId) => { return this.props.layerList.find(layer => { return layer.getId() === layerId; @@ -308,6 +324,7 @@ export class TooltipControl extends React.Component { anchorLocation={this.props.tooltipState.location} findLayerById={this._findLayerById} geoFields={this.props.geoFields} + loadPreIndexedShape={this._loadPreIndexedShape} /> ); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap index fb352e9c2d9e..eb30a5c33496 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap @@ -76,7 +76,7 @@ exports[`LayerControl is rendered 1`] = ` `; -exports[`LayerControl props Should not render LayerTOC when isLayerTOCOpen is false 1`] = ` +exports[`LayerControl isLayerTOCOpen Should render expand button 1`] = ` `; -exports[`LayerControl props isReadOnly 1`] = ` +exports[`LayerControl isLayerTOCOpen Should render expand button with error icon when layer has error 1`] = ` + + + +`; + +exports[`LayerControl isLayerTOCOpen Should render expand button with loading icon when layer is loading 1`] = ` + + + +`; + +exports[`LayerControl isReadOnly 1`] = ` + + + ); + } + + return ( + + ); +} + +export function LayerControl({ isReadOnly, isLayerTOCOpen, showAddLayerWizard, closeLayerTOC, openLayerTOC, layerList }) { if (!isLayerTOCOpen) { + const hasErrors = layerList.some(layer => { + return layer.hasErrors(); + }); + const isLoading = layerList.some(layer => { + return layer.isLayerLoading(); + }); + return ( - + {renderExpandButton({ hasErrors, isLoading, onClick: openLayerTOC })} ); } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/view.test.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/view.test.js index e4369140d816..e318606db46f 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/view.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/view.test.js @@ -11,7 +11,7 @@ jest.mock('./layer_toc', () => ({ })); import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { shallow } from 'enzyme'; import { LayerControl } from './view'; @@ -20,43 +20,75 @@ const defaultProps = { closeLayerTOC: () => {}, openLayerTOC: () => {}, isLayerTOCOpen: true, + layerList: [], }; describe('LayerControl', () => { test('is rendered', () => { - const component = shallowWithIntl( + const component = shallow( ); - expect(component) - .toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); - describe('props', () => { - test('isReadOnly', () => { - const component = shallowWithIntl( + test('isReadOnly', () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); + }); + + describe('isLayerTOCOpen', () => { + + test('Should render expand button', () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('Should render expand button with loading icon when layer is loading', () => { + const mockLayerThatIsLoading = { + hasErrors: () => { return false; }, + isLayerLoading: () => { return true; } + }; + const component = shallow( ); - expect(component) - .toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); - test('Should not render LayerTOC when isLayerTOCOpen is false', () => { - const component = shallowWithIntl( + test('Should render expand button with error icon when layer has error', () => { + const mockLayerThatHasError = { + hasErrors: () => { return true; }, + isLayerLoading: () => { return false; } + }; + const component = shallow( ); - expect(component) - .toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); + }); diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js index 2ad8cc856838..bfb3f710116e 100644 --- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js +++ b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js @@ -297,6 +297,7 @@ export function createSpatialFilterWithGeometry(options) { } function createGeometryFilterWithMeta({ + preIndexedShape, geometry, geometryLabel, indexPatternId, @@ -320,14 +321,21 @@ function createGeometryFilterWithMeta({ }; if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_SHAPE) { + const shapeQuery = { + relation + }; + + if (preIndexedShape) { + shapeQuery.indexed_shape = preIndexedShape; + } else { + shapeQuery.shape = geometry; + } + return { meta, geo_shape: { ignore_unmapped: true, - [geoFieldName]: { - shape: geometry, - relation - } + [geoFieldName]: shapeQuery } }; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index 694733340951..071e231bb6e2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -240,7 +240,7 @@ export class ESSearchSource extends AbstractESSource { const indexPattern = await this._getIndexPattern(); const unusedMetaFields = indexPattern.metaFields.filter(metaField => { - return metaField !== '_id'; + return !['_id', '_index'].includes(metaField); }); const flattenHit = hit => { const properties = indexPattern.flattenHit(hit); @@ -412,4 +412,14 @@ export class ESSearchSource extends AbstractESSource { topHitsSize: this._descriptor.topHitsSize, }; } + + async getPreIndexedShape(properties) { + const geoField = await this._getGeoField(); + + return { + index: properties._index, // Can not use index pattern title because it may reference many indices + id: properties._id, + path: geoField.name, + }; + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/legacy/plugins/maps/public/layers/sources/source.js index 01479c4319d9..a9df3592af27 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.js @@ -122,6 +122,11 @@ export class AbstractSource { supportsElasticsearchFilters() { return false; } + + // Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes + async getPreIndexedShape(/* properties */) { + return null; + } } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/browsers.js b/x-pack/legacy/plugins/reporting/server/browsers/browsers.ts similarity index 85% rename from x-pack/legacy/plugins/reporting/server/browsers/browsers.js rename to x-pack/legacy/plugins/reporting/server/browsers/browsers.ts index 75f06de9a2a4..ff9dece504ea 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/browsers.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/browsers.ts @@ -6,6 +6,8 @@ import * as chromium from './chromium'; +export type BrowserType = keyof typeof BROWSERS_BY_TYPE; + export const BROWSERS_BY_TYPE = { chromium, }; diff --git a/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts b/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts index ada20914eccb..cf3e2fe25b4d 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts @@ -4,9 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { BROWSERS_BY_TYPE } from './browsers'; -// @ts-ignore +import { BROWSERS_BY_TYPE, BrowserType } from './browsers'; import { ensureBrowserDownloaded } from './download'; import { installBrowser } from './install'; import { LevelLogger } from '../lib/level_logger'; @@ -22,7 +20,7 @@ export async function createBrowserDriverFactory( const DATA_DIR = config.get('path.data'); const CAPTURE_CONFIG = config.get('xpack.reporting.capture'); - const BROWSER_TYPE = CAPTURE_CONFIG.browser.type; + const BROWSER_TYPE: BrowserType = CAPTURE_CONFIG.browser.type; const BROWSER_AUTO_DOWNLOAD = CAPTURE_CONFIG.browser.autoDownload; const BROWSER_CONFIG = CAPTURE_CONFIG.browser[BROWSER_TYPE]; const REPORTING_TIMEOUT = config.get('xpack.reporting.queue.timeout'); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.js b/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.ts similarity index 94% rename from x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.js rename to x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.ts index 952d6a99c4e4..a21a4b33722f 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/default_chromium_sandbox_disabled.ts @@ -9,7 +9,7 @@ import { promisify } from 'util'; const getos = promisify(getosSync); -const distroSupportsUnprivilegedUsernamespaces = (distro) => { +const distroSupportsUnprivilegedUsernamespaces = (distro: string) => { // Debian 7 and 8 don't support usernamespaces by default // this should be reevaluated when Debian 9 is available if (distro.toLowerCase() === 'debian') { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/checksum.ts b/x-pack/legacy/plugins/reporting/server/browsers/download/checksum.ts index b5e50fcebd45..fd68a8e356e1 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/checksum.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/checksum.ts @@ -7,7 +7,6 @@ import { createHash } from 'crypto'; import { createReadStream } from 'fs'; -// @ts-ignore import { readableEnd } from './util'; export async function md5(path: string) { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/clean.js b/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts similarity index 85% rename from x-pack/legacy/plugins/reporting/server/browsers/download/clean.js rename to x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts index ad487311e12d..394b76d77253 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/clean.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts @@ -14,12 +14,9 @@ import { log, asyncMap } from './util'; /** * Delete any file in the `dir` that is not in the expectedPaths - * @param {String} dir - * @param {Array} expectedPaths - * @return {Promise} */ -export async function clean(dir, expectedPaths) { - let filenames; +export async function clean(dir: string, expectedPaths: string[]) { + let filenames: string[]; try { filenames = await readdirSync(dir); } catch (error) { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/download.test.js b/x-pack/legacy/plugins/reporting/server/browsers/download/download.test.ts similarity index 55% rename from x-pack/legacy/plugins/reporting/server/browsers/download/download.test.js rename to x-pack/legacy/plugins/reporting/server/browsers/download/download.test.ts index 61b505703a75..901fc6ccf9b2 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/download.test.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/download.test.ts @@ -7,6 +7,7 @@ import { createHash } from 'crypto'; import { resolve as resolvePath } from 'path'; import { readFileSync } from 'fs'; +import { Readable } from 'stream'; import del from 'del'; import { download } from './download'; @@ -14,41 +15,27 @@ import { download } from './download'; const TEMP_DIR = resolvePath(__dirname, '__tmp__'); const TEMP_FILE = resolvePath(TEMP_DIR, 'foo/bar/download'); -jest.mock('request', () => { - let resp = ''; - const sinon = require('sinon'); - const Readable = require('stream').Readable; - const request = sinon.spy(function () { - return new Readable({ - read() { - if (resp instanceof Error) { - this.emit('error', resp); - return; - } +class ReadableOf extends Readable { + constructor(private readonly responseBody: string) { + super(); + } - this.push(resp.shift()); + _read() { + this.push(this.responseBody); + this.push(null); + } +} - if (resp.length === 0) { - this.push(null); - } - } - }); - }); - - request._setResponse = (chunks) => { - if (typeof chunks === 'string') { - chunks = chunks.split(''); - } - - resp = chunks; - }; - - return request; -}); +jest.mock('axios'); +const request: jest.Mock = jest.requireMock('axios').request; test('downloads the url to the path', async () => { const BODY = 'abdcefg'; - require('request')._setResponse(BODY); + request.mockImplementationOnce(async () => { + return { + data: new ReadableOf(BODY), + }; + }); await download('url', TEMP_FILE); expect(readFileSync(TEMP_FILE, 'utf8')).toEqual(BODY); @@ -56,18 +43,25 @@ test('downloads the url to the path', async () => { test('returns the md5 hex hash of the http body', async () => { const BODY = 'foobar'; - const HASH = createHash('md5').update(BODY).digest('hex'); - require('request')._setResponse(BODY); + const HASH = createHash('md5') + .update(BODY) + .digest('hex'); + request.mockImplementationOnce(async () => { + return { + data: new ReadableOf(BODY), + }; + }); const returned = await download('url', TEMP_FILE); expect(returned).toEqual(HASH); }); test('throws if request emits an error', async () => { - require('request')._setResponse(new Error('foo')); + request.mockImplementationOnce(async () => { + throw new Error('foo'); + }); + return expect(download('url', TEMP_FILE)).rejects.toThrow('foo'); }); -afterEach(async () => ( - await del(TEMP_DIR) -)); +afterEach(async () => await del(TEMP_DIR)); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/download.js b/x-pack/legacy/plugins/reporting/server/browsers/download/download.ts similarity index 59% rename from x-pack/legacy/plugins/reporting/server/browsers/download/download.js rename to x-pack/legacy/plugins/reporting/server/browsers/download/download.ts index df42f8320225..a5ad69b46e46 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/download.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/download.ts @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { openSync, writeSync, closeSync } from 'fs'; +import { openSync, writeSync, closeSync, mkdirSync } from 'fs'; import { createHash } from 'crypto'; import { dirname } from 'path'; -import mkdirp from 'mkdirp'; -import request from 'request'; +import Axios from 'axios'; -import { log, readableEnd } from './util'; +import { log } from './util'; /** * Download a url and calculate it's checksum @@ -19,20 +18,29 @@ import { log, readableEnd } from './util'; * @param {String} path * @return {Promise} checksum of the downloaded file */ -export async function download(url, path) { +export async function download(url: string, path: string) { log(`Downloading ${url}`); const hash = createHash('md5'); - mkdirp.sync(dirname(path)); + mkdirSync(dirname(path), { recursive: true }); const handle = openSync(path, 'w'); try { - const readable = request(url).on('data', chunk => { + const resp = await Axios.request({ + url, + method: 'GET', + responseType: 'stream', + }); + + resp.data.on('data', (chunk: Buffer) => { writeSync(handle, chunk); hash.update(chunk); }); - await readableEnd(readable); + + await new Promise((resolve, reject) => { + resp.data.on('error', reject).on('end', resolve); + }); } finally { closeSync(handle); } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.js b/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.ts similarity index 68% rename from x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.js rename to x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.ts index 2a8052689e02..d322566f9aa1 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.ts @@ -7,21 +7,20 @@ import { resolve as resolvePath } from 'path'; import { existsSync } from 'fs'; -import { BROWSERS_BY_TYPE } from '../browsers'; +import { BROWSERS_BY_TYPE, BrowserType } from '../browsers'; import { md5 } from './checksum'; import { asyncMap } from './util'; import { download } from './download'; import { clean } from './clean'; - /** * Check for the downloaded archive of each requested browser type and * download them if they are missing or their checksum is invalid * @param {String} browserType * @return {Promise} */ -export async function ensureBrowserDownloaded(browserType) { +export async function ensureBrowserDownloaded(browserType: BrowserType) { await ensureDownloaded([BROWSERS_BY_TYPE[browserType]]); } @@ -33,7 +32,6 @@ export async function ensureAllBrowsersDownloaded() { await ensureDownloaded(Object.values(BROWSERS_BY_TYPE)); } - /** * Clears the unexpected files in the browsers archivesPath * and ensures that all packages/archives are downloaded and @@ -41,20 +39,29 @@ export async function ensureAllBrowsersDownloaded() { * @param {BrowserSpec} browsers * @return {Promise} */ -async function ensureDownloaded(browsers) { - await asyncMap(Object.values(browsers), async (browser) => { +async function ensureDownloaded( + browsers: Array<{ + paths: { + archivesPath: string; + baseUrl: string; + packages: Array<{ archiveFilename: string; archiveChecksum: string }>; + }; + }> +) { + await asyncMap(browsers, async browser => { const { archivesPath } = browser.paths; - await clean(archivesPath, browser.paths.packages.map(p => ( - resolvePath(archivesPath, p.archiveFilename) - ))); + await clean( + archivesPath, + browser.paths.packages.map(p => resolvePath(archivesPath, p.archiveFilename)) + ); - const invalidChecksums = []; + const invalidChecksums: string[] = []; await asyncMap(browser.paths.packages, async ({ archiveFilename, archiveChecksum }) => { const url = `${browser.paths.baseUrl}${archiveFilename}`; const path = resolvePath(archivesPath, archiveFilename); - if (existsSync(path) && await md5(path) === archiveChecksum) { + if (existsSync(path) && (await md5(path)) === archiveChecksum) { return; } @@ -65,7 +72,11 @@ async function ensureDownloaded(browsers) { }); if (invalidChecksums.length) { - throw new Error(`Error downloading browsers, checksums incorrect for:\n - ${invalidChecksums.join('\n - ')}`); + throw new Error( + `Error downloading browsers, checksums incorrect for:\n - ${invalidChecksums.join( + '\n - ' + )}` + ); } }); } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/index.js b/x-pack/legacy/plugins/reporting/server/browsers/download/index.js deleted file mode 100644 index 9fbfa918e769..000000000000 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - ensureBrowserDownloaded, - ensureAllBrowsersDownloaded, -} from './ensure_downloaded'; diff --git a/x-pack/tasks/helpers/get_flags.js b/x-pack/legacy/plugins/reporting/server/browsers/download/index.ts similarity index 72% rename from x-pack/tasks/helpers/get_flags.js rename to x-pack/legacy/plugins/reporting/server/browsers/download/index.ts index 397330daf4bd..bf7ed450b462 100644 --- a/x-pack/tasks/helpers/get_flags.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function getFlags() { - return process.argv.slice(3); -} +export { ensureBrowserDownloaded, ensureAllBrowsersDownloaded } from './ensure_downloaded'; diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/util.js b/x-pack/legacy/plugins/reporting/server/browsers/download/util.ts similarity index 65% rename from x-pack/legacy/plugins/reporting/server/browsers/download/util.js rename to x-pack/legacy/plugins/reporting/server/browsers/download/util.ts index f9cc905e0126..679106742e3d 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/util.js +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/util.ts @@ -4,33 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Readable } from 'stream'; + /** * Log a message if the DEBUG environment variable is set - * @param {...any} args - * @return {undefined} */ -export function log(...args) { +export function log(...args: any[]) { if (process.env.DEBUG) { + // allow console log since this is off by default and only for debugging + // eslint-disable-next-line no-console console.log(...args); } } /** * Iterate an array asynchronously and in parallel - * @param {Array} array - * @param {Function} asyncFn - * @return {Promise} */ -export function asyncMap(array, asyncFn) { +export function asyncMap(array: T[], asyncFn: (x: T) => T2): Promise { return Promise.all(array.map(asyncFn)); } /** * Wait for a readable stream to end - * @param {Stream.Readable} stream - * @return {Promise} */ -export function readableEnd(stream) { +export function readableEnd(stream: Readable) { return new Promise((resolve, reject) => { stream.on('error', reject).on('end', resolve); }); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/index.js b/x-pack/legacy/plugins/reporting/server/browsers/index.ts similarity index 100% rename from x-pack/legacy/plugins/reporting/server/browsers/index.js rename to x-pack/legacy/plugins/reporting/server/browsers/index.ts diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 7338a959495f..910e576e6e1e 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx @@ -7,13 +7,135 @@ import { ShallowWrapper, shallow } from 'enzyme'; import * as React from 'react'; -import { AreaChartBaseComponent, AreaChartWithCustomPrompt, AreaChart } from './areachart'; -import { ChartHolder, ChartSeriesData } from './common'; +import { AreaChartBaseComponent, AreaChart } from './areachart'; +import { ChartSeriesData } from './common'; import { ScaleType, AreaSeries, Axis } from '@elastic/charts'; -jest.mock('@elastic/charts'); const customHeight = '100px'; const customWidth = '120px'; +const chartDataSets = [ + [ + [ + { + key: 'uniqueSourceIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: null }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 }, + ], + color: '#DB1374', + }, + { + key: 'uniqueDestinationIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 }, + ], + color: '#490092', + }, + ], + ], + [ + [ + { + key: 'uniqueSourceIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1096175 }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 }, + ], + color: '#DB1374', + }, + { + key: 'uniqueDestinationIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 }, + ], + color: '#490092', + }, + ], + ], + [ + [ + { + key: 'uniqueSourceIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: {} }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 }, + ], + color: '#DB1374', + }, + { + key: 'uniqueDestinationIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 }, + ], + color: '#490092', + }, + ], + ], + [ + [ + { + key: 'uniqueSourceIpsHistogram', + value: [], + color: '#DB1374', + }, + { + key: 'uniqueDestinationIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 }, + ], + color: '#490092', + }, + ], + ], +]; + +const chartHolderDataSets = [ + [null], + [[]], + [ + { + key: 'uniqueSourceIpsHistogram', + value: null, + color: '#DB1374', + }, + { + key: 'uniqueDestinationIpsHistogram', + value: null, + color: '#490092', + }, + ], + [ + { + key: 'uniqueSourceIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf() }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf() }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf() }, + ], + color: '#DB1374', + }, + { + key: 'uniqueDestinationIpsHistogram', + value: [ + { x: new Date('2019-05-03T13:00:00.000Z').valueOf() }, + { x: new Date('2019-05-04T01:00:00.000Z').valueOf() }, + { x: new Date('2019-05-04T13:00:00.000Z').valueOf() }, + ], + color: '#490092', + }, + ], +]; describe('AreaChartBaseComponent', () => { let shallowWrapper: ShallowWrapper; const mockAreaChartData: ChartSeriesData[] = [ @@ -186,137 +308,6 @@ describe('AreaChartBaseComponent', () => { }); }); -describe('AreaChartWithCustomPrompt', () => { - let shallowWrapper: ShallowWrapper; - describe.each([ - [ - [ - { - key: 'uniqueSourceIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1096175 }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 }, - ], - color: '#DB1374', - }, - { - key: 'uniqueDestinationIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 }, - ], - color: '#490092', - }, - ], - ], - ] as Array<[ChartSeriesData[]]>)('renders areachart', data => { - beforeAll(() => { - shallowWrapper = shallow( - - ); - }); - - it('render AreaChartBaseComponent', () => { - expect(shallowWrapper.find(AreaChartBaseComponent)).toHaveLength(1); - expect(shallowWrapper.find(ChartHolder)).toHaveLength(0); - }); - }); - - describe.each([ - null, - [], - [ - { - key: 'uniqueSourceIpsHistogram', - value: null, - color: '#DB1374', - }, - { - key: 'uniqueDestinationIpsHistogram', - value: null, - color: '#490092', - }, - ], - [ - { - key: 'uniqueSourceIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf() }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf() }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf() }, - ], - color: '#DB1374', - }, - { - key: 'uniqueDestinationIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf() }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf() }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf() }, - ], - color: '#490092', - }, - ], - [ - [ - { - key: 'uniqueSourceIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: null }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 }, - ], - color: '#DB1374', - }, - { - key: 'uniqueDestinationIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 }, - ], - color: '#490092', - }, - ], - ], - [ - [ - { - key: 'uniqueSourceIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 580213 }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: {} }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12382 }, - ], - color: '#DB1374', - }, - { - key: 'uniqueDestinationIpsHistogram', - value: [ - { x: new Date('2019-05-03T13:00:00.000Z').valueOf(), y: 565975 }, - { x: new Date('2019-05-04T01:00:00.000Z').valueOf(), y: 1084366 }, - { x: new Date('2019-05-04T13:00:00.000Z').valueOf(), y: 12280 }, - ], - color: '#490092', - }, - ], - ], - ] as Array<[ChartSeriesData[] | null | undefined]>)('renders prompt', data => { - beforeAll(() => { - shallowWrapper = shallow( - - ); - }); - - it('render Chart Holder', () => { - expect(shallowWrapper.find(AreaChartBaseComponent)).toHaveLength(0); - expect(shallowWrapper.find(ChartHolder)).toHaveLength(1); - }); - }); -}); - describe('AreaChart', () => { let shallowWrapper: ShallowWrapper; const mockConfig = { @@ -332,20 +323,28 @@ describe('AreaChart', () => { }, customHeight: 324, }; + describe.each(chartDataSets as Array<[ChartSeriesData[]]>)('with valid data [%o]', data => { + beforeAll(() => { + shallowWrapper = shallow(); + }); - it('should render if data exist', () => { - const mockData = [ - { key: 'uniqueSourceIps', value: [{ y: 100, x: 100, g: 'group' }], color: '#DB1374' }, - ]; - shallowWrapper = shallow(); - expect(shallowWrapper.find('AutoSizer')).toHaveLength(1); - expect(shallowWrapper.find('ChartHolder')).toHaveLength(0); + it(`should render area chart`, () => { + expect(shallowWrapper.find('AutoSizer')).toHaveLength(1); + expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(0); + }); }); - it('should render a chartHolder if no data given', () => { - const mockData = [{ key: 'uniqueSourceIps', value: [], color: '#DB1374' }]; - shallowWrapper = shallow(); - expect(shallowWrapper.find('AutoSizer')).toHaveLength(0); - expect(shallowWrapper.find('ChartHolder')).toHaveLength(1); - }); + describe.each(chartHolderDataSets as Array<[ChartSeriesData[] | null | undefined]>)( + 'with invalid data [%o]', + data => { + beforeAll(() => { + shallowWrapper = shallow(); + }); + + it(`should render a chart place holder`, () => { + expect(shallowWrapper.find('AutoSizer')).toHaveLength(0); + expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(1); + }); + } + ); }); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index c4bb01a66753..6347b93772b4 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -17,19 +17,19 @@ import { AreaSeriesStyle, RecursivePartial, } from '@elastic/charts'; -import { getOr, get } from 'lodash/fp'; +import { getOr, get, isNull, isNumber } from 'lodash/fp'; +import { AutoSizer } from '../auto_sizer'; +import { ChartPlaceHolder } from './chart_place_holder'; import { - ChartSeriesData, - ChartHolder, - getSeriesStyle, - WrappedByAutoSizer, - ChartSeriesConfigs, browserTimezone, chartDefaultSettings, + ChartSeriesConfigs, + ChartSeriesData, getChartHeight, getChartWidth, + getSeriesStyle, + WrappedByAutoSizer, } from './common'; -import { AutoSizer } from '../auto_sizer'; // custom series styles: https://ela.st/areachart-styling const getSeriesLineStyle = (): RecursivePartial => { @@ -51,6 +51,17 @@ const getSeriesLineStyle = (): RecursivePartial => { }; }; +const checkIfAllTheDataInTheSeriesAreValid = (series: unknown): series is ChartSeriesData => + !!get('value.length', series) && + get('value', series).every( + ({ x, y }: { x: unknown; y: unknown }) => !isNull(x) && isNumber(y) && y > 0 + ); + +const checkIfAnyValidSeriesExist = ( + data: ChartSeriesData[] | null | undefined +): data is ChartSeriesData[] => + Array.isArray(data) && data.some(checkIfAllTheDataInTheSeriesAreValid); + // https://ela.st/multi-areaseries export const AreaChartBaseComponent = React.memo<{ data: ChartSeriesData[]; @@ -73,12 +84,12 @@ export const AreaChartBaseComponent = React.memo<{ {data.map(series => { const seriesKey = series.key; const seriesSpecId = getSpecId(seriesKey); - return series.value != null ? ( + return checkIfAllTheDataInTheSeriesAreValid(series) ? ( (({ data, height, width, configs }) => { - return data != null && - data.length && - data.every( - ({ value }) => - value != null && - value.length > 0 && - value.every(chart => chart.x != null && chart.y != null) - ) ? ( - - ) : ( - - ); -}); - -AreaChartWithCustomPrompt.displayName = 'AreaChartWithCustomPrompt'; - export const AreaChart = React.memo<{ areaChart: ChartSeriesData[] | null | undefined; configs?: ChartSeriesConfigs | undefined; @@ -135,11 +124,11 @@ export const AreaChart = React.memo<{ const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); - return get(`0.value.length`, areaChart) ? ( + return checkIfAnyValidSeriesExist(areaChart) ? ( {({ measureRef, content: { height, width } }) => ( - ) : ( - + ); }); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx index 527556842126..4b3ec577e648 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx @@ -7,13 +7,113 @@ import { shallow, ShallowWrapper } from 'enzyme'; import * as React from 'react'; -import { BarChartBaseComponent, BarChartWithCustomPrompt, BarChart } from './barchart'; -import { ChartSeriesData, ChartHolder } from './common'; +import { BarChartBaseComponent, BarChart } from './barchart'; +import { ChartSeriesData } from './common'; import { BarSeries, ScaleType, Axis } from '@elastic/charts'; -jest.mock('@elastic/charts'); const customHeight = '100px'; const customWidth = '120px'; +const chartDataSets = [ + [ + [ + { key: 'uniqueSourceIps', value: [{ y: 1714, x: 'uniqueSourceIps' }], color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + value: [{ y: 2354, x: 'uniqueDestinationIps' }], + color: '#490092', + }, + ], + ], + [ + [ + { key: 'uniqueSourceIps', value: [{ y: 1714, x: '' }], color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + value: [{ y: 2354, x: '' }], + color: '#490092', + }, + ], + ], + [ + [ + { key: 'uniqueSourceIps', value: [{ y: 1714, x: 'uniqueSourceIps' }], color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + value: [{ y: 0, x: 'uniqueDestinationIps' }], + color: '#490092', + }, + ], + ], + [ + [ + { key: 'uniqueSourceIps', value: [{ y: null, x: 'uniqueSourceIps' }], color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + value: [{ y: 2354, x: 'uniqueDestinationIps' }], + color: '#490092', + }, + ], + ], +]; + +const chartHolderDataSets: Array<[ChartSeriesData[] | undefined | null]> = [ + [[]], + [null], + [ + [ + { key: 'uniqueSourceIps', color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + color: '#490092', + }, + ], + ], + [ + [ + { key: 'uniqueSourceIps', value: [], color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + value: [], + color: '#490092', + }, + ], + ], + [ + [ + { key: 'uniqueSourceIps', value: [{}], color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + value: [{}], + color: '#490092', + }, + ], + ], + [ + [ + { key: 'uniqueSourceIps', value: [{ y: 0, x: 'uniqueSourceIps' }], color: '#DB1374' }, + { + key: 'uniqueDestinationIps', + value: [{ y: 0, x: 'uniqueDestinationIps' }], + color: '#490092', + }, + ], + ], +] as any; // eslint-disable-line @typescript-eslint/no-explicit-any + +const mockConfig = { + series: { + xScaleType: ScaleType.Time, + yScaleType: ScaleType.Linear, + stackAccessors: ['g'], + }, + axis: { + xTickFormatter: jest.fn(), + yTickFormatter: jest.fn(), + tickSize: 8, + }, + customHeight: 324, +}; + describe('BarChartBaseComponent', () => { let shallowWrapper: ShallowWrapper; const mockBarChartData: ChartSeriesData[] = [ @@ -168,164 +268,28 @@ describe('BarChartBaseComponent', () => { }); }); -describe.each([ - [ - [ - { key: 'uniqueSourceIps', value: [{ y: 1714, x: 'uniqueSourceIps' }], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [{ y: 2354, x: 'uniqueDestinationIps' }], - color: '#490092', - }, - ], - ], - [ - [ - { key: 'uniqueSourceIps', value: [{ y: 1714, x: '' }], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [{ y: 2354, x: '' }], - color: '#490092', - }, - ], - ], - [ - [ - { key: 'uniqueSourceIps', value: [{ y: 1714, x: 'uniqueSourceIps' }], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [{ y: 0, x: 'uniqueDestinationIps' }], - color: '#490092', - }, - ], - ], -])('BarChartWithCustomPrompt', mockBarChartData => { +describe.each(chartDataSets)('BarChart with valid data [%o]', data => { let shallowWrapper: ShallowWrapper; - describe('renders barchart', () => { - beforeAll(() => { - shallowWrapper = shallow( - - ); - }); - - it('render BarChartBaseComponent', () => { - expect(shallowWrapper.find(BarChartBaseComponent)).toHaveLength(1); - expect(shallowWrapper.find(ChartHolder)).toHaveLength(0); - }); - }); -}); -const table: Array<[ChartSeriesData[] | undefined | null]> = [ - [], - null, - [ - [ - { key: 'uniqueSourceIps', color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - color: '#490092', - }, - ], - ], - [ - [ - { key: 'uniqueSourceIps', value: [], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [], - color: '#490092', - }, - ], - ], - [ - [ - { key: 'uniqueSourceIps', value: [{}], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [{}], - color: '#490092', - }, - ], - ], - [ - [ - { key: 'uniqueSourceIps', value: [{ y: 0, x: 'uniqueSourceIps' }], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [{ y: 0, x: 'uniqueDestinationIps' }], - color: '#490092', - }, - ], - ], - [ - [ - { key: 'uniqueSourceIps', value: [{ y: null, x: 'uniqueSourceIps' }], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [{ y: 2354, x: 'uniqueDestinationIps' }], - color: '#490092', - }, - ], - ], - [ - [ - { key: 'uniqueSourceIps', value: [{ y: null, x: 'uniqueSourceIps' }], color: '#DB1374' }, - { - key: 'uniqueDestinationIps', - value: [{ y: null, x: 'uniqueDestinationIps' }], - color: '#490092', - }, - ], - ], -] as any; // eslint-disable-line @typescript-eslint/no-explicit-any - -describe.each(table)('renders prompt', data => { - let shallowWrapper: ShallowWrapper; beforeAll(() => { - shallowWrapper = shallow( - - ); + shallowWrapper = shallow(); }); - it('render Chart Holder', () => { - expect(shallowWrapper.find(BarChartBaseComponent)).toHaveLength(0); - expect(shallowWrapper.find(ChartHolder)).toHaveLength(1); + it(`should render chart`, () => { + expect(shallowWrapper.find('AutoSizer')).toHaveLength(1); + expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(0); }); }); -describe('BarChart', () => { +describe.each(chartHolderDataSets)('BarChart with invalid data [%o]', data => { let shallowWrapper: ShallowWrapper; - const mockConfig = { - series: { - xScaleType: ScaleType.Time, - yScaleType: ScaleType.Linear, - stackAccessors: ['g'], - }, - axis: { - xTickFormatter: jest.fn(), - yTickFormatter: jest.fn(), - tickSize: 8, - }, - customHeight: 324, - }; - it('should render if data exist', () => { - const mockData = [ - { key: 'uniqueSourceIps', value: [{ y: 100, x: 100, g: 'group' }], color: '#DB1374' }, - ]; - shallowWrapper = shallow(); - expect(shallowWrapper.find('AutoSizer')).toHaveLength(1); - expect(shallowWrapper.find('ChartHolder')).toHaveLength(0); + beforeAll(() => { + shallowWrapper = shallow(); }); - it('should render a chartHolder if no data given', () => { - const mockData = [{ key: 'uniqueSourceIps', value: [], color: '#DB1374' }]; - shallowWrapper = shallow(); + it(`should render chart holder`, () => { expect(shallowWrapper.find('AutoSizer')).toHaveLength(0); - expect(shallowWrapper.find('ChartHolder')).toHaveLength(1); + expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(1); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index 02345fc149c2..9ef26c690c56 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -16,20 +16,33 @@ import { ScaleType, Settings, } from '@elastic/charts'; -import { getOr, get } from 'lodash/fp'; +import { getOr, get, isNumber } from 'lodash/fp'; +import { AutoSizer } from '../auto_sizer'; +import { ChartPlaceHolder } from './chart_place_holder'; import { - ChartSeriesData, - WrappedByAutoSizer, - ChartHolder, - SeriesType, - getSeriesStyle, - ChartSeriesConfigs, browserTimezone, chartDefaultSettings, + ChartSeriesConfigs, + ChartSeriesData, + checkIfAllValuesAreZero, + getSeriesStyle, getChartHeight, getChartWidth, + SeriesType, + WrappedByAutoSizer, } from './common'; -import { AutoSizer } from '../auto_sizer'; + +const checkIfAllTheDataInTheSeriesAreValid = (series: ChartSeriesData): series is ChartSeriesData => + series != null && + !!get('value.length', series) && + (series.value || []).every(({ x, y }) => isNumber(y) && y >= 0); + +const checkIfAnyValidSeriesExist = ( + data: ChartSeriesData[] | null | undefined +): data is ChartSeriesData[] => + Array.isArray(data) && + !checkIfAllValuesAreZero(data) && + data.some(checkIfAllTheDataInTheSeriesAreValid); // Bar chart rotation: https://ela.st/chart-rotations export const BarChartBaseComponent = React.memo<{ @@ -54,7 +67,7 @@ export const BarChartBaseComponent = React.memo<{ const barSeriesKey = series.key; const barSeriesSpecId = getSpecId(barSeriesKey); const seriesType = SeriesType.BAR; - return ( + return checkIfAllTheDataInTheSeriesAreValid ? ( - ); + ) : null; })} (({ data, height, width, configs }) => { - return data && - data.length && - data.some( - ({ value }) => - value != null && value.length > 0 && value.every(chart => chart.y != null && chart.y >= 0) - ) ? ( - - ) : ( - - ); -}); - -BarChartWithCustomPrompt.displayName = 'BarChartWithCustomPrompt'; - export const BarChart = React.memo<{ barChart: ChartSeriesData[] | null | undefined; configs?: ChartSeriesConfigs | undefined; }>(({ barChart, configs }) => { const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); - return get(`0.value.length`, barChart) ? ( + return checkIfAnyValidSeriesExist(barChart) ? ( {({ measureRef, content: { height, width } }) => ( - ) : ( - + ); }); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/chart_place_holder.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/chart_place_holder.test.tsx new file mode 100644 index 000000000000..7674fd09739f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/charts/chart_place_holder.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallow, ShallowWrapper } from 'enzyme'; +import React from 'react'; +import { ChartPlaceHolder } from './chart_place_holder'; +import { ChartSeriesData } from './common'; + +describe('ChartPlaceHolder', () => { + let shallowWrapper: ShallowWrapper; + const mockDataAllZeros = [ + { + key: 'mockKeyA', + color: 'mockColor', + value: [{ x: 'a', y: 0 }, { x: 'b', y: 0 }], + }, + { + key: 'mockKeyB', + color: 'mockColor', + value: [{ x: 'a', y: 0 }, { x: 'b', y: 0 }], + }, + ]; + const mockDataUnexpectedValue = [ + { + key: 'mockKeyA', + color: 'mockColor', + value: [{ x: 'a', y: '' }, { x: 'b', y: 0 }], + }, + { + key: 'mockKeyB', + color: 'mockColor', + value: [{ x: 'a', y: {} }, { x: 'b', y: 0 }], + }, + ]; + + it('should render with default props', () => { + const height = `100%`; + const width = `100%`; + shallowWrapper = shallow(); + expect(shallowWrapper.props()).toMatchObject({ + height, + width, + }); + }); + + it('should render with given props', () => { + const height = `100px`; + const width = `100px`; + shallowWrapper = shallow( + + ); + expect(shallowWrapper.props()).toMatchObject({ + height, + width, + }); + }); + + it('should render correct wording when all values returned zero', () => { + const height = `100px`; + const width = `100px`; + shallowWrapper = shallow( + + ); + expect( + shallowWrapper + .find(`[data-test-subj="chartHolderText"]`) + .childAt(0) + .text() + ).toEqual('All values returned zero'); + }); + + it('should render correct wording when unexpected value exists', () => { + const height = `100px`; + const width = `100px`; + shallowWrapper = shallow( + + ); + expect( + shallowWrapper + .find(`[data-test-subj="chartHolderText"]`) + .childAt(0) + .text() + ).toEqual('Chart Data Not Available'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/chart_place_holder.tsx b/x-pack/legacy/plugins/siem/public/components/charts/chart_place_holder.tsx new file mode 100644 index 000000000000..22122b5fad74 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/charts/chart_place_holder.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFlexItem, EuiText, EuiFlexGroup } from '@elastic/eui'; +import styled from 'styled-components'; +import { ChartSeriesData, checkIfAllValuesAreZero } from './common'; +import * as i18n from './translation'; + +const FlexGroup = styled(EuiFlexGroup)<{ height?: string | null; width?: string | null }>` + height: ${({ height }) => (height ? height : '100%')}; + width: ${({ width }) => (width ? width : '100%')}; + position: relative; + margin: 0; +`; + +FlexGroup.displayName = 'FlexGroup'; + +export const ChartPlaceHolder = ({ + height = '100%', + width = '100%', + data, +}: { + height?: string | null; + width?: string | null; + data: ChartSeriesData[] | null | undefined; +}) => ( + + + + {checkIfAllValuesAreZero(data) + ? i18n.ALL_VALUES_ZEROS_TITLE + : i18n.DATA_NOT_AVAILABLE_TITLE} + + + +); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx index f23b97d8cd5f..0fc7bc6afc21 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx @@ -3,18 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { shallow, ShallowWrapper } from 'enzyme'; -import * as React from 'react'; +import { shallow } from 'enzyme'; +import React from 'react'; import { - ChartHolder, + checkIfAllValuesAreZero, + defaultChartHeight, getChartHeight, getChartWidth, - WrappedByAutoSizer, - defaultChartHeight, getSeriesStyle, - SeriesType, getTheme, + SeriesType, + WrappedByAutoSizer, + ChartSeriesData, } from './common'; import 'jest-styled-components'; import { mergeWithDefaultTheme, LIGHT_THEME } from '@elastic/charts'; @@ -26,30 +26,6 @@ jest.mock('@elastic/charts', () => { }; }); -describe('ChartHolder', () => { - let shallowWrapper: ShallowWrapper; - - it('should render with default props', () => { - const height = `100%`; - const width = `100%`; - shallowWrapper = shallow(); - expect(shallowWrapper.props()).toMatchObject({ - height, - width, - }); - }); - - it('should render with given props', () => { - const height = `100px`; - const width = `100px`; - shallowWrapper = shallow(); - expect(shallowWrapper.props()).toMatchObject({ - height, - width, - }); - }); -}); - describe('WrappedByAutoSizer', () => { it('should render correct default height', () => { const wrapper = shallow(); @@ -88,7 +64,7 @@ describe('getTheme', () => { chartMargins: { bottom: 0, left: 0, right: 0, top: 4 }, chartPaddings: { bottom: 0, left: 0, right: 0, top: 0 }, scales: { - barsPadding: 0.5, + barsPadding: 0.05, }, }; getTheme(); @@ -130,3 +106,46 @@ describe('getChartWidth', () => { expect(height).toEqual(defaultChartHeight); }); }); + +describe('checkIfAllValuesAreZero', () => { + const mockInvalidDataSets: Array<[ChartSeriesData[]]> = [ + [[{ key: 'mockKey', color: 'mockColor', value: [{ x: 1, y: 0 }, { x: 1, y: 1 }] }]], + [ + [ + { key: 'mockKeyA', color: 'mockColor', value: [{ x: 1, y: 0 }, { x: 1, y: 1 }] }, + { key: 'mockKeyB', color: 'mockColor', value: [{ x: 1, y: 0 }, { x: 1, y: 0 }] }, + ], + ], + ]; + const mockValidDataSets: Array<[ChartSeriesData[]]> = [ + [[{ key: 'mockKey', color: 'mockColor', value: [{ x: 0, y: 0 }, { x: 1, y: 0 }] }]], + [ + [ + { key: 'mockKeyA', color: 'mockColor', value: [{ x: 1, y: 0 }, { x: 3, y: 0 }] }, + { key: 'mockKeyB', color: 'mockColor', value: [{ x: 2, y: 0 }, { x: 4, y: 0 }] }, + ], + ], + ]; + + describe.each(mockInvalidDataSets)('with data [%o]', data => { + let result: boolean; + beforeAll(() => { + result = checkIfAllValuesAreZero(data); + }); + + it(`should return false`, () => { + expect(result).toBeFalsy(); + }); + }); + + describe.each(mockValidDataSets)('with data [%o]', data => { + let result: boolean; + beforeAll(() => { + result = checkIfAllValuesAreZero(data); + }); + + it(`should return true`, () => { + expect(result).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx index 59873b2cd6a3..7ac91437920e 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx @@ -3,58 +3,33 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiText, EuiFlexItem } from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; + +import chrome from 'ui/chrome'; import { CustomSeriesColorsMap, + DARK_THEME, DataSeriesColorsValues, getSpecId, + LIGHT_THEME, mergeWithDefaultTheme, PartialTheme, - LIGHT_THEME, - DARK_THEME, + Rendering, + Rotation, ScaleType, - TickFormatter, SettingSpecProps, - Rotation, - Rendering, + TickFormatter, } from '@elastic/charts'; -import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; import moment from 'moment-timezone'; +import styled from 'styled-components'; import { DEFAULT_DATE_FORMAT_TZ, DEFAULT_DARK_MODE } from '../../../common/constants'; + export const defaultChartHeight = '100%'; export const defaultChartWidth = '100%'; const chartDefaultRotation: Rotation = 0; const chartDefaultRendering: Rendering = 'canvas'; -const FlexGroup = styled(EuiFlexGroup)<{ height?: string | null; width?: string | null }>` - height: ${({ height }) => (height ? height : '100%')}; - width: ${({ width }) => (width ? width : '100%')}; -`; - -FlexGroup.displayName = 'FlexGroup'; export type UpdateDateRange = (min: number, max: number) => void; -export const ChartHolder = ({ - height = '100%', - width = '100%', -}: { - height?: string | null; - width?: string | null; -}) => ( - - - - {i18n.translate('xpack.siem.chart.dataNotAvailableTitle', { - defaultMessage: 'Chart Data Not Available', - })} - - - -); - export interface ChartData { x: number | string | null; y: number | string | null; @@ -136,7 +111,7 @@ export const getTheme = () => { bottom: 0, }, scales: { - barsPadding: 0.5, + barsPadding: 0.05, }, }; const isDarkMode: boolean = chrome.getUiSettingsClient().get(DEFAULT_DARK_MODE); @@ -166,3 +141,9 @@ export const getChartWidth = (customWidth?: number, autoSizerWidth?: number): st const height = customWidth || autoSizerWidth; return height ? `${height}px` : defaultChartWidth; }; + +export const checkIfAllValuesAreZero = (data: ChartSeriesData[] | null | undefined): boolean => + Array.isArray(data) && + data.every(series => { + return Array.isArray(series.value) && (series.value as ChartData[]).every(({ y }) => y === 0); + }); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/translation.ts b/x-pack/legacy/plugins/siem/public/components/charts/translation.ts new file mode 100644 index 000000000000..341cb7782f87 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/charts/translation.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ALL_VALUES_ZEROS_TITLE = i18n.translate('xpack.siem.chart.dataAllValuesZerosTitle', { + defaultMessage: 'All values returned zero', +}); + +export const DATA_NOT_AVAILABLE_TITLE = i18n.translate('xpack.siem.chart.dataNotAvailableTitle', { + defaultMessage: 'Chart Data Not Available', +}); diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx index f95b9e6b3ecf..2898541a4a3d 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx @@ -53,7 +53,7 @@ const getBarchartConfigs = (from: number, to: number, onBrushEnd: UpdateDateRang showLegend: true, theme: { scales: { - barsPadding: 0.05, + barsPadding: 0.08, }, chartMargins: { left: 0, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts index bb06926ec08f..e06bb1477bc7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts @@ -184,12 +184,19 @@ export const mockEnableChartsData = { { key: 'uniqueSourcePrivateIps', color: '#DB1374', - value: [{ x: 'Src.', y: 383, g: 'uniqueSourcePrivateIps' }], + value: [ + { + x: 'Src.', + y: 383, + g: 'uniqueSourcePrivateIps', + y0: 0, + }, + ], }, { key: 'uniqueDestinationPrivateIps', color: '#490092', - value: [{ x: 'Dest.', y: 18, g: 'uniqueDestinationPrivateIps' }], + value: [{ x: 'Dest.', y: 18, g: 'uniqueDestinationPrivateIps', y0: 0 }], }, ], description: 'Unique private IPs', diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap index 9541ad4de043..7475220b56e7 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap @@ -994,6 +994,9 @@ exports[`Stat Items Component rendering kpis with charts it renders the default }, "customHeight": 74, "series": Object { + "stackAccessors": Array [ + "y0", + ], "xScaleType": "ordinal", "yScaleType": "linear", }, diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx b/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx index 110d14638170..c206a4d33270 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx @@ -103,6 +103,7 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener series: { xScaleType: ScaleType.Ordinal, yScaleType: ScaleType.Linear, + stackAccessors: ['y0'], }, axis: { xTickFormatter: numberFormatter, @@ -145,6 +146,7 @@ export const addValueToBarChart = ( x, y, g: key, + y0: 0, }, ]; diff --git a/x-pack/legacy/plugins/task_manager/README.md b/x-pack/legacy/plugins/task_manager/README.md index adf770644369..63c92102af25 100644 --- a/x-pack/legacy/plugins/task_manager/README.md +++ b/x-pack/legacy/plugins/task_manager/README.md @@ -171,9 +171,14 @@ The data stored for a task instance looks something like this: // This is incremented if a task fails or times out. attempts: 0, - // Currently, this is either idle | running. It is used to + // Currently, this is either idle | claiming | running | failed. It is used to // coordinate which Kibana instance owns / is running a specific // task instance. + // idle: Task Instance isn't being worked on + // claiming: A Kibana instance has claimed ownership but hasn't started running + // the Task Instance yet + // running: A Kibana instance has began working on the Task Instance + // failed: The last run of the Task Instance failed, waiting to retry status: 'idle', // The params specific to this task instance, which will be @@ -207,6 +212,9 @@ The data stored for a task instance looks something like this: // An application-specific designation, allowing different Kibana // plugins / apps to query for only those tasks they care about. scope: ['alerting'], + + // The Kibana UUID of the Kibana instance who last claimed ownership for running this task. + ownerId: '123e4567-e89b-12d3-a456-426655440000' } ``` diff --git a/x-pack/legacy/plugins/task_manager/lib/middleware.test.ts b/x-pack/legacy/plugins/task_manager/lib/middleware.test.ts index ff840061285c..07afee179746 100644 --- a/x-pack/legacy/plugins/task_manager/lib/middleware.test.ts +++ b/x-pack/legacy/plugins/task_manager/lib/middleware.test.ts @@ -31,6 +31,7 @@ const getMockConcreteTaskInstance = () => { state: any; taskType: string; params: any; + ownerId: string | null; } = { id: 'hy8o99o83', sequenceNumber: 1, @@ -44,6 +45,7 @@ const getMockConcreteTaskInstance = () => { state: {}, taskType: 'nice_task', params: { abc: 'def' }, + ownerId: null, }; return concrete; }; @@ -92,19 +94,19 @@ describe('addMiddlewareToChain', () => { .beforeSave({ taskInstance: getMockTaskInstance() }) .then((saveOpts: any) => { expect(saveOpts).toMatchInlineSnapshot(` -Object { - "taskInstance": Object { - "params": Object { - "abc": "def", - "m1": true, - "m2": true, - "m3": true, - }, - "state": Object {}, - "taskType": "nice_task", - }, -} -`); + Object { + "taskInstance": Object { + "params": Object { + "abc": "def", + "m1": true, + "m2": true, + "m3": true, + }, + "state": Object {}, + "taskType": "nice_task", + }, + } + `); }); }); @@ -145,29 +147,30 @@ Object { .beforeRun(getMockRunContext(getMockConcreteTaskInstance())) .then(contextOpts => { expect(contextOpts).toMatchInlineSnapshot(` -Object { - "kbnServer": Object {}, - "m1": true, - "m2": true, - "m3": true, - "taskInstance": Object { - "attempts": 0, - "id": "hy8o99o83", - "params": Object { - "abc": "def", - }, - "primaryTerm": 1, - "retryAt": null, - "runAt": 2018-09-18T05:33:09.588Z, - "scheduledAt": 2018-09-18T05:33:09.588Z, - "sequenceNumber": 1, - "startedAt": null, - "state": Object {}, - "status": "idle", - "taskType": "nice_task", - }, -} -`); + Object { + "kbnServer": Object {}, + "m1": true, + "m2": true, + "m3": true, + "taskInstance": Object { + "attempts": 0, + "id": "hy8o99o83", + "ownerId": null, + "params": Object { + "abc": "def", + }, + "primaryTerm": 1, + "retryAt": null, + "runAt": 2018-09-18T05:33:09.588Z, + "scheduledAt": 2018-09-18T05:33:09.588Z, + "sequenceNumber": 1, + "startedAt": null, + "state": Object {}, + "status": "idle", + "taskType": "nice_task", + }, + } + `); }); }); }); diff --git a/x-pack/legacy/plugins/task_manager/mappings.json b/x-pack/legacy/plugins/task_manager/mappings.json index 6638a75d8254..96653a4de1b3 100644 --- a/x-pack/legacy/plugins/task_manager/mappings.json +++ b/x-pack/legacy/plugins/task_manager/mappings.json @@ -36,6 +36,9 @@ }, "scope": { "type": "keyword" + }, + "ownerId": { + "type": "keyword" } } } diff --git a/x-pack/legacy/plugins/task_manager/task.ts b/x-pack/legacy/plugins/task_manager/task.ts index 5c85f692fd61..dd74acc2636e 100644 --- a/x-pack/legacy/plugins/task_manager/task.ts +++ b/x-pack/legacy/plugins/task_manager/task.ts @@ -136,7 +136,7 @@ export interface TaskDictionary { [taskType: string]: T; } -export type TaskStatus = 'idle' | 'running' | 'failed'; +export type TaskStatus = 'idle' | 'claiming' | 'running' | 'failed'; /* * A task instance represents all of the data required to store, fetch, @@ -209,6 +209,11 @@ export interface TaskInstance { * and then query such tasks to provide a glimpse at only reporting tasks, rather than at all tasks. */ scope?: string[]; + + /** + * The random uuid of the Kibana instance which claimed ownership of the task last + */ + ownerId?: string | null; } /** @@ -268,4 +273,9 @@ export interface ConcreteTaskInstance extends TaskInstance { * any state, this will be the empy object: {} */ state: Record; + + /** + * The random uuid of the Kibana instance which claimed ownership of the task last + */ + ownerId: string | null; } diff --git a/x-pack/legacy/plugins/task_manager/task_manager.ts b/x-pack/legacy/plugins/task_manager/task_manager.ts index 01ddd1efbe76..7d2794fa33bc 100644 --- a/x-pack/legacy/plugins/task_manager/task_manager.ts +++ b/x-pack/legacy/plugins/task_manager/task_manager.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import uuid from 'uuid'; import { SavedObjectsClientContract, SavedObjectsSerializer } from 'src/core/server'; import { Logger } from './types'; import { fillPool } from './lib/fill_pool'; import { addMiddlewareToChain, BeforeSaveMiddlewareParams, Middleware } from './lib/middleware'; import { sanitizeTaskDefinitions } from './lib/sanitize_task_definitions'; +import { intervalFromNow } from './lib/intervals'; import { TaskDefinition, TaskDictionary, @@ -29,6 +31,12 @@ export interface TaskManagerOpts { serializer: SavedObjectsSerializer; } +function generateTaskManagerUUID(logger: Logger): string { + const taskManagerUUID = uuid.v4(); + logger.info(`Initialising Task Manager with UUID: ${taskManagerUUID}`); + return taskManagerUUID; +} + /* * The TaskManager is the public interface into the task manager system. This glues together * all of the disparate modules in one integration point. The task manager operates in two different ways: @@ -77,7 +85,9 @@ export class TaskManager { index: opts.config.get('xpack.task_manager.index'), maxAttempts: opts.config.get('xpack.task_manager.max_attempts'), definitions: this.definitions, + taskManagerId: generateTaskManagerUUID(this.logger), }); + const pool = new TaskPool({ logger: this.logger, maxWorkers: this.maxWorkers, @@ -93,9 +103,7 @@ export class TaskManager { const poller = new TaskPoller({ logger: this.logger, pollInterval: opts.config.get('xpack.task_manager.poll_interval'), - work(): Promise { - return fillPool(pool.run, store.fetchAvailableTasks, createRunner); - }, + work: (): Promise => fillPool(pool.run, () => this.claimAvailableTasks(), createRunner), }); this.pool = pool; @@ -127,6 +135,20 @@ export class TaskManager { startPoller(); } + private async claimAvailableTasks() { + const { docs, claimedTasks } = await this.store.claimAvailableTasks({ + size: this.pool.availableWorkers, + claimOwnershipUntil: intervalFromNow('30s')!, + }); + + if (docs.length !== claimedTasks) { + this.logger.warn( + `[Task Ownership error]: (${claimedTasks}) tasks were claimed by Kibana, but (${docs.length}) tasks were fetched` + ); + } + return docs; + } + private async waitUntilStarted() { if (!this.isStarted) { await new Promise(resolve => { diff --git a/x-pack/legacy/plugins/task_manager/task_pool.test.ts b/x-pack/legacy/plugins/task_manager/task_pool.test.ts index 795d1099f3c6..e6a83dd1911b 100644 --- a/x-pack/legacy/plugins/task_manager/task_pool.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_pool.test.ts @@ -195,7 +195,7 @@ describe('TaskPool', () => { return { isExpired: false, cancel: async () => undefined, - claimOwnership: async () => true, + markTaskAsRunning: async () => true, run: mockRun(), }; } diff --git a/x-pack/legacy/plugins/task_manager/task_pool.ts b/x-pack/legacy/plugins/task_manager/task_pool.ts index 42a0e9ad436a..7afbec65a0d8 100644 --- a/x-pack/legacy/plugins/task_manager/task_pool.ts +++ b/x-pack/legacy/plugins/task_manager/task_pool.ts @@ -75,7 +75,7 @@ export class TaskPool { private async attemptToRun(tasks: TaskRunner[]) { for (const task of tasks) { if (this.availableWorkers > 0) { - if (await task.claimOwnership()) { + if (await task.markTaskAsRunning()) { this.running.add(task); task .run() @@ -83,6 +83,8 @@ export class TaskPool { this.logger.warn(`Task ${task} failed in attempt to run: ${err.message}`); }) .then(() => this.running.delete(task)); + } else { + this.logger.warn(`Failed to mark Task ${task} as running`); } } else { return false; diff --git a/x-pack/legacy/plugins/task_manager/task_runner.test.ts b/x-pack/legacy/plugins/task_manager/task_runner.test.ts index eaaa6717230a..49c55279eafe 100644 --- a/x-pack/legacy/plugins/task_manager/task_runner.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_runner.test.ts @@ -243,7 +243,7 @@ describe('TaskManagerRunner', () => { }, }); - await runner.claimOwnership(); + await runner.markTaskAsRunning(); sinon.assert.calledOnce(store.update); const instance = store.update.args[0][0]; @@ -409,7 +409,7 @@ describe('TaskManagerRunner', () => { }, }); - await runner.claimOwnership(); + await runner.markTaskAsRunning(); sinon.assert.calledOnce(store.update); sinon.assert.calledWith(getRetryStub, initialAttempts + 1); @@ -442,7 +442,7 @@ describe('TaskManagerRunner', () => { }, }); - await runner.claimOwnership(); + await runner.markTaskAsRunning(); sinon.assert.calledOnce(store.update); sinon.assert.calledWith(getRetryStub, initialAttempts + 1); @@ -477,7 +477,7 @@ describe('TaskManagerRunner', () => { }, }); - await runner.claimOwnership(); + await runner.markTaskAsRunning(); sinon.assert.calledOnce(store.update); sinon.assert.calledWith(getRetryStub, initialAttempts + 1); @@ -510,7 +510,7 @@ describe('TaskManagerRunner', () => { }, }); - await runner.claimOwnership(); + await runner.markTaskAsRunning(); sinon.assert.calledOnce(store.update); sinon.assert.notCalled(getRetryStub); @@ -619,6 +619,7 @@ describe('TaskManagerRunner', () => { state: {}, status: 'idle', user: 'example', + ownerId: null, }, opts.instance || {} ), diff --git a/x-pack/legacy/plugins/task_manager/task_runner.ts b/x-pack/legacy/plugins/task_manager/task_runner.ts index e97f361b2ca0..a51e0c885b97 100644 --- a/x-pack/legacy/plugins/task_manager/task_runner.ts +++ b/x-pack/legacy/plugins/task_manager/task_runner.ts @@ -30,7 +30,7 @@ const defaultBackoffPerFailure = 5 * 60 * 1000; export interface TaskRunner { isExpired: boolean; cancel: CancelFunction; - claimOwnership: () => Promise; + markTaskAsRunning: () => Promise; run: () => Promise; toString?: () => string; } @@ -152,12 +152,25 @@ export class TaskManagerRunner implements TaskRunner { * * @returns {Promise} */ - public async claimOwnership(): Promise { + public async markTaskAsRunning(): Promise { const VERSION_CONFLICT_STATUS = 409; const attempts = this.instance.attempts + 1; const now = new Date(); + const ownershipClaimedUntil = this.instance.retryAt; + try { + const { id } = this.instance; + + const timeUntilClaimExpires = howManyMsUntilOwnershipClaimExpires(ownershipClaimedUntil); + if (timeUntilClaimExpires < 0) { + this.logger.debug( + `[Task Runner] Task ${id} started after ownership expired (${Math.abs( + timeUntilClaimExpires + )}ms after expiry)` + ); + } + this.instance = await this.store.update({ ...this.instance, status: 'running', @@ -174,6 +187,17 @@ export class TaskManagerRunner implements TaskRunner { }), }); + const timeUntilClaimExpiresAfterUpdate = howManyMsUntilOwnershipClaimExpires( + ownershipClaimedUntil + ); + if (timeUntilClaimExpiresAfterUpdate < 0) { + this.logger.debug( + `[Task Runner] Task ${id} ran after ownership expired (${Math.abs( + timeUntilClaimExpiresAfterUpdate + )}ms after expiry)` + ); + } + return true; } catch (error) { if (error.statusCode !== VERSION_CONFLICT_STATUS) { @@ -246,6 +270,7 @@ export class TaskManagerRunner implements TaskRunner { status, startedAt: null, retryAt: null, + ownerId: null, attempts: result.error ? this.instance.attempts : 0, }); @@ -323,3 +348,7 @@ function sanitizeInstance(instance: ConcreteTaskInstance): ConcreteTaskInstance state: instance.state || {}, }; } + +function howManyMsUntilOwnershipClaimExpires(ownershipClaimedUntil: Date | null): number { + return ownershipClaimedUntil ? ownershipClaimedUntil.getTime() - Date.now() : 0; +} diff --git a/x-pack/legacy/plugins/task_manager/task_store.test.ts b/x-pack/legacy/plugins/task_manager/task_store.test.ts index c24ffe09d18e..65b49820d6e6 100644 --- a/x-pack/legacy/plugins/task_manager/task_store.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_store.test.ts @@ -6,8 +6,9 @@ import _ from 'lodash'; import sinon from 'sinon'; +import uuid from 'uuid'; import { TaskDictionary, TaskDefinition, TaskInstance, TaskStatus } from './task'; -import { FetchOpts, TaskStore } from './task_store'; +import { FetchOpts, StoreOpts, OwnershipClaimingOpts, TaskStore } from './task_store'; import { mockLogger } from './test_utils'; import { SavedObjectsClientMock } from 'src/core/server/mocks'; import { SavedObjectsSerializer, SavedObjectsSchema, SavedObjectAttributes } from 'src/core/server'; @@ -60,6 +61,7 @@ describe('TaskStore', () => { ); const store = new TaskStore({ index: 'tasky', + taskManagerId: '', serializer, callCluster, maxAttempts: 2, @@ -159,6 +161,7 @@ describe('TaskStore', () => { const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits } })); const store = new TaskStore({ index: 'tasky', + taskManagerId: '', serializer, callCluster, maxAttempts: 2, @@ -350,6 +353,7 @@ describe('TaskStore', () => { const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits: [] } })); const store = new TaskStore({ index: 'tasky', + taskManagerId: '', serializer, callCluster, definitions: taskDefinitions, @@ -549,6 +553,321 @@ describe('TaskStore', () => { }); }); + describe('claimAvailableTasks', () => { + async function testClaimAvailableTasks({ + opts = {}, + hits = generateFakeTasks(1), + claimingOpts, + }: { + opts: Partial; + hits?: any[]; + claimingOpts: OwnershipClaimingOpts; + }) { + const versionConflicts = 2; + const callCluster = sinon.spy(async (name: string, params?: any) => + name === 'updateByQuery' + ? { + total: hits.length + versionConflicts, + updated: hits.length, + version_conflicts: versionConflicts, + } + : { hits: { hits } } + ); + const store = new TaskStore({ + callCluster, + maxAttempts: 2, + definitions: taskDefinitions, + serializer, + savedObjectsRepository: savedObjectsClient, + taskManagerId: '', + index: '', + ...opts, + }); + + const result = await store.claimAvailableTasks(claimingOpts); + + sinon.assert.calledTwice(callCluster); + sinon.assert.calledWithMatch(callCluster, 'updateByQuery', { max_docs: claimingOpts.size }); + sinon.assert.calledWithMatch(callCluster, 'search', { body: { size: claimingOpts.size } }); + + return { + result, + args: Object.assign({}, ...callCluster.args.map(([name, args]) => ({ [name]: args }))), + }; + } + + test('it returns normally with no tasks when the index does not exist.', async () => { + const callCluster = sinon.spy(async (name: string, params?: any) => ({ + total: 0, + updated: 0, + })); + const store = new TaskStore({ + index: 'tasky', + taskManagerId: '', + serializer, + callCluster, + definitions: taskDefinitions, + maxAttempts: 2, + savedObjectsRepository: savedObjectsClient, + }); + const { docs } = await store.claimAvailableTasks({ + claimOwnershipUntil: new Date(), + size: 10, + }); + sinon.assert.calledOnce(callCluster); + sinon.assert.calledWithMatch(callCluster, 'updateByQuery', { + ignoreUnavailable: true, + max_docs: 10, + }); + expect(docs.length).toBe(0); + }); + + test('it filters claimed tasks down by supported types, maxAttempts, status, and runAt', async () => { + const maxAttempts = _.random(2, 43); + const customMaxAttempts = _.random(44, 100); + const { + args: { + updateByQuery: { + body: { query }, + }, + }, + } = await testClaimAvailableTasks({ + opts: { + maxAttempts, + definitions: { + foo: { + type: 'foo', + title: '', + createTaskRunner: jest.fn(), + }, + bar: { + type: 'bar', + title: '', + maxAttempts: customMaxAttempts, + createTaskRunner: jest.fn(), + }, + }, + }, + claimingOpts: { claimOwnershipUntil: new Date(), size: 10 }, + }); + expect(query).toMatchObject({ + bool: { + must: [ + { term: { type: 'task' } }, + { + bool: { + must: [ + { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'task.status': 'idle' } }, + { range: { 'task.runAt': { lte: 'now' } } }, + ], + }, + }, + { + bool: { + must: [ + { + bool: { + should: [ + { term: { 'task.status': 'running' } }, + { term: { 'task.status': 'claiming' } }, + ], + }, + }, + { range: { 'task.retryAt': { lte: 'now' } } }, + ], + }, + }, + ], + }, + }, + { + bool: { + should: [ + { exists: { field: 'task.interval' } }, + { + bool: { + must: [ + { term: { 'task.taskType': 'foo' } }, + { + range: { + 'task.attempts': { + lt: maxAttempts, + }, + }, + }, + ], + }, + }, + { + bool: { + must: [ + { term: { 'task.taskType': 'bar' } }, + { + range: { + 'task.attempts': { + lt: customMaxAttempts, + }, + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }); + }); + + test('it claims tasks by setting their ownerId, status and retryAt', async () => { + const taskManagerId = uuid.v1(); + const claimOwnershipUntil = new Date(Date.now()); + const { + args: { + updateByQuery: { + body: { script }, + }, + }, + } = await testClaimAvailableTasks({ + opts: { + taskManagerId, + }, + claimingOpts: { + claimOwnershipUntil, + size: 10, + }, + }); + expect(script).toMatchObject({ + source: `ctx._source.task.ownerId=params.ownerId; ctx._source.task.status=params.status; ctx._source.task.retryAt=params.retryAt;`, + lang: 'painless', + params: { + ownerId: taskManagerId, + retryAt: claimOwnershipUntil, + status: 'claiming', + }, + }); + }); + + test('it returns task objects', async () => { + const taskManagerId = uuid.v1(); + const claimOwnershipUntil = new Date(Date.now()); + const runAt = new Date(); + const tasks = [ + { + _id: 'aaa', + _source: { + type: 'task', + task: { + runAt, + taskType: 'foo', + interval: undefined, + attempts: 0, + status: 'idle', + params: '{ "hello": "world" }', + state: '{ "baby": "Henhen" }', + user: 'jimbo', + scope: ['reporting'], + ownerId: taskManagerId, + }, + }, + _seq_no: 1, + _primary_term: 2, + sort: ['a', 1], + }, + { + _id: 'bbb', + _source: { + type: 'task', + task: { + runAt, + taskType: 'bar', + interval: '5m', + attempts: 2, + status: 'running', + params: '{ "shazm": 1 }', + state: '{ "henry": "The 8th" }', + user: 'dabo', + scope: ['reporting', 'ceo'], + ownerId: taskManagerId, + }, + }, + _seq_no: 3, + _primary_term: 4, + sort: ['b', 2], + }, + ]; + const { + result: { docs }, + args: { + search: { + body: { query }, + }, + }, + } = await testClaimAvailableTasks({ + opts: { + taskManagerId, + }, + claimingOpts: { + claimOwnershipUntil, + size: 10, + }, + hits: tasks, + }); + + expect(query.bool.must).toContainEqual({ + bool: { + must: [ + { + term: { + 'task.ownerId': taskManagerId, + }, + }, + { term: { 'task.status': 'claiming' } }, + ], + }, + }); + + expect(docs).toMatchObject([ + { + attempts: 0, + id: 'aaa', + interval: undefined, + params: { hello: 'world' }, + runAt, + scope: ['reporting'], + state: { baby: 'Henhen' }, + status: 'idle', + taskType: 'foo', + user: 'jimbo', + ownerId: taskManagerId, + }, + { + attempts: 2, + id: 'bbb', + interval: '5m', + params: { shazm: 1 }, + runAt, + scope: ['reporting', 'ceo'], + state: { henry: 'The 8th' }, + status: 'running', + taskType: 'bar', + user: 'dabo', + ownerId: taskManagerId, + }, + ]); + }); + }); + describe('update', () => { test('refreshes the index, handles versioning', async () => { const task = { @@ -563,6 +882,7 @@ describe('TaskStore', () => { attempts: 3, status: 'idle' as TaskStatus, version: '123', + ownerId: null, }; savedObjectsClient.update.mockImplementation( @@ -579,6 +899,7 @@ describe('TaskStore', () => { const store = new TaskStore({ index: 'tasky', + taskManagerId: '', serializer, callCluster: jest.fn(), maxAttempts: 2, @@ -604,6 +925,7 @@ describe('TaskStore', () => { status: task.status, taskType: task.taskType, user: undefined, + ownerId: null, }, { version: '123' } ); @@ -626,6 +948,7 @@ describe('TaskStore', () => { const callCluster = jest.fn(); const store = new TaskStore({ index: 'tasky', + taskManagerId: '', serializer, callCluster, maxAttempts: 2, @@ -638,3 +961,16 @@ describe('TaskStore', () => { }); }); }); + +function generateFakeTasks(count: number = 1) { + return _.times(count, () => ({ + _id: 'aaa', + _source: { + type: 'task', + task: {}, + }, + _seq_no: _.random(1, 5), + _primary_term: _.random(1, 5), + sort: ['a', _.random(1, 5)], + })); +} diff --git a/x-pack/legacy/plugins/task_manager/task_store.ts b/x-pack/legacy/plugins/task_manager/task_store.ts index ddd1b0deb600..2314d705544d 100644 --- a/x-pack/legacy/plugins/task_manager/task_store.ts +++ b/x-pack/legacy/plugins/task_manager/task_store.ts @@ -27,16 +27,37 @@ import { export interface StoreOpts { callCluster: ElasticJs; index: string; + taskManagerId: string; maxAttempts: number; definitions: TaskDictionary; savedObjectsRepository: SavedObjectsClientContract; serializer: SavedObjectsSerializer; } -export interface FetchOpts { +export interface SearchOpts { searchAfter?: any[]; - sort?: object[]; + sort?: object | object[]; query?: object; + size?: number; + seq_no_primary_term?: boolean; + search_after?: any[]; +} + +export interface FetchOpts extends SearchOpts { + sort?: object[]; +} + +export interface UpdateByQuerySearchOpts extends SearchOpts { + script?: object; +} + +export interface UpdateByQueryOpts extends SearchOpts { + max_docs?: number; +} + +export interface OwnershipClaimingOpts { + claimOwnershipUntil: Date; + size: number; } export interface FetchResult { @@ -44,6 +65,17 @@ export interface FetchResult { docs: ConcreteTaskInstance[]; } +export interface ClaimOwnershipResult { + claimedTasks: number; + docs: ConcreteTaskInstance[]; +} + +export interface UpdateByQueryResult { + updated: number; + version_conflicts: number; + total: number; +} + /** * Wraps an elasticsearch connection and provides a task manager-specific * interface into the index. @@ -51,6 +83,7 @@ export interface FetchResult { export class TaskStore { public readonly maxAttempts: number; public readonly index: string; + public readonly taskManagerId: string; private callCluster: ElasticJs; private definitions: TaskDictionary; private savedObjectsRepository: SavedObjectsClientContract; @@ -69,6 +102,7 @@ export class TaskStore { constructor(opts: StoreOpts) { this.callCluster = opts.callCluster; this.index = opts.index; + this.taskManagerId = opts.taskManagerId; this.maxAttempts = opts.maxAttempts; this.definitions = opts.definitions; this.serializer = opts.serializer; @@ -196,6 +230,152 @@ export class TaskStore { return docs; } + /** + * Claims available tasks from the index, which are ready to be run. + * - runAt is now or past + * - is not currently claimed by any instance of Kibana + * - has a type that is in our task definitions + * + * @param {OwnershipClaimingOpts} options + * @returns {Promise} + */ + public async claimAvailableTasks(opts: OwnershipClaimingOpts): Promise { + const claimedTasks = await this.markAvailableTasksAsClaimed(opts); + const docs = claimedTasks > 0 ? await this.sweepForClaimedTasks(opts) : []; + return { + claimedTasks, + docs, + }; + } + + private async markAvailableTasksAsClaimed({ + size, + claimOwnershipUntil, + }: OwnershipClaimingOpts): Promise { + const { updated } = await this.updateByQuery( + { + query: { + bool: { + must: [ + // Either a task with idle status and runAt <= now or + // status running or claiming with a retryAt <= now. + { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'task.status': 'idle' } }, + { range: { 'task.runAt': { lte: 'now' } } }, + ], + }, + }, + { + bool: { + must: [ + { + bool: { + should: [ + { term: { 'task.status': 'running' } }, + { term: { 'task.status': 'claiming' } }, + ], + }, + }, + { range: { 'task.retryAt': { lte: 'now' } } }, + ], + }, + }, + ], + }, + }, + // Either task has an interval or the attempts < the maximum configured + { + bool: { + should: [ + { exists: { field: 'task.interval' } }, + ...Object.entries(this.definitions).map(([type, definition]) => ({ + bool: { + must: [ + { term: { 'task.taskType': type } }, + { + range: { + 'task.attempts': { + lt: definition.maxAttempts || this.maxAttempts, + }, + }, + }, + ], + }, + })), + ], + }, + }, + ], + }, + }, + sort: { + _script: { + type: 'number', + order: 'asc', + script: { + lang: 'expression', + source: `doc['task.retryAt'].value || doc['task.runAt'].value`, + }, + }, + }, + seq_no_primary_term: true, + script: { + source: `ctx._source.task.ownerId=params.ownerId; ctx._source.task.status=params.status; ctx._source.task.retryAt=params.retryAt;`, + lang: 'painless', + params: { + ownerId: this.taskManagerId, + retryAt: claimOwnershipUntil, + status: 'claiming', + }, + }, + }, + { + max_docs: size, + } + ); + return updated; + } + + /** + * Fetches tasks from the index, which are owned by the current Kibana instance + */ + private async sweepForClaimedTasks({ + size, + }: OwnershipClaimingOpts): Promise { + const { docs } = await this.search({ + query: { + bool: { + must: [ + { + term: { + 'task.ownerId': this.taskManagerId, + }, + }, + { term: { 'task.status': 'claiming' } }, + ], + }, + }, + size, + sort: { + _script: { + type: 'number', + order: 'asc', + script: { + lang: 'expression', + source: `doc['task.retryAt'].value || doc['task.runAt'].value`, + }, + }, + }, + seq_no_primary_term: true, + }); + + return docs; + } /** * Updates the specified doc in the index, returning the doc * with its version up to date. @@ -224,12 +404,8 @@ export class TaskStore { await this.savedObjectsRepository.delete('task', id); } - private async search(opts: any = {}): Promise { - const originalQuery = opts.query; - const queryOnlyTasks = { term: { type: 'task' } }; - const query = originalQuery - ? { bool: { must: [queryOnlyTasks, originalQuery] } } - : queryOnlyTasks; + private async search(opts: SearchOpts = {}): Promise { + const { query } = ensureQueryOnlyReturnsTaskObjects(opts); const result = await this.callCluster('search', { index: this.index, @@ -250,6 +426,31 @@ export class TaskStore { searchAfter: (rawDocs.length && rawDocs[rawDocs.length - 1].sort) || [], }; } + + private async updateByQuery( + opts: UpdateByQuerySearchOpts = {}, + { max_docs }: UpdateByQueryOpts = {} + ): Promise { + const { query } = ensureQueryOnlyReturnsTaskObjects(opts); + const result = await this.callCluster('updateByQuery', { + index: this.index, + ignoreUnavailable: true, + refresh: true, + max_docs, + conflicts: 'proceed', + body: { + ...opts, + query, + }, + }); + + const { total, updated, version_conflicts } = result; + return { + total, + updated, + version_conflicts, + }; + } } function paginatableSort(sort: any[] = []) { @@ -301,3 +502,16 @@ function parseJSONField(json: string, fieldName: string, id: string) { throw new Error(`Task "${id}"'s ${fieldName} field has invalid JSON: ${json}`); } } + +function ensureQueryOnlyReturnsTaskObjects(opts: SearchOpts): SearchOpts { + const originalQuery = opts.query; + const queryOnlyTasks = { term: { type: 'task' } }; + const query = originalQuery + ? { bool: { must: [queryOnlyTasks, originalQuery] } } + : queryOnlyTasks; + + return { + ...opts, + query, + }; +} diff --git a/x-pack/package.json b/x-pack/package.json index 5a27764bdf9e..23a6e0ea0d75 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -9,7 +9,7 @@ "kbn:bootstrap": "node legacy/plugins/canvas/scripts/storybook --clean", "start": "gulp dev", "build": "gulp build", - "testonly": "gulp testonly", + "testonly": "echo 'Deprecated, use `yarn test`' && gulp test", "test": "gulp test", "test:browser:dev": "gulp testbrowser-dev", "test:browser": "gulp testbrowser", @@ -56,10 +56,13 @@ "@types/d3-time": "^1.0.10", "@types/d3-time-format": "^2.1.1", "@types/elasticsearch": "^5.0.33", + "@types/fancy-log": "^1.3.1", "@types/file-saver": "^2.0.0", + "@types/getos": "^3.0.0", "@types/git-url-parse": "^9.0.0", "@types/glob": "^7.1.1", "@types/graphql": "^0.13.1", + "@types/gulp": "^4.0.6", "@types/hapi__wreck": "^15.0.1", "@types/history": "^4.7.3", "@types/jest": "^24.0.18", @@ -67,12 +70,11 @@ "@types/js-yaml": "^3.11.1", "@types/jsdom": "^12.2.4", "@types/json-stable-stringify": "^1.0.32", - "@types/jsonwebtoken": "^7.2.7", + "@types/jsonwebtoken": "^7.2.8", "@types/lodash": "^3.10.1", "@types/mapbox-gl": "^0.54.1", "@types/memoize-one": "^4.1.0", "@types/mime": "^2.0.1", - "@types/mkdirp": "^0.5.2", "@types/mocha": "^5.2.7", "@types/nock": "^10.0.3", "@types/node": "^10.12.27", @@ -83,7 +85,7 @@ "@types/pngjs": "^3.3.2", "@types/prop-types": "^15.5.3", "@types/proper-lockfile": "^3.0.1", - "@types/puppeteer": "^1.19.0", + "@types/puppeteer": "^1.20.1", "@types/react": "^16.8.0", "@types/react-dom": "^16.8.0", "@types/react-redux": "^6.0.6", @@ -123,7 +125,6 @@ "copy-webpack-plugin": "^5.0.4", "cypress": "^3.4.1", "del": "^4.1.1", - "dotenv": "2.0.0", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-utils": "^1.12.0", @@ -135,8 +136,8 @@ "graphql-codegen-introspection-template": "^0.13.0", "graphql-codegen-typescript-resolvers-template": "^0.13.0", "graphql-codegen-typescript-template": "^0.13.0", - "gulp": "3.9.1", - "gulp-mocha": "^7.0.1", + "gulp": "4.0.2", + "gulp-mocha": "^7.0.2", "gulp-multi-process": "1.3.1", "hapi": "^17.5.3", "jest": "^24.9.0", @@ -163,7 +164,6 @@ "react-testing-library": "^6.0.0", "redux-test-utils": "0.2.2", "rsync": "0.6.1", - "run-sequence": "^2.2.1", "sass-loader": "^7.3.1", "sass-resources-loader": "^2.0.1", "simple-git": "1.116.0", @@ -259,7 +259,7 @@ "humps": "2.0.1", "i18n-iso-countries": "^4.3.1", "icalendar": "0.7.1", - "idx": "^2.5.2", + "idx": "^2.5.6", "immer": "^1.5.0", "inline-style": "^2.0.0", "intl": "^1.2.5", @@ -270,7 +270,7 @@ "jquery": "^3.4.1", "js-yaml": "3.13.1", "json-stable-stringify": "^1.0.1", - "jsonwebtoken": "^8.3.0", + "jsonwebtoken": "^8.5.1", "jsts": "^1.6.2", "lodash": "npm:@elastic/lodash@3.10.1-kibana3", "lodash.keyby": "^4.6.0", @@ -287,7 +287,6 @@ "markdown-it": "^8.4.1", "memoize-one": "^5.0.0", "mime": "^2.4.4", - "mkdirp": "0.5.1", "moment": "^2.20.1", "moment-duration-format": "^1.3.0", "moment-timezone": "^0.5.14", diff --git a/x-pack/plugins/code/kibana.json b/x-pack/plugins/code/kibana.json new file mode 100644 index 000000000000..2aed61eda966 --- /dev/null +++ b/x-pack/plugins/code/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "code", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["x-pack", "code"], + "server": true, + "ui": false, + "requiredPlugins": ["features"] +} diff --git a/x-pack/plugins/code/server/config.ts b/x-pack/plugins/code/server/config.ts new file mode 100644 index 000000000000..97b9ded08525 --- /dev/null +++ b/x-pack/plugins/code/server/config.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import moment from 'moment'; + +// TODO: update these legacy imports to new ones. +import { + LanguageServers, + LanguageServersDeveloping, +} from '../../../legacy/plugins/code/server/lsp/language_servers'; +import { DEFAULT_WATERMARK_LOW_PERCENTAGE } from '../../../legacy/plugins/code/server/disk_watermark'; + +const createCodeConfigSchema = () => { + const langSwitches: any = {}; + LanguageServers.forEach(lang => { + langSwitches[lang.name] = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }); + }); + LanguageServersDeveloping.forEach(lang => { + langSwitches[lang.name] = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }); + }); + + return schema.object({ + ui: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), + enabled: schema.boolean({ defaultValue: true }), + queueIndex: schema.string({ defaultValue: '.code_internal-worker-queue' }), + // 1 hour by default. + queueTimeoutMs: schema.number({ + defaultValue: moment.duration(1, 'hour').asMilliseconds(), + }), + // The frequency which update scheduler executes. 1 minute by default. + updateFrequencyMs: schema.number({ + defaultValue: moment.duration(1, 'minute').asMilliseconds(), + }), + // The frequency which index scheduler executes. 1 day by default. + indexFrequencyMs: schema.number({ + defaultValue: moment.duration(1, 'day').asMilliseconds(), + }), + // The frequency which each repo tries to update. 5 minutes by default. + updateRepoFrequencyMs: schema.number({ + defaultValue: moment.duration(5, 'minute').asMilliseconds(), + }), + // The frequency which each repo tries to index. 1 day by default. + indexRepoFrequencyMs: schema.number({ + defaultValue: moment.duration(1, 'day').asMilliseconds(), + }), + // whether we want to show more logs + verbose: schema.boolean({ defaultValue: false }), + lsp: schema.object({ + ...langSwitches, + // timeout of a request + requestTimeoutMs: schema.number({ + defaultValue: moment.duration(10, 'second').asMilliseconds(), + }), + // if we want the language server run in seperately + detach: schema.boolean({ defaultValue: false }), + // enable oom_score_adj on linux + oomScoreAdj: schema.boolean({ defaultValue: true }), + }), + repos: schema.arrayOf(schema.string(), { defaultValue: [] }), + security: schema.object({ + enableMavenImport: schema.boolean({ defaultValue: true }), + enableGradleImport: schema.boolean({ defaultValue: false }), + installGoDependency: schema.boolean({ defaultValue: false }), + installNodeDependency: schema.boolean({ defaultValue: true }), + gitHostWhitelist: schema.arrayOf(schema.string(), { + defaultValue: [ + 'github.com', + 'gitlab.com', + 'bitbucket.org', + 'gitbox.apache.org', + 'eclipse.org', + ], + }), + gitProtocolWhitelist: schema.arrayOf(schema.string(), { + defaultValue: ['https', 'git', 'ssh'], + }), + enableGitCertCheck: schema.boolean({ defaultValue: true }), + }), + disk: schema.object({ + thresholdEnabled: schema.boolean({ defaultValue: true }), + watermarkLow: schema.string({ defaultValue: `${DEFAULT_WATERMARK_LOW_PERCENTAGE}%` }), + }), + maxWorkspace: schema.number({ defaultValue: 5 }), // max workspace folder for each language server + enableGlobalReference: schema.boolean({ defaultValue: false }), // Global reference as optional feature for now + enableCommitIndexing: schema.boolean({ defaultValue: false }), + codeNodeUrl: schema.maybe(schema.string()), + clustering: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + codeNodes: schema.arrayOf( + schema.object({ + id: schema.string(), + address: schema.string(), + }), + { + defaultValue: [], + } + ), + }), + }); +}; + +export const CodeConfigSchema = createCodeConfigSchema(); diff --git a/x-pack/plugins/code/server/index.ts b/x-pack/plugins/code/server/index.ts new file mode 100644 index 000000000000..554974349f2f --- /dev/null +++ b/x-pack/plugins/code/server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { CodeConfigSchema } from './config'; +import { CodePlugin } from './plugin'; + +export { PluginSetupContract } from './plugin'; + +export const config = { schema: CodeConfigSchema }; +export const plugin = (initializerContext: PluginInitializerContext) => + new CodePlugin(initializerContext); diff --git a/x-pack/plugins/code/server/plugin.ts b/x-pack/plugins/code/server/plugin.ts new file mode 100644 index 000000000000..63948ee48501 --- /dev/null +++ b/x-pack/plugins/code/server/plugin.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { first } from 'rxjs/operators'; +import { TypeOf } from '@kbn/config-schema'; +import { + CoreSetup, + LoggerFactory, + PluginInitializerContext, + RecursiveReadonly, +} from 'src/core/server'; +import { deepFreeze } from '../../../../src/core/utils'; +import { PluginSetupContract as FeaturesSetupContract } from '../../features/server'; +import { CodeConfigSchema } from './config'; + +/** + * Describes public Code plugin contract returned at the `setup` stage. + */ +export interface PluginSetupContract { + /** @deprecated */ + legacy: { + config: TypeOf; + logger: LoggerFactory; + }; +} + +/** + * Represents Code Plugin instance that will be managed by the Kibana plugin system. + */ +export class CodePlugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public async setup( + coreSetup: CoreSetup, + { features }: { features: FeaturesSetupContract } + ): Promise> { + const config = await this.initializerContext.config + .create>() + .pipe(first()) + .toPromise(); + + features.registerFeature({ + id: 'code', + name: i18n.translate('xpack.code.featureRegistry.codeFeatureName', { + defaultMessage: 'Code', + }), + icon: 'codeApp', + navLinkId: 'code', + app: ['code', 'kibana'], + catalogue: [], // TODO add catalogue here + privileges: { + all: { + excludeFromBasePrivileges: true, + api: ['code_user', 'code_admin'], + savedObject: { + all: [], + read: ['config'], + }, + ui: ['show', 'user', 'admin'], + }, + read: { + api: ['code_user'], + savedObject: { + all: [], + read: ['config'], + }, + ui: ['show', 'user'], + }, + }, + }); + + return deepFreeze({ + /** @deprecated */ + legacy: { + config, + logger: this.initializerContext.logger, + }, + }); + } + + public start() { + this.initializerContext.logger.get().debug('Starting Code plugin'); + } + + public stop() { + this.initializerContext.logger.get().debug('Stopping Code plugin'); + } +} diff --git a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts index 2642cbb57ddd..81ad9715da78 100644 --- a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts +++ b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts @@ -15,7 +15,7 @@ describe('licensingRouteHandlerContext', () => { const context = createRouteHandlerContext(license$); - const { license: contextResult } = await context({}); + const { license: contextResult } = await context({}, {} as any, {} as any); expect(contextResult).toBe(license); }); @@ -29,7 +29,7 @@ describe('licensingRouteHandlerContext', () => { const latestLicense = (Symbol() as unknown) as ILicense; license$.next(latestLicense); - const { license: contextResult } = await context({}); + const { license: contextResult } = await context({}, {} as any, {} as any); expect(contextResult).toBe(latestLicense); }); diff --git a/x-pack/plugins/licensing/server/licensing_route_handler_context.ts b/x-pack/plugins/licensing/server/licensing_route_handler_context.ts index 4705a6705da0..8ee49e9aa084 100644 --- a/x-pack/plugins/licensing/server/licensing_route_handler_context.ts +++ b/x-pack/plugins/licensing/server/licensing_route_handler_context.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext, IContextProvider } from 'src/core/server'; +import { IContextProvider, RequestHandler } from 'src/core/server'; import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { ILicense } from './types'; export function createRouteHandlerContext( license$: Observable -): IContextProvider { +): IContextProvider, 'licensing'> { return async function licensingRouteHandlerContext() { const license = await license$.pipe(take(1)).toPromise(); return { license }; diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index 32add24cbb23..4cd40379b859 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -20,6 +20,14 @@ import { LicensingConfig } from './licensing_config'; import { License } from './license'; import { createRouteHandlerContext } from './licensing_route_handler_context'; +declare module 'src/core/server' { + interface RequestHandlerContext { + licensing: { + license: ILicense; + }; + } +} + export class Plugin implements CorePlugin { private readonly logger: Logger; private readonly config$: Observable; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 676cf78de9ff..b7a6b2424292 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4517,7 +4517,6 @@ "xpack.graph.savedWorkspace.workspaceNameTitle": "新規グラフワークスペース", "xpack.graph.savedWorkspaces.graphWorkspaceLabel": "グラフワークスペース", "xpack.graph.savedWorkspaces.graphWorkspacesLabel": "グラフワークスペース", - "xpack.graph.saveWorkspace.disabledWarning": "保存が無効になっています", "xpack.graph.saveWorkspace.successNotification.noDataSavedText": "構成が保存されましたが、データは保存されませんでした", "xpack.graph.saveWorkspace.successNotificationTitle": "「{workspaceTitle}」が保存されました", "xpack.graph.serverSideErrors.expiredLicenseErrorMessage": "グラフを利用できません。ライセンスが期限切れです。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c2054497c96f..79cba3ee696b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4520,7 +4520,6 @@ "xpack.graph.savedWorkspace.workspaceNameTitle": "新建 Graph 工作空间", "xpack.graph.savedWorkspaces.graphWorkspaceLabel": "Graph 工作空间", "xpack.graph.savedWorkspaces.graphWorkspacesLabel": "Graph 工作空间", - "xpack.graph.saveWorkspace.disabledWarning": "已禁用保存", "xpack.graph.saveWorkspace.successNotification.noDataSavedText": "配置会被保存,但不保存数据", "xpack.graph.saveWorkspace.successNotificationTitle": "已保存“{workspaceTitle}”", "xpack.graph.serverSideErrors.expiredLicenseErrorMessage": "Graph 不可用 - 许可已过期。", diff --git a/x-pack/tasks/build.js b/x-pack/tasks/build.js deleted file mode 100644 index ee7f22a4f203..000000000000 --- a/x-pack/tasks/build.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; -import { writeFileSync } from 'fs'; -import pluginHelpers from '@kbn/plugin-helpers'; -import { ToolingLog } from '@kbn/dev-utils'; -import { generateNoticeFromSource } from '../../src/dev'; - -export default (gulp, { buildTarget }) => { - gulp.task('build', ['clean', 'report', 'prepare:build'], async () => { - await pluginHelpers.run('build', { - skipArchive: true, - buildDestination: buildTarget, - }); - - const buildRoot = resolve(buildTarget, 'kibana/x-pack'); - - const log = new ToolingLog({ - level: 'info', - writeTo: process.stdout - }); - - writeFileSync( - resolve(buildRoot, 'NOTICE.txt'), - await generateNoticeFromSource({ - productName: 'Kibana X-Pack', - log, - directory: buildRoot - }) - ); - }); -}; diff --git a/x-pack/tasks/build.ts b/x-pack/tasks/build.ts new file mode 100644 index 000000000000..6bc615613387 --- /dev/null +++ b/x-pack/tasks/build.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve } from 'path'; +import { writeFileSync } from 'fs'; + +import pluginHelpers from '@kbn/plugin-helpers'; +import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; +import gulp from 'gulp'; +import del from 'del'; +import fancyLog from 'fancy-log'; +import chalk from 'chalk'; + +import { generateNoticeFromSource } from '../../src/dev/notice'; +import { prepareTask } from './prepare'; +import { gitInfo } from './helpers/git_info'; +import { PKG_NAME } from './helpers/pkg'; +import { BUILD_VERSION } from './helpers/build_version'; + +const BUILD_DIR = resolve(REPO_ROOT, 'x-pack/build'); +const PLUGIN_BUILD_DIR = resolve(BUILD_DIR, 'plugin'); + +async function cleanBuildTask() { + fancyLog('Deleting', BUILD_DIR); + await del(BUILD_DIR); +} + +async function reportTask() { + const info = await gitInfo(); + + fancyLog('Package Name', chalk.yellow(PKG_NAME)); + fancyLog('Version', chalk.yellow(BUILD_VERSION)); + fancyLog('Build Number', chalk.yellow(`${info.number}`)); + fancyLog('Build SHA', chalk.yellow(info.sha)); +} + +async function pluginHelpersBuild() { + await pluginHelpers.run('build', { + skipArchive: true, + buildDestination: PLUGIN_BUILD_DIR, + }); +} + +async function generateNoticeText() { + const buildRoot = resolve(PLUGIN_BUILD_DIR, 'kibana/x-pack'); + const log = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + + writeFileSync( + resolve(buildRoot, 'NOTICE.txt'), + await generateNoticeFromSource({ + productName: 'Kibana X-Pack', + log, + directory: buildRoot, + }) + ); +} + +export const buildTask = gulp.series( + cleanBuildTask, + reportTask, + prepareTask, + pluginHelpersBuild, + generateNoticeText +); diff --git a/x-pack/tasks/clean.js b/x-pack/tasks/clean.js deleted file mode 100644 index 8e2098c3b474..000000000000 --- a/x-pack/tasks/clean.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import del from 'del'; - -export default (gulp, { coverageDir, buildDir, packageDir, log }) => { - gulp.task('clean-test', () => { - log('Deleting', coverageDir); - return del([coverageDir]); - }); - - gulp.task('clean', ['clean-test'], () => { - const toDelete = [ - buildDir, - packageDir, - ]; - log('Deleting', toDelete.join(', ')); - return del(toDelete); - }); -}; diff --git a/x-pack/tasks/dev.js b/x-pack/tasks/dev.ts similarity index 55% rename from x-pack/tasks/dev.js rename to x-pack/tasks/dev.ts index 048e32ed8657..6e398f231a27 100644 --- a/x-pack/tasks/dev.js +++ b/x-pack/tasks/dev.ts @@ -5,8 +5,12 @@ */ import pluginHelpers from '@kbn/plugin-helpers'; -import getFlags from './helpers/get_flags'; +import gulp from 'gulp'; -export default (gulp) => { - gulp.task('dev', ['prepare:dev'], () => pluginHelpers.run('start', { flags: getFlags() })); -}; +import { prepareTask } from './prepare'; + +export const devTask = gulp.series(prepareTask, async function startKibanaServer() { + await pluginHelpers.run('start', { + flags: process.argv.slice(3), + }); +}); diff --git a/x-pack/tasks/helpers/build_version.js b/x-pack/tasks/helpers/build_version.js deleted file mode 100644 index f77e1fb7168f..000000000000 --- a/x-pack/tasks/helpers/build_version.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import yargs from 'yargs'; -import semver from 'semver'; - -yargs - .alias('r', 'release').describe('r', 'Create a release build, not a snapshot') - .option('build-qualifier', { - default: null - }); -const argv = yargs.argv; - -export default function getVersion(pkg) { - const { version } = pkg; - if (!version) { - throw new Error('No version found in package.json'); - } - if (!semver.valid(version)) { - throw new Error(`Version is not valid semver: ${version}`); - } - - const snapshotText = (argv.release) ? '' : '-SNAPSHOT'; - const qualifierText = argv.buildQualifier ? '-' + argv.buildQualifier : ''; - return `${version}${qualifierText}${snapshotText}`; -} diff --git a/x-pack/tasks/helpers/build_version.ts b/x-pack/tasks/helpers/build_version.ts new file mode 100644 index 000000000000..7e9954ed3f75 --- /dev/null +++ b/x-pack/tasks/helpers/build_version.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PKG_VERSION } from './pkg'; +import { FLAGS } from './flags'; + +const snapshotText = FLAGS.release ? '' : '-SNAPSHOT'; +const qualifierText = FLAGS.buildQualifier ? '-' + FLAGS.buildQualifier : ''; +export const BUILD_VERSION = `${PKG_VERSION}${qualifierText}${snapshotText}`; diff --git a/x-pack/tasks/helpers/flags.ts b/x-pack/tasks/helpers/flags.ts new file mode 100644 index 000000000000..62a382b02ed2 --- /dev/null +++ b/x-pack/tasks/helpers/flags.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve } from 'path'; + +import log from 'fancy-log'; +import getopts from 'getopts'; +import { toArray } from 'rxjs/operators'; + +// @ts-ignore complicated module doesn't have types yet +import { findPluginSpecs } from '../../../src/legacy/plugin_discovery'; + +/* + Usage: + Specifying which plugins to run tests can be done with the --plugins flag. + One of more plugins can be specified, and each one should be command separated, like so: + gulp testserver --plugins monitoring,reporting + If using with yarn: + yarn test:server --plugins graph +*/ + +const opts = Object.freeze( + getopts(process.argv.slice(2), { + alias: { + release: 'r', + }, + boolean: ['release', 'flags'], + string: ['build-qualifier', 'plugins'], + }) +); + +if (opts.flags) { + log(` + X-Pack Gulpfile Flags: + + --flags Print this message + --plugins Comma-separated list of plugins + --release, -r Build to a release version + --build-qualifier Qualifier to include in the build version + `); + process.exit(0); +} + +export const FLAGS = { + release: !!opts.release, + buildQualifier: opts.buildQualifier as string | undefined, + plugins: opts.plugins + ? String(opts.plugins) + .split(',') + .map(id => id.trim()) + : undefined, +}; + +export async function getEnabledPlugins() { + if (FLAGS.plugins) { + return FLAGS.plugins; + } + + const { spec$ } = findPluginSpecs({ + plugins: { + paths: [resolve(__dirname, '..', '..')], + }, + }); + + const enabledPlugins: Array<{ getId: () => string }> = await spec$.pipe(toArray()).toPromise(); + return enabledPlugins.map(spec => spec.getId()); +} diff --git a/x-pack/tasks/helpers/get_plugins.js b/x-pack/tasks/helpers/get_plugins.js deleted file mode 100644 index a540cda27bd7..000000000000 --- a/x-pack/tasks/helpers/get_plugins.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; -import yargs from 'yargs'; -import glob from 'glob'; -import { toArray } from 'rxjs/operators'; -import { findPluginSpecs } from '../../../src/legacy/plugin_discovery'; - -/* - Usage: - Specifying which plugins to run tests can be done with the --plugins flag. - One of more plugins can be specified, and each one should be command separated, like so: - gulp testserver --plugins monitoring,reporting - If using with yarn: - yarn test:server --plugins graph -*/ - -const argv = yargs - .describe('plugins', 'Comma-separated list of plugins') - .argv; -const allPlugins = glob.sync('*', { cwd: resolve(__dirname, '..', '..', 'legacy', 'plugins') }); - -export function getPlugins() { - const plugins = argv.plugins && argv.plugins.split(','); - if (!Array.isArray(plugins) || plugins.length === 0) { - return allPlugins; - } - return plugins; -} - -const { spec$ } = findPluginSpecs({ - plugins: { paths: [resolve(__dirname, '..', '..')] } -}); - -export async function getEnabledPlugins() { - const plugins = argv.plugins && argv.plugins.split(','); - if (!Array.isArray(plugins) || plugins.length === 0) { - const enabledPlugins = await spec$.pipe(toArray()).toPromise(); - return enabledPlugins.map(spec => spec.getId()); - } - return plugins; -} diff --git a/x-pack/tasks/helpers/git_info.js b/x-pack/tasks/helpers/git_info.ts similarity index 50% rename from x-pack/tasks/helpers/git_info.js rename to x-pack/tasks/helpers/git_info.ts index 8e6f46300474..3c144e6cc10d 100644 --- a/x-pack/tasks/helpers/git_info.js +++ b/x-pack/tasks/helpers/git_info.ts @@ -5,20 +5,24 @@ */ import path from 'path'; +// @ts-ignore barely used, untyped module import simpleGit from 'simple-git'; const gitDir = path.resolve(__dirname, '..', '..'); -export default function gitInfo() { +export async function gitInfo() { const git = simpleGit(gitDir); - return new Promise((resolve, reject) => { - git.log((err, log) => { - if (err) return reject(err); - resolve({ - number: log.total, - sha: log.latest.hash, - }); + return new Promise<{ number: number; sha: string }>((resolve, reject) => { + git.log((err: undefined | Error, log: { total: number; latest: { hash: string } }) => { + if (err) { + reject(err); + } else { + resolve({ + number: log.total, + sha: log.latest.hash, + }); + } }); }); } diff --git a/x-pack/tasks/helpers/globs.js b/x-pack/tasks/helpers/globs.js deleted file mode 100644 index 0576389c9424..000000000000 --- a/x-pack/tasks/helpers/globs.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getPlugins } from './get_plugins'; - -/* - * Note: The path `plugins / pluginName / ** / __tests__ / ** / *.js` will match - * all public and server tests, so a special var must be used for "index" tests - * paths: `plugins / pluginName / __tests__ / ** / *.js` - */ -function getPluginPaths(plugins, opts = {}) { - const testPath = opts.tests ? '__tests__/**' : ''; - - return plugins.reduce((paths, pluginName) => { - const plugin = pluginName.trim(); - - const commonPath = `${plugin}/common`; - const serverPath = `${plugin}/**/server`; - const publicPath = `${plugin}/**/public`; - - const indexPaths = `legacy/plugins/${plugin}/${testPath}/*.js`; // index and helpers - const commonPaths = `legacy/plugins/${commonPath}/**/${testPath}/*.js`; - const serverPaths = `legacy/plugins/${serverPath}/**/${testPath}/*.js`; - const publicPaths = `legacy/plugins/${publicPath}/**/${testPath}/*.js`; - - paths = paths.concat([indexPaths, commonPaths, serverPaths]); - if (plugin === 'code') { - paths.push(`legacy/plugins/${serverPath}/**/${testPath}/*.ts`); - } - if (opts.browser) { - paths = paths.concat(publicPaths); - } - - return paths; - }, []); -} - -export function forPlugins() { - const plugins = getPlugins(); - return getPluginPaths(plugins, { browser: true }); -} - -export function forPluginServerTests() { - const plugins = getPlugins(); - return getPluginPaths(plugins, { tests: true }); -} diff --git a/x-pack/tasks/helpers/pkg.ts b/x-pack/tasks/helpers/pkg.ts new file mode 100644 index 000000000000..8411ebcf7186 --- /dev/null +++ b/x-pack/tasks/helpers/pkg.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Fs from 'fs'; +import semver from 'semver'; + +interface PackageJson { + name: string; + version: string; + dependencies: Record; + devDependencies: Record; + [key: string]: unknown; +} + +const PKG_PATH = require.resolve('../../package.json'); +export const PKG: PackageJson = JSON.parse(Fs.readFileSync(PKG_PATH, 'utf8')); +export const PKG_VERSION = PKG.version; +export const PKG_NAME = PKG.name; + +if (!PKG_VERSION) { + throw new Error('No "version" found in package.json'); +} + +if (!PKG_NAME) { + throw new Error('No "name" found in package.json'); +} + +if (!semver.valid(PKG_VERSION)) { + throw new Error(`Version is not valid semver: ${PKG_VERSION}`); +} diff --git a/x-pack/tasks/prepare.js b/x-pack/tasks/prepare.js deleted file mode 100644 index 287781a81940..000000000000 --- a/x-pack/tasks/prepare.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ensureAllBrowsersDownloaded } from '../legacy/plugins/reporting/server/browsers'; - -export default gulp => { - // anything that should always happen before anything else - gulp.task('prepare', () => ensureAllBrowsersDownloaded()); - - // anything that needs to happen before development - gulp.task('prepare:dev', ['prepare']); - - // anything that needs to happen before building - gulp.task('prepare:build', ['prepare']); -}; diff --git a/x-pack/legacy/plugins/canvas/tasks/helpers/enzyme_setup.js b/x-pack/tasks/prepare.ts similarity index 58% rename from x-pack/legacy/plugins/canvas/tasks/helpers/enzyme_setup.js rename to x-pack/tasks/prepare.ts index 290e3d220aa4..6d4eb5603ad3 100644 --- a/x-pack/legacy/plugins/canvas/tasks/helpers/enzyme_setup.js +++ b/x-pack/tasks/prepare.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { configure } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; +import { ensureAllBrowsersDownloaded } from '../legacy/plugins/reporting/server/browsers'; -configure({ adapter: new Adapter() }); +export const prepareTask = async () => { + await ensureAllBrowsersDownloaded(); +}; diff --git a/x-pack/tasks/report.js b/x-pack/tasks/report.js deleted file mode 100644 index a7e1a2e31de5..000000000000 --- a/x-pack/tasks/report.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import buildVersion from './helpers/build_version'; -import gitInfo from './helpers/git_info'; -import chalk from 'chalk'; - -export default (gulp, { log, pkg }) => { - gulp.task('report', () => { - return gitInfo() - .then(function (info) { - log('Package Name', chalk.yellow(pkg.name)); - log('Version', chalk.yellow(buildVersion(pkg))); - log('Build Number', chalk.yellow(info.number)); - log('Build SHA', chalk.yellow(info.sha)); - }); - }); -}; diff --git a/x-pack/tasks/test.js b/x-pack/tasks/test.js deleted file mode 100644 index 5aae780dda6d..000000000000 --- a/x-pack/tasks/test.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import runSequence from 'run-sequence'; -import pluginHelpers from '@kbn/plugin-helpers'; -import { getEnabledPlugins } from './helpers/get_plugins'; -import { forPluginServerTests } from './helpers/globs'; -import { createAutoJUnitReporter } from '../../src/dev'; - -const MOCHA_OPTIONS = { - ui: 'bdd', - require: require.resolve('../../src/setup_node_env'), - reporter: createAutoJUnitReporter({ - reportName: 'X-Pack Mocha Tests', - }), -}; - -export default (gulp, { mocha }) => { - gulp.task('test', (cb) => { - const preTasks = ['clean-test']; - runSequence(preTasks, 'testserver', 'testbrowser', cb); - }); - - gulp.task('testonly', ['testserver', 'testbrowser']); - - gulp.task('testserver', () => { - const globs = [ - 'common/**/__tests__/**/*.js', - 'server/**/__tests__/**/*.js', - ].concat(forPluginServerTests()); - return gulp.src(globs, { read: false }) - .pipe(mocha(MOCHA_OPTIONS)); - }); - - gulp.task('testbrowser', () => { - return getEnabledPlugins().then(plugins => { - return pluginHelpers.run('testBrowser', { - plugins: plugins.join(','), - }); - }); - }); - - gulp.task('testbrowser-dev', () => { - return getEnabledPlugins().then(plugins => { - return pluginHelpers.run('testBrowser', { - dev: true, - plugins: plugins.join(','), - }); - }); - }); -}; diff --git a/x-pack/tasks/test.ts b/x-pack/tasks/test.ts new file mode 100644 index 000000000000..7be64f7179f6 --- /dev/null +++ b/x-pack/tasks/test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import pluginHelpers from '@kbn/plugin-helpers'; +import { createAutoJUnitReporter } from '@kbn/test'; +// @ts-ignore no types available +import mocha from 'gulp-mocha'; +import gulp from 'gulp'; + +import { getEnabledPlugins } from './helpers/flags'; + +export const testServerTask = async () => { + const pluginIds = await getEnabledPlugins(); + + const testGlobs = ['common/**/__tests__/**/*.js', 'server/**/__tests__/**/*.js']; + + if (pluginIds.includes('code')) { + testGlobs.push(`legacy/plugins/**/server/**/__tests__/**/*.ts`); + } + + for (const pluginId of pluginIds) { + testGlobs.push( + `legacy/plugins/${pluginId}/__tests__/**/*.js`, + `legacy/plugins/${pluginId}/common/**/__tests__/**/*.js`, + `legacy/plugins/${pluginId}/**/server/**/__tests__/**/*.js` + ); + } + + return gulp.src(testGlobs, { read: false }).pipe( + mocha({ + ui: 'bdd', + require: require.resolve('../../src/setup_node_env'), + reporter: createAutoJUnitReporter({ + reportName: 'X-Pack Mocha Tests', + }), + }) + ); +}; + +export const testBrowserTask = async () => { + const plugins = await getEnabledPlugins(); + await pluginHelpers.run('testBrowser', { + plugins: plugins.join(','), + }); +}; + +export const testBrowserDevTask = async () => { + const plugins = await getEnabledPlugins(); + await pluginHelpers.run('testBrowser', { + dev: true, + plugins: plugins.join(','), + }); +}; + +export const testTask = gulp.series(testServerTask, testBrowserTask); diff --git a/x-pack/test/functional/apps/code/repo_archiver.ts b/x-pack/test/functional/apps/code/repo_archiver.ts index ea09bc141ab3..fc6baaab5742 100644 --- a/x-pack/test/functional/apps/code/repo_archiver.ts +++ b/x-pack/test/functional/apps/code/repo_archiver.ts @@ -8,12 +8,11 @@ import del from 'del'; // @ts-ignore import extractZip from 'extract-zip'; import fs from 'fs'; -import mkdirp from 'mkdirp'; import path from 'path'; import { promisify } from 'util'; const asyncExtractZip = promisify(extractZip); -const asyncMkdirp = promisify(mkdirp); +const asyncMkdir = promisify(fs.mkdir); const archiveFilePath = (zipFileName: string) => { return path.resolve(__dirname, `./fixtures/${zipFileName}.zip`); @@ -29,7 +28,7 @@ const workspaceDir = (repoUri: string, kibanaDir: string) => { const unzip = async (filepath: string, target: string) => { if (!fs.existsSync(target)) { - await asyncMkdirp(target); + await asyncMkdir(target, { recursive: true }); } await asyncExtractZip(filepath, { dir: target }); }; diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 1abd137659d9..3cb74307c3c0 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -79,6 +79,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { dimension: '[data-test-subj="lnsXY_xDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]', operation: 'date_histogram', + field: '@timestamp', }); await PageObjects.lens.configureDimension({ @@ -88,6 +89,13 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { field: 'bytes', }); + await PageObjects.lens.configureDimension({ + dimension: + '[data-test-subj="lnsXY_splitDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]', + operation: 'terms', + field: 'ip', + }); + await PageObjects.lens.setTitle('Afancilenstest'); await PageObjects.lens.save(); @@ -102,10 +110,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { // .echLegendItem__title is the only viable way of getting the xy chart's // legend item(s), so we're using a class selector here. - await PageObjects.lens.assertExpectedText( - '.echLegendItem__title', - legendText => !!legendText && legendText.includes('Average of bytes') - ); + expect(await find.allByCssSelector('.echLegendItem')).to.have.length(3); }); }); } diff --git a/x-pack/test/functional/config.ie.js b/x-pack/test/functional/config.ie.js new file mode 100644 index 000000000000..7a46471726fc --- /dev/null +++ b/x-pack/test/functional/config.ie.js @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + + + +export default async function ({ readConfigFile }) { + const defaultConfig = await readConfigFile(require.resolve('./config')); + + return { + ...defaultConfig.getAll(), + //csp.strict: false + // testFiles: [ + // require.resolve(__dirname, './apps/advanced_settings'), + // require.resolve(__dirname, './apps/canvas'), + // require.resolve(__dirname, './apps/graph'), + // require.resolve(__dirname, './apps/monitoring'), + // require.resolve(__dirname, './apps/watcher'), + // require.resolve(__dirname, './apps/dashboard'), + // require.resolve(__dirname, './apps/dashboard_mode'), + // require.resolve(__dirname, './apps/discover'), + // require.resolve(__dirname, './apps/security'), + // require.resolve(__dirname, './apps/spaces'), + // require.resolve(__dirname, './apps/lens'), + // require.resolve(__dirname, './apps/logstash'), + // require.resolve(__dirname, './apps/grok_debugger'), + // require.resolve(__dirname, './apps/infra'), + // require.resolve(__dirname, './apps/machine_learning'), + // require.resolve(__dirname, './apps/rollup_job'), + // require.resolve(__dirname, './apps/maps'), + // require.resolve(__dirname, './apps/status_page'), + // require.resolve(__dirname, './apps/timelion'), + // require.resolve(__dirname, './apps/upgrade_assistant'), + // require.resolve(__dirname, './apps/code'), + // require.resolve(__dirname, './apps/visualize'), + // require.resolve(__dirname, './apps/uptime'), + // require.resolve(__dirname, './apps/saved_objects_management'), + // require.resolve(__dirname, './apps/dev_tools'), + // require.resolve(__dirname, './apps/apm'), + // require.resolve(__dirname, './apps/index_patterns'), + // require.resolve(__dirname, './apps/index_management'), + // require.resolve(__dirname, './apps/index_lifecycle_management'), + // require.resolve(__dirname, './apps/snapshot_restore'), + // require.resolve(__dirname, './apps/cross_cluster_replication'), + // require.resolve(__dirname, './apps/remote_clusters'), + // // This license_management file must be last because it is destructive. + // require.resolve(__dirname, './apps/license_management'), + // ], + + browser: { + type: 'ie', + }, + + junit: { + reportName: 'Internet Explorer UI Functional X-Pack Tests' + }, + + uiSettings: { + defaults: { + 'accessibility:disableAnimations': true, + 'dateFormat:tz': 'UTC', + 'telemetry:optIn': false, + 'state:storeInSessionStorage': true, + }, + }, + + + kbnTestServer: { + ...defaultConfig.get('kbnTestServer'), + serverArgs: [ + ...defaultConfig.get('kbnTestServer.serverArgs'), + '--csp.strict=false', + ], + }, + + + }; +} diff --git a/x-pack/test/functional/es_archives/lens/reporting/data.json.gz b/x-pack/test/functional/es_archives/lens/reporting/data.json.gz index 93ceaf3d8f6f..3a55820a3551 100644 Binary files a/x-pack/test/functional/es_archives/lens/reporting/data.json.gz and b/x-pack/test/functional/es_archives/lens/reporting/data.json.gz differ diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index 88501aad57b4..67b2497cc91e 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -19,7 +19,7 @@ export function InfraHomePageProvider({ getService }: FtrProviderContext) { const datePickerInput = await find.byCssSelector( `${testSubjSelector('waffleDatePicker')} .euiDatePicker.euiFieldText` ); - await datePickerInput.type(Array(30).fill(browser.keys.BACK_SPACE)); + await datePickerInput.clearValueWithKeyboard({ charByChar: true }); await datePickerInput.type([time, browser.keys.RETURN]); }, diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js index 5c0b59674bde..30d830cd6c91 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js @@ -9,6 +9,8 @@ import expect from '@kbn/expect'; import url from 'url'; import supertestAsPromised from 'supertest-as-promised'; +const { task: { properties: taskManagerIndexMapping } } = require('../../../../legacy/plugins/task_manager/mappings.json'); + export default function ({ getService }) { const es = getService('es'); const log = getService('log'); @@ -30,6 +32,15 @@ export default function ({ getService }) { q: 'type:task', refresh: true, }); + } else { + await es.indices.create({ + index: testHistoryIndex, + body: { + mappings: { + properties: taskManagerIndexMapping + }, + }, + }); } }); diff --git a/x-pack/test/plugin_api_perf/README.md b/x-pack/test/plugin_api_perf/README.md index 034374e196dc..f47a2aeb7878 100644 --- a/x-pack/test/plugin_api_perf/README.md +++ b/x-pack/test/plugin_api_perf/README.md @@ -50,4 +50,28 @@ After the test runs you should get the following output: If you look at the debug output you'll see a summary of how the test went: You'll see the average number of tasks executed per second, over a period of each 5 second window (meaning we calculate the running average based on a sliding window of 5 seconds). -You'll also see the average time it takes from the moment a task's scheduled time was reached, until Task Manager picked it up for execution. \ No newline at end of file +You'll also see the average time it takes from the moment a task's scheduled time was reached, until Task Manager picked it up for execution. + + +# Running the test against multiple Kibana and a single Elasticsearch +This is not a clean and ideal way of running this test, but it is a workaround that's worth understanding. + +## Test Description +The idea is that we would like to test performance of running multiple Kibana instances side by side, both pointing at the same Elasticsearch cluster. + +This is needed in order to verify that the distributed nature of Kibana doesn't introduce issues or break assumptions in our developed solutions. + +The challenge is that the _plugin_ used to create the Perf test is exposed in the FTS, but not in a standard Kibana build. + +Running two Kibana in FTS side by side is actually very tricky, so below is a step by step method for achieving this. +Ideally we can clean this up and make it easier and less hacky in the future, but for now, this documents how this can be achieved. + +## Method +1. You need two cloned repos of Kibana, so clone a second Kibana of your personal form along side your existing clone. Personally I have two co-located Kibana folders (`./elastic/kibana` and `./elastic/_kibana`, where the first is my working clone and the other is never used for actual dev work, but that's just me -GM). +1. You can run the FTS in the main clone of your fork by running `node scripts/functional_tests_server.js --config=test/plugin_api_perf/config.js` in the `x-pack` folder. +1. Once you've began running the default FTS, you want your second FTS to run such that it is referencing the Elasticsearch instance started by that first FTS. You achieve this by exporting a `TEST_ES_URL` Environment variable that points at it. By default, you should be able to run this: `export TEST_ES_URL=http://elastic:changeme@localhost:9220`. Do this in a terminal window opened in your **second** clone of Kibana (in my case, the `./elastic/_kibana` folder). +1. One issue I encountered with FTS is that I can't tell it _not to start its own ES instance at all_. To achieve this, in `packages/kbn-test/src/functional_tests/tasks.js` you need to comment out the line that starts up its own ES (`const es = await runElasticsearch({ config, options: opts });` [line 85] and `await es.cleanup();` shortly after) +1. Next you want each instance of Kibana to run with its own UUID as that is used to identify each Kibana's owned tasks. In the file `x-pack/test/functional/config.js` simple change the uuid on the line `--server.uuid=` into any random UUID. +1. Now that you've made these changes you can kick off your second Kibana FTS by running ths following in the second clone's `x-pack` folder: `TEST_KIBANA_PORT=5621 node scripts/functional_tests_server.js --config=test/plugin_api_perf/config.js`. This runs Kibana on a different port than the first FTS (`5621` instead of `5620`). +1. With two FTS Kibana running and both pointing at the same Elasticsearch. Now, you can run the actual perf test by running `node scripts/functional_test_runner.js --config=test/plugin_api_perf/config.js` in a third terminal + diff --git a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js index abad95729dcf..0547069dfb46 100644 --- a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js +++ b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js @@ -62,12 +62,32 @@ export default function TaskManagerPerformanceAPI(kibana) { createTaskRunner: ({ taskInstance }) => { return { async run() { - const { state } = taskInstance; - const leadTime = Date.now() - taskInstance.runAt; + const { params, state } = taskInstance; + + const runAt = millisecondsFromNow(5000); + const now = Date.now(); + const leadTime = now - taskInstance.runAt; performanceState.leadTimeQueue.push(leadTime); + + const counter = (state.counter ? 1 + state.counter : 1); + + const stateUpdated = { + ...state, + counter + }; + + if(params.trackExecutionTimeline) { + stateUpdated.timeline = stateUpdated.timeline || []; + stateUpdated.timeline.push({ + owner: taskInstance.owner.split('-')[0], + counter, + leadTime, + ranAt: now + }); + } return { - state, - runAt: millisecondsFromNow(1000), + state: stateUpdated, + runAt, }; }, }; diff --git a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js index 316c434b33c4..b9fe788093d1 100644 --- a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js +++ b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js @@ -18,11 +18,12 @@ export function initRoutes(server, performanceState) { payload: Joi.object({ tasksToSpawn: Joi.number().required(), durationInSeconds: Joi.number().required(), + trackExecutionTimeline: Joi.boolean().default(false).required(), }), }, }, async handler(request) { - const { tasksToSpawn, durationInSeconds } = request.payload; + const { tasksToSpawn, durationInSeconds, trackExecutionTimeline } = request.payload; const tasks = []; for (let taskIndex = 0; taskIndex < tasksToSpawn; taskIndex++) { @@ -30,7 +31,7 @@ export function initRoutes(server, performanceState) { await taskManager.schedule( { taskType: 'performanceTestTask', - params: { taskIndex }, + params: { taskIndex, trackExecutionTimeline }, scope: [scope], }, { request } diff --git a/x-pack/test/plugin_api_perf/test_suites/task_manager/task_manager_perf_integration.ts b/x-pack/test/plugin_api_perf/test_suites/task_manager/task_manager_perf_integration.ts index 292a96baacd9..e8dbf7c50928 100644 --- a/x-pack/test/plugin_api_perf/test_suites/task_manager/task_manager_perf_integration.ts +++ b/x-pack/test/plugin_api_perf/test_suites/task_manager/task_manager_perf_integration.ts @@ -15,7 +15,7 @@ export default function({ getService }: { getService: (service: string) => any } const { runningAverageTasks, runningAverageLeadTime } = await supertest .post('/api/perf_tasks') .set('kbn-xsrf', 'xxx') - .send({ tasksToSpawn: 10, durationInSeconds: 60 }) + .send({ tasksToSpawn: 20, trackExecutionTimeline: true, durationInSeconds: 60 }) .expect(200) .then((response: any) => response.body); diff --git a/x-pack/test/reporting/functional/lib/compare_pdfs.js b/x-pack/test/reporting/functional/lib/compare_pdfs.js index f7fbfe051b84..9463a96140b1 100644 --- a/x-pack/test/reporting/functional/lib/compare_pdfs.js +++ b/x-pack/test/reporting/functional/lib/compare_pdfs.js @@ -6,13 +6,12 @@ import path from 'path'; import fs from 'fs'; -import { promisify } from 'bluebird'; -import mkdirp from 'mkdirp'; +import { promisify } from 'util'; import { PDFImage } from 'pdf-image'; import PDFJS from 'pdfjs-dist'; import { comparePngs } from '../../../../../test/functional/services/lib/compare_pngs'; -const mkdirAsync = promisify(mkdirp); +const mkdirAsync = promisify(fs.mkdir); export async function checkIfPdfsMatch(actualPdfPath, baselinePdfPath, screenshotsDirectory, log) { log.debug(`checkIfPdfsMatch: ${actualPdfPath} vs ${baselinePdfPath}`); @@ -21,8 +20,8 @@ export async function checkIfPdfsMatch(actualPdfPath, baselinePdfPath, screensho const sessionDirectoryPath = path.resolve(screenshotsDirectory, 'session'); const failureDirectoryPath = path.resolve(screenshotsDirectory, 'failure'); - await mkdirAsync(sessionDirectoryPath); - await mkdirAsync(failureDirectoryPath); + await mkdirAsync(sessionDirectoryPath, { recursive: true }); + await mkdirAsync(failureDirectoryPath, { recursive: true }); const actualPdfFileName = path.basename(actualPdfPath, '.pdf'); const baselinePdfFileName = path.basename(baselinePdfPath, '.pdf'); diff --git a/x-pack/test/reporting/functional/lib/compare_pngs.js b/x-pack/test/reporting/functional/lib/compare_pngs.js index afbebc03406f..2bde535c3b69 100644 --- a/x-pack/test/reporting/functional/lib/compare_pngs.js +++ b/x-pack/test/reporting/functional/lib/compare_pngs.js @@ -7,10 +7,9 @@ import path from 'path'; import fs from 'fs'; import { promisify } from 'bluebird'; -import mkdirp from 'mkdirp'; import { comparePngs } from '../../../../../test/functional/services/lib/compare_pngs'; -const mkdirAsync = promisify(mkdirp); +const mkdirAsync = promisify(fs.mkdir); export async function checkIfPngsMatch(actualpngPath, baselinepngPath, screenshotsDirectory, log) { log.debug(`checkIfpngsMatch: ${actualpngPath} vs ${baselinepngPath}`); @@ -19,8 +18,8 @@ export async function checkIfPngsMatch(actualpngPath, baselinepngPath, screensho const sessionDirectoryPath = path.resolve(screenshotsDirectory, 'session'); const failureDirectoryPath = path.resolve(screenshotsDirectory, 'failure'); - await mkdirAsync(sessionDirectoryPath); - await mkdirAsync(failureDirectoryPath); + await mkdirAsync(sessionDirectoryPath, { recursive: true }); + await mkdirAsync(failureDirectoryPath, { recursive: true }); const actualpngFileName = path.basename(actualpngPath, '.png'); const baselinepngFileName = path.basename(baselinepngPath, '.png'); diff --git a/x-pack/test/reporting/functional/reporting.js b/x-pack/test/reporting/functional/reporting.js index 814e85ade157..0c528a80b433 100644 --- a/x-pack/test/reporting/functional/reporting.js +++ b/x-pack/test/reporting/functional/reporting.js @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import path from 'path'; -import mkdirp from 'mkdirp'; import fs from 'fs'; -import { promisify } from 'bluebird'; +import { promisify } from 'util'; import { checkIfPdfsMatch, checkIfPngsMatch } from './lib'; + const writeFileAsync = promisify(fs.writeFile); -const mkdirAsync = promisify(mkdirp); +const mkdirAsync = promisify(fs.mkdir); const REPORTS_FOLDER = path.resolve(__dirname, 'reports'); @@ -51,7 +51,7 @@ export default function ({ getService, getPageObjects }) { const writeSessionReport = async (name, rawPdf, reportExt = 'pdf') => { const sessionDirectory = path.resolve(REPORTS_FOLDER, 'session'); - await mkdirAsync(sessionDirectory); + await mkdirAsync(sessionDirectory, { recursive: true }); const sessionReportPath = path.resolve(sessionDirectory, `${name}.${reportExt}`); await writeFileAsync(sessionReportPath, rawPdf); return sessionReportPath; diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index d0452ae20a69..82b2131a0a85 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -6,7 +6,8 @@ "legacy/server/**/*", "legacy/plugins/**/*", "plugins/**/*", - "test_utils/**/*" + "test_utils/**/*", + "tasks/**/*" ], "exclude": [ "test/**/*", diff --git a/yarn.lock b/yarn.lock index 9d73d21a044b..f2c687407836 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1064,10 +1064,10 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@elastic/charts@^12.0.2": - version "12.0.2" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-12.0.2.tgz#576fafccd9e9f6ca751b6e846be3a5c954e8865b" - integrity sha512-BxdJVXUkYE11X+n5QWfu6ntDCm6wbkvLRNWrJG30pgGv9QEDhEbraQ8ql9Vx1454EuEjgXP6xOM0X+3rCO4Nqw== +"@elastic/charts@^12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-12.1.0.tgz#a1ad871cf1d90efc2450d29aa41bc9872434616a" + integrity sha512-sXZ0KHN29icVbeCU0x6LsiYI++w55kUJA0bpH7vpGExny70UBHNqjnLcBui0DBhlS3Bsz64xW5c4i9TKWeqXLA== dependencies: "@types/d3-shape" "^1.3.1" "@types/luxon" "^1.11.1" @@ -1106,10 +1106,10 @@ parse-gitignore "1.0.1" vscode-languageserver "^5.2.1" -"@elastic/elasticsearch@^7.3.0": - version "7.3.0" - resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.3.0.tgz#d62508cc03e91dd0676914a50af6500b45bfb199" - integrity sha512-CA8V4txIS+BPZg37ZVtOi5mN2xnXYAeQUCvgkjdtc2CzTd5pJrjdPzdmaDDATNc8nhlHMrqxMZZmpKD3OUkjAg== +"@elastic/elasticsearch@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.4.0.tgz#57f4066acf25e9d4e9b4f6376088433aae6f25d4" + integrity sha512-HpEKHH6mHQRvea3lw4NNJw9ZUS1KmkpwWKHucaHi1svDn+/fEAwY0wD8egL1vZJo4ZmWfCQMjVqGL+Hoy1HYRw== dependencies: debug "^4.1.1" decompress-response "^4.2.0" @@ -3314,6 +3314,11 @@ dependencies: "@types/node" "*" +"@types/fancy-log@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.1.tgz#dd94fbc8c2e2ab8ab402ca8d04bb8c34965f0696" + integrity sha512-31Dt9JaGfHretvwVxCBrCFL5iC9MQ3zOXpu+8C4qzW0cxc5rJJVGxB5c/vZ+wmeTk/JjPz/D0gv8BZ+Ip6iCqQ== + "@types/fetch-mock@^7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.1.tgz#df7421e8bcb351b430bfbfa5c52bb353826ac94f" @@ -3348,11 +3353,24 @@ resolved "https://registry.yarnpkg.com/@types/getopts/-/getopts-2.0.1.tgz#b7e5478fe7571838b45aff736a59ab69b8bcda18" integrity sha512-JsQJHtzLYKunMz7acYOX6x5IJ/42CsjjHFfLCmis1Hn/qFoD/y0kJFUIAg8HoDigQpKUcWj55d8rxZQBnvz1Pw== +"@types/getos@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/getos/-/getos-3.0.0.tgz#582c758e99e9d634f31f471faf7ce59cf1c39a71" + integrity sha512-g5O9kykBPMaK5USwU+zM5AyXaztqbvHjSQ7HaBjqgO3f5lKGChkRhLP58Z/Nrr4RBGNNPrBcJkWZwnmbmi9YjQ== + "@types/git-url-parse@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@types/git-url-parse/-/git-url-parse-9.0.0.tgz#aac1315a44fa4ed5a52c3820f6c3c2fb79cbd12d" integrity sha512-kA2RxBT/r/ZuDDKwMl+vFWn1Z0lfm1/Ik6Qb91wnSzyzCDa/fkM8gIOq6ruB7xfr37n6Mj5dyivileUVKsidlg== +"@types/glob-stream@*": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@types/glob-stream/-/glob-stream-6.1.0.tgz#7ede8a33e59140534f8d8adfb8ac9edfb31897bc" + integrity sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg== + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/glob@*", "@types/glob@^5.0.35": version "5.0.35" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" @@ -3391,6 +3409,15 @@ resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.4.tgz#55ae9c29f0fd6b85ee536f5c72b4769d5c5e06b1" integrity sha512-B4yel4ro2nTb3v0pYO8vO6SjgvFJSrwUY+IO6TUSLdOSB+gQFslylrhRCHxvXMIhxB71mv5PEE9dAX+24S8sew== +"@types/gulp@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/gulp/-/gulp-4.0.6.tgz#68fe0e1f0ff3657cfca46fb564806b744a1bf899" + integrity sha512-0E8/iV/7FKWyQWSmi7jnUvgXXgaw+pfAzEB06Xu+l0iXVJppLbpOye5z7E2klw5akXd+8kPtYuk65YBcZPM4ow== + dependencies: + "@types/undertaker" "*" + "@types/vinyl-fs" "*" + chokidar "^2.1.2" + "@types/hapi-auth-cookie@^9.1.0": version "9.1.0" resolved "https://registry.yarnpkg.com/@types/hapi-auth-cookie/-/hapi-auth-cookie-9.1.0.tgz#cbcd2236b7d429bd0632a8cc45cfd355fdd7e7a2" @@ -3562,7 +3589,7 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.30.tgz#44cb52f32a809734ca562e685c6473b5754a7818" integrity sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA== -"@types/jsonwebtoken@^7.2.7": +"@types/jsonwebtoken@^7.2.8": version "7.2.8" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" integrity sha512-XENN3YzEB8D6TiUww0O8SRznzy1v+77lH7UmuN54xq/IHIsyWjWOzZuFFTtoiRuaE782uAoRwBe/wwow+vQXZw== @@ -3677,13 +3704,6 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" integrity sha1-UALhT3Xi1x5WQoHfBDHIwbSio2o= -"@types/mkdirp@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" - integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== - dependencies: - "@types/node" "*" - "@types/mocha@^5.2.7": version "5.2.7" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" @@ -3828,10 +3848,10 @@ resolved "https://registry.yarnpkg.com/@types/proper-lockfile/-/proper-lockfile-3.0.1.tgz#dd770a2abce3adbcce3bd1ed892ce2f5f17fbc86" integrity sha512-ODOjqxmaNs0Zkij+BJovsNJRSX7BJrr681o8ZnNTNIcTermvVFzLpz/XFtfg3vNrlPVTJY1l4e9h2LvHoxC1lg== -"@types/puppeteer@^1.19.0": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.19.0.tgz#59f0050bae019cee7c3af2bb840a25892a3078b6" - integrity sha512-Db9LWOuTm2bR/qgPE7PQCmnsCQ6flHdULuIDWTks8YdQ/SGHKg5WGWG54gl0734NDKCTF5MbqAp2qWuvBiyQ3Q== +"@types/puppeteer@^1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.20.1.tgz#0aba5ae3d290daa91cd3ba9f66ba5e9fba3499cc" + integrity sha512-F91CqYDHETg3pQfIPNBNZKmi7R1xS1y4yycOYX7o6Xk16KF+IV+9LqTmVuG+FIxw/53/JEy94zKjjGjg92V6bg== dependencies: "@types/node" "*" @@ -4132,6 +4152,18 @@ resolved "https://registry.yarnpkg.com/@types/type-detect/-/type-detect-4.0.1.tgz#3b0f5ac82ea630090cbf57c57a1bf5a63a29b9b6" integrity sha512-0+S1S9Iq0oJ9w9IaBC5W/z1WsPNDUIAJG+THGmqR4vUAxUPCzIY+dApTvyGsaBUWjafTDL0Dg8Z9+iRuk3/BQA== +"@types/undertaker-registry@*": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz#4306d4a03d7acedb974b66530832b90729e1d1da" + integrity sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ== + +"@types/undertaker@*": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/undertaker/-/undertaker-1.2.2.tgz#927da24d0d3279830af96386862b035e040ead74" + integrity sha512-j4iepCSuY2JGW/hShVtUBagic0klYNFIXP7VweavnYnNC2EjiKxJFeaS9uaJmAT0ty9sQSqTS1aagWMZMV0HyA== + dependencies: + "@types/undertaker-registry" "*" + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" @@ -4166,6 +4198,22 @@ "@types/unist" "*" "@types/vfile-message" "*" +"@types/vinyl-fs@*", "@types/vinyl-fs@^2.4.11": + version "2.4.11" + resolved "https://registry.yarnpkg.com/@types/vinyl-fs/-/vinyl-fs-2.4.11.tgz#b98119b8bb2494141eaf649b09fbfeb311161206" + integrity sha512-2OzQSfIr9CqqWMGqmcERE6Hnd2KY3eBVtFaulVo3sJghplUcaeMdL9ZjEiljcQQeHjheWY9RlNmumjIAvsBNaA== + dependencies: + "@types/glob-stream" "*" + "@types/node" "*" + "@types/vinyl" "*" + +"@types/vinyl@*": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/vinyl/-/vinyl-2.0.3.tgz#80a6ce362ab5b32a0c98e860748a31bce9bff0de" + integrity sha512-hrT6xg16CWSmndZqOTJ6BGIn2abKyTw0B58bI+7ioUoj3Sma6u8ftZ1DTI2yCaJamOVGLOnQWiPH3a74+EaqTA== + dependencies: + "@types/node" "*" + "@types/webpack-env@*": version "1.13.9" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.9.tgz#a67287861c928ebf4159a908d1fb1a2a34d4097a" @@ -4599,6 +4647,11 @@ adm-zip@0.4.11: resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" integrity sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA== +adm-zip@^0.4.13: + version "0.4.13" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.13.tgz#597e2f8cc3672151e1307d3e95cddbc75672314a" + integrity sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw== + affine-hull@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/affine-hull/-/affine-hull-1.0.0.tgz#763ff1d38d063ceb7e272f17ee4d7bbcaf905c5d" @@ -4882,13 +4935,6 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.1.tgz#9638047e4213f3428a11944a7d4b31cba0a3ff95" integrity sha512-Xt+zb6nqgvV9SWAVp0EG3lRsHcbq5DDgqjPPz6pwgtj6RKz65zGXMNa82oJfOSBA/to6GmRP7Dr+6o+kbApTzQ== -ansi-cyan@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" - integrity sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM= - dependencies: - ansi-wrap "0.1.0" - ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" @@ -4935,13 +4981,6 @@ ansi-html@0.0.7: resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= -ansi-red@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" - integrity sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw= - dependencies: - ansi-wrap "0.1.0" - ansi-regex@^0.2.0, ansi-regex@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" @@ -5039,10 +5078,10 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.2.tgz#ddb3a8495d44875423af7b919aace11e91732a41" - integrity sha512-rUe9SxpRQlVg4EM8It7JMNWWYHAirTPpbTuvaSKybb5IejNgWB3PGBBX9rrPKDx2pM/p3Wh+7+ASaWRyyAbxmQ== +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -5332,28 +5371,29 @@ aria-query@^3.0.0: ast-types-flow "0.0.7" commander "^2.11.0" -arr-diff@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" - integrity sha1-aHwydYFjWI/vfeezb6vklesaOZo= - dependencies: - arr-flatten "^1.0.1" - array-slice "^0.2.3" - arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= +arr-filter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee" + integrity sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4= + dependencies: + make-iterator "^1.0.0" + arr-flatten@^1.0.1, arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== -arr-union@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d" - integrity sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0= +arr-map@^2.0.0, arr-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4" + integrity sha1-Onc0X/wc814qkYJWAfnljy4kysQ= + dependencies: + make-iterator "^1.0.0" arr-union@^3.1.0: version "3.1.0" @@ -5370,7 +5410,7 @@ array-differ@^3.0.0: resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== -array-each@^1.0.1: +array-each@^1.0.0, array-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= @@ -5423,6 +5463,21 @@ array-includes@^3.0.3: define-properties "^1.1.2" es-abstract "^1.7.0" +array-initial@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" + integrity sha1-L6dLJnOTccOUe9enrcc74zSz15U= + dependencies: + array-slice "^1.0.0" + is-number "^4.0.0" + +array-last@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.3.0.tgz#7aa77073fec565ddab2493f5f88185f404a9d336" + integrity sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg== + dependencies: + is-number "^4.0.0" + array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -5453,6 +5508,15 @@ array-slice@^1.0.0: resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== +array-sort@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" + integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + dependencies: + default-compare "^1.0.0" + get-value "^2.0.6" + kind-of "^5.0.2" + array-union@^1.0.1, array-union@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -5465,7 +5529,7 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-uniq@^1.0.0, array-uniq@^1.0.1, array-uniq@^1.0.2: +array-uniq@^1.0.0, array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= @@ -5623,6 +5687,16 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +async-done@^1.2.0, async-done@^1.2.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2" + integrity sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.2" + process-nextick-args "^2.0.0" + stream-exhaust "^1.0.1" + async-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -5650,6 +5724,13 @@ async-retry@^1.2.3: dependencies: retry "0.12.0" +async-settle@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" + integrity sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs= + dependencies: + async-done "^1.2.2" + async.queue@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/async.queue/-/async.queue-0.5.2.tgz#8d5d90812e1481066bc0904e8cc1712b17c3bd7c" @@ -6359,6 +6440,21 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== +bach@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880" + integrity sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA= + dependencies: + arr-filter "^1.1.1" + arr-flatten "^1.0.1" + arr-map "^2.0.0" + array-each "^1.0.0" + array-initial "^1.0.0" + array-last "^1.1.1" + async-done "^1.2.2" + async-settle "^1.0.0" + now-and-later "^2.0.0" + backo2@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -6459,11 +6555,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= - before-after-hook@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.1.0.tgz#83165e15a59460d13702cb8febd6a1807896db5a" @@ -6762,7 +6853,7 @@ boxen@^3.0.0: type-fest "^0.3.0" widest-line "^2.0.0" -brace-expansion@^1.0.0, brace-expansion@^1.1.7: +brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" integrity sha1-wHshHHyVLsH479Uad+8NHTmQopI= @@ -6798,7 +6889,7 @@ braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@^3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -7136,6 +7227,30 @@ cacache@^12.0.2: unique-filename "^1.1.1" y18n "^4.0.0" +cacache@^13.0.0: + version "13.0.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" + integrity sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w== + dependencies: + chownr "^1.1.2" + figgy-pudding "^3.5.1" + fs-minipass "^2.0.0" + glob "^7.1.4" + graceful-fs "^4.2.2" + infer-owner "^1.0.4" + lru-cache "^5.1.1" + minipass "^3.0.0" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + p-map "^3.0.0" + promise-inflight "^1.0.1" + rimraf "^2.7.1" + ssri "^7.0.0" + unique-filename "^1.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -7658,25 +7773,25 @@ chokidar@2.1.2, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4: optionalDependencies: fsevents "^1.2.7" -chokidar@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681" - integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA== - dependencies: - anymatch "^3.0.1" - braces "^3.0.2" - glob-parent "^5.0.0" - is-binary-path "^2.1.0" - is-glob "^4.0.1" - normalize-path "^3.0.0" - readdirp "^3.1.1" +chokidar@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.1.tgz#4634772a1924512d990d4505957bf3a510611387" + integrity sha512-/j5PPkb5Feyps9e+jo07jUZGvkB5Aj953NrI4s8xSVScrAo/RHeILrtdb4uzR7N6aaFFxxJ+gt8mA8HfNpw76w== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.1.3" optionalDependencies: - fsevents "^2.0.6" + fsevents "~2.1.0" -chokidar@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5" - integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g== +chokidar@^2.0.0, chokidar@^2.1.2, chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== dependencies: anymatch "^2.0.0" async-each "^1.0.1" @@ -7697,6 +7812,11 @@ chownr@^1.0.1, chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== +chownr@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== + chroma-js@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.4.1.tgz#eb2d9c4d1ff24616be84b35119f4d26f8205f134" @@ -8027,11 +8147,6 @@ clone-stats@^1.0.0: resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - integrity sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8= - clone@^1.0.0, clone@^1.0.1, clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -8105,6 +8220,15 @@ collapse-white-space@^1.0.2: resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c" integrity sha1-S5BvZw5aljqHt2sOFolkM0G2Ajw= +collection-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c" + integrity sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw= + dependencies: + arr-map "^2.0.2" + for-own "^1.0.0" + make-iterator "^1.0.0" + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -8347,21 +8471,21 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== +concat-stream@1.6.0, concat-stream@^1.4.7, concat-stream@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc= dependencies: - buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^1.4.7, concat-stream@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" - integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc= +concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== dependencies: + buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" @@ -8394,6 +8518,14 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" +config-chain@~1.1.8: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + configstore@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021" @@ -8604,6 +8736,14 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy-props@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" + integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + dependencies: + each-props "^1.3.0" + is-plain-object "^2.0.1" + copy-to-clipboard@^3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" @@ -9746,6 +9886,13 @@ deepmerge@^4.0.0: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.0.0.tgz#3e3110ca29205f120d7cb064960a39c3d2087c09" integrity sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww== +default-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" + integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + dependencies: + kind-of "^5.0.2" + default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -9761,12 +9908,17 @@ default-require-extensions@^2.0.0: dependencies: strip-bom "^3.0.0" +default-resolution@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" + integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= + default-uid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/default-uid/-/default-uid-1.0.0.tgz#fcefa9df9f5ac40c8916d912dd1fe1146aa3c59e" integrity sha1-/O+p359axAyJFtkS3R/hFGqjxZ4= -defaults@^1.0.0, defaults@^1.0.3: +defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= @@ -9896,11 +10048,6 @@ deprecated-decorator@^0.1.6: resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc= -deprecated@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" - integrity sha1-+cmvVGSvoeepcUWKi97yqpTVuxk= - deprecation@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-1.0.1.tgz#2df79b79005752180816b7b6e079cbd80490d711" @@ -10351,11 +10498,6 @@ dotenv-webpack@^1.7.0: dependencies: dotenv-defaults "^1.0.2" -dotenv@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-2.0.0.tgz#bd759c357aaa70365e01c96b7b0bec08a6e0d949" - integrity sha1-vXWcNXqqcDZeAclrewvsCKbg2Uk= - dotenv@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" @@ -10401,13 +10543,6 @@ dragselect@1.13.1: resolved "https://registry.yarnpkg.com/dragselect/-/dragselect-1.13.1.tgz#aa4166e1164b51ed5ee0cd89e0c5310a9c35be6a" integrity sha512-spfUz6/sNnlY4fF/OxPBwaKLa5hVz6V+fq5XhVuD+h47RAkA75TMkfvr4AoWUh5Ufq3V1oIAbfu+sjc9QbewoA== -duplexer2@0.0.2, duplexer2@~0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= - dependencies: - readable-stream "~1.1.9" - duplexer2@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -10415,6 +10550,13 @@ duplexer2@^0.1.4: dependencies: readable-stream "^2.0.2" +duplexer2@~0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" + integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= + dependencies: + readable-stream "~1.1.9" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -10453,6 +10595,14 @@ each-async@^1.1.1: onetime "^1.0.0" set-immediate-shim "^1.0.0" +each-props@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" + integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== + dependencies: + is-plain-object "^2.0.1" + object.defaults "^1.1.0" + eachr@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/eachr/-/eachr-3.2.0.tgz#2c35e43ea086516f7997cf80b7aa64d55a4a4484" @@ -10486,6 +10636,13 @@ ecdsa-sig-formatter@1.0.10: dependencies: safe-buffer "^5.0.1" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + editions@^1.1.1, editions@^1.3.3, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -10642,13 +10799,6 @@ end-of-stream@^1.4.1: dependencies: once "^1.4.0" -end-of-stream@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" - integrity sha1-jhdyBsPICDfYVjLouTWd/osvbq8= - dependencies: - once "~1.3.0" - engine.io-client@~3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" @@ -11787,13 +11937,6 @@ express@^4.17.0, express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -extend-shallow@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" - integrity sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE= - dependencies: - kind-of "^1.1.0" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -11906,6 +12049,16 @@ extract-stack@^1.0.0: resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-1.0.0.tgz#b97acaf9441eea2332529624b732fc5a1c8165fa" integrity sha1-uXrK+UQe6iMyUpYktzL8WhyBZfo= +extract-zip@1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c" + integrity sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw= + dependencies: + concat-stream "1.6.0" + debug "2.6.9" + mkdirp "0.5.0" + yauzl "2.4.1" + extract-zip@1.6.7, extract-zip@^1.6.6, extract-zip@^1.6.7: version "1.6.7" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" @@ -11946,7 +12099,7 @@ falafel@^2.1.0: isarray "0.0.1" object-keys "^1.0.6" -fancy-log@^1.1.0, fancy-log@^1.3.2: +fancy-log@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" integrity sha1-9BEl49hPLn2JpD0G2VjI94vha+E= @@ -12355,11 +12508,6 @@ find-cache-dir@^3.0.0: make-dir "^3.0.0" pkg-dir "^4.1.0" -find-index@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" - integrity sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ= - find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -12410,7 +12558,7 @@ find@^0.3.0: dependencies: traverse-chain "~0.1.0" -findup-sync@3.0.0: +findup-sync@3.0.0, findup-sync@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== @@ -12453,11 +12601,6 @@ finity@^0.5.4: resolved "https://registry.yarnpkg.com/finity/-/finity-0.5.4.tgz#f2a8a9198e8286467328ec32c8bfcc19a2229c11" integrity sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA== -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04= - first-chunk-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70" @@ -12840,6 +12983,13 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.2.1" +fs-minipass@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.0.0.tgz#a6415edab02fae4b9e9230bc87ee2e4472003cd1" + integrity sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A== + dependencies: + minipass "^3.0.0" + fs-mkdirp-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" @@ -12876,10 +13026,10 @@ fsevents@^1.2.7: nan "^2.9.2" node-pre-gyp "^0.10.0" -fsevents@^2.0.6: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" - integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== +fsevents@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.0.tgz#ce1a5f9ac71c6d75278b0c5bd236d7dfece4cbaa" + integrity sha512-+iXhW3LuDQsno8dOIrCIT/CBjeBWuP7PXe8w9shnj9Lebny/Gx1ZjVBYwexLz36Ri2jKuXMNpV6CYNh8lHHgrQ== fstream@^1.0.0: version "1.0.11" @@ -12968,13 +13118,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaze@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" - integrity sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8= - dependencies: - globule "~0.1.0" - gaze@^1.0.0, gaze@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" @@ -13127,7 +13270,7 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" -get-stream@^5.0.0: +get-stream@^5.0.0, get-stream@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== @@ -13260,17 +13403,12 @@ glob-parent@^5.0.0: dependencies: is-glob "^4.0.1" -glob-stream@^3.1.5: - version "3.1.18" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" - integrity sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs= +glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: - glob "^4.3.1" - glob2base "^0.0.12" - minimatch "^2.0.1" - ordered-read-streams "^0.1.0" - through2 "^0.6.1" - unique-stream "^1.0.0" + is-glob "^4.0.1" glob-stream@^6.1.0: version "6.1.0" @@ -13298,19 +13436,17 @@ glob-to-regexp@^0.4.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.0.tgz#49bd677b1671022bd10921c3788f23cdebf9c7e6" integrity sha512-fyPCII4vn9Gvjq2U/oDAfP433aiE64cyP/CJjRJcpVGjqqNdioUYn9+r0cSzT1XPwmGAHuTT7iv+rQT8u/YHKQ== -glob-watcher@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" - integrity sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs= - dependencies: - gaze "^0.5.1" - -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - integrity sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY= +glob-watcher@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" + integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== dependencies: - find-index "^0.1.1" + anymatch "^2.0.0" + async-done "^1.2.0" + chokidar "^2.0.0" + is-negated-glob "^1.0.0" + just-debounce "^1.0.0" + object.defaults "^1.1.0" glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: version "7.1.3" @@ -13336,16 +13472,6 @@ glob@7.1.4, glob@^7.1.4, glob@~7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^4.3.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" - integrity sha1-xstz0yJsHv7wTePFbQEvAzd+4V8= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - glob@^5.0.15, glob@~5.0.0: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -13368,15 +13494,6 @@ glob@^6.0.1, glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@~3.1.21: - version "3.1.21" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" - integrity sha1-0p4KBV3qUTj00H7UDomC6DwgZs0= - dependencies: - graceful-fs "~1.2.0" - inherits "1" - minimatch "~0.2.11" - glob@~7.0.0: version "7.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" @@ -13585,15 +13702,6 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -globule@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" - integrity sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU= - dependencies: - glob "~3.1.21" - lodash "~1.0.1" - minimatch "~0.2.11" - glogg@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" @@ -13742,13 +13850,6 @@ graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, g resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -graceful-fs@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - integrity sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg= - dependencies: - natives "^1.1.0" - graceful-fs@^4.1.15, graceful-fs@^4.1.9: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" @@ -13759,10 +13860,10 @@ graceful-fs@^4.2.0, graceful-fs@^4.2.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== -graceful-fs@~1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" - integrity sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q= +graceful-fs@~1.1: + version "1.1.14" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.1.14.tgz#07078db5f6377f6321fceaaedf497de124dc9465" + integrity sha1-BweNtfY3f2Mh/Oqu30l94STclGU= "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -14125,10 +14226,34 @@ gulp-babel@^8.0.0: through2 "^2.0.0" vinyl-sourcemaps-apply "^0.2.0" -gulp-mocha@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/gulp-mocha/-/gulp-mocha-7.0.1.tgz#cd29f2fc214a8c08c7d96bf13927d539385a856d" - integrity sha512-LYBEWdOw52kvP+si91iR00LYX9iKXLTBjcKh9b3ChHvVmKtpoITjeRFslPEzDubEk+z6VI1ONEwn9ABqW9/tig== +gulp-cli@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.0.tgz#5533126eeb7fe415a7e3e84a297d334d5cf70ebc" + integrity sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA== + dependencies: + ansi-colors "^1.0.1" + archy "^1.0.0" + array-sort "^1.0.0" + color-support "^1.1.3" + concat-stream "^1.6.0" + copy-props "^2.0.1" + fancy-log "^1.3.2" + gulplog "^1.0.0" + interpret "^1.1.0" + isobject "^3.0.1" + liftoff "^3.1.0" + matchdep "^2.0.0" + mute-stdout "^1.0.0" + pretty-hrtime "^1.0.0" + replace-homedir "^1.0.0" + semver-greatest-satisfied-range "^1.1.0" + v8flags "^3.0.1" + yargs "^7.1.0" + +gulp-mocha@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/gulp-mocha/-/gulp-mocha-7.0.2.tgz#c7e13d133b3fde96d777e877f90b46225255e408" + integrity sha512-ZXBGN60TXYnFhttr19mfZBOtlHYGx9SvCSc+Kr/m2cMIGloUe176HBPwvPqlakPuQgeTGVRS47NmcdZUereKMQ== dependencies: dargs "^7.0.0" execa "^2.0.4" @@ -14166,59 +14291,26 @@ gulp-sourcemaps@2.6.5: strip-bom-string "1.X" through2 "2.X" -gulp-util@^3.0.0: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08= - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-zip@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/gulp-zip/-/gulp-zip-4.2.0.tgz#e25e738c41ad0795ad853d1d8aeb1744d2a4ca82" - integrity sha512-I+697f6jf+PncdTrqfuwoauxgnLG1yHRg3vlmvDgmJuEnlEHy4meBktJ/oHgfyg4tp6X25wuZqUOraVeVg97wQ== +gulp-zip@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/gulp-zip/-/gulp-zip-5.0.1.tgz#0dba5094901b0d91bcc8b53b3f6ff797c0f2002d" + integrity sha512-M/IWLh9RvOpuofDZkgDirtiyz9J3yIqnDOJ3muzk2D/XnZ1ruqPlPLRIpXnl/aZU+xXwKPdOIxjRzkUcVEQyZQ== dependencies: - get-stream "^3.0.0" - plugin-error "^0.1.2" - through2 "^2.0.1" + get-stream "^5.1.0" + plugin-error "^1.0.1" + through2 "^3.0.1" vinyl "^2.1.0" - yazl "^2.1.0" + yazl "^2.5.1" -gulp@3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" - integrity sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ= +gulp@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.2.tgz#543651070fd0f6ab0a0650c6a3e6ff5a7cb09caa" + integrity sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA== dependencies: - archy "^1.0.0" - chalk "^1.0.0" - deprecated "^0.0.1" - gulp-util "^3.0.0" - interpret "^1.0.0" - liftoff "^2.1.0" - minimist "^1.1.0" - orchestrator "^0.3.0" - pretty-hrtime "^1.0.0" - semver "^4.1.0" - tildify "^1.0.0" - v8flags "^2.0.2" - vinyl-fs "^0.3.0" + glob-watcher "^5.0.3" + gulp-cli "^2.2.0" + undertaker "^1.2.1" + vinyl-fs "^3.0.0" gulplog@^1.0.0: version "1.0.0" @@ -14397,13 +14489,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - integrity sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4= - dependencies: - sparkles "^1.0.0" - has-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" @@ -14854,7 +14939,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-middleware@^0.19.1: +http-proxy-middleware@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== @@ -14987,10 +15072,24 @@ icss-utils@^4.1.0: dependencies: postcss "^7.0.14" -idx@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/idx/-/idx-2.5.2.tgz#4b405c2e6d68d04136e0a368a7ab35b9caa0595f" - integrity sha512-MLoGF4lQU5q/RqJJjRsuid52emu7tPVtSSZaYXsqRvSjvXdBEmIwk2urvbNvPBRU9Ox9I4WYnxiz2GjhU34Lrw== +idx@^2.5.6: + version "2.5.6" + resolved "https://registry.yarnpkg.com/idx/-/idx-2.5.6.tgz#1f824595070100ae9ad585c86db08dc74f83a59d" + integrity sha512-WFXLF7JgPytbMgelpRY46nHz5tyDcedJ76pLV+RJWdb8h33bxFq4bdZau38DhNSzk5eVniBf1K3jwfK+Lb5nYA== + +iedriver@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/iedriver/-/iedriver-3.14.1.tgz#447c49be83c62d3f2f158283d58ccf7b35002be8" + integrity sha512-YyCi703BGK7R37A8QlSe2B87xgwDGGoPqBrlXe4Q68o/MNLJrR53/IpTs6J1+KKk51MLiTbWa57N7P3KZ11tow== + dependencies: + adm-zip "^0.4.13" + extract-zip "1.6.6" + kew "~0.1.7" + md5-file "^1.1.4" + mkdirp "0.3.5" + npmconf "^2.1.3" + request "^2.88.0" + rimraf "~2.0.2" ieee754@^1.1.4: version "1.1.8" @@ -15164,7 +15263,7 @@ inert@^5.1.0: joi "13.x.x" lru-cache "4.1.x" -infer-owner@^1.0.3: +infer-owner@^1.0.3, infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== @@ -15182,11 +15281,6 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" - integrity sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js= - inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -15197,7 +15291,7 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.2.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -15432,7 +15526,7 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -interpret@1.2.0, interpret@^1.2.0: +interpret@1.2.0, interpret@^1.1.0, interpret@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== @@ -15550,10 +15644,10 @@ irregular-plurals@^1.0.0: resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" integrity sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y= -is-absolute-url@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.1.tgz#e315cbdcbbc3d6789532d591954ac78a0e5049f6" - integrity sha512-c2QjUwuMxLsld90sj3xYzpFYWJtuxkIn1f5ua9RTEYJt/vV2IsM+Py00/6qjV7qExgifUvt7qfyBGBBKm+2iBg== +is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== is-absolute@^1.0.0: version "1.0.0" @@ -15614,7 +15708,7 @@ is-binary-path@^2.0.0: dependencies: binary-extensions "^1.0.0" -is-binary-path@^2.1.0: +is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== @@ -15811,7 +15905,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.1: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -17184,6 +17278,22 @@ jsonwebtoken@^8.3.0: lodash.once "^4.0.0" ms "^2.1.1" +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -17256,6 +17366,11 @@ jszip@^3.1.5: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" +just-debounce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" + integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= + just-extend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc" @@ -17275,6 +17390,15 @@ jwa@^1.1.5: ecdsa-sig-formatter "1.0.10" safe-buffer "^5.0.1" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + jws@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" @@ -17283,6 +17407,14 @@ jws@^3.1.5: jwa "^1.1.5" safe-buffer "^5.0.1" +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + karma-chrome-launcher@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf" @@ -17373,6 +17505,11 @@ kdbush@^3.0.0: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== +kew@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/kew/-/kew-0.1.7.tgz#0a32a817ff1a9b3b12b8c9bacf4bc4d679af8e72" + integrity sha1-CjKoF/8amzsSuMm6z0vE1nmvjnI= + keymirror@0.1.1, keymirror@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" @@ -17390,11 +17527,6 @@ killable@^1.0.1: resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== -kind-of@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" - integrity sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ= - kind-of@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" @@ -17469,6 +17601,14 @@ kuler@1.0.x: dependencies: colornames "^1.1.1" +last-run@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" + integrity sha1-RblpQsF7HHnHchmCWbqUO+v4yls= + dependencies: + default-resolution "^2.0.0" + es6-weak-map "^2.0.1" + latest-version@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb" @@ -17635,13 +17775,13 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" -liftoff@^2.1.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" - integrity sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew= +liftoff@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" + integrity sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog== dependencies: extend "^3.0.0" - findup-sync "^2.0.0" + findup-sync "^3.0.0" fined "^1.0.1" flagged-respawn "^1.0.0" is-plain-object "^2.0.4" @@ -17937,51 +18077,11 @@ lodash-es@^4.17.11, lodash-es@^4.17.4, lodash-es@^4.17.5, lodash-es@^4.2.1: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY= - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U= - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc= - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw= - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo= - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0= - lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= - lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -18037,13 +18137,6 @@ lodash.difference@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg= - dependencies: - lodash._root "^3.0.0" - lodash.escape@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" @@ -18089,16 +18182,6 @@ lodash.intersection@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.intersection/-/lodash.intersection-4.4.0.tgz#0a11ba631d0e95c23c7f2f4cbb9a692ed178e705" integrity sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU= -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= - lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" @@ -18154,15 +18237,6 @@ lodash.keyby@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.keyby/-/lodash.keyby-4.6.0.tgz#7f6a1abda93fd24e22728a4d361ed8bcba5a4354" integrity sha1-f2oavak/0k4icopNNh7YvLpaQ1Q= -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - lodash.lowercase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.lowercase/-/lodash.lowercase-4.3.0.tgz#46515aced4acb0b7093133333af068e4c3b14e9d" @@ -18253,11 +18327,6 @@ lodash.reject@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -18283,21 +18352,6 @@ lodash.startcase@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8" integrity sha1-lDbjTtJgk+1/+uGTYUQ1CRXZrdg= -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8= - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - lodash.template@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" @@ -18306,14 +18360,6 @@ lodash.template@^4.4.0: lodash._reinterpolate "^3.0.0" lodash.templatesettings "^4.0.0" -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU= - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.templatesettings@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" @@ -18366,11 +18412,6 @@ lodash@^3.10.1, lodash@^3.3.1: resolved "https://registry.yarnpkg.com/@elastic/lodash/-/lodash-3.10.1-kibana3.tgz#c0e318245219eeeff535895c429e0cef5058a9ad" integrity sha512-HMfwwT2yAkEQNzHSR1BxgE5YcDMUaZ/skhNyjy1nvM/A4m0Kh940hLZeCqKBCsSaUJz/8A/9cQGd9BaAOCIBLg== -lodash@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" - integrity sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE= - lodash@~2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" @@ -18442,10 +18483,10 @@ loglevel@^1.6.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loglevel@^1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280" - integrity sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA== +loglevel@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56" + integrity sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g== loglevelnext@^1.0.1: version "1.0.5" @@ -18526,11 +18567,6 @@ lowlight@~1.9.1: dependencies: highlight.js "~9.12.0" -lru-cache@2, lru-cache@^2.6.5: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= - lru-cache@4.1.5, lru-cache@^4.0.1, lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -18547,6 +18583,11 @@ lru-cache@4.1.x, lru-cache@^4.0.0: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^2.6.5: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -18768,11 +18809,26 @@ marky@^1.2.1: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.1.tgz#a3fcf82ffd357756b8b8affec9fdbf3a30dc1b02" integrity sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ== +matchdep@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" + integrity sha1-xvNINKDY28OzfCfui7yyfHd1WC4= + dependencies: + findup-sync "^2.0.0" + micromatch "^3.0.4" + resolve "^1.4.0" + stack-trace "0.0.10" + material-colors@^1.2.1: version "1.2.5" resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1" integrity sha1-UpJZPmdUyxvMK5gDDk4Najr8nqE= +md5-file@^1.1.4: + version "1.1.10" + resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-1.1.10.tgz#d8f4fce76c92cb20b7d143a59f58ca49b4cf3174" + integrity sha1-2PT852ySyyC30UOln1jKSbTPMXQ= + md5.js@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -19173,21 +19229,6 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" -minimatch@^2.0.1: - version "2.0.10" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" - integrity sha1-jQh8OcazjAAbl/ynzm0OHoCvusc= - dependencies: - brace-expansion "^1.0.0" - -minimatch@~0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" - integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo= - dependencies: - lru-cache "2" - sigmund "~1.0.0" - minimist-options@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" @@ -19241,6 +19282,27 @@ minimost@^1.0.0: camelcase-keys "^4.0.0" minimist "^1.2.0" +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz#3dcb6bb4a546e32969c7ad710f2c79a86abba93a" + integrity sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA== + dependencies: + minipass "^3.0.0" + minipass@^2.2.1: version "2.3.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.0.tgz#2e11b1c46df7fe7f1afbe9a490280add21ffe384" @@ -19265,6 +19327,13 @@ minipass@^2.8.6: safe-buffer "^5.1.2" yallist "^3.0.0" +minipass@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.0.1.tgz#b4fec73bd61e8a40f0b374ddd04260ade2c8ec20" + integrity sha512-2y5okJ4uBsjoD2vAbLKL9EUQPPkC0YMIp+2mZOXG3nBba++pdfJWRxx2Ewirc0pwAJYu4XtWg2EkVo1nRXuO/w== + dependencies: + yallist "^4.0.0" + minizlib@^1.1.0, minizlib@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" @@ -19311,6 +19380,18 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp@0.3.5, mkdirp@^0.3.5, mkdirp@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= + +mkdirp@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" + integrity sha1-HXMHam35hs2TROFecfzAWkyavxI= + dependencies: + minimist "0.0.8" + mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -19318,11 +19399,6 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi dependencies: minimist "0.0.8" -mkdirp@^0.3.5, mkdirp@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= - mobx-react@^5.4.3: version "5.4.3" resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-5.4.3.tgz#6709b7dd89670c40e9815914ac2ca49cc02bfb47" @@ -19569,13 +19645,6 @@ multimatch@^4.0.0: arrify "^2.0.1" minimatch "^3.0.4" -multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s= - dependencies: - duplexer2 "0.0.2" - multistream@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/multistream/-/multistream-2.1.1.tgz#629d3a29bd76623489980d04519a2c365948148c" @@ -19609,6 +19678,11 @@ mutation-observer@^1.0.3: resolved "https://registry.yarnpkg.com/mutation-observer/-/mutation-observer-1.0.3.tgz#42e9222b101bca82e5ba9d5a7acf4a14c0f263d0" integrity sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA== +mute-stdout@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" + integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== + mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" @@ -19683,11 +19757,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -natives@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb" - integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -19879,10 +19948,10 @@ node-fetch@^2.6.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -node-forge@0.7.5: - version "0.7.5" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" - integrity sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ== +node-forge@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" + integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== node-forge@^0.7.6: version "0.7.6" @@ -20103,7 +20172,7 @@ nodemailer@^4.7.0: chalk "~0.4.0" underscore "~1.6.0" -"nopt@2 || 3", nopt@3.x, nopt@~3.0.6: +"nopt@2 || 3", nopt@3.x, nopt@~3.0.1, nopt@~3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= @@ -20159,7 +20228,7 @@ normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -20250,6 +20319,22 @@ npm-run-path@^3.0.0: dependencies: path-key "^3.0.0" +npmconf@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/npmconf/-/npmconf-2.1.3.tgz#1cbe5dd02e899d365fed7260b54055473f90a15c" + integrity sha512-iTK+HI68GceCoGOHAQiJ/ik1iDfI7S+cgyG8A+PP18IU3X83kRhQIRhAUNj4Bp2JMx6Zrt5kCiozYa9uGWTjhA== + dependencies: + config-chain "~1.1.8" + inherits "~2.0.0" + ini "^1.2.0" + mkdirp "^0.5.0" + nopt "~3.0.1" + once "~1.3.0" + osenv "^0.1.0" + safe-buffer "^5.1.1" + semver "2 || 3 || 4" + uid-number "0.0.5" + "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -20440,7 +20525,7 @@ object.assign@4.1.0, object.assign@^4.0.4, object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.defaults@^1.1.0: +object.defaults@^1.0.0, object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= @@ -20503,6 +20588,14 @@ object.pick@^1.2.0, object.pick@^1.3.0: dependencies: isobject "^3.0.1" +object.reduce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad" + integrity sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + object.values@^1.0.4, object.values@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9" @@ -20692,20 +20785,6 @@ ora@^3.0.0, ora@^3.4.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" -orchestrator@^0.3.0: - version "0.3.8" - resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" - integrity sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4= - dependencies: - end-of-stream "~0.1.5" - sequencify "~0.0.7" - stream-consume "~0.1.0" - -ordered-read-streams@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" - integrity sha1-/VZamvjrRHO6abbtijQ1LLVS8SY= - ordered-read-streams@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" @@ -21617,17 +21696,6 @@ platform@^1.3.0: resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q== -plugin-error@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" - integrity sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4= - dependencies: - ansi-cyan "^0.1.1" - ansi-red "^0.1.1" - arr-diff "^1.0.1" - arr-union "^2.0.1" - extend-shallow "^1.1.2" - plugin-error@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" @@ -21717,10 +21785,10 @@ popper.js@^1.14.4: resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e" integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ== -portfinder@^1.0.21: - version "1.0.23" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.23.tgz#894db4bcc5daf02b6614517ce89cd21a38226b82" - integrity sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ== +portfinder@^1.0.24: + version "1.0.24" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.24.tgz#11efbc6865f12f37624b6531ead1d809ed965cfa" + integrity sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg== dependencies: async "^1.5.2" debug "^2.2.0" @@ -21887,7 +21955,7 @@ prettier@1.16.4: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g== -prettier@1.18.2, prettier@^1.13.7, prettier@^1.14.3: +prettier@1.18.2, prettier@^1.13.7, prettier@^1.18.2: version "1.18.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== @@ -23569,7 +23637,7 @@ read-pkg@^5.1.1, read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17, readable-stream@~1.0.27-1: +readable-stream@1.0, readable-stream@~1.0.17, readable-stream@~1.0.27-1: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= @@ -23629,10 +23697,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.2.tgz#fa85d2d14d4289920e4671dead96431add2ee78a" - integrity sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw== +readdirp@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.3.tgz#d6e011ed5b9240a92f08651eeb40f7942ceb6cc1" + integrity sha512-ZOsfTGkjO2kqeR5Mzr5RYDbTGYneSkdNKX2fOX2P5jF7vMrd/GNnIAUtDldeHHumHUCQ3V05YfWUdxMPAsRu9Q== dependencies: picomatch "^2.0.4" @@ -24039,7 +24107,7 @@ remove-bom-stream@^1.2.0: safe-buffer "^5.1.0" through2 "^2.0.3" -remove-trailing-separator@^1.0.1: +remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= @@ -24099,6 +24167,15 @@ replace-ext@1.0.0, replace-ext@^1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= +replace-homedir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-homedir/-/replace-homedir-1.0.0.tgz#e87f6d513b928dde808260c12be7fec6ff6e798c" + integrity sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw= + dependencies: + homedir-polyfill "^1.0.1" + is-absolute "^1.0.0" + remove-trailing-separator "^1.1.0" + request-progress@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" @@ -24437,7 +24514,7 @@ resolve@^1.11.1: dependencies: path-parse "^1.0.6" -resolve@^1.12.0: +resolve@^1.12.0, resolve@^1.4.0: version "1.12.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== @@ -24563,6 +24640,13 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" +rimraf@~2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.0.3.tgz#f50a2965e7144e9afd998982f15df706730f56a9" + integrity sha1-9QopZecUTpr9mYmC8V33BnMPVqk= + optionalDependencies: + graceful-fs "~1.1" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" @@ -24670,15 +24754,6 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -run-sequence@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/run-sequence/-/run-sequence-2.2.1.tgz#1ce643da36fd8c7ea7e1a9329da33fc2b8898495" - integrity sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw== - dependencies: - chalk "^1.1.3" - fancy-log "^1.3.2" - plugin-error "^0.1.2" - rw@1, rw@^1.3.2, rw@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" @@ -24940,6 +25015,14 @@ schema-utils@^2.0.0, schema-utils@^2.0.1: ajv "^6.1.0" ajv-keywords "^3.1.0" +schema-utils@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.4.1.tgz#e89ade5d056dc8bcaca377574bb4a9c4e1b8be56" + integrity sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w== + dependencies: + ajv "^6.10.2" + ajv-keywords "^3.4.1" + scoped-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8" @@ -24987,12 +25070,12 @@ selenium-webdriver@^4.0.0-alpha.4: tmp "0.0.30" xml2js "^0.4.19" -selfsigned@^1.10.4: - version "1.10.4" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd" - integrity sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw== +selfsigned@^1.10.7: + version "1.10.7" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" + integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== dependencies: - node-forge "0.7.5" + node-forge "0.9.0" semaphore-async-await@^1.5.1: version "1.5.1" @@ -25006,6 +25089,13 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" +semver-greatest-satisfied-range@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" + integrity sha1-E+jCZYq5aRywzXEJMkAoDTb3els= + dependencies: + sver-compat "^1.5.0" + semver-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" @@ -25018,6 +25108,11 @@ semver-truncate@^1.0.0: dependencies: semver "^5.3.0" +"semver@2 || 3 || 4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" + integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= + "semver@2 || 3 || 4 || 5", semver@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" @@ -25033,11 +25128,6 @@ semver@5.7.0, semver@^5.4.1, semver@^5.6.0, semver@^5.7.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== -semver@^4.1.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= - semver@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" @@ -25109,16 +25199,16 @@ sentence-case@^2.1.0: no-case "^2.2.0" upper-case-first "^1.1.2" -sequencify@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" - integrity sha1-kM/xnQLgcCf9dn9erT57ldHnOAw= - serialize-javascript@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA== +serialize-javascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.0.tgz#9310276819efd0eb128258bb341957f6eb2fc570" + integrity sha512-a/mxFfU00QT88umAJQsNWOnUKckhNCqOl028N48e7wFmo2/EHpTo9Wso+iJJCMrQnmFvcjto5RJdAHEvVhcyUQ== + serve-favicon@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.5.0.tgz#935d240cdfe0f5805307fdfe967d88942a2cbcf0" @@ -25328,11 +25418,6 @@ shot@4.x.x: hoek "5.x.x" joi "13.x.x" -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= - signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -25567,6 +25652,18 @@ sockjs-client@1.3.0: json3 "^3.3.2" url-parse "^1.4.3" +sockjs-client@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" + integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== + dependencies: + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" + json3 "^3.3.2" + url-parse "^1.4.3" + sockjs@0.3.19: version "0.3.19" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" @@ -25883,12 +25980,20 @@ ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" +ssri@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.0.1.tgz#b0cab7bbb11ac9ea07f003453e2011f8cbed9f34" + integrity sha512-FfndBvkXL9AHyGLNzU3r9AvYIBBZ7gm+m+kd0p8cT3/v4OliMAyipZAhLVEv1Zi/k4QFq9CstRGVd9pW/zcHFQ== + dependencies: + figgy-pudding "^3.5.1" + minipass "^3.0.0" + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-trace@0.0.x: +stack-trace@0.0.10, stack-trace@0.0.x: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= @@ -25994,11 +26099,6 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-consume@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" - integrity sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8= - stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -26007,6 +26107,11 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" +stream-exhaust@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" + integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== + stream-http@^2.7.2: version "2.8.0" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10" @@ -26272,14 +26377,6 @@ strip-bom-string@1.X: resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= -strip-bom@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" - integrity sha1-hbiGLzhEtabV7IRnqTWYFzo295Q= - dependencies: - first-chunk-stream "^1.0.0" - is-utf8 "^0.2.0" - strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -26536,6 +26633,14 @@ suricata-sid-db@^1.0.2: dependencies: typescript "^3.3.3333" +sver-compat@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" + integrity sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg= + dependencies: + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + svgo@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -26685,7 +26790,7 @@ tape@^4.5.1: string.prototype.trim "~1.1.2" through "~2.3.8" -tar-fs@^1.16.2, tar-fs@^1.16.3: +tar-fs@^1.16.3: version "1.16.3" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== @@ -26843,6 +26948,20 @@ terser-webpack-plugin@^1.2.4, terser-webpack-plugin@^1.4.1: webpack-sources "^1.4.0" worker-farm "^1.7.0" +terser-webpack-plugin@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.1.2.tgz#2b9b8147a6f18918348200800cf9560c50f701bb" + integrity sha512-MF/C4KABwqYOfRDi87f7gG07GP7Wj/kyiX938UxIGIO6l5mkh8XJL7xtS0hX/CRdVQaZI7ThGUPZbznrCjsGpg== + dependencies: + cacache "^13.0.0" + find-cache-dir "^3.0.0" + jest-worker "^24.9.0" + schema-utils "^2.4.1" + serialize-javascript "^2.1.0" + source-map "^0.6.1" + terser "^4.3.4" + webpack-sources "^1.4.3" + terser@^4.1.2: version "4.2.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.2.0.tgz#4b1b5f4424b426a7a47e80d6aae45e0d7979aef0" @@ -26852,6 +26971,15 @@ terser@^4.1.2: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.4.tgz#ad91bade95619e3434685d69efa621a5af5f877d" + integrity sha512-Kcrn3RiW8NtHBP0ssOAzwa2MsIRQ8lJWiBG/K7JgqPlomA3mtb2DEmp4/hrUA+Jujx+WZ02zqd7GYD+QRBB/2Q== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + test-exclude@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.1.0.tgz#6ba6b25179d2d38724824661323b73e03c0c1de1" @@ -26945,7 +27073,7 @@ through2-map@^3.0.0: through2 "~2.0.0" xtend "^4.0.0" -through2@2.X, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0: +through2@2.X, through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= @@ -26953,14 +27081,6 @@ through2@2.X, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0 readable-stream "^2.1.5" xtend "~4.0.1" -through2@^0.6.1: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - through2@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" @@ -26986,7 +27106,7 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371" integrity sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E= -tildify@^1.0.0, tildify@^1.2.0: +tildify@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" integrity sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo= @@ -28060,6 +28180,11 @@ ui-select@0.19.8: resolved "https://registry.yarnpkg.com/ui-select/-/ui-select-0.19.8.tgz#74860848a7fd8bc494d9856d2f62776ea98637c1" integrity sha1-dIYISKf9i8SU2YVtL2J3bqmGN8E= +uid-number@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.5.tgz#5a3db23ef5dbd55b81fce0ec9a2ac6fccdebb81e" + integrity sha1-Wj2yPvXb1VuB/ODsmirG/M3ruB4= + ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" @@ -28091,6 +28216,26 @@ underscore@~1.6.0: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" integrity sha1-izixDKze9jM3uLJOT/htRa6lKag= +undertaker-registry@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50" + integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= + +undertaker@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.1.tgz#701662ff8ce358715324dfd492a4f036055dfe4b" + integrity sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA== + dependencies: + arr-flatten "^1.0.1" + arr-map "^2.0.0" + bach "^1.0.0" + collection-map "^1.0.0" + es6-weak-map "^2.0.1" + last-run "^1.1.0" + object.defaults "^1.0.0" + object.reduce "^1.0.0" + undertaker-registry "^1.0.0" + unfetch@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db" @@ -28204,11 +28349,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" - integrity sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs= - unique-stream@^2.0.2: version "2.2.1" resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" @@ -28404,14 +28544,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-loader@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961" - integrity sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A== +url-loader@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.2.0.tgz#af321aece1fd0d683adc8aaeb27829f29c75b46e" + integrity sha512-G8nk3np8ZAnwhHXas1JxJEwJyQdqFXAKJehfgZ/XrC48volFBRtO+FIKtF2u0Ma3bw+4vnDVjHPAQYlF9p2vsw== dependencies: loader-utils "^1.2.3" mime "^2.4.4" - schema-utils "^2.0.0" + schema-utils "^2.4.1" url-loader@^1.1.2: version "1.1.2" @@ -28472,11 +28612,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - integrity sha1-K1viOjK2Onyd640PKNSFcko98ZA= - user-home@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" @@ -28621,12 +28756,12 @@ v8-compile-cache@2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== -v8flags@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - integrity sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ= +v8flags@^3.0.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" + integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== dependencies: - user-home "^1.1.1" + homedir-polyfill "^1.0.1" val-loader@^1.1.1: version "1.1.1" @@ -29117,21 +29252,7 @@ vinyl-file@^2.0.0: strip-bom-stream "^2.0.0" vinyl "^1.1.0" -vinyl-fs@^0.3.0: - version "0.3.14" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" - integrity sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY= - dependencies: - defaults "^1.0.0" - glob-stream "^3.1.5" - glob-watcher "^0.0.6" - graceful-fs "^3.0.0" - mkdirp "^0.5.0" - strip-bom "^1.0.0" - through2 "^0.6.1" - vinyl "^0.4.0" - -vinyl-fs@^3.0.3: +vinyl-fs@^3.0.0, vinyl-fs@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== @@ -29174,23 +29295,6 @@ vinyl-sourcemaps-apply@^0.2.0: dependencies: source-map "^0.5.1" -vinyl@^0.4.0: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - integrity sha1-LzVsh6VQolVGHza76ypbqL94SEc= - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - vinyl@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" @@ -29392,10 +29496,10 @@ webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-cli@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.7.tgz#77c8580dd8e92f69d635e0238eaf9d9c15759a91" - integrity sha512-OhTUCttAsr+IZSMVwGROGRHvT+QAs8H6/mHIl4SvhAwYywjiylYjpwybGx7WQ9Hkb45FhjtsymkwiRRbGJ1SZQ== +webpack-cli@^3.3.9: + version "3.3.9" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.9.tgz#79c27e71f94b7fe324d594ab64a8e396b9daa91a" + integrity sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A== dependencies: chalk "2.4.2" cross-spawn "6.0.5" @@ -29419,41 +29523,52 @@ webpack-dev-middleware@^3.7.0: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.8.0.tgz#06cc4fc2f440428508d0e9770da1fef10e5ef28d" - integrity sha512-Hs8K9yI6pyMvGkaPTeTonhD6JXVsigXDApYk9JLW4M7viVBspQvb1WdAcWxqtmttxNW4zf2UFLsLNe0y87pIGQ== +webpack-dev-middleware@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" + integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + +webpack-dev-server@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.8.2.tgz#3292427bf6510da9a3ac2d500b924a4197667ff9" + integrity sha512-0xxogS7n5jHDQWy0WST0q6Ykp7UGj4YvWh+HVN71JoE7BwPxMZrwgraBvmdEMbDVMBzF0u+mEzn8TQzBm5NYJQ== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" - chokidar "^2.1.6" + chokidar "^2.1.8" compression "^1.7.4" connect-history-api-fallback "^1.6.0" debug "^4.1.1" del "^4.1.1" express "^4.17.1" html-entities "^1.2.1" - http-proxy-middleware "^0.19.1" + http-proxy-middleware "0.19.1" import-local "^2.0.0" internal-ip "^4.3.0" ip "^1.1.5" - is-absolute-url "^3.0.0" + is-absolute-url "^3.0.3" killable "^1.0.1" - loglevel "^1.6.3" + loglevel "^1.6.4" opn "^5.5.0" p-retry "^3.0.1" - portfinder "^1.0.21" + portfinder "^1.0.24" schema-utils "^1.0.0" - selfsigned "^1.10.4" + selfsigned "^1.10.7" semver "^6.3.0" serve-index "^1.9.1" sockjs "0.3.19" - sockjs-client "1.3.0" + sockjs-client "1.4.0" spdy "^4.0.1" strip-ansi "^3.0.1" supports-color "^6.1.0" url "^0.11.0" - webpack-dev-middleware "^3.7.0" + webpack-dev-middleware "^3.7.2" webpack-log "^2.0.0" ws "^6.2.1" yargs "12.0.5" @@ -29486,12 +29601,12 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4" - integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw== +webpack-merge@4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" + integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== dependencies: - lodash "^4.17.5" + lodash "^4.17.15" webpack-sources@^1.1.0: version "1.3.0" @@ -29501,7 +29616,7 @@ webpack-sources@^1.1.0: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^1.4.0, webpack-sources@^1.4.1: +webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -29509,10 +29624,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@4.39.2, webpack@^4.39.2: - version "4.39.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.39.2.tgz#c9aa5c1776d7c309d1b3911764f0288c8c2816aa" - integrity sha512-AKgTfz3xPSsEibH00JfZ9sHXGUwIQ6eZ9tLN8+VLzachk1Cw2LVmy+4R7ZiwTa9cZZ15tzySjeMui/UnSCAZhA== +webpack@4.41.0, webpack@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b" + integrity sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g== dependencies: "@webassemblyjs/ast" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5" @@ -30099,7 +30214,7 @@ xregexp@4.2.4: dependencies: "@babel/runtime-corejs2" "^7.2.0" -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= @@ -30143,6 +30258,11 @@ yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yargs-parser@13.1.1, yargs-parser@^13.0.0, yargs-parser@^13.1.0, yargs-parser@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" @@ -30303,7 +30423,7 @@ yargs@^14.0.0: y18n "^4.0.0" yargs-parser "^13.1.1" -yargs@^7.0.0: +yargs@^7.0.0, yargs@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= @@ -30376,10 +30496,10 @@ yauzl@^2.4.2: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" -yazl@^2.1.0: - version "2.4.3" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" - integrity sha1-7CblzIfVYBud+EMtvdPNLlFzoHE= +yazl@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== dependencies: buffer-crc32 "~0.2.3"