diff --git a/.ci/jobs.yml b/.ci/jobs.yml index 3f1b5302f87b0..fe28ae79268de 100644 --- a/.ci/jobs.yml +++ b/.ci/jobs.yml @@ -1,34 +1,21 @@ JOB: - - kibana-intake - - x-pack-intake - - kibana-firefoxSmoke + - intake + - firefoxSmoke - kibana-ciGroup1 - kibana-ciGroup2 - kibana-ciGroup3 - kibana-ciGroup4 - kibana-ciGroup5 - kibana-ciGroup6 - - kibana-ciGroup7 - - kibana-ciGroup8 - - kibana-ciGroup9 - - kibana-ciGroup10 - - kibana-ciGroup11 - - kibana-ciGroup12 - - kibana-visualRegression + # - kibana-visualRegression # make sure all x-pack-ciGroups are listed in test/scripts/jenkins_xpack_ci_group.sh - - x-pack-firefoxSmoke - x-pack-ciGroup1 - x-pack-ciGroup2 - x-pack-ciGroup3 - x-pack-ciGroup4 - x-pack-ciGroup5 - - x-pack-ciGroup6 - - x-pack-ciGroup7 - - x-pack-ciGroup8 - - x-pack-ciGroup9 - - x-pack-ciGroup10 - - x-pack-visualRegression + # - x-pack-visualRegression # `~` is yaml for `null` exclude: ~ \ No newline at end of file diff --git a/.ci/run.sh b/.ci/run.sh index 88ce0bd9986a1..e5c26c48546f7 100755 --- a/.ci/run.sh +++ b/.ci/run.sh @@ -11,7 +11,7 @@ source src/dev/ci_setup/setup.sh source src/dev/ci_setup/checkout_sibling_es.sh case "$JOB" in -kibana-intake) +intake) ./test/scripts/jenkins_unit.sh ;; kibana-ciGroup*) @@ -21,12 +21,9 @@ kibana-ciGroup*) kibana-visualRegression*) ./test/scripts/jenkins_visual_regression.sh ;; -kibana-firefoxSmoke*) +firefoxSmoke*) ./test/scripts/jenkins_firefox_smoke.sh ;; -x-pack-intake) - ./test/scripts/jenkins_xpack.sh - ;; x-pack-ciGroup*) export CI_GROUP="${JOB##x-pack-ciGroup}" ./test/scripts/jenkins_xpack_ci_group.sh @@ -34,9 +31,6 @@ x-pack-ciGroup*) x-pack-visualRegression*) ./test/scripts/jenkins_xpack_visual_regression.sh ;; -x-pack-firefoxSmoke*) - ./test/scripts/jenkins_xpack_firefox_smoke.sh - ;; *) echo "JOB '$JOB' is not implemented." exit 1 diff --git a/.eslintrc.js b/.eslintrc.js index 63f30b427e162..8a641af8856c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,8 +31,6 @@ const ELASTIC_LICENSE_HEADER = ` `; module.exports = { - root: true, - extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], overrides: [ diff --git a/docs/development/core/public/kibana-plugin-public.app.md b/docs/development/core/public/kibana-plugin-public.app.md deleted file mode 100644 index 60cac357d1fe0..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.app.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [App](./kibana-plugin-public.app.md) - -## App interface - -Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. - -Signature: - -```typescript -export interface App extends AppBase -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [mount](./kibana-plugin-public.app.mount.md) | (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise<AppUnmount> | A mount function called when the user navigates to this app's route. | - diff --git a/docs/development/core/public/kibana-plugin-public.app.mount.md b/docs/development/core/public/kibana-plugin-public.app.mount.md deleted file mode 100644 index dda06b035db4a..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.app.mount.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [App](./kibana-plugin-public.app.md) > [mount](./kibana-plugin-public.app.mount.md) - -## App.mount property - -A mount function called when the user navigates to this app's route. - -Signature: - -```typescript -mount: (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.capabilities.md b/docs/development/core/public/kibana-plugin-public.appbase.capabilities.md deleted file mode 100644 index 450972e41bb29..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.capabilities.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [capabilities](./kibana-plugin-public.appbase.capabilities.md) - -## AppBase.capabilities property - -Custom capabilities defined by the app. - -Signature: - -```typescript -capabilities?: Partial; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.euiicontype.md b/docs/development/core/public/kibana-plugin-public.appbase.euiicontype.md deleted file mode 100644 index 99c7e852ff905..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.euiicontype.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [euiIconType](./kibana-plugin-public.appbase.euiicontype.md) - -## AppBase.euiIconType property - -A EUI iconType that will be used for the app's icon. This icon takes precendence over the `icon` property. - -Signature: - -```typescript -euiIconType?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.icon.md b/docs/development/core/public/kibana-plugin-public.appbase.icon.md deleted file mode 100644 index d94d0897bc5b7..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.icon.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [icon](./kibana-plugin-public.appbase.icon.md) - -## AppBase.icon property - -A URL to an image file used as an icon. Used as a fallback if `euiIconType` is not provided. - -Signature: - -```typescript -icon?: string; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.id.md b/docs/development/core/public/kibana-plugin-public.appbase.id.md deleted file mode 100644 index 57daa0c94bdf6..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [id](./kibana-plugin-public.appbase.id.md) - -## AppBase.id property - -Signature: - -```typescript -id: string; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.md b/docs/development/core/public/kibana-plugin-public.appbase.md deleted file mode 100644 index 338d30e780aaf..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) - -## AppBase interface - - -Signature: - -```typescript -export interface AppBase -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [capabilities](./kibana-plugin-public.appbase.capabilities.md) | Partial<Capabilities> | Custom capabilities defined by the app. | -| [euiIconType](./kibana-plugin-public.appbase.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | -| [icon](./kibana-plugin-public.appbase.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | -| [id](./kibana-plugin-public.appbase.id.md) | string | | -| [order](./kibana-plugin-public.appbase.order.md) | number | An ordinal used to sort nav links relative to one another for display. | -| [title](./kibana-plugin-public.appbase.title.md) | string | The title of the application. | -| [tooltip$](./kibana-plugin-public.appbase.tooltip$.md) | Observable<string> | An observable for a tooltip shown when hovering over app link. | - diff --git a/docs/development/core/public/kibana-plugin-public.appbase.order.md b/docs/development/core/public/kibana-plugin-public.appbase.order.md deleted file mode 100644 index dc0ea14a7b860..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.order.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [order](./kibana-plugin-public.appbase.order.md) - -## AppBase.order property - -An ordinal used to sort nav links relative to one another for display. - -Signature: - -```typescript -order?: number; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.title.md b/docs/development/core/public/kibana-plugin-public.appbase.title.md deleted file mode 100644 index 4d0fb0c18e814..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.title.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [title](./kibana-plugin-public.appbase.title.md) - -## AppBase.title property - -The title of the application. - -Signature: - -```typescript -title: string; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.tooltip$.md b/docs/development/core/public/kibana-plugin-public.appbase.tooltip$.md deleted file mode 100644 index 1b8ca490825f9..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.tooltip$.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [tooltip$](./kibana-plugin-public.appbase.tooltip$.md) - -## AppBase.tooltip$ property - -An observable for a tooltip shown when hovering over app link. - -Signature: - -```typescript -tooltip$?: Observable; -``` diff --git a/docs/development/core/public/kibana-plugin-public.applicationsetup.md b/docs/development/core/public/kibana-plugin-public.applicationsetup.md index b53873bc0fb8a..a3ab77e43446c 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationsetup.md +++ b/docs/development/core/public/kibana-plugin-public.applicationsetup.md @@ -15,6 +15,5 @@ export interface ApplicationSetup | Method | Description | | --- | --- | -| [register(app)](./kibana-plugin-public.applicationsetup.register.md) | Register an mountable application to the system. | -| [registerMountContext(contextName, provider)](./kibana-plugin-public.applicationsetup.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. | +| [registerApp(app)](./kibana-plugin-public.applicationsetup.registerapp.md) | Register an mountable application to the system. Apps will be mounted based on their rootRoute. | diff --git a/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md b/docs/development/core/public/kibana-plugin-public.applicationsetup.registerapp.md similarity index 52% rename from docs/development/core/public/kibana-plugin-public.applicationsetup.register.md rename to docs/development/core/public/kibana-plugin-public.applicationsetup.registerapp.md index b4ccb6a01c600..f2532ae71ca2f 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md +++ b/docs/development/core/public/kibana-plugin-public.applicationsetup.registerapp.md @@ -1,22 +1,22 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) > [register](./kibana-plugin-public.applicationsetup.register.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) > [registerApp](./kibana-plugin-public.applicationsetup.registerapp.md) -## ApplicationSetup.register() method +## ApplicationSetup.registerApp() method -Register an mountable application to the system. +Register an mountable application to the system. Apps will be mounted based on their `rootRoute`. Signature: ```typescript -register(app: App): void; +registerApp(app: App): void; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| app | App | an [App](./kibana-plugin-public.app.md) | +| app | App | | Returns: diff --git a/docs/development/core/public/kibana-plugin-public.applicationsetup.registermountcontext.md b/docs/development/core/public/kibana-plugin-public.applicationsetup.registermountcontext.md deleted file mode 100644 index 0b5bd8eeb36ec..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.applicationsetup.registermountcontext.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) > [registerMountContext](./kibana-plugin-public.applicationsetup.registermountcontext.md) - -## ApplicationSetup.registerMountContext() method - -Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. - -Signature: - -```typescript -registerMountContext(contextName: T, provider: IContextProvider): void; -``` - -## Parameters - -| 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 | - -Returns: - -`void` - diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.availableapps.md b/docs/development/core/public/kibana-plugin-public.applicationstart.availableapps.md new file mode 100644 index 0000000000000..8bbd1dfcd31fa --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.applicationstart.availableapps.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [availableApps](./kibana-plugin-public.applicationstart.availableapps.md) + +## ApplicationStart.availableApps property + +Apps available based on the current capabilities. Should be used to show navigation links and make routing decisions. + +Signature: + +```typescript +availableApps: readonly App[]; +``` diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.geturlforapp.md b/docs/development/core/public/kibana-plugin-public.applicationstart.geturlforapp.md deleted file mode 100644 index 422fbdf7418c2..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.applicationstart.geturlforapp.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [getUrlForApp](./kibana-plugin-public.applicationstart.geturlforapp.md) - -## ApplicationStart.getUrlForApp() method - -Returns a relative URL to a given app, including the global base path. - -Signature: - -```typescript -getUrlForApp(appId: string, options?: { - path?: string; - }): string; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| appId | string | | -| options | {
path?: string;
} | | - -Returns: - -`string` - diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.md b/docs/development/core/public/kibana-plugin-public.applicationstart.md index 2a60ff449e44e..5854a7c65714e 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationstart.md +++ b/docs/development/core/public/kibana-plugin-public.applicationstart.md @@ -15,13 +15,6 @@ export interface ApplicationStart | Property | Type | Description | | --- | --- | --- | +| [availableApps](./kibana-plugin-public.applicationstart.availableapps.md) | readonly App[] | Apps available based on the current capabilities. Should be used to show navigation links and make routing decisions. | | [capabilities](./kibana-plugin-public.applicationstart.capabilities.md) | RecursiveReadonly<Capabilities> | Gets the read-only capabilities. | -## Methods - -| Method | Description | -| --- | --- | -| [getUrlForApp(appId, options)](./kibana-plugin-public.applicationstart.geturlforapp.md) | Returns a relative URL to a given app, including the global base path. | -| [navigateToApp(appId, options)](./kibana-plugin-public.applicationstart.navigatetoapp.md) | Navigiate to a given app | -| [registerMountContext(contextName, provider)](./kibana-plugin-public.applicationstart.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. | - diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.navigatetoapp.md b/docs/development/core/public/kibana-plugin-public.applicationstart.navigatetoapp.md deleted file mode 100644 index eef31fe661f54..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.applicationstart.navigatetoapp.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [navigateToApp](./kibana-plugin-public.applicationstart.navigatetoapp.md) - -## ApplicationStart.navigateToApp() method - -Navigiate to a given app - -Signature: - -```typescript -navigateToApp(appId: string, options?: { - path?: string; - state?: any; - }): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| appId | string | | -| options | {
path?: string;
state?: any;
} | | - -Returns: - -`void` - diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.registermountcontext.md b/docs/development/core/public/kibana-plugin-public.applicationstart.registermountcontext.md deleted file mode 100644 index fc86aaf658b68..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.applicationstart.registermountcontext.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [registerMountContext](./kibana-plugin-public.applicationstart.registermountcontext.md) - -## ApplicationStart.registerMountContext() method - -Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. - -Signature: - -```typescript -registerMountContext(contextName: T, provider: IContextProvider): void; -``` - -## Parameters - -| 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 | - -Returns: - -`void` - diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md deleted file mode 100644 index 63b3ead814f00..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppMountContext](./kibana-plugin-public.appmountcontext.md) > [core](./kibana-plugin-public.appmountcontext.core.md) - -## AppMountContext.core property - -Core service APIs available to mounted applications. - -Signature: - -```typescript -core: { - application: Pick; - chrome: ChromeStart; - docLinks: DocLinksStart; - http: HttpStart; - i18n: I18nStart; - notifications: NotificationsStart; - overlays: OverlayStart; - uiSettings: UiSettingsClientContract; - }; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.md deleted file mode 100644 index c6541e3eca392..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppMountContext](./kibana-plugin-public.appmountcontext.md) - -## AppMountContext interface - -The context object received when applications are mounted to the DOM. - -Signature: - -```typescript -export interface AppMountContext -``` - -## Properties - -| 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. | - diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md deleted file mode 100644 index 16c8ffe07fc15..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md +++ /dev/null @@ -1,53 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppMountParameters](./kibana-plugin-public.appmountparameters.md) > [appBasePath](./kibana-plugin-public.appmountparameters.appbasepath.md) - -## AppMountParameters.appBasePath property - -The base path for configuring the application's router. - -Signature: - -```typescript -appBasePath: string; -``` - -## Example - -How to configure react-router with a base path: - -```ts -// inside your plugin's setup function -export class MyPlugin implements Plugin { - setup({ application }) { - application.register({ - id: 'my-app', - async mount(context, params) { - const { renderApp } = await import('./application'); - return renderApp(context, params); - }, - }); -} - -``` - -```ts -// application.tsx -import React from 'react'; -import ReactDOM from 'react-dom'; -import { BrowserRouter, Route } from 'react-router-dom'; - -export renderApp = (context, { appBasePath, element }) => { - ReactDOM.render( - // pass `appBasePath` to `basename` - - - , - element - ); - - return () => ReactDOM.unmountComponentAtNode(element); -} - -``` - diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.element.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.element.md deleted file mode 100644 index dbe496c01c215..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appmountparameters.element.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppMountParameters](./kibana-plugin-public.appmountparameters.md) > [element](./kibana-plugin-public.appmountparameters.element.md) - -## AppMountParameters.element property - -The container element to render the application into. - -Signature: - -```typescript -element: HTMLElement; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.md deleted file mode 100644 index 8733f9cd4915d..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appmountparameters.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppMountParameters](./kibana-plugin-public.appmountparameters.md) - -## AppMountParameters interface - - -Signature: - -```typescript -export interface AppMountParameters -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [appBasePath](./kibana-plugin-public.appmountparameters.appbasepath.md) | string | The base path for configuring the application's router. | -| [element](./kibana-plugin-public.appmountparameters.element.md) | HTMLElement | The container element to render the application into. | - diff --git a/docs/development/core/public/kibana-plugin-public.appunmount.md b/docs/development/core/public/kibana-plugin-public.appunmount.md deleted file mode 100644 index 61782d19ca8c5..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appunmount.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppUnmount](./kibana-plugin-public.appunmount.md) - -## AppUnmount type - -A function called when an application should be unmounted from the page. This function should be synchronous. - -Signature: - -```typescript -export declare type AppUnmount = () => void; -``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.order.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.order.md index 1fef9fc1dc359..e4e2ad2c7a3a7 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlink.order.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.order.md @@ -9,5 +9,5 @@ An ordinal used to sort nav links relative to one another for display. Signature: ```typescript -readonly order?: number; +readonly order: number; ``` diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.application.md b/docs/development/core/public/kibana-plugin-public.coresetup.application.md deleted file mode 100644 index 4b39b2c76802b..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.coresetup.application.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreSetup](./kibana-plugin-public.coresetup.md) > [application](./kibana-plugin-public.coresetup.application.md) - -## CoreSetup.application property - -[ApplicationSetup](./kibana-plugin-public.applicationsetup.md) - -Signature: - -```typescript -application: ApplicationSetup; -``` diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.md b/docs/development/core/public/kibana-plugin-public.coresetup.md index 9b94e2db52831..a4b5b88df36dc 100644 --- a/docs/development/core/public/kibana-plugin-public.coresetup.md +++ b/docs/development/core/public/kibana-plugin-public.coresetup.md @@ -16,7 +16,6 @@ export interface CoreSetup | Property | Type | Description | | --- | --- | --- | -| [application](./kibana-plugin-public.coresetup.application.md) | ApplicationSetup | [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | [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) | diff --git a/docs/development/core/public/kibana-plugin-public.corestart.application.md b/docs/development/core/public/kibana-plugin-public.corestart.application.md index c26701ca80529..1dd05ff947aeb 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.application.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.application.md @@ -9,5 +9,5 @@ Signature: ```typescript -application: ApplicationStart; +application: Pick; ``` diff --git a/docs/development/core/public/kibana-plugin-public.corestart.md b/docs/development/core/public/kibana-plugin-public.corestart.md index 5c1626958c4df..446e458735214 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.md @@ -16,7 +16,7 @@ export interface CoreStart | Property | Type | Description | | --- | --- | --- | -| [application](./kibana-plugin-public.corestart.application.md) | ApplicationStart | [ApplicationStart](./kibana-plugin-public.applicationstart.md) | +| [application](./kibana-plugin-public.corestart.application.md) | Pick<ApplicationStart, 'capabilities'> | [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | [chrome](./kibana-plugin-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-public.chromestart.md) | | [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) | diff --git a/docs/development/core/public/kibana-plugin-public.legacycoresetup.injectedmetadata.md b/docs/development/core/public/kibana-plugin-public.legacycoresetup.injectedmetadata.md deleted file mode 100644 index f71277e64ff17..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.legacycoresetup.injectedmetadata.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) > [injectedMetadata](./kibana-plugin-public.legacycoresetup.injectedmetadata.md) - -## LegacyCoreSetup.injectedMetadata property - -> Warning: This API is now obsolete. -> -> - -Signature: - -```typescript -injectedMetadata: InjectedMetadataSetup; -``` diff --git a/docs/development/core/public/kibana-plugin-public.legacycoresetup.md b/docs/development/core/public/kibana-plugin-public.legacycoresetup.md deleted file mode 100644 index f704bc65d12a5..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.legacycoresetup.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) - -## LegacyCoreSetup interface - -> Warning: This API is now obsolete. -> -> - -Setup interface exposed to the legacy platform via the `ui/new_platform` module. - -Signature: - -```typescript -export interface LegacyCoreSetup extends CoreSetup -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [injectedMetadata](./kibana-plugin-public.legacycoresetup.injectedmetadata.md) | InjectedMetadataSetup | | - -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreSetup](./kibana-plugin-public.coresetup.md), unsupported methods will throw exceptions when called. - diff --git a/docs/development/core/public/kibana-plugin-public.legacycorestart.injectedmetadata.md b/docs/development/core/public/kibana-plugin-public.legacycorestart.injectedmetadata.md deleted file mode 100644 index cd818c3f5adc7..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.legacycorestart.injectedmetadata.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) > [injectedMetadata](./kibana-plugin-public.legacycorestart.injectedmetadata.md) - -## LegacyCoreStart.injectedMetadata property - -> Warning: This API is now obsolete. -> -> - -Signature: - -```typescript -injectedMetadata: InjectedMetadataStart; -``` diff --git a/docs/development/core/public/kibana-plugin-public.legacycorestart.md b/docs/development/core/public/kibana-plugin-public.legacycorestart.md deleted file mode 100644 index 775c3fb1ffe3d..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.legacycorestart.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) - -## LegacyCoreStart interface - -> Warning: This API is now obsolete. -> -> - -Start interface exposed to the legacy platform via the `ui/new_platform` module. - -Signature: - -```typescript -export interface LegacyCoreStart extends CoreStart -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [injectedMetadata](./kibana-plugin-public.legacycorestart.injectedmetadata.md) | InjectedMetadataStart | | - -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreStart](./kibana-plugin-public.corestart.md), unsupported methods will throw exceptions when called. - diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index ccabdc62c5e7a..5fda9f9159306 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -23,12 +23,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | Interface | Description | | --- | --- | -| [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. | -| [AppBase](./kibana-plugin-public.appbase.md) | | | [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | | [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | -| [AppMountContext](./kibana-plugin-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. | -| [AppMountParameters](./kibana-plugin-public.appmountparameters.md) | | | [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | | [ChromeBadge](./kibana-plugin-public.chromebadge.md) | | | [ChromeBrand](./kibana-plugin-public.chromebrand.md) | | @@ -58,8 +54,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | | | [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | | [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | -| [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | -| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | | [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | | | [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | | | [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | @@ -86,7 +80,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | Type Alias | Description | | --- | --- | -| [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) | | | [HttpBody](./kibana-plugin-public.httpbody.md) | | diff --git a/docs/discover/images/saved-query-management-component-all-privileges.png b/docs/discover/images/saved-query-management-component-all-privileges.png deleted file mode 100644 index d3746248f6e27..0000000000000 Binary files a/docs/discover/images/saved-query-management-component-all-privileges.png and /dev/null differ diff --git a/docs/discover/images/saved-query-management-component-delete-query-button.png b/docs/discover/images/saved-query-management-component-delete-query-button.png deleted file mode 100644 index 23af8022069b6..0000000000000 Binary files a/docs/discover/images/saved-query-management-component-delete-query-button.png and /dev/null differ diff --git a/docs/discover/images/saved-query-management-component-save-as-new-query.png b/docs/discover/images/saved-query-management-component-save-as-new-query.png deleted file mode 100644 index 4ba4d1d660268..0000000000000 Binary files a/docs/discover/images/saved-query-management-component-save-as-new-query.png and /dev/null differ diff --git a/docs/discover/images/saved-query-management-component-save-as-new.png b/docs/discover/images/saved-query-management-component-save-as-new.png deleted file mode 100644 index 0899df5c608f5..0000000000000 Binary files a/docs/discover/images/saved-query-management-component-save-as-new.png and /dev/null differ diff --git a/docs/discover/images/saved-query-save-form-default-filters.png b/docs/discover/images/saved-query-save-form-default-filters.png deleted file mode 100644 index 948e474251a37..0000000000000 Binary files a/docs/discover/images/saved-query-save-form-default-filters.png and /dev/null differ diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 790cc669927c1..9286b3ce31ba5 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -55,10 +55,10 @@ query language you can also submit queries using the {ref}/query-dsl.html[Elasti [[save-open-search]] -=== Saving searches -A saved search persists your current view of Discover for later retrieval and reuse. You can reload a saved search into Discover, add it to a dashboard, and use it as the basis for a <>. - -A saved search includes the query text, filters, and optionally, the time filter. A saved search also includes the selected columns in the document table, the sort order, and the current index pattern. +=== Saving and Opening Searches +Saving searches enables you to reload them into Discover and use them as the basis +for <>. Saving a search saves both the search query string +and the currently selected index pattern. [role="xpack"] [[discover-read-only-access]] @@ -88,78 +88,6 @@ If the saved search is associated with a different index pattern than is current selected, opening the saved search changes the selected index pattern. The query language used for the saved search will also be automatically selected. -[[save-load-delete-query]] -=== Saving queries -A saved query is a portable collection of query text and filters that you can reuse in <>, <>, and <>. Save a query when you want to: - -* Retrieve results from the same query at a later time without having to reenter the query text, add the filters or set the time filter -* View the results of the same query in multiple apps -* Share your query - -Saved queries don't include information specific to Discover, such as the currently selected columns in the document table, the sort order, and the index pattern. If you want to save your current view of Discover for later retrieval and reuse, create a <> instead. - -[role="xpack"] -==== Read only access -If you have insufficient privileges to save queries, the *Save current query* button isn't visible in the saved query management popover. For more information, see <> - -==== Saving a query -To save the current query text, filters, and time filter: - -. Click *#* in the search bar, next to the query text input. -. Click *Save current query* in the popover. -+ -[role="screenshot"] -image::discover/images/saved-query-management-component-all-privileges.png["Example of the saved query management popover with a list of saved queries with write access",width="80%"] -+ -. Enter a name, a description, and then select the filter options that you want to include. By default, filters are automatically included, but the time filter is not. -+ -[role="screenshot"] -image::discover/images/saved-query-save-form-default-filters.png["Example of the saved query management save form with the filters option included and the time filter option excluded",width="80%"] -. Click *Save*. - -==== Loading a query -To load a saved query into Discover, Dashboard, or Visualize: - -. Click *#* in the search bar, next to the query text input. -. Select the query you want to load. You might need to scroll down to find the query you are looking for. - -==== Saving changes to a query -If you load a query and then make changes to the query text, the filters, or the time filter, you can save the changes as a new query or update the existing query. - -To save the changes as a new query: - -. Click *#* in the search bar, next to the query text input. -. Click *Save as new* in the popover. -. Enter a name and a description, and then select the filter options that you want to include. -. Click *Save*. -+ -[role="screenshot"] -image::discover/images/saved-query-management-component-save-as-new-query.png["Example of the saved query management popover when a query is loaded and we have made changes to the query",width="80%"] - -To save the changes to the current query: - -. Click *#* in the search bar. -. Click *Save changes* in the popover. -. Enter a description, and then select the filter options that you want to include. -. Click *Save*. - -==== Clearing a query -To clear a query that is currently loaded in an application: - -. Click *#* in the search bar. -. Click *Clear* in the popover. - -==== Deleting a query -To completely delete a query: - -. Click *#* in the search bar, next to the query text input. -. Hover over the query you want to delete. -. Click the trash can icon. -+ -[role="screenshot"] -image::discover/images/saved-query-management-component-delete-query-button.png["Example of the saved query management popover when a query is hovered over and we are about to delete a query",width="80%"] - -You can import, export, and delete saved queries from <>. [[select-pattern]] === Changing Which Indices You're Searching diff --git a/docs/infrastructure/images/infra-time-selector.png b/docs/infrastructure/images/infra-time-selector.png deleted file mode 100644 index 181fac4c7b39b..0000000000000 Binary files a/docs/infrastructure/images/infra-time-selector.png and /dev/null differ diff --git a/docs/infrastructure/images/infrastructure-configure-source-dialog.png b/docs/infrastructure/images/infrastructure-configure-source-dialog.png new file mode 100644 index 0000000000000..31f496aaf56c7 Binary files /dev/null and b/docs/infrastructure/images/infrastructure-configure-source-dialog.png differ diff --git a/docs/infrastructure/images/infrastructure-configure-source-gear-icon.png b/docs/infrastructure/images/infrastructure-configure-source-gear-icon.png new file mode 100644 index 0000000000000..8b062c627143a Binary files /dev/null and b/docs/infrastructure/images/infrastructure-configure-source-gear-icon.png differ diff --git a/docs/infrastructure/images/infrastructure-configure-source.png b/docs/infrastructure/images/infrastructure-configure-source.png new file mode 100644 index 0000000000000..d1e9a2fd59246 Binary files /dev/null and b/docs/infrastructure/images/infrastructure-configure-source.png differ diff --git a/docs/infrastructure/images/infrastructure-time-selector.png b/docs/infrastructure/images/infrastructure-time-selector.png new file mode 100644 index 0000000000000..ecf3c6ac34462 Binary files /dev/null and b/docs/infrastructure/images/infrastructure-time-selector.png differ diff --git a/docs/infrastructure/images/read-only-badge.png b/docs/infrastructure/images/read-only-badge.png new file mode 100644 index 0000000000000..7911a21e4985f Binary files /dev/null and b/docs/infrastructure/images/read-only-badge.png differ diff --git a/docs/infrastructure/infra-ui.asciidoc b/docs/infrastructure/infra-ui.asciidoc index be8fc29b98ab2..0e1a10807df6e 100644 --- a/docs/infrastructure/infra-ui.asciidoc +++ b/docs/infrastructure/infra-ui.asciidoc @@ -49,7 +49,7 @@ For example, enter `host.hostname : "host1"` to see only the information for `ho [[infra-date]] === Specify the time and date -Click the time selector image:infrastructure/images/infra-time-selector.png[time selector icon] to choose the timeframe for the metrics. +Click the time selector image:logs/images/logs-time-selector.png[time selector icon] to choose the timeframe for the metrics. The values shown are the values for the last minute at the specified time and date. [float] diff --git a/docs/logs/images/log-details-actions.png b/docs/logs/images/log-details-actions.png new file mode 100644 index 0000000000000..49192d0e3135c Binary files /dev/null and b/docs/logs/images/log-details-actions.png differ diff --git a/docs/logs/images/log-details-filter.png b/docs/logs/images/log-details-filter.png new file mode 100644 index 0000000000000..5037c848d83fe Binary files /dev/null and b/docs/logs/images/log-details-filter.png differ diff --git a/docs/logs/images/log-details-flyover.png b/docs/logs/images/log-details-flyover.png new file mode 100644 index 0000000000000..b28040d0e1425 Binary files /dev/null and b/docs/logs/images/log-details-flyover.png differ diff --git a/docs/logs/images/logs-configure-source-dialog-add-column-button.png b/docs/logs/images/logs-configure-source-dialog-add-column-button.png new file mode 100644 index 0000000000000..2d4c1986d1336 Binary files /dev/null and b/docs/logs/images/logs-configure-source-dialog-add-column-button.png differ diff --git a/docs/logs/images/logs-configure-source-dialog-add-column-popover.png b/docs/logs/images/logs-configure-source-dialog-add-column-popover.png new file mode 100644 index 0000000000000..b273ab6911b27 Binary files /dev/null and b/docs/logs/images/logs-configure-source-dialog-add-column-popover.png differ diff --git a/docs/logs/images/logs-configure-source-dialog-indices-tab.png b/docs/logs/images/logs-configure-source-dialog-indices-tab.png new file mode 100644 index 0000000000000..4f2460debf128 Binary files /dev/null and b/docs/logs/images/logs-configure-source-dialog-indices-tab.png differ diff --git a/docs/logs/images/logs-configure-source-dialog-log-columns-tab.png b/docs/logs/images/logs-configure-source-dialog-log-columns-tab.png new file mode 100644 index 0000000000000..c89da36d9d9b2 Binary files /dev/null and b/docs/logs/images/logs-configure-source-dialog-log-columns-tab.png differ diff --git a/docs/logs/images/logs-configure-source-gear-icon.png b/docs/logs/images/logs-configure-source-gear-icon.png new file mode 100644 index 0000000000000..8b062c627143a Binary files /dev/null and b/docs/logs/images/logs-configure-source-gear-icon.png differ diff --git a/docs/logs/images/logs-configure-source.png b/docs/logs/images/logs-configure-source.png new file mode 100644 index 0000000000000..dec6e0ffe11fa Binary files /dev/null and b/docs/logs/images/logs-configure-source.png differ diff --git a/docs/logs/images/logs-stream-click-entry.png b/docs/logs/images/logs-stream-click-entry.png new file mode 100644 index 0000000000000..12705493dce06 Binary files /dev/null and b/docs/logs/images/logs-stream-click-entry.png differ diff --git a/docs/logs/images/logs-stream-filtered-by-value.png b/docs/logs/images/logs-stream-filtered-by-value.png new file mode 100644 index 0000000000000..af78e1633bf19 Binary files /dev/null and b/docs/logs/images/logs-stream-filtered-by-value.png differ diff --git a/docs/logs/images/logs-stream-highlight-box.png b/docs/logs/images/logs-stream-highlight-box.png new file mode 100644 index 0000000000000..1a6cb2b59dd59 Binary files /dev/null and b/docs/logs/images/logs-stream-highlight-box.png differ diff --git a/docs/logs/images/logs-stream-highlight-entries.png b/docs/logs/images/logs-stream-highlight-entries.png new file mode 100644 index 0000000000000..d23c7c9853304 Binary files /dev/null and b/docs/logs/images/logs-stream-highlight-entries.png differ diff --git a/docs/logs/images/logs-time-selector.png b/docs/logs/images/logs-time-selector.png index 5e6a9b7222c54..ecf3c6ac34462 100644 Binary files a/docs/logs/images/logs-time-selector.png and b/docs/logs/images/logs-time-selector.png differ diff --git a/docs/logs/images/logs-usage-column-headers.png b/docs/logs/images/logs-usage-column-headers.png new file mode 100644 index 0000000000000..7040d5a5c8d00 Binary files /dev/null and b/docs/logs/images/logs-usage-column-headers.png differ diff --git a/docs/logs/images/logs-usage-customize.png b/docs/logs/images/logs-usage-customize.png new file mode 100644 index 0000000000000..12089987c6b6e Binary files /dev/null and b/docs/logs/images/logs-usage-customize.png differ diff --git a/docs/logs/images/logs-usage-query-bar.png b/docs/logs/images/logs-usage-query-bar.png new file mode 100644 index 0000000000000..45ba5e4647e18 Binary files /dev/null and b/docs/logs/images/logs-usage-query-bar.png differ diff --git a/docs/logs/images/logs-usage-start-streaming.png b/docs/logs/images/logs-usage-start-streaming.png new file mode 100644 index 0000000000000..72221f919b563 Binary files /dev/null and b/docs/logs/images/logs-usage-start-streaming.png differ diff --git a/docs/logs/images/logs-usage-stop-streaming.png b/docs/logs/images/logs-usage-stop-streaming.png new file mode 100644 index 0000000000000..77cf9aac596a5 Binary files /dev/null and b/docs/logs/images/logs-usage-stop-streaming.png differ diff --git a/docs/logs/images/logs-usage-streaming-indicator.png b/docs/logs/images/logs-usage-streaming-indicator.png new file mode 100644 index 0000000000000..807c0053699ef Binary files /dev/null and b/docs/logs/images/logs-usage-streaming-indicator.png differ diff --git a/docs/logs/images/logs-usage-time-picker.png b/docs/logs/images/logs-usage-time-picker.png new file mode 100644 index 0000000000000..42a0e4d946bb3 Binary files /dev/null and b/docs/logs/images/logs-usage-time-picker.png differ diff --git a/docs/logs/images/logs-usage-timeline.png b/docs/logs/images/logs-usage-timeline.png new file mode 100644 index 0000000000000..4ad49f45b3392 Binary files /dev/null and b/docs/logs/images/logs-usage-timeline.png differ diff --git a/docs/logs/images/read-only-badge.png b/docs/logs/images/read-only-badge.png new file mode 100644 index 0000000000000..ab7cc296477dc Binary files /dev/null and b/docs/logs/images/read-only-badge.png differ diff --git a/docs/logs/using.asciidoc b/docs/logs/using.asciidoc index cdc990ff1caa0..0b70712337182 100644 --- a/docs/logs/using.asciidoc +++ b/docs/logs/using.asciidoc @@ -47,6 +47,12 @@ Here, you can set the scale to use for the minimap timeline, choose whether to w === Stream or pause logs Click *Stream live* to start streaming live log data, or click *Stop streaming* to focus on historical data. +[role="screenshot"] +image::logs/images/logs-usage-start-streaming.png[Logs start streaming] + +[role="screenshot"] +image::logs/images/logs-usage-stop-streaming.png[Logs stop streaming] + When you are viewing historical data, you can scroll back through the entries as far as there is data available. When you are streaming live data, the most recent log appears at the bottom of the page. @@ -69,11 +75,12 @@ To highlight a word or phrase in the logs stream, click *Highlights* and enter y To inspect a log event, hover over it, then click the *View details* icon image:logs/images/logs-view-event.png[View event icon] beside the event. This opens the *Log event document details* fly-out that shows the fields associated with the log event. -To quickly filter the logs stream by one of the field values, click the *View event with filter* icon image:logs/images/logs-view-event-with-filter.png[View event icon] beside the field. +To quickly filter the logs stream by one of the field values shown here, click the *View event with filter* icon image:logs/images/logs-view-event-with-filter.png[View event icon] beside the field. This automatically adds a search filter to the logs stream to filter the entries by this field and value. In the log event details, click *Actions* to see the other actions related to the event. Depending on the event and the features you have installed and configured, you may also be able to: -* Select *View status in Uptime* to <> in the *Uptime* app. +* Select *View monitor status* to <> in the *Uptime* app. +// ++ Is "monitor" the best choice of word here? * Select *View in APM* to <> in the *APM* app. diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index 6b4eb9928cea0..75018e6dd4d97 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -4,9 +4,9 @@ https://www.elastic.co/elastic-maps-service[Elastic Maps Service (EMS)] is a service that hosts tile layers and vector shapes of administrative boundaries. -If you are using Kibana's out-of-the-box settings, **Elastic Maps** is already configured to use EMS. +If you are using Kibana's out-of-the-box settings, the **Maps** application is already configured to use EMS. -**Elastic Maps** makes requests directly from the browser to EMS. +The **Maps** application makes requests directly from the browser to EMS. To proxy EMS requests through the Kibana server, set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file. @@ -19,5 +19,5 @@ behind a firewall. If this happens, you can disable the EMS connection to avoid To disable EMS, change your <> file. . Set `map.includeElasticMapsService` to `false` to turn off the EMS connection. -. Set `map.tilemap.url` to the URL of your tile server. This configures the default tile layer of **Elastic Maps**. +. Set `map.tilemap.url` to the URL of your tile server. This configures the **Maps** default tile layer. . (Optional) Set `map.regionmap` to the vector shapes of the administrative boundaries that you want to use. diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index bb569a11ed874..cb7ddd62d6c47 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -1,23 +1,11 @@ [role="xpack"] [[maps]] -= Elastic Maps += Maps [partintro] -- -*Elastic Maps* enables you to parse through your geographical data at scale, with speed, and in real time. With features like multiple layers and indices in a map, plotting of raw documents, dynamic client-side styling, and global search across multiple layers, you can understand and monitor your data with ease. - -With *Elastic Maps*, you can: - -* Create maps with multiple layers and indices. -* Upload GeoJSON files into Elasticsearch. -* Embed your map in Dashboards. -* Plot individual documents or use aggregations to plot any data set, no matter how large. -* Create choropleth maps. -* Use data driven styling to symbolize features from property values. -* Focus the data you want to display with searches. - -Start your tour of *Elastic Maps* with the <>. +The **Maps** application enables you to parse through your geographical data at scale, with speed, and in real time. With features like multiple layers and indices in a map, plotting of raw documents, dynamic client-side styling, and global search across multiple layers, you can understand and monitor your data with ease. [role="screenshot"] image::maps/images/sample_data_ecommerce.png[] diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index f6db2f0fff219..68163f5e88a0e 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -1,8 +1,8 @@ [role="xpack"] [[maps-getting-started]] -== Getting started with Elastic Maps +== Getting started with Maps -You work with *Elastic Maps* by adding layers. The data for a layer can come from +You work with *Maps* by adding layers. The data for a layer can come from sources such as {es} documents, vector sources, tile map services, web map services, and more. You can symbolize the data in different ways. For example, you might show which airports have the longest flight @@ -14,7 +14,7 @@ light to dark. image::maps/images/sample_data_web_logs.png[] [[maps-read-only-access]] -NOTE: If you have insufficient privileges to create or save maps, a read-only icon +NOTE: If you have insufficient privileges to create or save maps, a read-only icon appears in the application header. The buttons to create new maps or edit existing maps won't be visible. For more information on granting access to Kibana see <>. @@ -25,7 +25,7 @@ image::maps/images/read-only-badge.png[Example of Maps' read only access indicat [float] === Prerequisites Before you start this tutorial, <>. Each -sample data set includes a map to go along with the data. Once you've added the data, open *Elastic Maps* and +sample data set includes a map to go along with the data. Once you've added the data, open *Maps* and explore the different layers of the *[Logs] Total Requests and Bytes* map. You'll re-create this map in this tutorial. @@ -44,7 +44,7 @@ In this tutorial, you'll learn to: The first thing to do is to create a new map. -. If you haven't already, open *Elastic Maps*. +. If you haven't already, open *Maps*. . On the maps list page, click *Create map*. . Set the time range to *Last 7 days*. + diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 3830ecdcbc1f0..33a8ba15113d8 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -2,7 +2,7 @@ [[maps-search]] == Searching your data -**Elastic Maps** embeds the query bar for real-time ad hoc search. +The **Maps** application embeds the query bar for real-time ad hoc search. Only layers requesting data from {es} are filtered when you submit a search request. You can create a layer that requests data from {es} from the following: diff --git a/docs/maps/tile-layer.asciidoc b/docs/maps/tile-layer.asciidoc index 059dd527f4810..1681cd7dcca85 100644 --- a/docs/maps/tile-layer.asciidoc +++ b/docs/maps/tile-layer.asciidoc @@ -9,11 +9,11 @@ image::maps/images/tile_layer.png[] You can create a tile layer from the following data sources: -*Configured Tile Map Service*:: Tile map service configured in kibana.yml. +*Custom Tile Map Service*:: Map tiles configured in kibana.yml. See map.tilemap.url in <> for details. -*EMS Basemaps*:: Tile map service from https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. +*Tiles*:: Map tiles from https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. -*Tile Map Service*:: Tile map service configured in interface. +*Tile Map Service from URL*:: Map tiles from a URL that includes the XYZ coordinates. *Web Map Service*:: Maps from OGC Standard WMS. diff --git a/docs/maps/trouble-shooting.asciidoc b/docs/maps/trouble-shooting.asciidoc index 542138828530b..d23bed546a6dc 100644 --- a/docs/maps/trouble-shooting.asciidoc +++ b/docs/maps/trouble-shooting.asciidoc @@ -1,13 +1,13 @@ [role="xpack"] [[maps-troubleshooting]] -== Elastic Maps troubleshooting +== Maps troubleshooting Use the information in this section to inspect Elasticsearch requests and find solutions to common problems. [float] === Inspect Elasticsearch requests -*Elastic Maps* uses the {ref}/search-search.html[{es} search API] to get documents and aggregation results from {es}. To troubleshoot these requests, open the Inspector, which shows the most recent requests for each layer. You can switch between different requests using the *Request* dropdown. +The Maps application uses the {ref}/search-search.html[{es} search API] to get documents and aggregation results from {es}. To troubleshoot these requests, open the Inspector, which shows the most recent requests for each layer. You can switch between different requests using the *Request* dropdown. [role="screenshot"] image::maps/images/inspector.png[] diff --git a/docs/maps/vector-layer.asciidoc b/docs/maps/vector-layer.asciidoc index 8dade1adec6f8..c790107cbd874 100644 --- a/docs/maps/vector-layer.asciidoc +++ b/docs/maps/vector-layer.asciidoc @@ -9,10 +9,10 @@ image::maps/images/vector_layer.png[] You can create a vector layer from the following sources: -*Configured GeoJSON*:: Vector data from hosted GeoJSON configured in kibana.yml. +*Custom vector shapes*:: Vector shapes from static files configured in kibana.yml. See map.regionmap.* in <> for details. -*Documents*:: Vector data from a Kibana index pattern. +*Documents*:: Geospatial data from a Kibana index pattern. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point] or {ref}/geo-shape.html[geo_shape]. NOTE: Document results are limited to the first 10000 matching documents. @@ -22,7 +22,7 @@ Use <> to plot large data sets. Set *Show as* to *grid rectangles* or *points*. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. -*EMS Boundaries*:: Administrative boundaries from https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. +*Vector shapes*:: Vector shapes of administrative boundaries from https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. include::vector-style.asciidoc[] include::vector-style-properties.asciidoc[] diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index eac1971b22b17..4798dd8678d0a 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -83,10 +83,6 @@ Defaults to `120000` (two minutes). Reporting works by capturing screenshots from Kibana. The following settings control the capturing process. -`xpack.reporting.capture.maxAttempts`:: -If capturing a report fails for any reason, Kibana will re-attempt othe reporting -job, as many times as this setting. Defaults to `3`. - `xpack.reporting.capture.loadDelay`:: When visualizations are not evented, this is the amount of time before taking a screenshot. All visualizations that ship with Kibana are evented, so this diff --git a/package.json b/package.json index 3dadd53a88ae7..3f095c8b82248 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", "d3-cloud": "1.2.5", - "del": "^4.1.1", + "del": "^4.0.0", "elasticsearch": "^16.2.0", "elasticsearch-browser": "^16.2.0", "encode-uri-query": "1.0.1", @@ -166,11 +166,10 @@ "handlebars": "4.1.2", "hapi": "^17.5.3", "hapi-auth-cookie": "^9.0.0", - "history": "^4.9.0", "hjson": "3.1.2", "hoek": "^5.0.4", "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.2", + "https-proxy-agent": "^2.2.1", "inert": "^5.1.0", "inline-style": "^2.0.0", "joi": "^13.5.2", @@ -198,7 +197,7 @@ "mustache": "2.3.2", "ngreact": "0.5.1", "node-fetch": "1.7.3", - "opn": "^5.5.0", + "opn": "^5.4.0", "oppsy": "^2.0.0", "pegjs": "0.10.0", "postcss-loader": "3.0.0", @@ -305,7 +304,6 @@ "@types/hapi": "^17.0.18", "@types/hapi-auth-cookie": "^9.1.0", "@types/has-ansi": "^3.0.0", - "@types/history": "^4.7.3", "@types/hoek": "^4.1.3", "@types/humps": "^1.1.2", "@types/jest": "^24.0.9", @@ -319,7 +317,7 @@ "@types/lru-cache": "^5.1.0", "@types/markdown-it": "^0.0.7", "@types/minimatch": "^2.0.29", - "@types/mocha": "^5.2.7", + "@types/mocha": "^5.2.6", "@types/moment-timezone": "^0.5.8", "@types/mustache": "^0.8.31", "@types/node": "^10.12.27", @@ -337,7 +335,7 @@ "@types/rimraf": "^2.0.2", "@types/selenium-webdriver": "^3.0.16", "@types/semver": "^5.5.0", - "@types/sinon": "^7.0.13", + "@types/sinon": "^7.0.0", "@types/strip-ansi": "^3.0.0", "@types/styled-components": "^3.0.2", "@types/supertest": "^2.0.5", @@ -395,7 +393,7 @@ "has-ansi": "^3.0.0", "image-diff": "1.6.3", "intl-messageformat-parser": "^1.4.0", - "is-path-inside": "^2.1.0", + "is-path-inside": "^2.0.0", "istanbul-instrumenter-loader": "3.0.1", "jest": "^24.8.0", "jest-cli": "^24.8.0", @@ -433,7 +431,7 @@ "sass-lint": "^1.12.1", "selenium-webdriver": "^4.0.0-alpha.4", "simple-git": "1.116.0", - "sinon": "^7.4.2", + "sinon": "^7.2.2", "strip-ansi": "^3.0.1", "supertest": "^3.1.0", "supertest-as-promised": "^4.0.2", diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 6d3914eb56218..bea8ed11e87ac 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -21,5 +21,5 @@ export { withProcRunner } from './proc_runner'; export { ToolingLog, ToolingLogTextWriter, pickLevelFromFlags } from './tooling_log'; export { createAbsolutePathSerializer } from './serializers'; export { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } from './certs'; -export { run, createFailError, createFlagError, combineErrors, isFailError, Flags } from './run'; +export { run, createFailError, createFlagError, combineErrors, isFailError } from './run'; export { REPO_ROOT } from './constants'; diff --git a/packages/kbn-dev-utils/src/run/index.ts b/packages/kbn-dev-utils/src/run/index.ts index 5e1a42deefffb..9d81ff0c9af3b 100644 --- a/packages/kbn-dev-utils/src/run/index.ts +++ b/packages/kbn-dev-utils/src/run/index.ts @@ -18,5 +18,4 @@ */ export { run } from './run'; -export { Flags } from './flags'; export { createFailError, createFlagError, combineErrors, isFailError } from './fail'; diff --git a/packages/kbn-es-query/package.json b/packages/kbn-es-query/package.json index 5ef1b6e125413..b16c45bb53dca 100644 --- a/packages/kbn-es-query/package.json +++ b/packages/kbn-es-query/package.json @@ -21,7 +21,7 @@ "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@kbn/expect": "1.0.0", - "del": "^4.1.1", + "del": "^4.0.0", "getopts": "^2.2.4", "supports-color": "^7.0.0" } diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index 1064d719af837..7e8e2e227eb3c 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -10,7 +10,7 @@ "abort-controller": "^2.0.3", "chalk": "^2.4.2", "dedent": "^0.7.0", - "del": "^4.1.1", + "del": "^4.0.0", "execa": "^1.0.0", "getopts": "^2.2.4", "glob": "^7.1.2", diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index dff8a3f2f4b78..80162c9c6bafa 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -18,7 +18,7 @@ "@kbn/dev-utils": "1.0.0", "@types/intl-relativeformat": "^2.1.0", "@types/react-intl": "^2.3.15", - "del": "^4.1.1", + "del": "^4.0.0", "getopts": "^2.2.4", "supports-color": "^7.0.0", "typescript": "3.5.3" diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index a9b30c17e2927..aa8eb8abd96c0 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -24,7 +24,7 @@ "babel-loader": "^8.0.6", "copy-webpack-plugin": "^5.0.4", "css-loader": "2.1.1", - "del": "^4.1.1", + "del": "^4.0.0", "getopts": "^2.2.4", "pegjs": "0.10.0", "sass-loader": "^7.3.1", diff --git a/packages/kbn-interpreter/src/common/index.d.ts b/packages/kbn-interpreter/src/common/index.d.ts index 7201ccbb35635..a8917b7a65df1 100644 --- a/packages/kbn-interpreter/src/common/index.d.ts +++ b/packages/kbn-interpreter/src/common/index.d.ts @@ -19,4 +19,4 @@ export { Registry } from './lib/registry'; -export { fromExpression, toExpression, Ast } from './lib/ast'; +export { fromExpression, Ast } from './lib/ast'; diff --git a/packages/kbn-interpreter/src/common/lib/ast.d.ts b/packages/kbn-interpreter/src/common/lib/ast.d.ts index a4ee235359463..2b0328bda9392 100644 --- a/packages/kbn-interpreter/src/common/lib/ast.d.ts +++ b/packages/kbn-interpreter/src/common/lib/ast.d.ts @@ -20,4 +20,3 @@ export type Ast = unknown; export declare function fromExpression(expression: string): Ast; -export declare function toExpression(astObj: Ast, type?: string): string; diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index ce496abe9c7f1..f4669a5e906ef 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -16,7 +16,7 @@ "@babel/core": "^7.5.5", "argv-split": "^2.0.1", "commander": "^3.0.0", - "del": "^4.1.1", + "del": "^4.0.0", "execa": "^1.0.0", "globby": "^8.0.1", "gulp-babel": "^8.0.0", diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index 70adf5c43939d..9223f144a6f58 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -41,7 +41,7 @@ "cmd-shim": "^2.1.0", "cpy": "^7.0.1", "dedent": "^0.7.0", - "del": "^4.1.1", + "del": "^4.0.0", "execa": "^1.0.0", "getopts": "^2.2.4", "glob": "^7.1.2", diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 6891f01bae2a9..de8ef26636715 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -17,7 +17,7 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "del": "^4.1.1", + "del": "^4.0.0", "getopts": "^2.2.4", "glob": "^7.1.2", "rxjs": "^6.2.1", diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts index 36412961ce75b..d864729a85236 100644 --- a/packages/kbn-test/src/functional_test_runner/cli.ts +++ b/packages/kbn-test/src/functional_test_runner/cli.ts @@ -18,36 +18,24 @@ */ import { resolve } from 'path'; -import { run, createFlagError, Flags } from '@kbn/dev-utils'; +import { run } from '@kbn/dev-utils'; import { FunctionalTestRunner } from './functional_test_runner'; -const makeAbsolutePath = (v: string) => resolve(process.cwd(), v); -const toArray = (v: string | string[]) => ([] as string[]).concat(v || []); -const parseInstallDir = (flags: Flags) => { - const flag = flags['kibana-install-dir']; - - if (typeof flag !== 'string' && flag !== undefined) { - throw createFlagError('--kibana-install-dir must be a string or not defined'); - } - - return flag ? makeAbsolutePath(flag) : undefined; -}; - export function runFtrCli() { run( async ({ flags, log }) => { + const resolveConfigPath = (v: string) => resolve(process.cwd(), v); + const toArray = (v: string | string[]) => ([] as string[]).concat(v || []); + const functionalTestRunner = new FunctionalTestRunner( log, - makeAbsolutePath(flags.config as string), + resolveConfigPath(flags.config as string), { mochaOpts: { bail: flags.bail, grep: flags.grep || undefined, invert: flags.invert, }, - kbnTestServer: { - installDir: parseInstallDir(flags), - }, suiteTags: { include: toArray(flags['include-tag'] as string | string[]), exclude: toArray(flags['exclude-tag'] as string | string[]), @@ -96,7 +84,7 @@ export function runFtrCli() { }, { flags: { - string: ['config', 'grep', 'exclude', 'include-tag', 'exclude-tag', 'kibana-install-dir'], + string: ['config', 'grep', 'exclude', 'include-tag', 'exclude-tag'], boolean: ['bail', 'invert', 'test-stats', 'updateBaselines'], default: { config: 'test/functional/config.js', @@ -112,7 +100,6 @@ export function runFtrCli() { --exclude-tag=tag a tag to be excluded, pass multiple times for multiple tags --test-stats print the number of tests (included and excluded) to STDERR --updateBaselines replace baseline screenshots with whatever is generated from the test - --kibana-install-dir directory where the Kibana install being tested resides `, }, } 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 d9cf282d8f4b6..4887ad2c6e1d7 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 @@ -187,7 +187,6 @@ export const schema = Joi.object() buildArgs: Joi.array(), sourceArgs: Joi.array(), serverArgs: Joi.array(), - installDir: Joi.string(), }) .default(), diff --git a/packages/kbn-test/src/functional_tests/lib/run_ftr.js b/packages/kbn-test/src/functional_tests/lib/run_ftr.js index aeda84f9524ed..a0edfcdb8c7b5 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_ftr.js +++ b/packages/kbn-test/src/functional_tests/lib/run_ftr.js @@ -20,10 +20,7 @@ import { FunctionalTestRunner, readConfigFile } from '../../functional_test_runner'; import { CliError } from './run_cli'; -async function createFtr({ - configPath, - options: { installDir, log, bail, grep, updateBaselines, suiteTags }, -}) { +async function createFtr({ configPath, options: { log, bail, grep, updateBaselines, suiteTags } }) { const config = await readConfigFile(log, configPath); return new FunctionalTestRunner(log, configPath, { @@ -31,9 +28,6 @@ async function createFtr({ bail: !!bail, grep, }, - kbnTestServer: { - installDir, - }, updateBaselines, suiteTags: { include: [...suiteTags.include, ...config.get('suiteTags.include')], diff --git a/packages/kbn-test/src/functional_tests/tasks.js b/packages/kbn-test/src/functional_tests/tasks.js index 6ebc49679fbf3..966b148f0ce6d 100644 --- a/packages/kbn-test/src/functional_tests/tasks.js +++ b/packages/kbn-test/src/functional_tests/tasks.js @@ -21,7 +21,6 @@ import { relative } from 'path'; import * as Rx from 'rxjs'; import { startWith, switchMap, take } from 'rxjs/operators'; import { withProcRunner } from '@kbn/dev-utils'; -import dedent from 'dedent'; import { runElasticsearch, @@ -34,20 +33,14 @@ import { import { readConfigFile } from '../functional_test_runner/lib'; -const makeSuccessMessage = options => { - const installDirFlag = options.installDir ? ` --kibana-install-dir=${options.installDir}` : ''; +const SUCCESS_MESSAGE = ` - return ( - '\n\n' + - dedent` - Elasticsearch and Kibana are ready for functional testing. Start the functional tests - in another terminal session by running this command from this directory: +Elasticsearch and Kibana are ready for functional testing. Start the functional tests +in another terminal session by running this command from this directory: - node ${relative(process.cwd(), KIBANA_FTR_SCRIPT)}${installDirFlag} - ` + - '\n\n' - ); -}; + node ${relative(process.cwd(), KIBANA_FTR_SCRIPT)} + +`; /** * Run servers and tests for each config @@ -125,15 +118,15 @@ export async function startServers(options) { // wait for 5 seconds of silence before logging the // success message so that it doesn't get buried - await silence(log, 5000); - log.success(makeSuccessMessage(options)); + await silence(5000, { log }); + log.info(SUCCESS_MESSAGE); await procs.waitForAllToStop(); await es.cleanup(); }); } -async function silence(log, milliseconds) { +async function silence(milliseconds, { log }) { await log .getWritten$() .pipe( diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index 158ff54336075..5b4a849d4209b 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -66,7 +66,7 @@ "redux-thunk": "2.2.0", "regenerator-runtime": "^0.13.3", "sass-loader": "^7.3.1", - "sinon": "^7.4.2", + "sinon": "^7.2.2", "style-loader": "^0.23.1", "webpack": "^4.39.2", "webpack-dev-server": "^3.8.0", diff --git a/renovate.json5 b/renovate.json5 index 7798909bd3f41..e3b887d52149c 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -369,14 +369,6 @@ '@types/has-ansi', ], }, - { - groupSlug: 'history', - groupName: 'history related packages', - packageNames: [ - 'history', - '@types/history', - ], - }, { groupSlug: 'humps', groupName: 'humps related packages', @@ -625,6 +617,14 @@ '@types/git-url-parse', ], }, + { + groupSlug: 'history', + groupName: 'history related packages', + packageNames: [ + 'history', + '@types/history', + ], + }, { groupSlug: 'jsdom', groupName: 'jsdom related packages', diff --git a/rfcs/text/0004_application_service_mounting.md b/rfcs/text/0004_application_service_mounting.md index 7dc577abc48e3..30e8d9a05b8b4 100644 --- a/rfcs/text/0004_application_service_mounting.md +++ b/rfcs/text/0004_application_service_mounting.md @@ -18,14 +18,14 @@ import ReactDOM from 'react-dom'; import { MyApp } from './componnets'; -export function renderApp(context, { element }) { +export function renderApp(context, targetDomElement) { ReactDOM.render( , - element + targetDomElement ); return () => { - ReactDOM.unmountComponentAtNode(element); + ReactDOM.unmountComponentAtNode(targetDomElement); }; } ``` @@ -38,9 +38,9 @@ class MyPlugin { application.register({ id: 'my-app', title: 'My Application', - async mount(context, params) { + async mount(context, targetDomElement) { const { renderApp } = await import('./applcation'); - return renderApp(context, params); + return renderApp(context, targetDomElement); } }); } @@ -63,7 +63,9 @@ lock-in. ```ts /** A context type that implements the Handler Context pattern from RFC-0003 */ -export interface AppMountContext { +export interface MountContext { + /** This is the base path for setting up your router. */ + basename: string; /** These services serve as an example, but are subject to change. */ core: { http: { @@ -91,13 +93,6 @@ export interface AppMountContext { [contextName: string]: unknown; } -export interface AppMountParams { - /** The base path the application is mounted on. Used to configure routers. */ - appBasePath: string; - /** The element the application should render into */ - element: HTMLElement; -} - export type Unmount = () => Promise | void; export interface AppSpec { @@ -114,11 +109,11 @@ export interface AppSpec { /** * A mount function called when the user navigates to this app's route. - * @param context the `AppMountContext` generated for this app - * @param params the `AppMountParams` + * @param context the `MountContext generated for this app + * @param targetDomElement An HTMLElement to mount the application onto. * @returns An unmounting function that will be called to unmount the application. */ - mount(context: MountContext, params: AppMountParams): Unmount | Promise; + mount(context: MountContext, targetDomElement: HTMLElement): Unmount | Promise; /** * A EUI iconType that will be used for the app's icon. This icon @@ -163,21 +158,19 @@ When an app is registered via `register`, it must provide a `mount` function that will be invoked whenever the window's location has changed from another app to this app. -This function is called with a `AppMountContext` and an -`AppMountParams` which contains a `HTMLElement` for the application to -render itself to. The mount function must also return a function that can be -called by the ApplicationService to unmount the application at the given DOM -Element. The mount function may return a Promise of an unmount function in order -to import UI code dynamically. +This function is called with a `MountContext` and an `HTMLElement` for the +application to render itself to. The mount function must also return a function +that can be called by the ApplicationService to unmount the application at the +given DOM node. The mount function may return a Promise of an unmount function +in order to import UI code dynamically. The ApplicationService's `register` method will only be available during the *setup* lifecycle event. This allows the system to know when all applications have been registered. -The `mount` function will also get access to the `AppMountContext` that -has many of the same core services available during the `start` lifecycle. -Plugins can also register additional context attributes via the -`registerMountContext` function. +The `mount` function will also get access to the `MountContext` that has many of +the same core services available during the `start` lifecycle. Plugins can also +register additional context attributes via the `registerMountContext` function. ## Routing @@ -197,7 +190,7 @@ An example: "overview" page: mykibana.com/app/my-app/overview When setting up a router, your application should only handle the part of the -URL following the `params.appBasePath` provided when you application is mounted. +URL following the `context.basename` provided when you application is mounted. ### Legacy Applications @@ -218,7 +211,7 @@ a full-featured router and code-splitting. Note that using React or any other 3rd party tools featured here is not required to build a Kibana Application. ```tsx -// my_plugin/public/application.tsx +// my_plugin/public/application.ts import React from 'react'; import ReactDOM from 'react-dom'; @@ -246,16 +239,16 @@ const MyApp = ({ basename }) => ( , ); -export function renderApp(context, params) { +export function renderApp(context, targetDomElement) { ReactDOM.render( - // `params.appBasePath` would be `/app/my-app` in this example. - // This exact string is not guaranteed to be stable, always reference the - // provided value at `params.appBasePath`. - , - params.element + // `context.basename` would be `/app/my-app` in this example. + // This exact string is not guaranteed to be stable, always reference + // `context.basename`. + , + targetDomElem ); - return () => ReactDOM.unmountComponentAtNode(params.element); + return () => ReactDOM.unmountComponentAtNode(targetDomElem); } ``` @@ -266,9 +259,9 @@ export class MyPlugin { setup({ application }) { application.register({ id: 'my-app', - async mount(context, params) { + async mount(context, targetDomElem) { const { renderApp } = await import('./applcation'); - return renderApp(context, params); + return renderApp(context, targetDomElement); } }); } diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index ee37ae48823b3..e2e5cf7d129bc 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -39,7 +39,7 @@ We'll start with an overview of how plugins work in the new platform, and we'll Plugins in the new platform are not especially novel or complicated to describe. Our intention wasn't to build some clever system that magically solved problems through abstractions and layers of obscurity, and we wanted to make sure plugins could continue to use most of the same technologies they use today, at least from a technical perspective. -New platform plugins exist in the `src/plugins` and `x-pack/plugins` directories. +New platform plugins exist in the `src/plugins` and `x-pack/legacy/plugins` directories. ### Architecture diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index a2db755224636..85d997f3dc9aa 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -17,51 +17,23 @@ * under the License. */ -import { Subject } from 'rxjs'; - import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; -import { ApplicationService } from './application_service'; -import { - ApplicationSetup, - InternalApplicationStart, - ApplicationStart, - InternalApplicationSetup, -} from './types'; +import { ApplicationService, ApplicationSetup, ApplicationStart } from './application_service'; type ApplicationServiceContract = PublicMethodsOf; const createSetupContractMock = (): jest.Mocked => ({ - register: jest.fn(), - registerMountContext: jest.fn(), -}); - -const createInternalSetupContractMock = (): jest.Mocked => ({ - register: jest.fn(), + registerApp: jest.fn(), registerLegacyApp: jest.fn(), - registerMountContext: jest.fn(), }); -const createStartContractMock = (legacyMode = false): jest.Mocked => ({ - capabilities: capabilitiesServiceMock.createStartContract().capabilities, - navigateToApp: jest.fn(), - getUrlForApp: jest.fn(), - registerMountContext: jest.fn(), -}); - -const createInternalStartContractMock = (): jest.Mocked => ({ - availableApps: new Map(), - availableLegacyApps: new Map(), - capabilities: capabilitiesServiceMock.createStartContract().capabilities, - navigateToApp: jest.fn(), - getUrlForApp: jest.fn(), - registerMountContext: jest.fn(), - currentAppId$: new Subject(), - getComponent: jest.fn(), +const createStartContractMock = (): jest.Mocked => ({ + ...capabilitiesServiceMock.createStartContract(), }); const createMock = (): jest.Mocked => ({ - setup: jest.fn().mockReturnValue(createInternalSetupContractMock()), - start: jest.fn().mockReturnValue(createInternalStartContractMock()), + setup: jest.fn().mockReturnValue(createSetupContractMock()), + start: jest.fn().mockReturnValue(createStartContractMock()), stop: jest.fn(), }); @@ -69,7 +41,4 @@ export const applicationServiceMock = { create: createMock, createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, - - createInternalSetupContract: createInternalSetupContractMock, - createInternalStartContract: createInternalStartContractMock, }; diff --git a/src/core/public/application/application_service.test.mocks.ts b/src/core/public/application/application_service.test.mocks.ts index d829cf18e56be..c28d0a203068a 100644 --- a/src/core/public/application/application_service.test.mocks.ts +++ b/src/core/public/application/application_service.test.mocks.ts @@ -26,11 +26,3 @@ export const CapabilitiesServiceConstructor = jest jest.doMock('./capabilities', () => ({ CapabilitiesService: CapabilitiesServiceConstructor, })); - -export const MockHistory = { - push: jest.fn(), -}; -export const createBrowserHistoryMock = jest.fn().mockReturnValue(MockHistory); -jest.doMock('history', () => ({ - createBrowserHistory: createBrowserHistoryMock, -})); diff --git a/src/core/public/application/application_service.test.tsx b/src/core/public/application/application_service.test.tsx index 5b374218a5932..d2266671367a2 100644 --- a/src/core/public/application/application_service.test.tsx +++ b/src/core/public/application/application_service.test.tsx @@ -17,219 +17,57 @@ * under the License. */ -import { shallow } from 'enzyme'; -import React from 'react'; - import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; -import { MockCapabilitiesService, MockHistory } from './application_service.test.mocks'; +import { MockCapabilitiesService } from './application_service.test.mocks'; import { ApplicationService } from './application_service'; -import { contextServiceMock } from '../context/context_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; - -describe('#setup()', () => { - describe('register', () => { - it('throws an error if two apps with the same id are registered', () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); - setup.register(Symbol(), { id: 'app1' } as any); - expect(() => - setup.register(Symbol(), { id: 'app1' } as any) - ).toThrowErrorMatchingInlineSnapshot( - `"An application is already registered with the id \\"app1\\""` - ); - }); - - it('throws error if additional apps are registered after setup', async () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); - const http = httpServiceMock.createStartContract(); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - await service.start({ http, injectedMetadata }); - expect(() => - setup.register(Symbol(), { id: 'app1' } as any) - ).toThrowErrorMatchingInlineSnapshot(`"Applications cannot be registered after \\"setup\\""`); - }); - }); - - describe('registerLegacyApp', () => { - it('throws an error if two apps with the same id are registered', () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); - setup.registerLegacyApp({ id: 'app2' } as any); - expect(() => - setup.registerLegacyApp({ id: 'app2' } as any) - ).toThrowErrorMatchingInlineSnapshot( - `"A legacy application is already registered with the id \\"app2\\""` - ); - }); - - it('throws error if additional apps are registered after setup', async () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); - const http = httpServiceMock.createStartContract(); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - await service.start({ http, injectedMetadata }); - expect(() => - setup.registerLegacyApp({ id: 'app2' } as any) - ).toThrowErrorMatchingInlineSnapshot(`"Applications cannot be registered after \\"setup\\""`); - }); - }); - - it("`registerMountContext` calls context container's registerContext", () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); - const container = context.createContextContainer.mock.results[0].value; - const pluginId = Symbol(); - const noop = () => {}; - setup.registerMountContext(pluginId, 'test' as any, noop as any); - expect(container.registerContext).toHaveBeenCalledWith(pluginId, 'test', noop); - }); -}); describe('#start()', () => { - beforeEach(() => { - MockHistory.push.mockReset(); - }); - it('exposes available apps from capabilities', async () => { const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); - setup.register(Symbol(), { id: 'app1' } as any); + const setup = service.setup(); + setup.registerApp({ id: 'app1' } as any); setup.registerLegacyApp({ id: 'app2' } as any); - - const http = httpServiceMock.createStartContract(); const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - const startContract = await service.start({ http, injectedMetadata }); - + const startContract = await service.start({ injectedMetadata }); expect(startContract.availableApps).toMatchInlineSnapshot(` - Map { - "app1" => Object { - "id": "app1", - }, - } - `); +Array [ + Object { + "id": "app1", + }, +] +`); expect(startContract.availableLegacyApps).toMatchInlineSnapshot(` - Map { - "app2" => Object { - "id": "app2", - }, - } - `); +Array [ + Object { + "id": "app2", + }, +] +`); }); it('passes registered applications to capabilities', async () => { const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); - setup.register(Symbol(), { id: 'app1' } as any); - - const http = httpServiceMock.createStartContract(); + const setup = service.setup(); + setup.registerApp({ id: 'app1' } as any); const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - await service.start({ http, injectedMetadata }); - + await service.start({ injectedMetadata }); expect(MockCapabilitiesService.start).toHaveBeenCalledWith({ - apps: new Map([['app1', { id: 'app1' }]]), - legacyApps: new Map(), + apps: [{ id: 'app1' }], + legacyApps: [], injectedMetadata, }); }); it('passes registered legacy applications to capabilities', async () => { const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - const setup = service.setup({ context }); + const setup = service.setup(); setup.registerLegacyApp({ id: 'legacyApp1' } as any); - - const http = httpServiceMock.createStartContract(); const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - await service.start({ http, injectedMetadata }); - + await service.start({ injectedMetadata }); expect(MockCapabilitiesService.start).toHaveBeenCalledWith({ - apps: new Map(), - legacyApps: new Map([['legacyApp1', { id: 'legacyApp1' }]]), + apps: [], + legacyApps: [{ id: 'legacyApp1' }], injectedMetadata, }); }); - - it('returns renderable JSX tree', async () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - service.setup({ context }); - - const http = httpServiceMock.createStartContract(); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getLegacyMode.mockReturnValue(false); - const start = await service.start({ http, injectedMetadata }); - - expect(() => shallow(React.createElement(() => start.getComponent()))).not.toThrow(); - }); - - describe('navigateToApp', () => { - it('changes the browser history to /app/:appId', async () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - service.setup({ context }); - - const http = httpServiceMock.createStartContract(); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getLegacyMode.mockReturnValue(false); - const start = await service.start({ http, injectedMetadata }); - - start.navigateToApp('myTestApp'); - expect(MockHistory.push).toHaveBeenCalledWith('/app/myTestApp', undefined); - start.navigateToApp('myOtherApp'); - expect(MockHistory.push).toHaveBeenCalledWith('/app/myOtherApp', undefined); - }); - - it('appends a path if specified', async () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - service.setup({ context }); - - const http = httpServiceMock.createStartContract(); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getLegacyMode.mockReturnValue(false); - const start = await service.start({ http, injectedMetadata }); - - start.navigateToApp('myTestApp', { path: 'deep/link/to/location/2' }); - expect(MockHistory.push).toHaveBeenCalledWith( - '/app/myTestApp/deep/link/to/location/2', - undefined - ); - }); - - it('includes state if specified', async () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - service.setup({ context }); - - const http = httpServiceMock.createStartContract(); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getLegacyMode.mockReturnValue(false); - const start = await service.start({ http, injectedMetadata }); - - start.navigateToApp('myTestApp', { state: 'my-state' }); - expect(MockHistory.push).toHaveBeenCalledWith('/app/myTestApp', 'my-state'); - }); - - it('redirects when in legacyMode', async () => { - const service = new ApplicationService(); - const context = contextServiceMock.createSetupContract(); - service.setup({ context }); - - const http = httpServiceMock.createStartContract(); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getLegacyMode.mockReturnValue(true); - const redirectTo = jest.fn(); - const start = await service.start({ http, injectedMetadata, redirectTo }); - start.navigateToApp('myTestApp'); - expect(redirectTo).toHaveBeenCalledWith('/app/myTestApp'); - }); - }); }); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index d1855a0370f00..528b81ad40be7 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -17,43 +17,108 @@ * under the License. */ -import { createBrowserHistory } from 'history'; -import { BehaviorSubject } from 'rxjs'; -import React from 'react'; - +import { Observable, BehaviorSubject } from 'rxjs'; +import { CapabilitiesService, Capabilities } from './capabilities'; import { InjectedMetadataStart } from '../injected_metadata'; -import { CapabilitiesService } from './capabilities'; -import { AppRouter } from './ui'; -import { HttpStart } from '../http'; -import { ContextSetup, IContextContainer } from '../context'; -import { - AppMountContext, - App, - LegacyApp, - AppMounter, - AppUnmount, - AppMountParameters, - InternalApplicationSetup, - InternalApplicationStart, -} from './types'; - -interface SetupDeps { - context: ContextSetup; +import { RecursiveReadonly } from '../../utils'; + +interface BaseApp { + id: string; + + /** + * An ordinal used to sort nav links relative to one another for display. + */ + order: number; + + /** + * The title of the application. + */ + title: string; + + /** + * An observable for a tooltip shown when hovering over app link. + */ + tooltip$?: Observable; + + /** + * A EUI iconType that will be used for the app's icon. This icon + * takes precendence over the `icon` property. + */ + euiIconType?: string; + + /** + * A URL to an image file used as an icon. Used as a fallback + * if `euiIconType` is not provided. + */ + icon?: string; + + /** + * Custom capabilities defined by the app. + */ + capabilities?: Partial; } -interface StartDeps { - http: HttpStart; - injectedMetadata: InjectedMetadataStart; +/** @public */ +export interface App extends BaseApp { + /** + * A mount function called when the user navigates to this app's `rootRoute`. + * @param targetDomElement An HTMLElement to mount the application onto. + * @returns An unmounting function that will be called to unmount the application. + */ + mount(targetDomElement: HTMLElement): () => void; +} + +/** @internal */ +export interface LegacyApp extends BaseApp { + appUrl: string; + subUrlBase?: string; + linkToLastSubUrl?: boolean; +} + +/** @internal */ +export type MixedApp = Partial & Partial & BaseApp; + +/** @public */ +export interface ApplicationSetup { /** - * Only necessary for redirecting to legacy apps - * @deprecated + * Register an mountable application to the system. Apps will be mounted based on their `rootRoute`. + * @param app */ - redirectTo?: (path: string) => void; + registerApp(app: App): void; + + /** + * Register metadata about legacy applications. Legacy apps will not be mounted when navigated to. + * @param app + * @internal + */ + registerLegacyApp(app: LegacyApp): void; } -interface AppBox { - app: App; - mount: AppMounter; +/** + * @public + */ +export interface ApplicationStart { + /** + * Gets the read-only capabilities. + */ + capabilities: RecursiveReadonly; + + /** + * Apps available based on the current capabilities. Should be used + * to show navigation links and make routing decisions. + */ + availableApps: readonly App[]; + + /** + * Apps available based on the current capabilities. Should be used + * to show navigation links and make routing decisions. + * @internal + */ + availableLegacyApps: readonly LegacyApp[]; +} + +interface StartDeps { + injectedMetadata: InjectedMetadataStart; } /** @@ -61,122 +126,31 @@ interface AppBox { * @internal */ export class ApplicationService { - private readonly apps$ = new BehaviorSubject>(new Map()); - private readonly legacyApps$ = new BehaviorSubject>(new Map()); + private readonly apps$ = new BehaviorSubject([]); + private readonly legacyApps$ = new BehaviorSubject([]); private readonly capabilities = new CapabilitiesService(); - private mountContext?: IContextContainer< - AppMountContext, - AppUnmount | Promise, - [AppMountParameters] - >; - - public setup({ context }: SetupDeps): InternalApplicationSetup { - this.mountContext = context.createContextContainer(); + public setup(): ApplicationSetup { return { - register: (plugin: symbol, app: App) => { - if (this.apps$.value.has(app.id)) { - throw new Error(`An application is already registered with the id "${app.id}"`); - } - if (this.apps$.isStopped) { - throw new Error(`Applications cannot be registered after "setup"`); - } - - const appBox: AppBox = { - app, - mount: this.mountContext!.createHandler(plugin, app.mount), - }; - this.apps$.next(new Map([...this.apps$.value.entries(), [app.id, appBox]])); + registerApp: (app: App) => { + this.apps$.next([...this.apps$.value, app]); }, registerLegacyApp: (app: LegacyApp) => { - if (this.legacyApps$.value.has(app.id)) { - throw new Error(`A legacy application is already registered with the id "${app.id}"`); - } - if (this.legacyApps$.isStopped) { - throw new Error(`Applications cannot be registered after "setup"`); - } - - this.legacyApps$.next(new Map([...this.legacyApps$.value.entries(), [app.id, app]])); + this.legacyApps$.next([...this.legacyApps$.value, app]); }, - registerMountContext: this.mountContext.registerContext, }; } - public async start({ - http, - injectedMetadata, - redirectTo = (path: string) => (window.location.href = path), - }: StartDeps): Promise { - if (!this.mountContext) { - throw new Error(`ApplicationService#setup() must be invoked before start.`); - } - - // Disable registration of new applications + public async start({ injectedMetadata }: StartDeps): Promise { this.apps$.complete(); this.legacyApps$.complete(); - const legacyMode = injectedMetadata.getLegacyMode(); - const currentAppId$ = new BehaviorSubject(undefined); - const { availableApps, availableLegacyApps, capabilities } = await this.capabilities.start({ - apps: new Map([...this.apps$.value].map(([id, { app }]) => [id, app])), + return this.capabilities.start({ + apps: this.apps$.value, legacyApps: this.legacyApps$.value, injectedMetadata, }); - - // Only setup history if we're not in legacy mode - const history = legacyMode ? null : createBrowserHistory({ basename: http.basePath.get() }); - - return { - availableApps, - availableLegacyApps, - capabilities, - registerMountContext: this.mountContext.registerContext, - currentAppId$, - - getUrlForApp: (appId, options: { path?: string } = {}) => { - return http.basePath.prepend(appPath(appId, options)); - }, - - navigateToApp: (appId, { path, state }: { path?: string; state?: any } = {}) => { - if (legacyMode) { - // If we're in legacy mode, do a full page refresh to load the NP app. - redirectTo(http.basePath.prepend(appPath(appId, { path }))); - } else { - // basePath not needed here because `history` is configured with basename - history!.push(appPath(appId, { path }), state); - } - }, - - getComponent: () => { - if (legacyMode) { - return null; - } - - // Filter only available apps and map to just the mount function. - const appMounters = new Map( - [...this.apps$.value] - .filter(([id]) => availableApps.has(id)) - .map(([id, { mount }]) => [id, mount]) - ); - - return ( - - ); - }, - }; } public stop() {} } - -const appPath = (appId: string, { path }: { path?: string } = {}): string => - path - ? `/app/${appId}/${path.replace(/^\//, '')}` // Remove preceding slash from path if present - : `/app/${appId}`; diff --git a/src/core/public/application/capabilities/capabilities_service.mock.ts b/src/core/public/application/capabilities/capabilities_service.mock.ts index 29c3275f0e3b2..71b069fd80434 100644 --- a/src/core/public/application/capabilities/capabilities_service.mock.ts +++ b/src/core/public/application/capabilities/capabilities_service.mock.ts @@ -18,11 +18,11 @@ */ import { CapabilitiesService, CapabilitiesStart } from './capabilities_service'; import { deepFreeze } from '../../../utils/'; -import { App, LegacyApp } from '../types'; +import { App, LegacyApp } from '../application_service'; const createStartContractMock = ( - apps: ReadonlyMap = new Map(), - legacyApps: ReadonlyMap = new Map() + apps: readonly App[] = [], + legacyApps: readonly LegacyApp[] = [] ): jest.Mocked => ({ availableApps: apps, availableLegacyApps: legacyApps, diff --git a/src/core/public/application/capabilities/capabilities_service.test.ts b/src/core/public/application/capabilities/capabilities_service.test.ts index e80e9a7af321a..1c60c1eeb195a 100644 --- a/src/core/public/application/capabilities/capabilities_service.test.ts +++ b/src/core/public/application/capabilities/capabilities_service.test.ts @@ -19,7 +19,6 @@ import { InjectedMetadataService } from '../../injected_metadata'; import { CapabilitiesService } from './capabilities_service'; -import { LegacyApp, App } from '../types'; describe('#start', () => { const injectedMetadata = new InjectedMetadataService({ @@ -40,22 +39,17 @@ describe('#start', () => { } as any, }).start(); - const apps = new Map([ - ['app1', { id: 'app1' }], - ['app2', { id: 'app2', capabilities: { app2: { feature: true } } }], - ] as Array<[string, App]>); - const legacyApps = new Map([ - ['legacyApp1', { id: 'legacyApp1' }], - ['legacyApp2', { id: 'legacyApp2', capabilities: { app2: { feature: true } } }], - ] as Array<[string, LegacyApp]>); + const apps = [{ id: 'app1' }, { id: 'app2', capabilities: { app2: { feature: true } } }] as any; + const legacyApps = [ + { id: 'legacyApp1' }, + { id: 'legacyApp2', capabilities: { app2: { feature: true } } }, + ] as any; it('filters available apps based on returned navLinks', async () => { const service = new CapabilitiesService(); const startContract = await service.start({ apps, legacyApps, injectedMetadata }); - expect(startContract.availableApps).toEqual(new Map([['app1', { id: 'app1' }]])); - expect(startContract.availableLegacyApps).toEqual( - new Map([['legacyApp1', { id: 'legacyApp1' }]]) - ); + expect(startContract.availableApps).toEqual([{ id: 'app1' }]); + expect(startContract.availableLegacyApps).toEqual([{ id: 'legacyApp1' }]); }); it('does not allow Capabilities to be modified', async () => { diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index b080f8c138cf2..51c5a218e70bd 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -18,12 +18,12 @@ */ import { deepFreeze, RecursiveReadonly } from '../../../utils'; -import { LegacyApp, App } from '../types'; +import { LegacyApp, App } from '../application_service'; import { InjectedMetadataStart } from '../../injected_metadata'; interface StartDeps { - apps: ReadonlyMap; - legacyApps: ReadonlyMap; + apps: readonly App[]; + legacyApps: readonly LegacyApp[]; injectedMetadata: InjectedMetadataStart; } @@ -53,8 +53,8 @@ export interface Capabilities { /** @internal */ export interface CapabilitiesStart { capabilities: RecursiveReadonly; - availableApps: ReadonlyMap; - availableLegacyApps: ReadonlyMap; + availableApps: readonly App[]; + availableLegacyApps: readonly LegacyApp[]; } /** @@ -68,23 +68,10 @@ export class CapabilitiesService { injectedMetadata, }: StartDeps): Promise { const capabilities = deepFreeze(injectedMetadata.getCapabilities()); - const availableApps = new Map( - [...apps].filter( - ([appId]) => - capabilities.navLinks[appId] === undefined || capabilities.navLinks[appId] === true - ) - ); - - const availableLegacyApps = new Map( - [...legacyApps].filter( - ([appId]) => - capabilities.navLinks[appId] === undefined || capabilities.navLinks[appId] === true - ) - ); return { - availableApps, - availableLegacyApps, + availableApps: apps.filter(app => capabilities.navLinks[app.id]), + availableLegacyApps: legacyApps.filter(app => capabilities.navLinks[app.id]), capabilities, }; } diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index ae25b54cf07a8..137b46e6573e6 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -17,17 +17,5 @@ * under the License. */ -export { ApplicationService } from './application_service'; +export { ApplicationService, ApplicationSetup, ApplicationStart } from './application_service'; export { Capabilities } from './capabilities'; -export { - App, - AppBase, - AppUnmount, - AppMountContext, - AppMountParameters, - ApplicationSetup, - ApplicationStart, - // Internal types - InternalApplicationStart, - LegacyApp, -} from './types'; diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx deleted file mode 100644 index e6a1070e1a684..0000000000000 --- a/src/core/public/application/integration_tests/router.test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 React from 'react'; -import ReactDOM from 'react-dom'; -import { mount, ReactWrapper } from 'enzyme'; -import { createMemoryHistory, History } from 'history'; -import { BehaviorSubject } from 'rxjs'; - -import { I18nProvider } from '@kbn/i18n/react'; - -import { AppMounter, LegacyApp, AppMountParameters } from '../types'; -import { httpServiceMock } from '../../http/http_service.mock'; -import { AppRouter, AppNotFound } from '../ui'; - -const createMountHandler = (htmlString: string) => - jest.fn(async ({ appBasePath: basename, element: el }: AppMountParameters) => { - ReactDOM.render( -
, - el - ); - return jest.fn(() => ReactDOM.unmountComponentAtNode(el)); - }); - -describe('AppContainer', () => { - let apps: Map, Parameters>>; - let legacyApps: Map; - let history: History; - let router: ReactWrapper; - let redirectTo: jest.Mock; - let currentAppId$: BehaviorSubject; - - const navigate = async (path: string) => { - history.push(path); - router.update(); - // flushes any pending promises - return new Promise(resolve => setImmediate(resolve)); - }; - - beforeEach(() => { - redirectTo = jest.fn(); - apps = new Map([ - ['app1', createMountHandler('App 1')], - ['app2', createMountHandler('
App 2
')], - ]); - legacyApps = new Map([ - ['legacyApp1', { id: 'legacyApp1' }], - ['baseApp:legacyApp2', { id: 'baseApp:legacyApp2' }], - ]) as Map; - history = createMemoryHistory(); - currentAppId$ = new BehaviorSubject(undefined); - // Use 'asdf' as the basepath - const http = httpServiceMock.createStartContract({ basePath: '/asdf' }); - router = mount( - - - - ); - }); - - it('calls mountHandler and returned unmount function when navigating between apps', async () => { - await navigate('/app/app1'); - expect(apps.get('app1')!).toHaveBeenCalled(); - expect(router.html()).toMatchInlineSnapshot(` - "
- basename: /asdf/app/app1 - html: App 1 -
" - `); - - const app1Unmount = await apps.get('app1')!.mock.results[0].value; - await navigate('/app/app2'); - expect(app1Unmount).toHaveBeenCalled(); - - expect(apps.get('app2')!).toHaveBeenCalled(); - expect(router.html()).toMatchInlineSnapshot(` - "
- basename: /asdf/app/app2 - html:
App 2
-
" - `); - }); - - it('updates currentApp$ after mounting', async () => { - await navigate('/app/app1'); - expect(currentAppId$.value).toEqual('app1'); - await navigate('/app/app2'); - expect(currentAppId$.value).toEqual('app2'); - }); - - it('sets window.location.href when navigating to legacy apps', async () => { - await navigate('/app/legacyApp1'); - expect(redirectTo).toHaveBeenCalledWith('/asdf/app/legacyApp1'); - }); - - it('handles legacy apps with subapps', async () => { - await navigate('/app/baseApp'); - expect(redirectTo).toHaveBeenCalledWith('/asdf/app/baseApp'); - }); - - it('displays error page if no app is found', async () => { - await navigate('/app/unknown'); - expect(router.exists(AppNotFound)).toBe(true); - }); -}); diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts deleted file mode 100644 index 018d7569ce411..0000000000000 --- a/src/core/public/application/types.ts +++ /dev/null @@ -1,300 +0,0 @@ -/* - * 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 { Observable, Subject } from 'rxjs'; - -import { Capabilities } from './capabilities'; -import { ChromeStart } from '../chrome'; -import { IContextProvider } from '../context'; -import { DocLinksStart } from '../doc_links'; -import { HttpStart } from '../http'; -import { I18nStart } from '../i18n'; -import { NotificationsStart } from '../notifications'; -import { OverlayStart } from '../overlays'; -import { PluginOpaqueId } from '../plugins'; -import { UiSettingsClientContract } from '../ui_settings'; -import { RecursiveReadonly } from '../../utils'; - -/** @public */ -export interface AppBase { - id: string; - - /** - * The title of the application. - */ - title: string; - - /** - * An ordinal used to sort nav links relative to one another for display. - */ - order?: number; - - /** - * An observable for a tooltip shown when hovering over app link. - */ - tooltip$?: Observable; - - /** - * A EUI iconType that will be used for the app's icon. This icon - * takes precendence over the `icon` property. - */ - euiIconType?: string; - - /** - * A URL to an image file used as an icon. Used as a fallback - * if `euiIconType` is not provided. - */ - icon?: string; - - /** - * Custom capabilities defined by the app. - */ - capabilities?: Partial; -} - -/** - * Extension of {@link AppBase | common app properties} with the mount function. - * @public - */ -export interface App extends AppBase { - /** - * A mount function called when the user navigates to this app's route. - * @param context The mount context for this app. - * @param targetDomElement An HTMLElement to mount the application onto. - * @returns An unmounting function that will be called to unmount the application. - */ - mount: (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; -} - -/** @internal */ -export interface LegacyApp extends AppBase { - appUrl: string; - subUrlBase?: string; - linkToLastSubUrl?: boolean; -} - -/** - * The context object received when applications are mounted to the DOM. - * @public - */ -export interface AppMountContext { - /** - * Core service APIs available to mounted applications. - */ - core: { - /** {@link ApplicationStart} */ - application: Pick; - /** {@link ChromeStart} */ - chrome: ChromeStart; - /** {@link DocLinksStart} */ - docLinks: DocLinksStart; - /** {@link HttpStart} */ - http: HttpStart; - /** {@link I18nStart} */ - i18n: I18nStart; - /** {@link NotificationsStart} */ - notifications: NotificationsStart; - /** {@link OverlayStart} */ - overlays: OverlayStart; - /** {@link UiSettingsClient} */ - uiSettings: UiSettingsClientContract; - }; -} - -/** @public */ -export interface AppMountParameters { - /** - * The container element to render the application into. - */ - element: HTMLElement; - - /** - * The base path for configuring the application's router. - * - * @example - * - * How to configure react-router with a base path: - * - * ```ts - * // inside your plugin's setup function - * export class MyPlugin implements Plugin { - * setup({ application }) { - * application.register({ - * id: 'my-app', - * async mount(context, params) { - * const { renderApp } = await import('./application'); - * return renderApp(context, params); - * }, - * }); - * } - * ``` - * - * ```ts - * // application.tsx - * import React from 'react'; - * import ReactDOM from 'react-dom'; - * import { BrowserRouter, Route } from 'react-router-dom'; - * - * export renderApp = (context, { appBasePath, element }) => { - * ReactDOM.render( - * // pass `appBasePath` to `basename` - * - * - * , - * element - * ); - * - * return () => ReactDOM.unmountComponentAtNode(element); - * } - * ``` - */ - appBasePath: string; -} - -/** - * A function called when an application should be unmounted from the page. This function should be synchronous. - * @public - */ -export type AppUnmount = () => void; - -/** @internal */ -export type AppMounter = (params: AppMountParameters) => Promise; - -/** @public */ -export interface ApplicationSetup { - /** - * Register an mountable application to the system. - * @param app - an {@link App} - */ - register(app: App): void; - - /** - * Register a context provider for application mounting. Will only be available to applications that depend on the - * plugin that registered this context. - * - * @param contextName - The key of {@link AppMountContext} this provider's return value should be attached to. - * @param provider - A {@link IContextProvider} function - */ - registerMountContext( - contextName: T, - provider: IContextProvider - ): void; -} - -/** @internal */ -export interface InternalApplicationSetup { - /** - * Register an mountable application to the system. - * @param plugin - opaque ID of the plugin that registers this application - * @param app - */ - register(plugin: PluginOpaqueId, app: App): void; - - /** - * Register metadata about legacy applications. Legacy apps will not be mounted when navigated to. - * @param app - * @internal - */ - registerLegacyApp(app: LegacyApp): void; - - /** - * Register a context provider for application mounting. Will only be available to applications that depend on the - * plugin that registered this context. - * - * @param pluginOpaqueId - The opaque ID of the plugin that is registering the context. - * @param contextName - The key of {@link AppMountContext} this provider's return value should be attached to. - * @param provider - A {@link IContextProvider} function - */ - registerMountContext( - pluginOpaqueId: PluginOpaqueId, - contextName: T, - provider: IContextProvider - ): void; -} - -/** @public */ -export interface ApplicationStart { - /** - * Gets the read-only capabilities. - */ - capabilities: RecursiveReadonly; - - /** - * Navigiate to a given app - * - * @param appId - * @param options.path - optional path inside application to deep link to - * @param options.state - optional state to forward to the application - */ - navigateToApp(appId: string, options?: { path?: string; state?: any }): void; - - /** - * Returns a relative URL to a given app, including the global base path. - * @param appId - * @param options.path - optional path inside application to deep link to - */ - getUrlForApp(appId: string, options?: { path?: string }): string; - - /** - * Register a context provider for application mounting. Will only be available to applications that depend on the - * plugin that registered this context. - * - * @param pluginOpaqueId - The opaque ID of the plugin that is registering the context. - * @param contextName - The key of {@link AppMountContext} this provider's return value should be attached to. - * @param provider - A {@link IContextProvider} function - */ - registerMountContext( - contextName: T, - provider: IContextProvider - ): void; -} - -/** @internal */ -export interface InternalApplicationStart - extends Pick { - /** - * Apps available based on the current capabilities. Should be used - * to show navigation links and make routing decisions. - */ - availableApps: ReadonlyMap; - /** - * Apps available based on the current capabilities. Should be used - * to show navigation links and make routing decisions. - * @internal - */ - availableLegacyApps: ReadonlyMap; - - /** - * Register a context provider for application mounting. Will only be available to applications that depend on the - * plugin that registered this context. - * - * @param pluginOpaqueId - The opaque ID of the plugin that is registering the context. - * @param contextName - The key of {@link AppMountContext} this provider's return value should be attached to. - * @param provider - A {@link IContextProvider} function - */ - registerMountContext( - pluginOpaqueId: PluginOpaqueId, - contextName: T, - provider: IContextProvider - ): void; - - // Internal APIs - currentAppId$: Subject; - getComponent(): JSX.Element | null; -} diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx deleted file mode 100644 index 876cd3aa3a3d3..0000000000000 --- a/src/core/public/application/ui/app_container.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; -import { Subject } from 'rxjs'; - -import { LegacyApp, AppMounter, AppUnmount } from '../types'; -import { HttpStart } from '../../http'; -import { AppNotFound } from './app_not_found_screen'; - -interface Props extends RouteComponentProps<{ appId: string }> { - apps: ReadonlyMap; - legacyApps: ReadonlyMap; - basePath: HttpStart['basePath']; - currentAppId$: Subject; - /** - * Only necessary for redirecting to legacy apps - * @deprecated - */ - redirectTo: (path: string) => void; -} - -interface State { - appNotFound: boolean; -} - -export class AppContainer extends React.Component { - private readonly containerDiv = React.createRef(); - private unmountFunc?: AppUnmount; - - state: State = { appNotFound: false }; - - componentDidMount() { - this.mountApp(); - } - - componentWillUnmount() { - this.unmountApp(); - } - - componentDidUpdate(prevProps: Props) { - if (prevProps.match.params.appId !== this.props.match.params.appId) { - this.unmountApp(); - this.mountApp(); - } - } - - async mountApp() { - const { apps, legacyApps, match, basePath, currentAppId$, redirectTo } = this.props; - const { appId } = match.params; - - const mount = apps.get(appId); - if (mount) { - this.unmountFunc = await mount({ - appBasePath: basePath.prepend(`/app/${appId}`), - element: this.containerDiv.current!, - }); - currentAppId$.next(appId); - this.setState({ appNotFound: false }); - return; - } - - const legacyApp = findLegacyApp(appId, legacyApps); - if (legacyApp) { - this.unmountApp(); - redirectTo(basePath.prepend(`/app/${appId}`)); - this.setState({ appNotFound: false }); - return; - } - - this.setState({ appNotFound: true }); - } - - async unmountApp() { - if (this.unmountFunc) { - this.unmountFunc(); - this.unmountFunc = undefined; - } - } - - render() { - return ( - - {this.state.appNotFound && } -
- - ); - } -} - -function findLegacyApp(appId: string, apps: ReadonlyMap) { - const matchingApps = [...apps.entries()].filter(([id]) => id.split(':')[0] === appId); - return matchingApps.length ? matchingApps[0][1] : null; -} diff --git a/src/core/public/application/ui/app_not_found_screen.tsx b/src/core/public/application/ui/app_not_found_screen.tsx deleted file mode 100644 index 73a999c5dbf16..0000000000000 --- a/src/core/public/application/ui/app_not_found_screen.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const AppNotFound = () => ( - - - - - - - } - body={ -

- -

- } - /> -
-
-
-); diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx deleted file mode 100644 index 9d8acf1978556..0000000000000 --- a/src/core/public/application/ui/app_router.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { History } from 'history'; -import React from 'react'; -import { Router, Route } from 'react-router-dom'; -import { Subject } from 'rxjs'; - -import { LegacyApp, AppMounter } from '../types'; -import { AppContainer } from './app_container'; -import { HttpStart } from '../../http'; - -interface Props { - apps: ReadonlyMap; - legacyApps: ReadonlyMap; - basePath: HttpStart['basePath']; - currentAppId$: Subject; - history: History; - /** - * Only necessary for redirecting to legacy apps - * @deprecated - */ - redirectTo?: (path: string) => void; -} - -export const AppRouter: React.StatelessComponent = ({ - history, - redirectTo = (path: string) => (window.location.href = path), - ...otherProps -}) => ( - - } - /> - -); diff --git a/src/core/public/application/ui/index.ts b/src/core/public/application/ui/index.ts deleted file mode 100644 index 7fa778740d3b4..0000000000000 --- a/src/core/public/application/ui/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { AppRouter } from './app_router'; -export { AppNotFound } from './app_not_found_screen'; diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 3775989c5126b..74f2a09b895de 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -27,7 +27,7 @@ import { const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { - getHeaderComponent: jest.fn(), + getComponent: jest.fn(), navLinks: { getNavLinks$: jest.fn(), has: jest.fn(), diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 45e94040eeb4a..392846f8433ba 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -38,7 +38,7 @@ const store = new Map(); function defaultStartDeps() { return { - application: applicationServiceMock.createInternalStartContract(), + application: applicationServiceMock.createStartContract(), docLinks: docLinksServiceMock.createStartContract(), http: httpServiceMock.createStartContract(), injectedMetadata: injectedMetadataServiceMock.createStartContract(), @@ -87,7 +87,7 @@ Array [ const start = await service.start(defaultStartDeps()); // Have to do some fanagling to get the type system and enzyme to accept this. // Don't capture the snapshot because it's 600+ lines long. - expect(shallow(React.createElement(() => start.getHeaderComponent()))).toBeDefined(); + expect(shallow(React.createElement(() => start.getComponent()))).toBeDefined(); }); }); diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 02195c794d280..d829a27260d27 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -27,7 +27,7 @@ import { IconType } from '@elastic/eui'; import { InjectedMetadataStart } from '../injected_metadata'; import { NotificationsStart } from '../notifications'; -import { InternalApplicationStart } from '../application'; +import { ApplicationStart } from '../application'; import { HttpStart } from '../http'; import { ChromeNavLinks, NavLinksService } from './nav_links'; @@ -74,7 +74,7 @@ interface ConstructorParams { } interface StartDeps { - application: InternalApplicationStart; + application: ApplicationStart; docLinks: DocLinksStart; http: HttpStart; injectedMetadata: InjectedMetadataStart; @@ -84,11 +84,14 @@ interface StartDeps { /** @internal */ export class ChromeService { private readonly stop$ = new ReplaySubject(1); + private readonly browserSupportsCsp: boolean; private readonly navControls = new NavControlsService(); private readonly navLinks = new NavLinksService(); private readonly recentlyAccessed = new RecentlyAccessedService(); - constructor(private readonly params: ConstructorParams) {} + constructor({ browserSupportsCsp }: ConstructorParams) { + this.browserSupportsCsp = browserSupportsCsp; + } public async start({ application, @@ -112,7 +115,7 @@ export class ChromeService { const navLinks = this.navLinks.start({ application, http }); const recentlyAccessed = await this.recentlyAccessed.start({ http }); - if (!this.params.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) { + if (!this.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) { notifications.toasts.addWarning( i18n.translate('core.chrome.legacyBrowserWarning', { defaultMessage: 'Your browser does not meet the security requirements for Kibana.', @@ -125,12 +128,11 @@ export class ChromeService { navLinks, recentlyAccessed, - getHeaderComponent: () => ( + getComponent: () => (
([ - [ - 'legacyApp1', - { id: 'legacyApp1', order: 0, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' }, - ], - [ - 'legacyApp2', - { - id: 'legacyApp2', - order: -10, - title: 'Legacy App 2', - euiIconType: 'canvasApp', - appUrl: '/app2', - }, - ], - ['legacyApp3', { id: 'legacyApp3', order: 20, title: 'Legacy App 3', appUrl: '/app3' }], - ]), + availableApps: [], + availableLegacyApps: [ + { id: 'legacyApp1', order: 0, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' }, + { + id: 'legacyApp2', + order: -10, + title: 'Legacy App 2', + euiIconType: 'canvasApp', + appUrl: '/app2', + }, + { id: 'legacyApp3', order: 20, title: 'Legacy App 3', appUrl: '/app3' }, + ], } as any; const mockHttp = { diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index affc639faf0b8..2250ec40f0f44 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -21,11 +21,11 @@ import { sortBy } from 'lodash'; import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import { NavLinkWrapper, ChromeNavLinkUpdateableFields, ChromeNavLink } from './nav_link'; -import { InternalApplicationStart } from '../../application'; +import { ApplicationStart } from '../../application'; import { HttpStart } from '../../http'; interface StartDeps { - application: InternalApplicationStart; + application: ApplicationStart; http: HttpStart; } @@ -99,22 +99,10 @@ export class NavLinksService { private readonly stop$ = new ReplaySubject(1); public start({ application, http }: StartDeps): ChromeNavLinks { - const appLinks = [...application.availableApps].map( - ([appId, app]) => + const legacyAppLinks = application.availableLegacyApps.map( + app => [ - appId, - new NavLinkWrapper({ - ...app, - legacy: false, - baseUrl: relativeToAbsolute(http.basePath.prepend(`/app/${appId}`)), - }), - ] as [string, NavLinkWrapper] - ); - - const legacyAppLinks = [...application.availableLegacyApps].map( - ([appId, app]) => - [ - appId, + app.id, new NavLinkWrapper({ ...app, legacy: true, @@ -124,7 +112,7 @@ export class NavLinksService { ); const navLinks$ = new BehaviorSubject>( - new Map([...legacyAppLinks, ...appLinks]) + new Map(legacyAppLinks) ); const forceAppSwitcherNavigation$ = new BehaviorSubject(false); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index afd9f8e4a3820..4dc64c57fa244 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -65,7 +65,6 @@ import { } from '../..'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { ApplicationStart, InternalApplicationStart } from '../../../application/types'; // Providing a buffer between the limit and the cut off index // protects from truncating just the last couple (6) characters @@ -116,24 +115,13 @@ function extendRecentlyAccessedHistoryItem( }; } -function extendNavLink(navLink: ChromeNavLink, urlForApp: ApplicationStart['getUrlForApp']) { - if (navLink.legacy) { - return { - ...navLink, - href: navLink.url && !navLink.active ? navLink.url : navLink.baseUrl, - }; - } - +function extendNavLink(navLink: ChromeNavLink) { return { ...navLink, - href: urlForApp(navLink.id), + href: navLink.url && !navLink.active ? navLink.url : navLink.baseUrl, }; } -function isModifiedEvent(event: MouseEvent) { - return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); -} - function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { let current = element; while (current) { @@ -161,7 +149,6 @@ export type HeaderProps = Pick>; interface Props { kibanaVersion: string; - application: InternalApplicationStart; appTitle$: Rx.Observable; badge$: Rx.Observable; breadcrumbs$: Rx.Observable; @@ -172,7 +159,6 @@ interface Props { recentlyAccessed$: Rx.Observable; forceAppSwitcherNavigation$: Rx.Observable; helpExtension$: Rx.Observable; - legacyMode: boolean; navControlsLeft$: Rx.Observable; navControlsRight$: Rx.Observable; intl: InjectedIntl; @@ -183,7 +169,6 @@ interface Props { interface State { appTitle: string; - currentAppId?: string; isVisible: boolean; navLinks: ReadonlyArray>; recentlyAccessed: ReadonlyArray>; @@ -218,11 +203,7 @@ class HeaderUI extends Component { this.props.navLinks$, this.props.recentlyAccessed$, // Types for combineLatest only handle up to 6 inferred types so we combine these two separately. - Rx.combineLatest( - this.props.navControlsLeft$, - this.props.navControlsRight$, - this.props.application.currentAppId$ - ) + Rx.combineLatest(this.props.navControlsLeft$, this.props.navControlsRight$) ).subscribe({ next: ([ appTitle, @@ -230,21 +211,18 @@ class HeaderUI extends Component { forceNavigation, navLinks, recentlyAccessed, - [navControlsLeft, navControlsRight, currentAppId], + [navControlsLeft, navControlsRight], ]) => { this.setState({ appTitle, isVisible, forceNavigation, - navLinks: navLinks.map(navLink => - extendNavLink(navLink, this.props.application.getUrlForApp) - ), + navLinks: navLinks.map(navLink => extendNavLink(navLink)), recentlyAccessed: recentlyAccessed.map(ra => extendRecentlyAccessedHistoryItem(navLinks, ra, this.props.basePath) ), navControlsLeft, navControlsRight, - currentAppId, }); }, }); @@ -285,7 +263,6 @@ class HeaderUI extends Component { public render() { const { - application, badge$, basePath, breadcrumbs$, @@ -295,11 +272,9 @@ class HeaderUI extends Component { kibanaDocLink, kibanaVersion, onIsLockedUpdate, - legacyMode, } = this.props; const { appTitle, - currentAppId, isVisible, navControlsLeft, navControlsRight, @@ -316,26 +291,9 @@ class HeaderUI extends Component { .map(navLink => ({ key: navLink.id, label: navLink.title, - - // Use href and onClick to support "open in new tab" and SPA navigation in the same link href: navLink.href, - onClick: (event: MouseEvent) => { - if ( - !legacyMode && // ignore when in legacy mode - !navLink.legacy && // ignore links to legacy apps - !event.defaultPrevented && // onClick prevented default - event.button === 0 && // ignore everything but left clicks - !isModifiedEvent(event) // ignore clicks with modifier keys - ) { - event.preventDefault(); - application.navigateToApp(navLink.id); - } - }, - - // Legacy apps use `active` property, NP apps should match the current app - isActive: navLink.active || currentAppId === navLink.id, isDisabled: navLink.disabled, - + isActive: navLink.active, iconType: navLink.euiIconType, icon: !navLink.euiIconType && navLink.icon ? ( diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 895fc785b11b1..7310a8f33eba4 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -272,9 +272,7 @@ describe('#start()', () => { await startCore(); expect(MockRenderingService.start).toHaveBeenCalledTimes(1); expect(MockRenderingService.start).toHaveBeenCalledWith({ - application: expect.any(Object), chrome: expect.any(Object), - injectedMetadata: expect.any(Object), targetDomElement: expect.any(HTMLElement), }); }); @@ -366,7 +364,7 @@ describe('LegacyPlatformService targetDomElement', () => { it('only mounts the element when start, after setting up the legacyPlatformService', async () => { const core = createCoreSystem(); - let targetDomElementInStart: HTMLElement | undefined; + let targetDomElementInStart: HTMLElement | null; MockLegacyPlatformService.start.mockImplementation(({ targetDomElement }) => { targetDomElementInStart = targetDomElement; }); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 4eb16572d8fec..7782c93c7bbb1 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -20,29 +20,23 @@ import './core.css'; import { CoreId } from '../server'; -import { CoreSetup, CoreStart } from '.'; +import { InternalCoreSetup, InternalCoreStart } from '.'; import { ChromeService } from './chrome'; import { FatalErrorsService, FatalErrorsSetup } from './fatal_errors'; import { HttpService } from './http'; import { I18nService } from './i18n'; -import { - InjectedMetadataParams, - InjectedMetadataService, - InjectedMetadataSetup, - InjectedMetadataStart, -} from './injected_metadata'; +import { InjectedMetadataParams, InjectedMetadataService } from './injected_metadata'; import { LegacyPlatformParams, LegacyPlatformService } from './legacy'; import { NotificationsService } from './notifications'; import { OverlayService } from './overlays'; import { PluginsService } from './plugins'; import { UiSettingsService } from './ui_settings'; import { ApplicationService } from './application'; -import { mapToObject, pick } from '../utils/'; +import { mapToObject } from '../utils/'; import { DocLinksService } from './doc_links'; import { RenderingService } from './rendering'; import { SavedObjectsService } from './saved_objects/saved_objects_service'; import { ContextService } from './context'; -import { InternalApplicationSetup, InternalApplicationStart } from './application/types'; interface Params { rootDomElement: HTMLElement; @@ -57,18 +51,6 @@ export interface CoreContext { coreId: CoreId; } -/** @internal */ -export interface InternalCoreSetup extends Omit { - application: InternalApplicationSetup; - injectedMetadata: InjectedMetadataSetup; -} - -/** @internal */ -export interface InternalCoreStart extends Omit { - application: InternalApplicationStart; - injectedMetadata: InjectedMetadataStart; -} - /** * The CoreSystem is the root of the new platform, and setups all parts * of Kibana in the UI, including the LegacyPlatform which is managed @@ -95,7 +77,6 @@ export class CoreSystem { private readonly context: ContextService; private readonly rootDomElement: HTMLElement; - private readonly coreContext: CoreContext; private fatalErrorsSetup: FatalErrorsSetup | null = null; constructor(params: Params) { @@ -125,14 +106,14 @@ export class CoreSystem { this.savedObjects = new SavedObjectsService(); this.uiSettings = new UiSettingsService(); this.overlay = new OverlayService(); + this.application = new ApplicationService(); this.chrome = new ChromeService({ browserSupportsCsp }); this.docLinks = new DocLinksService(); this.rendering = new RenderingService(); - this.application = new ApplicationService(); - this.coreContext = { coreId: Symbol('core') }; - this.context = new ContextService(this.coreContext); - this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins); + const core: CoreContext = { coreId: Symbol('core') }; + this.context = new ContextService(core); + this.plugins = new PluginsService(core, injectedMetadata.uiPlugins); this.legacyPlatform = new LegacyPlatformService({ requireLegacyFiles, @@ -152,10 +133,10 @@ export class CoreSystem { const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const uiSettings = this.uiSettings.setup({ http, injectedMetadata }); const notifications = this.notifications.setup({ uiSettings }); + const application = this.application.setup(); const pluginDependencies = this.plugins.getOpaqueIds(); const context = this.context.setup({ pluginDependencies }); - const application = this.application.setup({ context }); const core: InternalCoreSetup = { application, @@ -169,11 +150,7 @@ export class CoreSystem { // Services that do not expose contracts at setup const plugins = await this.plugins.setup(core); - - await this.legacyPlatform.setup({ - core, - plugins: mapToObject(plugins.contracts), - }); + await this.legacyPlatform.setup({ core, plugins: mapToObject(plugins.contracts) }); return { fatalErrors: this.fatalErrorsSetup }; } catch (error) { @@ -194,7 +171,7 @@ export class CoreSystem { const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); - const application = await this.application.start({ http, injectedMetadata }); + const application = await this.application.start({ injectedMetadata }); const coreUiTargetDomElement = document.createElement('div'); coreUiTargetDomElement.id = 'kibana-body'; @@ -223,17 +200,6 @@ export class CoreSystem { }); const uiSettings = await this.uiSettings.start(); - application.registerMountContext(this.coreContext.coreId, 'core', () => ({ - application: pick(application, ['capabilities', 'navigateToApp']), - chrome, - docLinks, - http, - i18n, - notifications, - overlays, - uiSettings, - })); - const core: InternalCoreStart = { application, chrome, @@ -249,12 +215,9 @@ export class CoreSystem { const plugins = await this.plugins.start(core); const rendering = this.rendering.start({ - application, chrome, - injectedMetadata, targetDomElement: coreUiTargetDomElement, }); - await this.legacyPlatform.start({ core, plugins: mapToObject(plugins.contracts), diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index a94543414acfa..4ce84f8ab38d1 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -25,7 +25,7 @@ type ServiceSetupMockType = jest.Mocked & { basePath: jest.Mocked; }; -const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ +const createServiceMock = (): ServiceSetupMockType => ({ fetch: jest.fn(), get: jest.fn(), head: jest.fn(), @@ -35,8 +35,8 @@ const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ delete: jest.fn(), options: jest.fn(), basePath: { - get: jest.fn(() => basePath), - prepend: jest.fn(path => `${basePath}${path}`), + get: jest.fn(), + prepend: jest.fn(), remove: jest.fn(), }, addLoadingCount: jest.fn(), @@ -46,19 +46,22 @@ const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ removeAllInterceptors: jest.fn(), }); -const createMock = ({ basePath = '' } = {}) => { +const createSetupContractMock = createServiceMock; +const createStartContractMock = createServiceMock; + +const createMock = () => { const mocked: jest.Mocked> = { setup: jest.fn(), start: jest.fn(), stop: jest.fn(), }; - mocked.setup.mockReturnValue(createServiceMock({ basePath })); - mocked.start.mockReturnValue(createServiceMock({ basePath })); + mocked.setup.mockReturnValue(createSetupContractMock()); + mocked.start.mockReturnValue(createSetupContractMock()); return mocked; }; export const httpServiceMock = { create: createMock, - createSetupContract: createServiceMock, - createStartContract: createServiceMock, + createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, }; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 89c309d8427d7..abc922ff97c1d 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -71,9 +71,6 @@ import { IContextContainer, IContextProvider, ContextSetup, IContextHandler } fr export { CoreContext, CoreSystem } from './core_system'; export { RecursiveReadonly } from '../utils'; - -export { App, AppBase, AppUnmount, AppMountContext, AppMountParameters } from './application'; - export { SavedObjectsBatchResponse, SavedObjectsBulkCreateObject, @@ -117,8 +114,6 @@ export { * https://github.com/Microsoft/web-build-tools/issues/1237 */ export interface CoreSetup { - /** {@link ApplicationSetup} */ - application: ApplicationSetup; /** {@link ContextSetup} */ context: ContextSetup; /** {@link FatalErrorsSetup} */ @@ -142,7 +137,7 @@ export interface CoreSetup { */ export interface CoreStart { /** {@link ApplicationStart} */ - application: ApplicationStart; + application: Pick; /** {@link ChromeStart} */ chrome: ChromeStart; /** {@link DocLinksStart} */ @@ -161,33 +156,15 @@ export interface CoreStart { uiSettings: UiSettingsClientContract; } -/** - * Setup interface exposed to the legacy platform via the `ui/new_platform` module. - * - * @remarks - * Some methods are not supported in the legacy platform and while present to make this type compatibile with - * {@link CoreSetup}, unsupported methods will throw exceptions when called. - * - * @public - * @deprecated - */ -export interface LegacyCoreSetup extends CoreSetup { - /** @deprecated */ +/** @internal */ +export interface InternalCoreSetup extends CoreSetup { + application: ApplicationSetup; injectedMetadata: InjectedMetadataSetup; } -/** - * Start interface exposed to the legacy platform via the `ui/new_platform` module. - * - * @remarks - * Some methods are not supported in the legacy platform and while present to make this type compatibile with - * {@link CoreStart}, unsupported methods will throw exceptions when called. - * - * @public - * @deprecated - */ -export interface LegacyCoreStart extends CoreStart { - /** @deprecated */ +/** @internal */ +export interface InternalCoreStart extends CoreStart { + application: ApplicationStart; injectedMetadata: InjectedMetadataStart; } diff --git a/src/core/public/injected_metadata/injected_metadata_service.mock.ts b/src/core/public/injected_metadata/injected_metadata_service.mock.ts index 9e1d5aeec7ff4..c4579bee3f131 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.mock.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.mock.ts @@ -25,7 +25,6 @@ const createSetupContractMock = () => { getKibanaBranch: jest.fn(), getCapabilities: jest.fn(), getCspConfig: jest.fn(), - getLegacyMode: jest.fn(), getLegacyMetadata: jest.fn(), getPlugins: jest.fn(), getInjectedVar: jest.fn(), @@ -35,7 +34,6 @@ const createSetupContractMock = () => { setupContract.getCapabilities.mockReturnValue({} as any); setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true }); setupContract.getKibanaVersion.mockReturnValue('kibanaVersion'); - setupContract.getLegacyMode.mockReturnValue(true); setupContract.getLegacyMetadata.mockReturnValue({ nav: [], uiSettings: { diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index fa93d0f5288b4..9fbc955485512 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -51,7 +51,6 @@ export interface InjectedMetadataParams { plugin: DiscoveredPlugin; }>; capabilities: Capabilities; - legacyMode: boolean; legacyMetadata: { app: unknown; translations: unknown; @@ -113,10 +112,6 @@ export class InjectedMetadataService { return this.state.uiPlugins; }, - getLegacyMode: () => { - return this.state.legacyMode; - }, - getLegacyMetadata: () => { return this.state.legacyMetadata; }, @@ -161,8 +156,6 @@ export interface InjectedMetadataSetup { id: string; plugin: DiscoveredPlugin; }>; - /** Indicates whether or not we are rendering a known legacy app. */ - getLegacyMode: () => boolean; getLegacyMetadata: () => { app: unknown; translations: unknown; diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts index 37e07af0a7da5..eb5b3e90f1a52 100644 --- a/src/core/public/legacy/legacy_service.test.ts +++ b/src/core/public/legacy/legacy_service.test.ts @@ -61,7 +61,7 @@ import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { savedObjectsMock } from '../saved_objects/saved_objects_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; -const applicationSetup = applicationServiceMock.createInternalSetupContract(); +const applicationSetup = applicationServiceMock.createSetupContract(); const contextSetup = contextServiceMock.createSetupContract(); const fatalErrorsSetup = fatalErrorsServiceMock.createSetupContract(); const httpSetup = httpServiceMock.createSetupContract(); @@ -88,7 +88,7 @@ const defaultSetupDeps = { plugins: {}, }; -const applicationStart = applicationServiceMock.createInternalStartContract(); +const applicationStart = applicationServiceMock.createStartContract(); const docLinksStart = docLinksServiceMock.createStartContract(); const httpStart = httpServiceMock.createStartContract(); const chromeStart = chromeServiceMock.createStartContract(); @@ -98,7 +98,6 @@ const notificationsStart = notificationServiceMock.createStartContract(); const overlayStart = overlayServiceMock.createStartContract(); const uiSettingsStart = uiSettingsServiceMock.createStartContract(); const savedObjectsStart = savedObjectsMock.createStartContract(); -const mockStorage = { getItem: jest.fn() } as any; const defaultStartDeps = { core: { @@ -113,7 +112,6 @@ const defaultStartDeps = { uiSettings: uiSettingsStart, savedObjects: savedObjectsStart, }, - lastSubUrlStorage: mockStorage, targetDomElement: document.createElement('div'), plugins: {}, }; @@ -134,29 +132,12 @@ describe('#setup()', () => { legacyPlatform.setup(defaultSetupDeps); expect(mockUiNewPlatformSetup).toHaveBeenCalledTimes(1); - expect(mockUiNewPlatformSetup).toHaveBeenCalledWith(expect.any(Object), {}); + expect(mockUiNewPlatformSetup).toHaveBeenCalledWith(defaultSetupDeps.core, {}); }); }); }); describe('#start()', () => { - it('fetches and sets legacy lastSubUrls', () => { - chromeStart.navLinks.getAll.mockReturnValue([ - { id: 'link1', baseUrl: 'http://wowza.com/app1', legacy: true } as any, - ]); - mockStorage.getItem.mockReturnValue('http://wowza.com/app1/subUrl'); - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start({ ...defaultStartDeps, lastSubUrlStorage: mockStorage }); - - expect(chromeStart.navLinks.update).toHaveBeenCalledWith('link1', { - url: 'http://wowza.com/app1/subUrl', - }); - }); - it('initializes ui/new_platform with core APIs', () => { const legacyPlatform = new LegacyPlatformService({ ...defaultParams, @@ -166,7 +147,7 @@ describe('#start()', () => { legacyPlatform.start(defaultStartDeps); expect(mockUiNewPlatformStart).toHaveBeenCalledTimes(1); - expect(mockUiNewPlatformStart).toHaveBeenCalledWith(expect.any(Object), {}); + expect(mockUiNewPlatformStart).toHaveBeenCalledWith(defaultStartDeps.core, {}); }); describe('useLegacyTestHarness = false', () => { diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index ba93cd7b6b5a7..7d852773ad03f 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -18,8 +18,7 @@ */ import angular from 'angular'; -import { InternalCoreSetup, InternalCoreStart } from '../core_system'; -import { LegacyCoreSetup, LegacyCoreStart } from '../'; +import { InternalCoreSetup, InternalCoreStart } from '../'; /** @internal */ export interface LegacyPlatformParams { @@ -35,8 +34,7 @@ interface SetupDeps { interface StartDeps { core: InternalCoreStart; plugins: Record; - lastSubUrlStorage?: Storage; - targetDomElement?: HTMLElement; + targetDomElement: HTMLElement; } interface BootstrapModule { @@ -57,7 +55,10 @@ export class LegacyPlatformService { constructor(private readonly params: LegacyPlatformParams) {} public setup({ core, plugins }: SetupDeps) { - // Always register legacy apps, even if not in legacy mode. + // Inject parts of the new platform into parts of the legacy platform + // so that legacy APIs/modules can mimic their new platform counterparts + require('ui/new_platform').__setup__(core, plugins); + core.injectedMetadata.getLegacyMetadata().nav.forEach((navLink: any) => core.application.registerLegacyApp({ id: navLink.id, @@ -70,57 +71,12 @@ export class LegacyPlatformService { linkToLastSubUrl: navLink.linkToLastSubUrl, }) ); - - const legacyCore: LegacyCoreSetup = { - ...core, - application: { - register: notSupported(`core.application.register()`), - registerMountContext: notSupported(`core.application.registerMountContext()`), - }, - }; - - // Inject parts of the new platform into parts of the legacy platform - // so that legacy APIs/modules can mimic their new platform counterparts - if (core.injectedMetadata.getLegacyMode()) { - require('ui/new_platform').__setup__(legacyCore, plugins); - } } - public start({ - core, - targetDomElement, - plugins, - lastSubUrlStorage = window.sessionStorage, - }: StartDeps) { - // Initialize legacy sub urls - core.chrome.navLinks - .getAll() - .filter(link => link.legacy) - .forEach(navLink => { - const lastSubUrl = lastSubUrlStorage.getItem(`lastSubUrl:${navLink.baseUrl}`); - core.chrome.navLinks.update(navLink.id, { - url: lastSubUrl || navLink.url || navLink.baseUrl, - }); - }); - - // Only import and bootstrap legacy platform if we're in legacy mode. - if (!core.injectedMetadata.getLegacyMode()) { - return; - } - - const legacyCore: LegacyCoreStart = { - ...core, - application: { - capabilities: core.application.capabilities, - getUrlForApp: core.application.getUrlForApp, - navigateToApp: core.application.navigateToApp, - registerMountContext: notSupported(`core.application.registerMountContext()`), - }, - }; - + public start({ core, targetDomElement, plugins }: StartDeps) { // Inject parts of the new platform into parts of the legacy platform // so that legacy APIs/modules can mimic their new platform counterparts - require('ui/new_platform').__start__(legacyCore, plugins); + require('ui/new_platform').__start__(core, plugins); // Load the bootstrap module before loading the legacy platform files so that // the bootstrap module can modify the environment a bit first @@ -135,8 +91,7 @@ export class LegacyPlatformService { this.targetDomElement = targetDomElement; - // `targetDomElement` is always defined when in legacy mode - this.bootstrapModule.bootstrap(this.targetDomElement!); + this.bootstrapModule.bootstrap(this.targetDomElement); } public stop() { @@ -174,7 +129,3 @@ export class LegacyPlatformService { return require('ui/chrome'); } } - -const notSupported = (methodName: string) => (...args: any[]) => { - throw new Error(`${methodName} is not supported in the legacy platform.`); -}; diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 7c99f69d6fd7a..0f3a01c793ae3 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -42,7 +42,6 @@ export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; function createCoreSetupMock() { const mock: MockedKeys = { - application: applicationServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index f4e25d27447bc..66cb7c4a1171e 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -76,12 +76,7 @@ export function createPluginSetupContext< plugin: PluginWrapper ): CoreSetup { return { - application: { - register: app => deps.application.register(plugin.opaqueId, app), - registerMountContext: (contextName, provider) => - deps.application.registerMountContext(plugin.opaqueId, contextName, provider), - }, - context: deps.context, + context: omit(deps.context, 'setCurrentPlugin'), fatalErrors: deps.fatalErrors, http: deps.http, notifications: deps.notifications, @@ -112,10 +107,6 @@ export function createPluginStartContext< return { application: { capabilities: deps.application.capabilities, - navigateToApp: deps.application.navigateToApp, - getUrlForApp: deps.application.getUrlForApp, - registerMountContext: (contextName, provider) => - deps.application.registerMountContext(plugin.opaqueId, contextName, provider), }, docLinks: deps.docLinks, http: deps.http, diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index d6411554e5f85..2b689e45b4f1a 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -72,7 +72,7 @@ beforeEach(() => { }, ]; mockSetupDeps = { - application: applicationServiceMock.createInternalSetupContract(), + application: applicationServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), @@ -81,11 +81,10 @@ beforeEach(() => { uiSettings: uiSettingsServiceMock.createSetupContract(), }; mockSetupContext = { - ...omit(mockSetupDeps, 'injectedMetadata'), - application: expect.any(Object), + ...omit(mockSetupDeps, 'application', 'injectedMetadata'), }; mockStartDeps = { - application: applicationServiceMock.createInternalStartContract(), + application: applicationServiceMock.createStartContract(), docLinks: docLinksServiceMock.createStartContract(), http: httpServiceMock.createStartContract(), chrome: chromeServiceMock.createStartContract(), @@ -98,7 +97,9 @@ beforeEach(() => { }; mockStartContext = { ...omit(mockStartDeps, 'injectedMetadata'), - application: expect.any(Object), + application: { + capabilities: mockStartDeps.application.capabilities, + }, chrome: omit(mockStartDeps.chrome, 'getComponent'), }; diff --git a/src/core/public/plugins/plugins_service.ts b/src/core/public/plugins/plugins_service.ts index 1ab9d7f2fa9b2..13a52d78d72fc 100644 --- a/src/core/public/plugins/plugins_service.ts +++ b/src/core/public/plugins/plugins_service.ts @@ -26,7 +26,7 @@ import { createPluginSetupContext, createPluginStartContext, } from './plugin_context'; -import { InternalCoreSetup, InternalCoreStart } from '../core_system'; +import { InternalCoreSetup, InternalCoreStart } from '..'; /** @internal */ export type PluginsServiceSetupDeps = InternalCoreSetup; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 552476425d8d8..060188608b860 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -11,65 +11,24 @@ import React from 'react'; import * as Rx from 'rxjs'; import { EuiGlobalToastListToast as Toast } from '@elastic/eui'; -// @public -export interface App extends AppBase { - mount: (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; -} - -// @public (undocumented) -export interface AppBase { - capabilities?: Partial; - euiIconType?: string; - icon?: string; - // (undocumented) - id: string; - order?: number; - title: string; - tooltip$?: Observable; -} - // @public (undocumented) export interface ApplicationSetup { - register(app: App): void; - registerMountContext(contextName: T, provider: IContextProvider): void; + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts + registerApp(app: App): void; + // Warning: (ae-forgotten-export) The symbol "LegacyApp" needs to be exported by the entry point index.d.ts + // + // @internal + registerLegacyApp(app: LegacyApp): void; } // @public (undocumented) export interface ApplicationStart { + availableApps: readonly App[]; + // @internal + availableLegacyApps: readonly LegacyApp[]; capabilities: RecursiveReadonly; - getUrlForApp(appId: string, options?: { - path?: string; - }): string; - navigateToApp(appId: string, options?: { - path?: string; - state?: any; - }): void; - registerMountContext(contextName: T, provider: IContextProvider): void; -} - -// @public -export interface AppMountContext { - core: { - application: Pick; - chrome: ChromeStart; - docLinks: DocLinksStart; - http: HttpStart; - i18n: I18nStart; - notifications: NotificationsStart; - overlays: OverlayStart; - uiSettings: UiSettingsClientContract; - }; -} - -// @public (undocumented) -export interface AppMountParameters { - appBasePath: string; - element: HTMLElement; } -// @public -export type AppUnmount = () => void; - // @public export interface Capabilities { [key: string]: Record>; @@ -146,7 +105,7 @@ export interface ChromeNavLink { readonly legacy: boolean; // @deprecated readonly linkToLastSubUrl?: boolean; - readonly order?: number; + readonly order: number; // @deprecated readonly subUrlBase?: string; readonly title: string; @@ -226,8 +185,6 @@ export interface CoreContext { // @public export interface CoreSetup { - // (undocumented) - application: ApplicationSetup; // (undocumented) context: ContextSetup; // (undocumented) @@ -243,7 +200,7 @@ export interface CoreSetup { // @public export interface CoreStart { // (undocumented) - application: ApplicationStart; + application: Pick; // (undocumented) chrome: ChromeStart; // (undocumented) @@ -548,19 +505,23 @@ export type IContextHandler, TContextName extends keyof TContext, TProviderParameters extends any[] = []> = (context: Partial, ...rest: TProviderParameters) => Promise | TContext[TContextName]; -// @public @deprecated -export interface LegacyCoreSetup extends CoreSetup { +// @internal (undocumented) +export interface InternalCoreSetup extends CoreSetup { + // (undocumented) + application: ApplicationSetup; // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts // - // @deprecated (undocumented) + // (undocumented) injectedMetadata: InjectedMetadataSetup; } -// @public @deprecated -export interface LegacyCoreStart extends CoreStart { +// @internal (undocumented) +export interface InternalCoreStart extends CoreStart { + // (undocumented) + application: ApplicationStart; // Warning: (ae-forgotten-export) The symbol "InjectedMetadataStart" needs to be exported by the entry point index.d.ts // - // @deprecated (undocumented) + // (undocumented) injectedMetadata: InjectedMetadataStart; } diff --git a/src/core/public/rendering/rendering_service.test.tsx b/src/core/public/rendering/rendering_service.test.tsx index 9a4b46c657f08..5b4ab93996657 100644 --- a/src/core/public/rendering/rendering_service.test.tsx +++ b/src/core/public/rendering/rendering_service.test.tsx @@ -21,67 +21,46 @@ import React from 'react'; import { chromeServiceMock } from '../chrome/chrome_service.mock'; import { RenderingService } from './rendering_service'; -import { InternalApplicationStart } from '../application'; -import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; describe('RenderingService#start', () => { - const getService = ({ legacyMode = false }: { legacyMode?: boolean } = {}) => { + const getService = () => { const rendering = new RenderingService(); - const application = { - getComponent: () =>
Hello application!
, - } as InternalApplicationStart; const chrome = chromeServiceMock.createStartContract(); - chrome.getHeaderComponent.mockReturnValue(
Hello chrome!
); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getLegacyMode.mockReturnValue(legacyMode); + chrome.getComponent.mockReturnValue(
Hello chrome!
); const targetDomElement = document.createElement('div'); - const start = rendering.start({ application, chrome, injectedMetadata, targetDomElement }); + const start = rendering.start({ chrome, targetDomElement }); return { start, targetDomElement }; }; - it('renders application service into provided DOM element', () => { + it('renders into provided DOM element', () => { const { targetDomElement } = getService(); - expect(targetDomElement.querySelector('div.application')).toMatchInlineSnapshot(` -
-
- Hello application! -
-
- `); + expect(targetDomElement).toMatchInlineSnapshot(` +
+
+
+ Hello chrome! +
+
+
+
+`); }); - it('contains wrapper divs', () => { - const { targetDomElement } = getService(); - expect(targetDomElement.querySelector('div.app-wrapper')).toBeDefined(); - expect(targetDomElement.querySelector('div.app-wrapper-pannel')).toBeDefined(); - }); - - describe('legacyMode', () => { - it('renders into provided DOM element', () => { - const { targetDomElement } = getService({ legacyMode: true }); - expect(targetDomElement).toMatchInlineSnapshot(` -
-
-
- Hello chrome! -
-
-
-
- `); - }); - - it('returns a div for the legacy service to render into', () => { - const { - start: { legacyTargetDomElement }, - targetDomElement, - } = getService({ legacyMode: true }); - expect(targetDomElement.contains(legacyTargetDomElement!)).toBe(true); - }); + it('returns a div for the legacy service to render into', () => { + const { + start: { legacyTargetDomElement }, + targetDomElement, + } = getService(); + legacyTargetDomElement.innerHTML = 'Hello legacy!'; + expect(targetDomElement.querySelector('#legacy')).toMatchInlineSnapshot(` + + Hello legacy! + +`); }); }); diff --git a/src/core/public/rendering/rendering_service.tsx b/src/core/public/rendering/rendering_service.tsx index 2e066feca8bf3..cbb931bf59ef9 100644 --- a/src/core/public/rendering/rendering_service.tsx +++ b/src/core/public/rendering/rendering_service.tsx @@ -22,13 +22,9 @@ import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { InternalChromeStart } from '../chrome'; -import { InternalApplicationStart } from '../application'; -import { InjectedMetadataStart } from '../injected_metadata'; interface StartDeps { - application: InternalApplicationStart; chrome: InternalChromeStart; - injectedMetadata: InjectedMetadataStart; targetDomElement: HTMLDivElement; } @@ -43,40 +39,28 @@ interface StartDeps { * @internal */ export class RenderingService { - start({ application, chrome, injectedMetadata, targetDomElement }: StartDeps): RenderingStart { - const chromeUi = chrome.getHeaderComponent(); - const appUi = application.getComponent(); - - const legacyMode = injectedMetadata.getLegacyMode(); - const legacyRef = legacyMode ? React.createRef() : null; + start({ chrome, targetDomElement }: StartDeps) { + const chromeUi = chrome.getComponent(); + const legacyRef = React.createRef(); ReactDOM.render(
{chromeUi} - {!legacyMode && ( -
-
-
{appUi}
-
-
- )} - - {legacyMode &&
} +
, targetDomElement ); return { - // When in legacy mode, return legacy div, otherwise undefined. - legacyTargetDomElement: legacyRef ? legacyRef.current! : undefined, + legacyTargetDomElement: legacyRef.current!, }; } } /** @internal */ export interface RenderingStart { - legacyTargetDomElement?: HTMLDivElement; + legacyTargetDomElement: HTMLDivElement; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 46432cbb0da75..3df5ebe6f5806 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -113,7 +113,7 @@ export function createPluginSetupContext( null, plugin.opaqueId ), - createRouter: () => deps.http.createRouter('', plugin.opaqueId), + createRouter: () => deps.http.createRouter(`/${plugin.name}`, plugin.opaqueId), registerOnPreAuth: deps.http.registerOnPreAuth, registerAuth: deps.http.registerAuth, registerOnPostAuth: deps.http.registerOnPostAuth, diff --git a/src/core/utils/context.mock.ts b/src/core/utils/context.mock.ts index 4d91c11542b2f..d59d0066c4e6e 100644 --- a/src/core/utils/context.mock.ts +++ b/src/core/utils/context.mock.ts @@ -24,9 +24,7 @@ export type ContextContainerMock = jest.Mocked> const createContextMock = () => { const contextMock: ContextContainerMock = { registerContext: jest.fn(), - createHandler: jest.fn((id, handler) => (...args: any[]) => - Promise.resolve(handler({}, ...args)) - ), + createHandler: jest.fn(), }; contextMock.createHandler.mockImplementation((pluginId, handler) => (...args) => handler({}, ...args) diff --git a/src/core/utils/pick.ts b/src/core/utils/pick.ts index 77854f9af680b..d55c76a3ca77d 100644 --- a/src/core/utils/pick.ts +++ b/src/core/utils/pick.ts @@ -17,7 +17,10 @@ * under the License. */ -export function pick(obj: T, keys: K[]): Pick { +export function pick, K extends keyof T>( + obj: T, + keys: K[] +): Pick { return keys.reduce( (acc, key) => { if (obj.hasOwnProperty(key)) { diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.test.js b/src/dev/build/tasks/nodejs/__tests__/node_shasums.js similarity index 70% rename from src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.test.js rename to src/dev/build/tasks/nodejs/__tests__/node_shasums.js index 80bc78111ca03..3e400283dbd80 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.test.js +++ b/src/dev/build/tasks/nodejs/__tests__/node_shasums.js @@ -17,12 +17,13 @@ * under the License. */ -import { ChartsEntities } from './charts'; +import expect from '@kbn/expect'; -describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.js', () => { - describe('ChartsEntities', () => { - test('should match a snapshot of ChartsEntities', () => { - expect(ChartsEntities).toMatchSnapshot(); - }); +import { getNodeShasums } from '../node_shasums'; + +describe('src/dev/build/tasks/nodejs/node_shasums', () => { + it('resolves to an object with shasums for node downloads for version', async () => { + const shasums = await getNodeShasums('8.9.4'); + expect(shasums).to.have.property('node-v8.9.4.tar.gz'); }); }); diff --git a/src/dev/build/tasks/nodejs/node_shasums.ts b/src/dev/build/tasks/nodejs/node_shasums.js similarity index 77% rename from src/dev/build/tasks/nodejs/node_shasums.ts rename to src/dev/build/tasks/nodejs/node_shasums.js index 1b8d01a9b1d94..150e9d4f1508b 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.js @@ -17,21 +17,21 @@ * under the License. */ -import axios from 'axios'; +import wreck from '@hapi/wreck'; -export async function getNodeShasums(nodeVersion: string) { +export async function getNodeShasums(nodeVersion) { const url = `https://nodejs.org/dist/v${nodeVersion}/SHASUMS256.txt`; - const { status, data } = await axios.get(url); + const { res, payload } = await wreck.get(url); - if (status !== 200) { - throw new Error(`${url} failed with a ${status} response`); + if (res.statusCode !== 200) { + throw new Error(`${url} failed with a ${res.statusCode} response`); } - return data + return payload .toString('utf8') .split('\n') - .reduce((acc: Record, line: string) => { + .reduce((acc, line) => { const [sha, platform] = line.split(' '); return { diff --git a/src/dev/build/tasks/nodejs/node_shasums.test.ts b/src/dev/build/tasks/nodejs/node_shasums.test.ts deleted file mode 100644 index ee91d2a370fb1..0000000000000 --- a/src/dev/build/tasks/nodejs/node_shasums.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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. - */ - -const mockResponse = `155ae63f0bb47050e0c31b4f8c17dadc79dcfa8e8f4ec9e3974fd7592afa9a4f node-v8.9.4-aix-ppc64.tar.gz -ca50f7d2035eb805306e303b644bb1cde170ce2615e0a2c6e95fb80881c48c24 node-v8.9.4-darwin-x64.tar.gz -cb79e2da37d2b646a06adaddcda67ff6ba0f77f9ca733b041dabf3dad79c7468 node-v8.9.4-darwin-x64.tar.xz -ef7248e81706daeeec946c19808a50b60ac250e648365d78fda6e40f1f9b23a5 node-v8.9.4-headers.tar.gz -11ed407a4bc3d8c3e73305ac54e91e64c9a9f6a2ae5476791d6fcc14ac159bfc node-v8.9.4-headers.tar.xz -2b133c7d23033fbc2419e66fc08bba35c427a97aba83ed6848b6b4678c0cac65 node-v8.9.4-linux-arm64.tar.gz -7c0369a5dbc98d0989c208ca3ee1b6db4cba576343014fdbf7d36fd2659f7089 node-v8.9.4-linux-arm64.tar.xz -81f138e935323246bd5da518eb0ea8ad00008f3c8a8d606e17589a545a9c73d1 node-v8.9.4-linux-armv6l.tar.gz -501bcae62ea1769924facc9628f407d37753e7a024cf3b12a18ea9dab1b380c9 node-v8.9.4-linux-armv6l.tar.xz -a0dd9009cb8d4be89c8a31131df16ad5ea1580d10ae426c5142aa34b0ad4ea76 node-v8.9.4-linux-armv7l.tar.gz -fe19f195df3d4f362d0cf0eef43c1a6a0b6006a1be2a89ee1808091c2ef4d722 node-v8.9.4-linux-armv7l.tar.xz -c5df73b8571edf97f83b484d6139332fad3b710d51be4aeb8d846059862d4675 node-v8.9.4-linux-ppc64le.tar.gz -21178be5e4c1dbdd99610d24aa934234a368c542ebabb3d98c31d393cf4adf06 node-v8.9.4-linux-ppc64le.tar.xz -d6e53ab2f8364528d4c6800adc1e7fccec607fd07a97b83985732c749a7fc846 node-v8.9.4-linux-s390x.tar.gz -90c6c284db9482a478dd5110e2171435156d56a013aeda2f636b6240eba156bd node-v8.9.4-linux-s390x.tar.xz -21fb4690e349f82d708ae766def01d7fec1b085ce1f5ab30d9bda8ee126ca8fc node-v8.9.4-linux-x64.tar.gz -68b94aac38cd5d87ab79c5b38306e34a20575f31a3ea788d117c20fffcca3370 node-v8.9.4-linux-x64.tar.xz -cc2f7a300353422ede336f5e72b71f0d6eac46732a31b7640648378830dd7513 node-v8.9.4-linux-x86.tar.gz -79f241f31eab5dfe2976fb0633c598dababd207ab0b8a163004f296cd7794a65 node-v8.9.4-linux-x86.tar.xz -b93767f7e186b1ae7204fedafa4110534f577d18d4204f422b626afdd5061e28 node-v8.9.4.pkg -e4a5d945091043c937125cd0d515258785cd4ea806fe3b77000d888de23d2ba0 node-v8.9.4-sunos-x64.tar.gz -b33e8f1495b88fcc0ab1e2579f2f7cf4d39886d577430dcb920a024829d4cf28 node-v8.9.4-sunos-x64.tar.xz -551729411793e427f5760fe8e46f45612e1e8e7c63e55ad34243ebf8ea9a4a7a node-v8.9.4-sunos-x86.tar.gz -6b439bb7204362c0af7a654bce24fcf8059e1772b2f0a9e4e1f8a0b8caa85d26 node-v8.9.4-sunos-x86.tar.xz -729b44b32b2f82ecd5befac4f7518de0c4e3add34e8fe878f745740a66cbbc01 node-v8.9.4.tar.gz -6cdcde9c9c1ca9f450a0b24eafa229ca759e576daa0fae892ce74d541ecdc86f node-v8.9.4.tar.xz -15a847a28358f9ae40bae42f49b033b0180bc10661632c63a9c8487ae980a8ba node-v8.9.4-win-x64.7z -48946e99ac4484e071df25741d2300f3a656f476c5ff3f8116a4746c07ebe3b7 node-v8.9.4-win-x64.zip -50ad674fb4c89edf35d3fee2136da86631cb7c0504589eb71ce8a3bb176493ed node-v8.9.4-win-x86.7z -02e3c65000ac055e05c604aec4cf318212efbd4b60a945ed319072d58314ca32 node-v8.9.4-win-x86.zip -547689da69bacadfee619d208702b73698d14297bd5fef5d80656897989e91b6 node-v8.9.4-x64.msi -f9442188c2f66d167a0ac610dee6d16e226ba28ca93f9569e0276268eb8f85dc node-v8.9.4-x86.msi -b73841f25d6e75d635770fd1a32e4d74d6ab2feed0fd7708bb40b967ae06f33e win-x64/node.exe -5439dc6f0d632ecdeb7342986743a03fe0818e34f0a67e38de74fa9c94886a39 win-x64/node.lib -6ab35445dd564978019cf4f3cfe11dd342b8450015fc054df99aa6f35f21736a win-x64/node_pdb.7z -c064abba981c2373e7e1a8c53b4e4ed1d4927bd9c0f7c065b24dd13b731598bd win-x64/node_pdb.zip -c8430b20cd067d8784d5faae04f9447987a472b22b6d0a2403ea4362ecd3d0bc win-x86/node.exe -c4edece2c0aa68e816c4e067f397eb12e9d0c81bb37b3d349dbaf47cf246b0b7 win-x86/node.lib -6a2ee7a0b0074ece27d171418d82ce25a60b87750ec30c5c9fbeaaca8c206fa5 win-x86/node_pdb.7z -1b44176d888c1bc6a6b05fcc6234031b3b8a58da9de8b99661088f998ac5e269 win-x86/node_pdb.zip`; - -jest.mock('axios', () => ({ - async get(url: string) { - expect(url).toBe('https://nodejs.org/dist/v8.9.4/SHASUMS256.txt'); - return { - status: 200, - data: mockResponse, - }; - }, -})); - -import { getNodeShasums } from './node_shasums'; - -describe('src/dev/build/tasks/nodejs/node_shasums', () => { - it('resolves to an object with shasums for node downloads for version', async () => { - const shasums = await getNodeShasums('8.9.4'); - expect(shasums).toEqual( - expect.objectContaining({ - 'node-v8.9.4.tar.gz': '729b44b32b2f82ecd5befac4f7518de0c4e3add34e8fe878f745740a66cbbc01', - 'node-v8.9.4-win-x64.zip': - '48946e99ac4484e071df25741d2300f3a656f476c5ff3f8116a4746c07ebe3b7', - }) - ); - }); -}); diff --git a/src/dev/jest/config.integration.js b/src/dev/jest/config.integration.js index 6ffa9ac362430..8348b7594961f 100644 --- a/src/dev/jest/config.integration.js +++ b/src/dev/jest/config.integration.js @@ -24,7 +24,6 @@ export default { testMatch: [ '**/integration_tests/**/*.test.js', '**/integration_tests/**/*.test.ts', - '**/integration_tests/**/*.test.tsx', ], testPathIgnorePatterns: config.testPathIgnorePatterns.filter( (pattern) => !pattern.includes('integration_tests') diff --git a/src/legacy/core_plugins/console/public/src/autocomplete.js b/src/legacy/core_plugins/console/public/src/autocomplete.js index 8275813ea2b98..3965a3e8ca379 100644 --- a/src/legacy/core_plugins/console/public/src/autocomplete.js +++ b/src/legacy/core_plugins/console/public/src/autocomplete.js @@ -345,7 +345,12 @@ export default function (editor) { let valueToInsert = termAsString; let templateInserted = false; if (context.addTemplate && !_.isUndefined(term.template) && !_.isNull(term.template)) { - const indentedTemplateLines = utils.jsonToString(term.template, true).split('\n'); + let indentedTemplateLines; + if (term.template.__raw && term.template.value) { + indentedTemplateLines = term.template.value.split('\n'); + } else { + indentedTemplateLines = utils.jsonToString(term.template, true).split('\n'); + } let currentIndentation = session.getLine(context.rangeToReplace.start.row); currentIndentation = currentIndentation.match(/^\s*/)[0]; for (let i = 1; i < indentedTemplateLines.length; i++) // skip first line diff --git a/src/legacy/core_plugins/console/public/src/autocomplete/body_completer.js b/src/legacy/core_plugins/console/public/src/autocomplete/body_completer.js index 68e0889f9c618..e377a749aaf78 100644 --- a/src/legacy/core_plugins/console/public/src/autocomplete/body_completer.js +++ b/src/legacy/core_plugins/console/public/src/autocomplete/body_completer.js @@ -127,6 +127,18 @@ class ScopeResolver extends SharedComponent { } function getTemplate(description) { if (description.__template) { + if (description.__raw && _.isString(description.__template)) { + return { + // This is a special secret attribute that gets passed through to indicate that + // the raw value should be passed through to the console without JSON.stringifying it + // first. + // + // Primary use case is to allow __templates to contain extended JSON special values like + // triple quotes. + __raw: true, + value: description.__template, + }; + } return description.__template; } else if (description.__one_of) { return getTemplate(description.__one_of[0]); diff --git a/src/legacy/core_plugins/console/public/src/utils.js b/src/legacy/core_plugins/console/public/src/utils.js index 1f2c3b9267b74..15a32f90942a2 100644 --- a/src/legacy/core_plugins/console/public/src/utils.js +++ b/src/legacy/core_plugins/console/public/src/utils.js @@ -59,9 +59,11 @@ utils.reformatData = function (data, indent) { }; utils.collapseLiteralStrings = function (data) { - return data.replace(/"""(?:\s*\r?\n)?((?:.|\r?\n)*?)(?:\r?\n\s*)?"""/g, function (match, literal) { - return JSON.stringify(literal); - }); + const splitData = data.split(`"""`); + for (let idx = 1; idx < splitData.length - 1; idx += 2) { + splitData[idx] = JSON.stringify(splitData[idx]); + } + return splitData.join(''); }; utils.expandLiteralStrings = function (data) { @@ -69,8 +71,7 @@ utils.expandLiteralStrings = function (data) { // expand things with two slashes or more if (string.split(/\\./).length > 2) { string = JSON.parse(string).replace('^\s*\n', '').replace('\n\s*^', ''); - const append = string.includes('\n') ? '\n' : ''; // only go multi line if the string has multiline - return '"""' + append + string + append + '"""'; + return '"""' + string + '"""'; } else { return string; } diff --git a/src/legacy/core_plugins/console/public/tests/src/utils_string_collapsing.txt b/src/legacy/core_plugins/console/public/tests/src/utils_string_collapsing.txt index 235151317377f..5ddd1b5a376fe 100644 --- a/src/legacy/core_plugins/console/public/tests/src/utils_string_collapsing.txt +++ b/src/legacy/core_plugins/console/public/tests/src/utils_string_collapsing.txt @@ -8,11 +8,11 @@ to you """ ========== String only 2 ------------------------------------- -""" +""" starting with new lines and ending as well - """ +""" ------------------------------------- -"starting with new lines and ending as well" +"\nstarting with new lines and ending as well\n" ========== Strings in requests ------------------------------------- @@ -27,8 +27,8 @@ test2 } ------------------------------------- { - "f": { "somefield" : "test\ntest2" }, + "f": { "somefield" : "\ntest\ntest2\n" }, "g": { "script" : "second + \"\\\";" }, "h": 1, "script": "a + 2" -} \ No newline at end of file +} diff --git a/src/legacy/core_plugins/console/public/tests/src/utils_string_expanding.txt b/src/legacy/core_plugins/console/public/tests/src/utils_string_expanding.txt index 762e0ad164634..4f3abf177490a 100644 --- a/src/legacy/core_plugins/console/public/tests/src/utils_string_expanding.txt +++ b/src/legacy/core_plugins/console/public/tests/src/utils_string_expanding.txt @@ -2,7 +2,7 @@ Scripts in requests ------------------------------------- { - "f": { "script" : { "source": "test\ntest\\2" } }, + "f": { "script" : { "source": "\ntest\ntest\\2\n" } }, "g": { "script" : "second + \"\\\";" }, "f": "short with \\", "h": 1, diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.tsx index 494110750fcf3..273234cf8d8d5 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.tsx +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/expand_panel_action.tsx @@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n'; import { Action, IEmbeddable, + ActionContext, IncompatibleActionError, } from '../../../../../../embeddable_api/public/np_ready/public'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; @@ -39,11 +40,7 @@ function isExpanded(embeddable: IEmbeddable) { return embeddable.id === embeddable.parent.getInput().expandedPanelId; } -interface ActionContext { - embeddable: IEmbeddable; -} - -export class ExpandPanelAction extends Action { +export class ExpandPanelAction extends Action { public readonly type = EXPAND_PANEL_ACTION; constructor() { @@ -83,7 +80,7 @@ export class ExpandPanelAction extends Action { return Boolean(embeddable.parent && isDashboard(embeddable.parent)); } - public async execute({ embeddable }: ActionContext) { + public execute({ embeddable }: ActionContext) { if (!embeddable.parent || !isDashboard(embeddable.parent)) { throw new IncompatibleActionError(); } diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.tsx index dc7aee514ab26..8dda752ba0b48 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.tsx +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/embeddable/dashboard_container.tsx @@ -21,7 +21,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { Filter } from '@kbn/es-query'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; +import { RefreshInterval, TimeRange } from '../../../../../../../../plugins/data/public'; import { Container, ContainerInput, diff --git a/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx b/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx index e5358acc1c05c..310a3b11a8456 100644 --- a/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx +++ b/src/legacy/core_plugins/data/public/expressions/expression_renderer.tsx @@ -19,60 +19,47 @@ import { useRef, useEffect } from 'react'; import React from 'react'; +import { Ast } from '@kbn/interpreter/common'; -import { ExpressionAST, IExpressionLoaderParams, IInterpreterResult } from './lib/_types'; -import { IExpressionLoader, ExpressionLoader } from './lib/loader'; +import { ExpressionRunnerOptions, ExpressionRunner } from './expression_runner'; +import { Result } from './expressions_service'; // Accept all options of the runner as props except for the // dom element which is provided by the component itself -export interface ExpressionRendererProps extends IExpressionLoaderParams { - className: 'string'; - expression: string | ExpressionAST; +export type ExpressionRendererProps = Pick< + ExpressionRunnerOptions, + Exclude +> & { + expression: string | Ast; /** * If an element is specified, but the response of the expression run can't be rendered * because it isn't a valid response or the specified renderer isn't available, * this callback is called with the given result. */ - onRenderFailure?: (result: IInterpreterResult) => void; -} + onRenderFailure?: (result: Result) => void; +}; export type ExpressionRenderer = React.FC; -export const createRenderer = (loader: IExpressionLoader): ExpressionRenderer => ({ - className, +export const createRenderer = (run: ExpressionRunner): ExpressionRenderer => ({ expression, onRenderFailure, ...options }: ExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); - const handlerRef: React.MutableRefObject = useRef(null); - useEffect(() => { if (mountpoint.current) { - if (!handlerRef.current) { - handlerRef.current = loader(mountpoint.current, expression, options); - } else { - handlerRef.current.update(expression, options); - } - handlerRef.current.data$.toPromise().catch(result => { + run(expression, { ...options, element: mountpoint.current }).catch(result => { if (onRenderFailure) { onRenderFailure(result); } }); } - }, [ - expression, - options.searchContext, - options.context, - options.variables, - options.disableCaching, - mountpoint.current, - ]); + }, [expression, mountpoint.current]); return (
{ mountpoint.current = el; }} diff --git a/src/legacy/core_plugins/data/public/expressions/expression_runner.ts b/src/legacy/core_plugins/data/public/expressions/expression_runner.ts new file mode 100644 index 0000000000000..86a36c9409a48 --- /dev/null +++ b/src/legacy/core_plugins/data/public/expressions/expression_runner.ts @@ -0,0 +1,75 @@ +/* + * 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 { Ast, fromExpression } from '@kbn/interpreter/common'; + +import { RequestAdapter, DataAdapter } from '../../../../../plugins/inspector/public'; +import { RenderFunctionsRegistry, Interpreter, Result } from './expressions_service'; + +export interface ExpressionRunnerOptions { + // TODO use the real types here once they are ready + context?: object; + getInitialContext?: () => object; + element?: Element; +} + +export type ExpressionRunner = ( + expression: string | Ast, + options: ExpressionRunnerOptions +) => Promise; + +export const createRunFn = ( + renderersRegistry: RenderFunctionsRegistry, + interpreterPromise: Promise +): ExpressionRunner => async (expressionOrAst, { element, context, getInitialContext }) => { + // TODO: make interpreter initialization synchronous to avoid this + const interpreter = await interpreterPromise; + const ast = + typeof expressionOrAst === 'string' ? fromExpression(expressionOrAst) : expressionOrAst; + + const response = await interpreter.interpretAst(ast, context || { type: 'null' }, { + getInitialContext: getInitialContext || (() => ({})), + inspectorAdapters: { + // TODO connect real adapters + requests: new RequestAdapter(), + data: new DataAdapter(), + }, + }); + + if (response.type === 'error') { + throw response; + } + + if (element) { + if (response.type === 'render' && response.as && renderersRegistry.get(response.as) !== null) { + renderersRegistry.get(response.as).render(element, response.value, { + onDestroy: fn => { + // TODO implement + }, + done: () => { + // TODO implement + }, + }); + } else { + throw response; + } + } + + return response; +}; diff --git a/src/legacy/core_plugins/data/public/expressions/expressions_service.test.tsx b/src/legacy/core_plugins/data/public/expressions/expressions_service.test.tsx new file mode 100644 index 0000000000000..83313e5a2b2fe --- /dev/null +++ b/src/legacy/core_plugins/data/public/expressions/expressions_service.test.tsx @@ -0,0 +1,276 @@ +/* + * 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 { fromExpression, Ast } from '@kbn/interpreter/common'; + +import { + ExpressionsService, + RenderFunctionsRegistry, + RenderFunction, + Interpreter, + ExpressionsServiceDependencies, + Result, + ExpressionsSetup, +} from './expressions_service'; +import { mount } from 'enzyme'; +import React from 'react'; + +const waitForInterpreterRun = async () => { + // Wait for two ticks with empty callback queues + // This makes sure the runFn promise and actual interpretAst + // promise have been resolved and processed + await new Promise(resolve => setTimeout(resolve)); + await new Promise(resolve => setTimeout(resolve)); +}; + +const RENDERER_ID = 'mockId'; + +describe('expressions_service', () => { + let interpretAstMock: jest.Mocked['interpretAst']; + let interpreterMock: jest.Mocked; + let renderFunctionMock: jest.Mocked; + let setupPluginsMock: ExpressionsServiceDependencies; + const expressionResult: Result = { type: 'render', as: RENDERER_ID, value: {} }; + + let api: ExpressionsSetup; + let testExpression: string; + let testAst: Ast; + + beforeEach(() => { + interpretAstMock = jest.fn((..._) => Promise.resolve(expressionResult)); + interpreterMock = { interpretAst: interpretAstMock }; + renderFunctionMock = ({ + render: jest.fn(), + } as unknown) as jest.Mocked; + setupPluginsMock = { + interpreter: { + getInterpreter: () => Promise.resolve({ interpreter: interpreterMock }), + renderersRegistry: ({ + get: (id: string) => (id === RENDERER_ID ? renderFunctionMock : null), + } as unknown) as RenderFunctionsRegistry, + }, + }; + api = new ExpressionsService().setup(setupPluginsMock); + testExpression = 'test | expression'; + testAst = fromExpression(testExpression); + }); + + describe('expression_runner', () => { + it('should return run function', () => { + expect(typeof api.run).toBe('function'); + }); + + it('should call the interpreter with parsed expression', async () => { + await api.run(testExpression, { element: document.createElement('div') }); + expect(interpreterMock.interpretAst).toHaveBeenCalledWith( + testAst, + expect.anything(), + expect.anything() + ); + }); + + it('should call the interpreter with given context and getInitialContext functions', async () => { + const getInitialContext = () => ({}); + const context = {}; + + await api.run(testExpression, { getInitialContext, context }); + const interpretCall = interpreterMock.interpretAst.mock.calls[0]; + + expect(interpretCall[1]).toBe(context); + expect(interpretCall[2].getInitialContext).toBe(getInitialContext); + }); + + it('should call the interpreter with passed in ast', async () => { + await api.run(testAst, { element: document.createElement('div') }); + expect(interpreterMock.interpretAst).toHaveBeenCalledWith( + testAst, + expect.anything(), + expect.anything() + ); + }); + + it('should return the result of the interpreter run', async () => { + const response = await api.run(testAst, {}); + expect(response).toBe(expressionResult); + }); + + it('should reject the promise if the response is not renderable but an element is passed', async () => { + const unexpectedResult = { type: 'datatable', value: {} }; + interpretAstMock.mockReturnValue(Promise.resolve(unexpectedResult)); + expect( + api.run(testAst, { + element: document.createElement('div'), + }) + ).rejects.toBe(unexpectedResult); + }); + + it('should reject the promise if the renderer is not known', async () => { + const unexpectedResult = { type: 'render', as: 'unknown_id' }; + interpretAstMock.mockReturnValue(Promise.resolve(unexpectedResult)); + expect( + api.run(testAst, { + element: document.createElement('div'), + }) + ).rejects.toBe(unexpectedResult); + }); + + it('should not reject the promise on unknown renderer if the runner is not rendering', async () => { + const unexpectedResult = { type: 'render', as: 'unknown_id' }; + interpretAstMock.mockReturnValue(Promise.resolve(unexpectedResult)); + expect(api.run(testAst, {})).resolves.toBe(unexpectedResult); + }); + + it('should reject the promise if the response is an error', async () => { + const errorResult = { type: 'error', error: {} }; + interpretAstMock.mockReturnValue(Promise.resolve(errorResult)); + expect(api.run(testAst, {})).rejects.toBe(errorResult); + }); + + it('should reject the promise if there are syntax errors', async () => { + expect(api.run('|||', {})).rejects.toBeInstanceOf(Error); + }); + + it('should call the render function with the result and element', async () => { + const element = document.createElement('div'); + + await api.run(testAst, { element }); + expect(renderFunctionMock.render).toHaveBeenCalledWith( + element, + expressionResult.value, + expect.anything() + ); + expect(interpreterMock.interpretAst).toHaveBeenCalledWith( + testAst, + expect.anything(), + expect.anything() + ); + }); + }); + + describe('expression_renderer', () => { + it('should call interpreter and render function when called through react component', async () => { + const ExpressionRenderer = api.ExpressionRenderer; + + mount(); + + await waitForInterpreterRun(); + + expect(renderFunctionMock.render).toHaveBeenCalledWith( + expect.any(Element), + expressionResult.value, + expect.anything() + ); + expect(interpreterMock.interpretAst).toHaveBeenCalledWith( + testAst, + expect.anything(), + expect.anything() + ); + }); + + it('should call the interpreter with given context and getInitialContext functions', async () => { + const getInitialContext = () => ({}); + const context = {}; + + const ExpressionRenderer = api.ExpressionRenderer; + + mount( + + ); + + await waitForInterpreterRun(); + + const interpretCall = interpreterMock.interpretAst.mock.calls[0]; + + expect(interpretCall[1]).toBe(context); + expect(interpretCall[2].getInitialContext).toBe(getInitialContext); + }); + + it('should call interpreter and render function again if expression changes', async () => { + const ExpressionRenderer = api.ExpressionRenderer; + + const instance = mount(); + + await waitForInterpreterRun(); + + expect(renderFunctionMock.render).toHaveBeenCalledWith( + expect.any(Element), + expressionResult.value, + expect.anything() + ); + expect(interpreterMock.interpretAst).toHaveBeenCalledWith( + testAst, + expect.anything(), + expect.anything() + ); + + instance.setProps({ expression: 'supertest | expression ' }); + + await waitForInterpreterRun(); + + expect(renderFunctionMock.render).toHaveBeenCalledTimes(2); + expect(interpreterMock.interpretAst).toHaveBeenCalledTimes(2); + }); + + it('should not call interpreter and render function again if expression does not change', async () => { + const ast = fromExpression(testExpression); + + const ExpressionRenderer = api.ExpressionRenderer; + + const instance = mount(); + + await waitForInterpreterRun(); + + expect(renderFunctionMock.render).toHaveBeenCalledWith( + expect.any(Element), + expressionResult.value, + expect.anything() + ); + expect(interpreterMock.interpretAst).toHaveBeenCalledWith( + ast, + expect.anything(), + expect.anything() + ); + + instance.update(); + + await waitForInterpreterRun(); + + expect(renderFunctionMock.render).toHaveBeenCalledTimes(1); + expect(interpreterMock.interpretAst).toHaveBeenCalledTimes(1); + }); + + it('should call onRenderFailure if the result can not be rendered', async () => { + const errorResult = { type: 'error', error: {} }; + interpretAstMock.mockReturnValue(Promise.resolve(errorResult)); + const renderFailureSpy = jest.fn(); + + const ExpressionRenderer = api.ExpressionRenderer; + + mount(); + + await waitForInterpreterRun(); + + expect(renderFailureSpy).toHaveBeenCalledWith(errorResult); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/expressions/expressions_service.ts b/src/legacy/core_plugins/data/public/expressions/expressions_service.ts index a00512f04c1ac..fc4cd853afe14 100644 --- a/src/legacy/core_plugins/data/public/expressions/expressions_service.ts +++ b/src/legacy/core_plugins/data/public/expressions/expressions_service.ts @@ -17,51 +17,107 @@ * under the License. */ -import { npSetup } from 'ui/new_platform'; -// @ts-ignore +import { Ast } from '@kbn/interpreter/common'; -import { setInspector, setInterpreter } from './services'; -import { execute } from './lib/execute'; -import { loader } from './lib/loader'; -import { render } from './lib/render'; +// TODO: +// this type import and the types below them should be switched to the types of +// the interpreter plugin itself once they are ready +import { Registry } from '@kbn/interpreter/common'; +import { Adapters } from 'src/plugins/inspector/public'; +import { Filter } from '@kbn/es-query'; +import { TimeRange } from 'src/plugins/data/public'; import { createRenderer } from './expression_renderer'; +import { createRunFn } from './expression_runner'; +import { Query } from '../query'; -import { Start as IInspector } from '../../../../../plugins/inspector/public'; +export interface InitialContextObject { + timeRange?: TimeRange; + filters?: Filter[]; + query?: Query; +} + +export type getInitialContextFunction = () => InitialContextObject; + +export interface Handlers { + getInitialContext: getInitialContextFunction; + inspectorAdapters?: Adapters; + abortSignal?: AbortSignal; +} + +type Context = object; +export interface Result { + type: string; + as?: string; + value?: unknown; + error?: unknown; +} + +interface RenderHandlers { + done: () => void; + onDestroy: (fn: () => void) => void; +} + +export interface RenderFunction { + name: string; + displayName: string; + help: string; + validate: () => void; + reuseDomNode: boolean; + render: (domNode: Element, data: unknown, handlers: RenderHandlers) => void; +} + +export type RenderFunctionsRegistry = Registry; -export interface ExpressionsServiceStartDependencies { - inspector: IInspector; +export interface Interpreter { + interpretAst(ast: Ast, context: Context, handlers: Handlers): Promise; } + +type InterpreterGetter = () => Promise<{ interpreter: Interpreter }>; + +export interface ExpressionsServiceDependencies { + interpreter: { + renderersRegistry: RenderFunctionsRegistry; + getInterpreter: InterpreterGetter; + }; +} + /** * Expressions Service * @internal */ export class ExpressionsService { - public setup() { - // eslint-disable-next-line - const { getInterpreter } = require('../../../interpreter/public/interpreter'); - getInterpreter() - .then(setInterpreter) - .catch((e: Error) => { - throw new Error('interpreter is not initialized'); - }); - - return { - registerType: npSetup.plugins.data.expressions.registerType, - registerFunction: npSetup.plugins.data.expressions.registerFunction, - registerRenderer: npSetup.plugins.data.expressions.registerRenderer, - }; - } - - public start({ inspector }: ExpressionsServiceStartDependencies) { - const ExpressionRenderer = createRenderer(loader); - setInspector(inspector); + public setup({ + interpreter: { renderersRegistry, getInterpreter }, + }: ExpressionsServiceDependencies) { + const run = createRunFn( + renderersRegistry, + getInterpreter().then(({ interpreter }) => interpreter) + ); return { - execute, - render, - loader, - - ExpressionRenderer, + /** + * **experimential** This API is experimential and might be removed in the future + * without notice + * + * Executes the given expression string or ast and renders the result into the + * given DOM element. + * + * + * @param expressionOrAst + * @param element + */ + run, + /** + * **experimential** This API is experimential and might be removed in the future + * without notice + * + * Component which executes and renders the given expression in a div element. + * The expression is re-executed on updating the props. + * + * This is a React bridge of the `run` method + * @param props + */ + ExpressionRenderer: createRenderer(run), }; } @@ -72,4 +128,3 @@ export class ExpressionsService { /** @public */ export type ExpressionsSetup = ReturnType; -export type ExpressionsStart = ReturnType; diff --git a/src/legacy/core_plugins/data/public/expressions/index.ts b/src/legacy/core_plugins/data/public/expressions/index.ts index ae51804a85fb5..fceefce44f81f 100644 --- a/src/legacy/core_plugins/data/public/expressions/index.ts +++ b/src/legacy/core_plugins/data/public/expressions/index.ts @@ -17,5 +17,6 @@ * under the License. */ -export { ExpressionsService, ExpressionsSetup, ExpressionsStart } from './expressions_service'; +export { ExpressionsService, ExpressionsSetup } from './expressions_service'; export { ExpressionRenderer, ExpressionRendererProps } from './expression_renderer'; +export { ExpressionRunner } from './expression_runner'; diff --git a/src/legacy/core_plugins/data/public/expressions/lib/_types.ts b/src/legacy/core_plugins/data/public/expressions/lib/_types.ts deleted file mode 100644 index b3185fda2c178..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/lib/_types.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 { TimeRange } from 'src/plugins/data/public'; -import { Filter } from '@kbn/es-query'; -import { Adapters } from '../../../../../ui/public/inspector'; -import { Query } from '../../query'; -import { ExpressionAST } from '../../../../../../plugins/data/common/expressions/types'; - -export { ExpressionAST, TimeRange, Adapters, Filter, Query }; - -export type RenderId = number; -export type Data = any; -export type event = any; -export type Context = object; - -export interface SearchContext { - type: 'kibana_context'; - filters?: Filter[]; - query?: Query; - timeRange?: TimeRange; -} - -export type IGetInitialContext = () => SearchContext | Context; - -export interface IExpressionLoaderParams { - searchContext?: SearchContext; - context?: Context; - variables?: Record; - disableCaching?: boolean; - customFunctions?: []; - customRenderers?: []; -} - -export interface IInterpreterHandlers { - getInitialContext: IGetInitialContext; - inspectorAdapters?: Adapters; -} - -export interface IInterpreterResult { - type: string; - as?: string; - value?: unknown; - error?: unknown; -} - -export interface IInterpreterRenderHandlers { - done: () => void; - onDestroy: (fn: () => void) => void; - reload: () => void; - update: (params: any) => void; - event: (event: event) => void; -} - -export interface IInterpreterRenderFunction { - name: string; - displayName: string; - help: string; - validate: () => void; - reuseDomNode: boolean; - render: (domNode: Element, data: unknown, handlers: IInterpreterRenderHandlers) => void; -} - -export interface IInterpreter { - interpretAst( - ast: ExpressionAST, - context: Context, - handlers: IInterpreterHandlers - ): Promise; -} diff --git a/src/legacy/core_plugins/data/public/expressions/lib/execute.test.ts b/src/legacy/core_plugins/data/public/expressions/lib/execute.test.ts deleted file mode 100644 index 978d0c834ca8e..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/lib/execute.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 { execute, ExpressionDataHandler } from './execute'; -import { fromExpression } from '@kbn/interpreter/common'; -import { ExpressionAST } from '../../../../../../plugins/data/common/expressions/types'; - -jest.mock('../services', () => ({ - getInterpreter: () => { - return { - interpretAst: async (expression: ExpressionAST) => { - return {}; - }, - }; - }, -})); - -describe('execute helper function', () => { - it('returns ExpressionDataHandler instance', () => { - const response = execute(''); - expect(response).toBeInstanceOf(ExpressionDataHandler); - }); -}); - -describe('ExpressionDataHandler', () => { - const expressionString = ''; - - describe('constructor', () => { - it('accepts expression string', () => { - const expressionDataHandler = new ExpressionDataHandler(expressionString, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - }); - - it('accepts expression AST', () => { - const expressionAST = fromExpression(expressionString) as ExpressionAST; - const expressionDataHandler = new ExpressionDataHandler(expressionAST, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - expect(expressionDataHandler.getAst()).toEqual(expressionAST); - }); - - it('allows passing in context', () => { - const expressionDataHandler = new ExpressionDataHandler(expressionString, { - context: { test: 'hello' }, - }); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - }); - - it('allows passing in search context', () => { - const expressionDataHandler = new ExpressionDataHandler(expressionString, { - searchContext: { type: 'kibana_context', filters: [] }, - }); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - }); - }); - - describe('getData()', () => { - it('returns a promise', () => { - const expressionDataHandler = new ExpressionDataHandler(expressionString, {}); - expect(expressionDataHandler.getData()).toBeInstanceOf(Promise); - }); - - it('promise resolves with data', async () => { - const expressionDataHandler = new ExpressionDataHandler(expressionString, {}); - expect(await expressionDataHandler.getData()).toEqual({}); - }); - }); - - it('cancel() aborts request', () => { - const expressionDataHandler = new ExpressionDataHandler(expressionString, {}); - expressionDataHandler.cancel(); - }); - - it('inspect() returns correct inspector adapters', () => { - const expressionDataHandler = new ExpressionDataHandler(expressionString, {}); - expect(expressionDataHandler.inspect()).toHaveProperty('requests'); - expect(expressionDataHandler.inspect()).toHaveProperty('data'); - }); -}); diff --git a/src/legacy/core_plugins/data/public/expressions/lib/execute.ts b/src/legacy/core_plugins/data/public/expressions/lib/execute.ts deleted file mode 100644 index 4cc1cead3e03c..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/lib/execute.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 { fromExpression } from '@kbn/interpreter/target/common'; -import { DataAdapter, RequestAdapter, Adapters } from '../../../../../../plugins/inspector/public'; -import { getInterpreter } from '../services'; -import { ExpressionAST, IExpressionLoaderParams, IInterpreterResult } from './_types'; - -/** - * The search context describes a specific context (filters, time range and query) - * that will be applied to the expression for execution. Not every expression will - * be effected by that. You have to use special functions - * that will pick up this search context and forward it to following functions that - * understand it. - */ - -export class ExpressionDataHandler { - private abortController: AbortController; - private expression: string; - private ast: ExpressionAST; - - private inspectorAdapters: Adapters; - private promise: Promise; - - constructor(expression: string | ExpressionAST, params: IExpressionLoaderParams) { - if (typeof expression === 'string') { - this.expression = expression; - this.ast = fromExpression(expression) as ExpressionAST; - } else { - this.ast = expression; - this.expression = ''; - } - - this.abortController = new AbortController(); - this.inspectorAdapters = this.getActiveInspectorAdapters(); - - const getInitialContext = () => ({ - type: 'kibana_context', - ...params.searchContext, - }); - - const defaultContext = { type: 'null' }; - - const interpreter = getInterpreter(); - this.promise = interpreter.interpretAst(this.ast, params.context || defaultContext, { - getInitialContext, - inspectorAdapters: this.inspectorAdapters, - }); - } - - cancel = () => { - this.abortController.abort(); - }; - - getData = async () => { - return await this.promise; - }; - - getExpression = () => { - return this.expression; - }; - - getAst = () => { - return this.ast; - }; - - inspect = () => { - return this.inspectorAdapters; - }; - - /** - * Returns an object of all inspectors for this vis object. - * This must only be called after this.type has properly be initialized, - * since we need to read out data from the the vis type to check which - * inspectors are available. - */ - private getActiveInspectorAdapters = (): Adapters => { - const adapters: Adapters = {}; - - // Add the requests inspector adapters if the vis type explicitly requested it via - // inspectorAdapters.requests: true in its definition or if it's using the courier - // request handler, since that will automatically log its requests. - adapters.requests = new RequestAdapter(); - - // Add the data inspector adapter if the vis type requested it or if the - // vis is using courier, since we know that courier supports logging - // its data. - adapters.data = new DataAdapter(); - - return adapters; - }; -} - -export function execute( - expression: string | ExpressionAST, - params: IExpressionLoaderParams = {} -): ExpressionDataHandler { - return new ExpressionDataHandler(expression, params); -} diff --git a/src/legacy/core_plugins/data/public/expressions/lib/loader.test.ts b/src/legacy/core_plugins/data/public/expressions/lib/loader.test.ts deleted file mode 100644 index ee9efd9d3373b..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/lib/loader.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 { first } from 'rxjs/operators'; -import { loader, ExpressionLoader } from './loader'; -import { fromExpression } from '@kbn/interpreter/common'; -import { IInterpreterRenderHandlers } from './_types'; -import { Observable } from 'rxjs'; -import { ExpressionAST } from '../../../../../../plugins/data/common/expressions/types'; - -const element: HTMLElement = null as any; - -jest.mock('../services', () => ({ - getInterpreter: () => { - return { - interpretAst: async (expression: ExpressionAST) => { - return { type: 'render', as: 'test' }; - }, - }; - }, -})); - -jest.mock('../../../../interpreter/public/registries', () => { - const _registry: Record = {}; - _registry.test = { - render: (el: HTMLElement, value: any, handlers: IInterpreterRenderHandlers) => { - handlers.done(); - }, - }; - return { - renderersRegistry: { - get: (id: string) => { - return _registry[id]; - }, - }, - }; -}); - -describe('execute helper function', () => { - it('returns ExpressionDataHandler instance', () => { - const response = loader(element, '', {}); - expect(response).toBeInstanceOf(ExpressionLoader); - }); -}); - -describe('ExpressionDataHandler', () => { - const expressionString = ''; - - describe('constructor', () => { - it('accepts expression string', () => { - const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - }); - - it('accepts expression AST', () => { - const expressionAST = fromExpression(expressionString) as ExpressionAST; - const expressionDataHandler = new ExpressionLoader(element, expressionAST, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - expect(expressionDataHandler.getAst()).toEqual(expressionAST); - }); - - it('creates observables', () => { - const expressionLoader = new ExpressionLoader(element, expressionString, {}); - expect(expressionLoader.events$).toBeInstanceOf(Observable); - expect(expressionLoader.render$).toBeInstanceOf(Observable); - expect(expressionLoader.update$).toBeInstanceOf(Observable); - expect(expressionLoader.data$).toBeInstanceOf(Observable); - }); - }); - - it('emits on $data when data is available', async () => { - const expressionLoader = new ExpressionLoader(element, expressionString, {}); - const response = await expressionLoader.data$.pipe(first()).toPromise(); - expect(response).toEqual({ type: 'render', as: 'test' }); - }); - - it('emits on render$ when rendering is done', async () => { - const expressionLoader = new ExpressionLoader(element, expressionString, {}); - const response = await expressionLoader.render$.pipe(first()).toPromise(); - expect(response).toBe(1); - }); - - it('allows updating configuration', async () => { - const expressionLoader = new ExpressionLoader(element, expressionString, {}); - let response = await expressionLoader.render$.pipe(first()).toPromise(); - expect(response).toBe(1); - expressionLoader.update('', {}); - response = await expressionLoader.render$.pipe(first()).toPromise(); - expect(response).toBe(2); - }); - - it('cancel() aborts request', () => { - const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expressionDataHandler.cancel(); - }); - - it('inspect() returns correct inspector adapters', () => { - const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expect(expressionDataHandler.inspect()).toHaveProperty('data'); - expect(expressionDataHandler.inspect()).toHaveProperty('requests'); - }); -}); diff --git a/src/legacy/core_plugins/data/public/expressions/lib/loader.ts b/src/legacy/core_plugins/data/public/expressions/lib/loader.ts deleted file mode 100644 index e3353ab676be6..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/lib/loader.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 { Observable, Subject } from 'rxjs'; -import { first, share } from 'rxjs/operators'; -import { Adapters, InspectorSession } from '../../../../../../plugins/inspector/public'; -import { execute, ExpressionDataHandler } from './execute'; -import { ExpressionRenderHandler } from './render'; -import { RenderId, Data, IExpressionLoaderParams, ExpressionAST } from './_types'; -import { getInspector } from '../services'; - -export class ExpressionLoader { - data$: Observable; - update$: Observable; - render$: Observable; - events$: Observable; - - private dataHandler: ExpressionDataHandler; - private renderHandler: ExpressionRenderHandler; - private dataSubject: Subject; - private data: Data; - - constructor( - element: HTMLElement, - expression: string | ExpressionAST, - params: IExpressionLoaderParams - ) { - this.dataSubject = new Subject(); - this.data$ = this.dataSubject.asObservable().pipe(share()); - - this.renderHandler = new ExpressionRenderHandler(element); - this.render$ = this.renderHandler.render$; - this.update$ = this.renderHandler.update$; - this.events$ = this.renderHandler.events$; - - this.update$.subscribe(({ newExpression, newParams }) => { - this.update(newExpression, newParams); - }); - - this.data$.subscribe(data => { - this.render(data); - }); - - this.execute(expression, params); - // @ts-ignore - this.dataHandler = this.dataHandler; - } - - destroy() {} - - cancel() { - this.dataHandler.cancel(); - } - - getExpression(): string { - return this.dataHandler.getExpression(); - } - - getAst(): ExpressionAST { - return this.dataHandler.getAst(); - } - - getElement(): HTMLElement { - return this.renderHandler.getElement(); - } - - openInspector(title: string): InspectorSession { - return getInspector().open(this.inspect(), { - title, - }); - } - - inspect(): Adapters { - return this.dataHandler.inspect(); - } - - update(expression: string | ExpressionAST, params: IExpressionLoaderParams): Promise { - const promise = this.render$.pipe(first()).toPromise(); - - if (expression !== null) { - this.execute(expression, params); - } else { - this.render(this.data); - } - return promise; - } - - private execute = async ( - expression: string | ExpressionAST, - params: IExpressionLoaderParams - ): Promise => { - if (this.dataHandler) { - this.dataHandler.cancel(); - } - this.dataHandler = execute(expression, params); - const data = await this.dataHandler.getData(); - this.dataSubject.next(data); - return data; - }; - - private async render(data: Data): Promise { - return this.renderHandler.render(data); - } -} - -export type IExpressionLoader = ( - element: HTMLElement, - expression: string | ExpressionAST, - params: IExpressionLoaderParams -) => ExpressionLoader; - -export const loader: IExpressionLoader = (element, expression, params) => { - return new ExpressionLoader(element, expression, params); -}; diff --git a/src/legacy/core_plugins/data/public/expressions/lib/render.test.ts b/src/legacy/core_plugins/data/public/expressions/lib/render.test.ts deleted file mode 100644 index 1bb2f8d6554b2..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/lib/render.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 { render, ExpressionRenderHandler } from './render'; -import { Observable } from 'rxjs'; -import { IInterpreterRenderHandlers } from './_types'; - -const element: HTMLElement = null as any; - -jest.mock('../../../../interpreter/public/registries', () => { - const _registry: Record = {}; - _registry.test = { - render: (el: HTMLElement, value: any, handlers: IInterpreterRenderHandlers) => { - handlers.done(); - }, - }; - return { - renderersRegistry: { - get: (id: string) => { - return _registry[id]; - }, - }, - }; -}); - -describe('render helper function', () => { - it('returns ExpressionRenderHandler instance', () => { - const response = render(element, {}); - expect(response).toBeInstanceOf(ExpressionRenderHandler); - }); -}); - -describe('ExpressionRenderHandler', () => { - const data = { type: 'render', as: 'test' }; - - it('constructor creates observers', () => { - const expressionRenderHandler = new ExpressionRenderHandler(element); - expect(expressionRenderHandler.events$).toBeInstanceOf(Observable); - expect(expressionRenderHandler.render$).toBeInstanceOf(Observable); - expect(expressionRenderHandler.update$).toBeInstanceOf(Observable); - }); - - it('getElement returns the element', () => { - const expressionRenderHandler = new ExpressionRenderHandler(element); - expect(expressionRenderHandler.getElement()).toBe(element); - }); - - describe('render()', () => { - it('throws if invalid data is provided', async () => { - const expressionRenderHandler = new ExpressionRenderHandler(element); - await expect(expressionRenderHandler.render({})).rejects.toThrow(); - }); - - it('throws if renderer does not exist', async () => { - const expressionRenderHandler = new ExpressionRenderHandler(element); - await expect( - expressionRenderHandler.render({ type: 'render', as: 'something' }) - ).rejects.toThrow(); - }); - - it('returns a promise', () => { - const expressionRenderHandler = new ExpressionRenderHandler(element); - expect(expressionRenderHandler.render(data)).toBeInstanceOf(Promise); - }); - - it('resolves a promise once rendering is complete', async () => { - const expressionRenderHandler = new ExpressionRenderHandler(element); - const response = await expressionRenderHandler.render(data); - expect(response).toBe(1); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/expressions/lib/render.ts b/src/legacy/core_plugins/data/public/expressions/lib/render.ts deleted file mode 100644 index 250fa14c9de1d..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/lib/render.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 { Observable } from 'rxjs'; -import * as Rx from 'rxjs'; -import { share, first } from 'rxjs/operators'; -import { renderersRegistry } from '../../../../interpreter/public/registries'; -import { event, RenderId, Data, IInterpreterRenderHandlers } from './_types'; - -export class ExpressionRenderHandler { - render$: Observable; - update$: Observable; - events$: Observable; - - private element: HTMLElement; - private destroyFn?: any; - private renderCount: number = 0; - private handlers: IInterpreterRenderHandlers; - - constructor(element: HTMLElement) { - this.element = element; - - const eventsSubject = new Rx.Subject(); - this.events$ = eventsSubject.asObservable().pipe(share()); - - const renderSubject = new Rx.Subject(); - this.render$ = renderSubject.asObservable().pipe(share()); - - const updateSubject = new Rx.Subject(); - this.update$ = updateSubject.asObservable().pipe(share()); - - this.handlers = { - onDestroy: (fn: any) => { - this.destroyFn = fn; - }, - done: () => { - this.renderCount++; - renderSubject.next(this.renderCount); - }, - reload: () => { - updateSubject.next(null); - }, - update: params => { - updateSubject.next(params); - }, - event: data => { - eventsSubject.next(data); - }, - }; - } - - render = async (data: Data) => { - if (data.type !== 'render' || !data.as) { - throw new Error('invalid data provided to expression renderer'); - } - - if (!renderersRegistry.get(data.as)) { - throw new Error(`invalid renderer id '${data.as}'`); - } - - const promise = this.render$.pipe(first()).toPromise(); - - renderersRegistry.get(data.as).render(this.element, data.value, this.handlers); - - return promise; - }; - - destroy = () => { - if (this.destroyFn) { - this.destroyFn(); - } - }; - - getElement = () => { - return this.element; - }; -} - -export function render(element: HTMLElement, data: Data): ExpressionRenderHandler { - const handler = new ExpressionRenderHandler(element); - handler.render(data); - return handler; -} diff --git a/src/legacy/core_plugins/data/public/expressions/services.ts b/src/legacy/core_plugins/data/public/expressions/services.ts deleted file mode 100644 index 73f627f8dc886..0000000000000 --- a/src/legacy/core_plugins/data/public/expressions/services.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { IInterpreter } from './lib/_types'; -import { Start as IInspector } from '../../../../../plugins/inspector/public'; - -let interpreter: IInterpreter | undefined; -let inspector: IInspector; - -export const getInterpreter = (): IInterpreter => { - if (!interpreter) throw new Error('interpreter was not set'); - return interpreter; -}; - -export const setInterpreter = (inspectorInstance: IInterpreter) => { - interpreter = inspectorInstance; -}; - -export const getInspector = (): IInspector => { - return inspector; -}; - -export const setInspector = (inspectorInstance: IInspector) => { - inspector = inspectorInstance; -}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx index 4ca1d581aef7d..d235cddccf165 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx @@ -56,9 +56,6 @@ class FilterBarUI extends Component { }; public render() { - if (!this.props.uiSettings) { - return null; - } const classes = classNames('globalFilterBar', this.props.className); return ( diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 8bf13a24f1775..4e1e83d71afe8 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -18,7 +18,7 @@ */ // /// Define plugin function -import { DataPlugin as Plugin, DataSetup, DataStart } from './plugin'; +import { DataPlugin as Plugin, DataSetup } from './plugin'; export function plugin() { return new Plugin(); @@ -28,9 +28,7 @@ export function plugin() { /** @public types */ export type DataSetup = DataSetup; -export type DataStart = DataStart; - -export { ExpressionRenderer, ExpressionRendererProps } from './expressions'; +export { ExpressionRenderer, ExpressionRendererProps, ExpressionRunner } from './expressions'; export { FilterBar, ApplyFiltersPopover } from './filter'; export { Field, diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.js similarity index 61% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts rename to src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.js index d89a4a35e6e9f..b7fcb79da3269 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.js @@ -17,23 +17,18 @@ * under the License. */ -import { defaults, pluck, last, get } from 'lodash'; +import _ from 'lodash'; +import mockLogstashFields from '../../../../../../fixtures/logstash_fields'; +import { stubbedSavedObjectIndexPattern } from '../../../../../../fixtures/stubbed_saved_object_index_pattern'; import { IndexedArray } from 'ui/indexed_array'; -import { IndexPattern } from './index_pattern'; - -// @ts-ignore import { DuplicateField } from 'ui/errors'; -// @ts-ignore -import mockLogStashFields from '../../../../../../fixtures/logstash_fields'; -// @ts-ignore -import { stubbedSavedObjectIndexPattern } from '../../../../../../fixtures/stubbed_saved_object_index_pattern'; -import { Field } from '../index_patterns_service'; +import { IndexPattern } from './index_pattern'; jest.mock('ui/registry/field_formats', () => ({ fieldFormats: { getDefaultInstance: jest.fn(), - }, + } })); jest.mock('ui/utils/mapping_setup', () => ({ @@ -41,16 +36,16 @@ jest.mock('ui/utils/mapping_setup', () => ({ id: true, title: true, fieldFormatMap: { - _deserialize: jest.fn().mockImplementation(() => []), + _deserialize: jest.fn().mockImplementation(() => ([])), }, - })), + })) })); jest.mock('ui/notify', () => ({ toastNotifications: { addDanger: jest.fn(), addError: jest.fn(), - }, + } })); jest.mock('ui/saved_objects', () => { @@ -59,32 +54,32 @@ jest.mock('ui/saved_objects', () => { }; }); -let mockFieldsFetcherResponse: any[] = []; - +let mockFieldsFetcherResponse = []; jest.mock('./_fields_fetcher', () => ({ createFieldsFetcher: jest.fn().mockImplementation(() => ({ fetch: jest.fn().mockImplementation(() => { return new Promise(resolve => resolve(mockFieldsFetcherResponse)); }), every: jest.fn(), - })), + })) })); -let object: any = {}; - +let object; const savedObjectsClient = { create: jest.fn(), get: jest.fn().mockImplementation(() => object), update: jest.fn().mockImplementation(async (type, id, body, { version }) => { if (object._version !== version) { - throw new Object({ + throw { res: { - status: 409, - }, - }); + status: 409 + } + }; } + object.attributes.title = body.title; object._version += 'a'; + return { id: object._id, _version: object._version, @@ -101,44 +96,35 @@ const config = { }; const apiClient = { - _getUrl: jest.fn(), getFieldsForTimePattern: jest.fn(), getFieldsForWildcard: jest.fn(), }; // helper function to create index patterns -function create(id: string, payload?: any): Promise { - const indexPattern = new IndexPattern( - id, - (cfg: any) => config.get(cfg), - savedObjectsClient as any, - apiClient, - patternCache - ); +function create(id, payload) { + const indexPattern = new IndexPattern(id, cfg => config.get(cfg), savedObjectsClient, apiClient, patternCache); setDocsourcePayload(id, payload); return indexPattern.init(); } -function setDocsourcePayload(id: string | null, providedPayload: any) { - object = defaults(providedPayload || {}, stubbedSavedObjectIndexPattern(id)); +function setDocsourcePayload(id, providedPayload) { + object = _.defaults(providedPayload || {}, stubbedSavedObjectIndexPattern(id)); } describe('IndexPattern', () => { const indexPatternId = 'test-pattern'; - - let indexPattern: IndexPattern; - + let indexPattern; // create an indexPattern instance for each test - beforeEach(() => { - return create(indexPatternId).then((pattern: IndexPattern) => { + beforeEach(function () { + return create(indexPatternId).then(function (pattern) { indexPattern = pattern; }); }); - describe('api', () => { - test('should have expected properties', () => { + describe('api', function () { + it('should have expected properties', function () { expect(indexPattern).toHaveProperty('refreshFields'); expect(indexPattern).toHaveProperty('popularizeField'); expect(indexPattern).toHaveProperty('getScriptedFields'); @@ -154,16 +140,16 @@ describe('IndexPattern', () => { }); }); - describe('init', () => { - test('should append the found fields', () => { + describe('init', function () { + it('should append the found fields', function () { expect(savedObjectsClient.get).toHaveBeenCalled(); - expect(indexPattern.fields).toHaveLength(mockLogStashFields().length); + expect(indexPattern.fields).toHaveLength(mockLogstashFields().length); expect(indexPattern.fields).toBeInstanceOf(IndexedArray); }); }); - describe('fields', () => { - test('should have expected properties on fields', function() { + describe('fields', function () { + it('should have expected properties on fields', function () { expect(indexPattern.fields[0]).toHaveProperty('displayName'); expect(indexPattern.fields[0]).toHaveProperty('filterable'); expect(indexPattern.fields[0]).toHaveProperty('format'); @@ -172,45 +158,42 @@ describe('IndexPattern', () => { }); }); - describe('getScriptedFields', () => { - test('should return all scripted fields', () => { - const scriptedNames = mockLogStashFields() - .filter((item: Field) => item.scripted === true) - .map((item: Field) => item.name); - const respNames = pluck(indexPattern.getScriptedFields(), 'name'); - + describe('getScriptedFields', function () { + it('should return all scripted fields', function () { + const scriptedNames = _(mockLogstashFields()).where({ scripted: true }).pluck('name').value(); + const respNames = _.pluck(indexPattern.getScriptedFields(), 'name'); expect(respNames).toEqual(scriptedNames); }); }); - describe('getNonScriptedFields', () => { - test('should return all non-scripted fields', () => { - const notScriptedNames = mockLogStashFields() - .filter((item: Field) => item.scripted === false) - .map((item: Field) => item.name); - const respNames = pluck(indexPattern.getNonScriptedFields(), 'name'); - + describe('getNonScriptedFields', function () { + it('should return all non-scripted fields', function () { + const notScriptedNames = _(mockLogstashFields()).where({ scripted: false }).pluck('name').value(); + const respNames = _.pluck(indexPattern.getNonScriptedFields(), 'name'); expect(respNames).toEqual(notScriptedNames); }); + }); - describe('refresh fields', () => { - test('should fetch fields from the fieldsFetcher', async () => { + describe('refresh fields', function () { + it('should fetch fields from the fieldsFetcher', async function () { expect(indexPattern.fields.length).toBeGreaterThan(2); - mockFieldsFetcherResponse = [{ name: 'foo' }, { name: 'bar' }]; + mockFieldsFetcherResponse = [ + { name: 'foo' }, + { name: 'bar' } + ]; await indexPattern.refreshFields(); mockFieldsFetcherResponse = []; const newFields = indexPattern.getNonScriptedFields(); - expect(newFields).toHaveLength(2); expect(newFields.map(f => f.name)).toEqual(['foo', 'bar']); }); - test('should preserve the scripted fields', async () => { + it('should preserve the scripted fields', async function () { // add spy to indexPattern.getScriptedFields // sinon.spy(indexPattern, 'getScriptedFields'); @@ -219,16 +202,13 @@ describe('IndexPattern', () => { // called to append scripted fields to the response from mapper.getFieldsForIndexPattern // sinon.assert.calledOnce(indexPattern.getScriptedFields); - expect(indexPattern.getScriptedFields().map(f => f.name)).toEqual( - mockLogStashFields() - .filter((f: Field) => f.scripted) - .map((f: Field) => f.name) - ); + expect(indexPattern.getScriptedFields().map(f => f.name)) + .toEqual(mockLogstashFields().filter(f => f.scripted).map(f => f.name)); }); }); - describe('add and remove scripted fields', () => { - test('should append the scripted field', async () => { + describe('add and remove scripted fields', function () { + it('should append the scripted field', function () { // keep a copy of the current scripted field count // const saveSpy = sinon.spy(indexPattern, 'save'); const oldCount = indexPattern.getScriptedFields().length; @@ -237,128 +217,107 @@ describe('IndexPattern', () => { const scriptedField = { name: 'new scripted field', script: 'false', - type: 'boolean', + type: 'boolean' }; - - await indexPattern.addScriptedField( - scriptedField.name, - scriptedField.script, - scriptedField.type, - 'lang' - ); - + indexPattern.addScriptedField(scriptedField.name, scriptedField.script, scriptedField.type); const scriptedFields = indexPattern.getScriptedFields(); // expect(saveSpy.callCount).to.equal(1); expect(scriptedFields).toHaveLength(oldCount + 1); expect(indexPattern.fields.byName[scriptedField.name].name).toEqual(scriptedField.name); }); - test('should remove scripted field, by name', async () => { + it('should remove scripted field, by name', function () { // const saveSpy = sinon.spy(indexPattern, 'save'); const scriptedFields = indexPattern.getScriptedFields(); const oldCount = scriptedFields.length; - const scriptedField = last(scriptedFields); + const scriptedField = _.last(scriptedFields); - await indexPattern.removeScriptedField(scriptedField.name); + indexPattern.removeScriptedField(scriptedField.name); // expect(saveSpy.callCount).to.equal(1); expect(indexPattern.getScriptedFields().length).toEqual(oldCount - 1); expect(indexPattern.fields.byName[scriptedField.name]).toEqual(undefined); }); - test('should not allow duplicate names', async () => { + it('should not allow duplicate names', async () => { const scriptedFields = indexPattern.getScriptedFields(); - const scriptedField = last(scriptedFields); + const scriptedField = _.last(scriptedFields); expect.assertions(1); try { - await indexPattern.addScriptedField(scriptedField.name, "'new script'", 'string', 'lang'); + await indexPattern.addScriptedField(scriptedField.name, '\'new script\'', 'string'); } catch (e) { expect(e).toBeInstanceOf(DuplicateField); } }); }); - describe('popularizeField', () => { - test('should increment the popularity count by default', () => { + describe('popularizeField', function () { + it('should increment the popularity count by default', function () { // const saveSpy = sinon.stub(indexPattern, 'save'); - indexPattern.fields.forEach(async field => { - const oldCount = field.count || 0; + indexPattern.fields.forEach(function (field) { + const oldCount = field.count; - await indexPattern.popularizeField(field.name); + indexPattern.popularizeField(field.name); // expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).toEqual(oldCount + 1); }); }); - test('should increment the popularity count', () => { + it('should increment the popularity count', function () { // const saveSpy = sinon.stub(indexPattern, 'save'); - indexPattern.fields.forEach(async field => { - const oldCount = field.count || 0; + indexPattern.fields.forEach(function (field) { + const oldCount = field.count; const incrementAmount = 4; - await indexPattern.popularizeField(field.name, incrementAmount); + indexPattern.popularizeField(field.name, incrementAmount); // expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).toEqual(oldCount + incrementAmount); }); }); - test('should decrement the popularity count', () => { - indexPattern.fields.forEach(async field => { - const oldCount = field.count || 0; + it('should decrement the popularity count', function () { + indexPattern.fields.forEach(function (field) { + const oldCount = field.count; const incrementAmount = 4; const decrementAmount = -2; - await indexPattern.popularizeField(field.name, incrementAmount); - await indexPattern.popularizeField(field.name, decrementAmount); + indexPattern.popularizeField(field.name, incrementAmount); + indexPattern.popularizeField(field.name, decrementAmount); expect(field.count).toEqual(oldCount + incrementAmount + decrementAmount); }); }); - test('should not go below 0', () => { - indexPattern.fields.forEach(async field => { + it('should not go below 0', function () { + indexPattern.fields.forEach(function (field) { const decrementAmount = -Number.MAX_VALUE; - - await indexPattern.popularizeField(field.name, decrementAmount); - + indexPattern.popularizeField(field.name, decrementAmount); expect(field.count).toEqual(0); }); }); }); - test('should handle version conflicts', async () => { + it('should handle version conflicts', async () => { setDocsourcePayload(null, { - _id: 'foo', _version: 'foo', + _id: 'foo', attributes: { - title: 'something', - }, + title: 'something' + } }); // Create a normal index pattern - const pattern = new IndexPattern( - 'foo', - (cfg: any) => config.get(cfg), - savedObjectsClient as any, - apiClient, - patternCache - ); + const pattern = new IndexPattern('foo', cfg => config.get(cfg), savedObjectsClient, apiClient, patternCache); await pattern.init(); - expect(get(pattern, 'version')).toBe('fooa'); + expect(pattern.version).toBe('fooa'); // Create the same one - we're going to handle concurrency - const samePattern = new IndexPattern( - 'foo', - (cfg: any) => config.get(cfg), - savedObjectsClient as any, - apiClient, - patternCache - ); + const samePattern = new IndexPattern('foo', cfg => config.get(cfg), savedObjectsClient, apiClient, patternCache); await samePattern.init(); - expect(get(samePattern, 'version')).toBe('fooaa'); + expect(samePattern.version).toBe('fooaa'); // This will conflict because samePattern did a save (from refreshFields) // but the resave should work fine diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.js similarity index 68% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts rename to src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.js index 8fc5c89e5d17c..039910e8d3b23 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.js @@ -17,65 +17,66 @@ * under the License. */ -// eslint-disable-next-line max-classes-per-file import { IndexPatterns } from './index_patterns'; -import { SavedObjectsClientContract, UiSettingsClientContract } from 'kibana/public'; jest.mock('../errors', () => ({ IndexPatternMissingIndices: jest.fn(), })); + jest.mock('ui/registry/field_formats', () => ({ fieldFormats: { getDefaultInstance: jest.fn(), - }, + } })); jest.mock('ui/notify', () => ({ toastNotifications: { addDanger: jest.fn(), - }, + } })); jest.mock('./index_pattern', () => { class IndexPattern { init = async () => { return this; - }; + } } return { - IndexPattern, + IndexPattern }; }); jest.mock('./index_patterns_api_client', () => { class IndexPatternsApiClient { - getFieldsForWildcard = async () => ({}); + getFieldsForWildcard = async () => ({}) } return { - IndexPatternsApiClient, + IndexPatternsApiClient }; }); -describe('IndexPatterns', () => { - let indexPatterns: IndexPatterns; +const savedObjectsClient = { + create: jest.fn(), + get: jest.fn(), + update: jest.fn() +}; - beforeEach(() => { - const savedObjectsClient = {} as SavedObjectsClientContract; - const uiSettings = {} as UiSettingsClientContract; +const config = { + get: jest.fn(), +}; - indexPatterns = new IndexPatterns(uiSettings, savedObjectsClient); - }); - test('does not cache gets without an id', () => { +describe('IndexPatterns', () => { + const indexPatterns = new IndexPatterns(config, savedObjectsClient); + + it('does not cache gets without an id', function () { expect(indexPatterns.get()).not.toBe(indexPatterns.get()); }); - test('does cache gets for the same id', () => { - const id = '1'; - - expect(indexPatterns.get(id)).toBe(indexPatterns.get(id)); + it('does cache gets for the same id', function () { + expect(indexPatterns.get(1)).toBe(indexPatterns.get(1)); }); }); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.js b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.js new file mode 100644 index 0000000000000..bea4c6c489c9b --- /dev/null +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.js @@ -0,0 +1,65 @@ +/* + * 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 { http } from './index_patterns_api_client.test.mock'; +import { IndexPatternsApiClient } from './index_patterns_api_client'; + +describe('IndexPatternsApiClient', () => { + it('uses the right URI to fetch fields for time patterns', async function () { + const fetchSpy = jest.spyOn(http, 'fetch').mockImplementation(() => ({})); + const indexPatternsApiClient = new IndexPatternsApiClient(); + await indexPatternsApiClient.getFieldsForTimePattern(); + + expect(fetchSpy).toHaveBeenCalledWith('/api/index_patterns/_fields_for_time_pattern', { + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + method: 'GET', + prependBasePath: true, + query: {}, + }); + }); + + it('uses the right URI to fetch fields for wildcard', async function () { + const fetchSpy = jest.spyOn(http, 'fetch').mockImplementation(() => ({})); + const indexPatternsApiClient = new IndexPatternsApiClient(); + await indexPatternsApiClient.getFieldsForWildcard(); + + expect(fetchSpy).toHaveBeenCalledWith('/api/index_patterns/_fields_for_wildcard', { + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + method: 'GET', + prependBasePath: true, + query: {}, + }); + }); + + it('uses the right URI to fetch fields for wildcard given a type', async function () { + const fetchSpy = jest.spyOn(http, 'fetch').mockImplementation(() => ({})); + const indexPatternsApiClient = new IndexPatternsApiClient(); + await indexPatternsApiClient.getFieldsForWildcard({ type: 'rollup' }); + + expect(fetchSpy).toHaveBeenCalledWith('/api/index_patterns/rollup/_fields_for_wildcard', { + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + method: 'GET', + prependBasePath: true, + query: {}, + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.js similarity index 99% rename from src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts rename to src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.js index 06933dc409052..a793fa6a25bea 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.js @@ -17,6 +17,7 @@ * under the License. */ + import { setup } from '../../../../../../test_utils/public/http_test_setup'; export const { http } = setup(injectedMetadata => { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts deleted file mode 100644 index 944108e1501f1..0000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { http } from './index_patterns_api_client.test.mock'; -import { IndexPatternsApiClient } from './index_patterns_api_client'; - -const requestData = { - credentials: 'same-origin', - headers: { 'Content-Type': 'application/json' }, - method: 'GET', - prependBasePath: true, - query: {}, -}; - -describe('IndexPatternsApiClient', () => { - let fetchSpy: jest.Mock; - let indexPatternsApiClient: IndexPatternsApiClient; - - beforeEach(() => { - fetchSpy = jest.spyOn(http, 'fetch').mockImplementation(() => Promise.resolve({})); - indexPatternsApiClient = new IndexPatternsApiClient(); - }); - - test('uses the right URI to fetch fields for time patterns', async function() { - const expectedPath = '/api/index_patterns/_fields_for_time_pattern'; - - await indexPatternsApiClient.getFieldsForTimePattern(); - - expect(fetchSpy).toHaveBeenCalledWith(expectedPath, requestData); - }); - - test('uses the right URI to fetch fields for wildcard', async function() { - const expectedPath = '/api/index_patterns/_fields_for_wildcard'; - - await indexPatternsApiClient.getFieldsForWildcard(); - - expect(fetchSpy).toHaveBeenCalledWith(expectedPath, requestData); - }); - - test('uses the right URI to fetch fields for wildcard given a type', async function() { - const expectedPath = '/api/index_patterns/rollup/_fields_for_wildcard'; - - await indexPatternsApiClient.getFieldsForWildcard({ type: 'rollup' }); - - expect(fetchSpy).toHaveBeenCalledWith(expectedPath, requestData); - }); -}); diff --git a/src/legacy/core_plugins/data/public/legacy.ts b/src/legacy/core_plugins/data/public/legacy.ts index 3d86a200eab9b..14350a6284191 100644 --- a/src/legacy/core_plugins/data/public/legacy.ts +++ b/src/legacy/core_plugins/data/public/legacy.ts @@ -34,7 +34,9 @@ * data that will eventually be injected by the new platform. */ -import { npSetup, npStart } from 'ui/new_platform'; +import { npSetup } from 'ui/new_platform'; +// @ts-ignore +import { renderersRegistry } from 'plugins/interpreter/registries'; // @ts-ignore import { getInterpreter } from 'plugins/interpreter/interpreter'; import { LegacyDependenciesPlugin } from './shim/legacy_dependencies_plugin'; @@ -45,9 +47,8 @@ const legacyPlugin = new LegacyDependenciesPlugin(); export const setup = dataPlugin.setup(npSetup.core, { __LEGACY: legacyPlugin.setup(), - inspector: npSetup.plugins.inspector, -}); - -export const start = dataPlugin.start(npStart.core, { - inspector: npStart.plugins.inspector, + interpreter: { + renderersRegistry, + getInterpreter, + }, }); diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index b62c8106bc168..a38e55e8139ed 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -18,16 +18,12 @@ */ import { CoreSetup, CoreStart, Plugin } from '../../../../core/public'; -import { ExpressionsService, ExpressionsSetup, ExpressionsStart } from './expressions'; +import { ExpressionsService, ExpressionsSetup } from './expressions'; import { SearchService, SearchSetup } from './search'; import { QueryService, QuerySetup } from './query'; import { FilterService, FilterSetup } from './filter'; import { IndexPatternsService, IndexPatternsSetup } from './index_patterns'; import { LegacyDependenciesPluginSetup } from './shim/legacy_dependencies_plugin'; -import { - Start as InspectorStart, - Setup as InspectorSetup, -} from '../../../../plugins/inspector/public'; /** * Interface for any dependencies on other plugins' `setup` contracts. @@ -36,11 +32,7 @@ import { */ export interface DataPluginSetupDependencies { __LEGACY: LegacyDependenciesPluginSetup; - inspector: InspectorSetup; -} - -export interface DataPluginStartDependencies { - inspector: InspectorStart; + interpreter: any; } /** @@ -56,10 +48,6 @@ export interface DataSetup { search: SearchSetup; } -export interface DataStart { - expressions: ExpressionsStart; -} - /** * Data Plugin - public * @@ -71,7 +59,7 @@ export interface DataStart { * in the setup/start interfaces. The remaining items exported here are either types, * or static code. */ -export class DataPlugin implements Plugin { +export class DataPlugin implements Plugin { // Exposed services, sorted alphabetically private readonly expressions: ExpressionsService = new ExpressionsService(); private readonly filter: FilterService = new FilterService(); @@ -79,7 +67,7 @@ export class DataPlugin implements Plugin = ({ } const saveQueryForm = ( - + {savedQueryDescriptionText} diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx index 3fbfec364fad9..8d98e4d2f9b7a 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx @@ -18,7 +18,7 @@ */ import { Filter } from '@kbn/es-query'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; +import { RefreshInterval, TimeRange } from 'ui/timefilter'; import { Query } from '../../query/query_bar'; export * from './components'; diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/execute_trigger_actions.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/execute_trigger_actions.ts index b7bf7e33e7c9d..5c6216fb629ac 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/execute_trigger_actions.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/execute_trigger_actions.ts @@ -18,9 +18,9 @@ */ import { EmbeddableApiPure } from './types'; -import { Action, buildContextMenuForActions, openContextMenu } from '../lib'; +import { Action, ActionContext, buildContextMenuForActions, openContextMenu } from '../lib'; -const executeSingleAction = async (action: Action, actionContext: A) => { +const executeSingleAction = async (action: Action, actionContext: ActionContext) => { const href = action.getHref(actionContext); // TODO: Do we need a `getHref()` special case? diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/types.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/types.ts index 18073d2b90083..e68b3b89f5eb0 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/types.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/api/types.ts @@ -24,6 +24,7 @@ import { EmbeddableFactory, ExecuteTriggerActions, GetEmbeddableFactories, + TriggerContext, } from '../lib'; export interface EmbeddableApi { @@ -34,7 +35,7 @@ export interface EmbeddableApi { getEmbeddableFactories: GetEmbeddableFactories; getTrigger: (id: string) => Trigger; getTriggerActions: (id: string) => Action[]; - getTriggerCompatibleActions: (triggerId: string, context: C) => Promise>>; + getTriggerCompatibleActions: (triggerId: string, context: TriggerContext) => Promise; registerAction: (action: Action) => void; // TODO: Make `registerEmbeddableFactory` receive only `factory` argument. registerEmbeddableFactory: (id: string, factory: EmbeddableFactory) => void; diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/index.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/index.ts index 79bdd65f9cfcd..4cceceb57824f 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/index.ts @@ -26,6 +26,7 @@ export { APPLY_FILTER_TRIGGER, PANEL_BADGE_TRIGGER, Action, + ActionContext, Adapters, AddPanelAction, ApplyFilterAction, @@ -58,6 +59,7 @@ export { PropertySpec, SavedObjectMetaData, Trigger, + TriggerContext, ViewMode, isErrorEmbeddable, openAddPanelFlyout, diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/action.ts index 5569a3938bc72..1002915de1d93 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/action.ts @@ -17,7 +17,20 @@ * under the License. */ -export abstract class Action { +import { IEmbeddable } from '../embeddables'; + +export interface ActionContext< + TEmbeddable extends IEmbeddable = IEmbeddable, + TTriggerContext extends {} = {} +> { + embeddable: TEmbeddable; + triggerContext?: TTriggerContext; +} + +export abstract class Action< + TEmbeddable extends IEmbeddable = IEmbeddable, + TTriggerContext extends {} = {} +> { /** * Determined the order when there is more than one action matched to a trigger. * Higher numbers are displayed first. @@ -30,7 +43,7 @@ export abstract class Action { /** * Optional EUI icon type that can be displayed along with the title. */ - public getIconType(context: ActionContext): string | undefined { + public getIconType(context: ActionContext): string | undefined { return undefined; } @@ -38,25 +51,27 @@ export abstract class Action { * Returns a title to be displayed to the user. * @param context */ - public abstract getDisplayName(context: ActionContext): string; + public abstract getDisplayName(context: ActionContext): string; /** * Returns a promise that resolves to true if this action is compatible given the context, * otherwise resolves to false. */ - public async isCompatible(context: ActionContext): Promise { + public async isCompatible( + context: ActionContext + ): Promise { return true; } /** * If this returns something truthy, this is used in addition to the `execute` method when clicked. */ - public getHref(context: ActionContext): string | undefined { + public getHref(context: ActionContext): string | undefined { return undefined; } /** * Executes the action. */ - public abstract async execute(context: ActionContext): Promise; + public abstract execute(context: ActionContext): void; } diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.test.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.test.ts index c14f258b5641c..e32500af1d7fd 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.test.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.test.ts @@ -48,7 +48,9 @@ describe('isCompatible()', () => { }), }), } as any, - filters: [], + triggerContext: { + filters: [], + }, }); expect(result).toBe(true); }); @@ -63,7 +65,9 @@ describe('isCompatible()', () => { }), }), } as any, - filters: [], + triggerContext: { + filters: [], + }, }); expect(result).toBe(false); }); @@ -79,8 +83,25 @@ describe('isCompatible()', () => { }), }), } as any, - } as any); + triggerContext: { + // filters: [], + } as any, + }); expect(result1).toBe(false); + + const result2 = await action.isCompatible({ + embeddable: { + getRoot: () => ({ + getInput: () => ({ + filters: [], + }), + }), + } as any, + // triggerContext: { + // filters: [], + // } as any + }); + expect(result2).toBe(false); }); }); @@ -104,41 +125,42 @@ describe('execute()', () => { const error = expectError(() => action.execute({ embeddable: getEmbeddable(), + triggerContext: {}, } as any) ); expect(error).toBeInstanceOf(Error); }); - test('updates filter input on success', async done => { + test('updates filter input on success', () => { const action = new ApplyFilterAction(); const [embeddable, root] = getEmbeddable(); - await action.execute({ + action.execute({ embeddable, - filters: ['FILTER' as any], + triggerContext: { + filters: ['FILTER' as any], + }, }); expect(root.updateInput).toHaveBeenCalledTimes(1); expect(root.updateInput.mock.calls[0][0]).toMatchObject({ filters: ['FILTER'], }); - - done(); }); - test('checks if action isCompatible', async done => { + test('checks if action isCompatible', () => { const action = new ApplyFilterAction(); const spy = jest.spyOn(action, 'isCompatible'); const [embeddable] = getEmbeddable(); - await action.execute({ + action.execute({ embeddable, - filters: ['FILTER' as any], + triggerContext: { + filters: ['FILTER' as any], + }, }); expect(spy).toHaveBeenCalledTimes(1); - - done(); }); }); }); diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.ts index 24549a64bfc23..c7b12629eeabe 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/apply_filter_action.ts @@ -20,18 +20,14 @@ import { i18n } from '@kbn/i18n'; import { Filter } from '@kbn/es-query'; import { IEmbeddable, EmbeddableInput } from '../embeddables'; -import { Action } from './action'; +import { Action, ActionContext } from './action'; import { IncompatibleActionError } from '../errors'; export const APPLY_FILTER_ACTION = 'APPLY_FILTER_ACTION'; type RootEmbeddable = IEmbeddable; -interface ActionContext { - embeddable: IEmbeddable; - filters: Filter[]; -} -export class ApplyFilterAction extends Action { +export class ApplyFilterAction extends Action { public readonly type = APPLY_FILTER_ACTION; constructor() { @@ -44,27 +40,30 @@ export class ApplyFilterAction extends Action { }); } - public async isCompatible(context: ActionContext) { - if (context.embeddable === undefined) { - return false; - } + public async isCompatible(context: ActionContext) { const root = context.embeddable.getRoot() as RootEmbeddable; - return Boolean(root.getInput().filters !== undefined && context.filters !== undefined); + return Boolean( + root.getInput().filters !== undefined && + context.triggerContext && + context.triggerContext.filters !== undefined + ); } - public async execute({ embeddable, filters }: ActionContext) { - if (!filters || !embeddable) { - throw new Error('Applying a filter requires a filter and embeddable as context'); + public execute({ + embeddable, + triggerContext, + }: ActionContext) { + if (!triggerContext) { + throw new Error('Applying a filter requires a filter as context'); } + const root = embeddable.getRoot() as RootEmbeddable; - if (!(await this.isCompatible({ embeddable, filters }))) { + if (!this.isCompatible({ triggerContext, embeddable })) { throw new IncompatibleActionError(); } - const root = embeddable.getRoot() as RootEmbeddable; - root.updateInput({ - filters, + filters: triggerContext.filters, }); } } diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/edit_panel_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/edit_panel_action.ts index 0b37491212810..5edd82bd14a37 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/edit_panel_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/edit_panel_action.ts @@ -18,18 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Action } from './action'; +import { Action, ActionContext } from './action'; import { GetEmbeddableFactory, ViewMode } from '../types'; import { EmbeddableFactoryNotFoundError } from '../errors'; -import { IEmbeddable } from '../embeddables'; export const EDIT_PANEL_ACTION_ID = 'editPanel'; -interface ActionContext { - embeddable: IEmbeddable; -} - -export class EditPanelAction extends Action { +export class EditPanelAction extends Action { public readonly type = EDIT_PANEL_ACTION_ID; constructor(private readonly getEmbeddableFactory: GetEmbeddableFactory) { super(EDIT_PANEL_ACTION_ID); @@ -61,8 +56,8 @@ export class EditPanelAction extends Action { return Boolean(canEditEmbeddable && inDashboardEditMode); } - public async execute() { - return; + public execute() { + return undefined; } public getHref({ embeddable }: ActionContext): string { diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/index.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/index.ts index 7deace8345af9..ae0df0a2122c6 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/index.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions/index.ts @@ -17,6 +17,6 @@ * under the License. */ -export { Action } from './action'; +export { Action, ActionContext } from './action'; export * from './apply_filter_action'; export * from './edit_panel_action'; diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/context_menu_actions/build_eui_context_menu_panels.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/context_menu_actions/build_eui_context_menu_panels.ts index 5b1998834a2ed..b51a40a3ef2d9 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/context_menu_actions/build_eui_context_menu_panels.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/context_menu_actions/build_eui_context_menu_panels.ts @@ -20,21 +20,21 @@ import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { Action } from '../actions'; +import { Action, ActionContext } from '../actions'; /** * Transforms an array of Actions to the shape EuiContextMenuPanel expects. */ -export async function buildContextMenuForActions({ +export async function buildContextMenuForActions({ actions, actionContext, closeMenu, }: { - actions: Array>; - actionContext: A; + actions: Action[]; + actionContext: ActionContext; closeMenu: () => void; }): Promise { - const menuItems = await buildEuiContextMenuPanelItems({ + const menuItems = await buildEuiContextMenuPanelItems({ actions, actionContext, closeMenu, @@ -52,13 +52,13 @@ export async function buildContextMenuForActions({ /** * Transform an array of Actions into the shape needed to build an EUIContextMenu */ -async function buildEuiContextMenuPanelItems({ +async function buildEuiContextMenuPanelItems({ actions, actionContext, closeMenu, }: { - actions: Array>; - actionContext: A; + actions: Action[]; + actionContext: ActionContext; closeMenu: () => void; }) { const items: EuiContextMenuPanelItemDescriptor[] = []; @@ -88,13 +88,13 @@ async function buildEuiContextMenuPanelItems({ * @param {Embeddable} embeddable * @return {EuiContextMenuPanelItemDescriptor} */ -function convertPanelActionToContextMenuItem({ +function convertPanelActionToContextMenuItem({ action, actionContext, closeMenu, }: { - action: Action; - actionContext: A; + action: Action; + actionContext: ActionContext; closeMenu: () => void; }): EuiContextMenuPanelItemDescriptor { const menuPanelItem: EuiContextMenuPanelItemDescriptor = { diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/embeddable_panel.tsx b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/embeddable_panel.tsx index 172a5a76c4b21..c06f4ed99e8fd 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/embeddable_panel.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/embeddable_panel.tsx @@ -37,7 +37,7 @@ import { AddPanelAction } from './panel_header/panel_actions/add_panel/add_panel import { CustomizePanelTitleAction } from './panel_header/panel_actions/customize_title/customize_panel_action'; import { PanelHeader } from './panel_header/panel_header'; import { InspectPanelAction } from './panel_header/panel_actions/inspect_panel_action'; -import { EditPanelAction, Action } from '../actions'; +import { EditPanelAction, Action, ActionContext } from '../actions'; import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal'; import { Start as InspectorStartContract } from '../../../../../../../../plugins/inspector/public'; @@ -192,7 +192,7 @@ export class EmbeddablePanel extends React.Component { }); const createGetUserData = (overlays: CoreStart['overlays']) => - async function getUserData(context: { embeddable: IEmbeddable }) { + async function getUserData(context: ActionContext) { return new Promise<{ title: string | undefined }>(resolve => { const session = overlays.openModal( { test('Is not compatible when embeddable is not a container', async () => { expect( - // @ts-ignore - await action.isCompatible({ embeddable }) + await action.isCompatible({ + embeddable, + }) ).toBe(false); }); diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts index 7a49927e47251..5c660f392d42c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts @@ -18,19 +18,14 @@ */ import { i18n } from '@kbn/i18n'; import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types'; -import { Action } from '../../../../actions'; +import { Action, ActionContext } from '../../../../actions'; import { openAddPanelFlyout } from './open_add_panel_flyout'; import { NotificationsStart } from '../../../../../../../../../../../core/public'; import { KibanaReactOverlays } from '../../../../../../../../../../../plugins/kibana_react/public'; -import { IContainer } from '../../../../containers'; export const ADD_PANEL_ACTION_ID = 'ADD_PANEL_ACTION_ID'; -interface ActionContext { - embeddable: IContainer; -} - -export class AddPanelAction extends Action { +export class AddPanelAction extends Action { public readonly type = ADD_PANEL_ACTION_ID; constructor( diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts index d91cee3048c1f..51001de0fefb5 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts @@ -18,19 +18,14 @@ */ import { i18n } from '@kbn/i18n'; -import { Action } from '../../../../actions'; +import { Action, ActionContext } from '../../../../actions'; import { ViewMode } from '../../../../types'; -import { IEmbeddable } from '../../../../embeddables'; const CUSTOMIZE_PANEL_ACTION_ID = 'CUSTOMIZE_PANEL_ACTION_ID'; type GetUserData = (context: ActionContext) => Promise<{ title: string | undefined }>; -interface ActionContext { - embeddable: IEmbeddable; -} - -export class CustomizePanelTitleAction extends Action { +export class CustomizePanelTitleAction extends Action { public readonly type = CUSTOMIZE_PANEL_ACTION_ID; constructor(private readonly getDataFromUser: GetUserData) { diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts index 3e4e2a36d46b9..06ba48a5e23b2 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts @@ -18,17 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { Action } from '../../../actions'; +import { Action, ActionContext } from '../../../actions'; import { Start as InspectorStartContract } from '../../../../../../../../../../plugins/inspector/public'; -import { IEmbeddable } from '../../../embeddables'; export const INSPECT_PANEL_ACTION_ID = 'openInspector'; -interface ActionContext { - embeddable: IEmbeddable; -} - -export class InspectPanelAction extends Action { +export class InspectPanelAction extends Action { public readonly type = INSPECT_PANEL_ACTION_ID; constructor(private readonly inspector: InspectorStartContract) { diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx index 22e3be89f1ae9..6421f4e85730c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx @@ -28,7 +28,7 @@ import { } from '../../../test_samples/embeddables/filterable_embeddable'; import { FilterableEmbeddableFactory } from '../../../test_samples/embeddables/filterable_embeddable_factory'; import { FilterableContainer } from '../../../test_samples/embeddables/filterable_container'; -import { GetEmbeddableFactory, ViewMode } from '../../../types'; +import { GetEmbeddableFactory } from '../../../types'; import { ContactCardEmbeddable } from '../../../test_samples/embeddables/contact_card/contact_card_embeddable'; const embeddableFactories = new Map(); @@ -45,7 +45,7 @@ beforeEach(async () => { query: { match: {} }, }; container = new FilterableContainer( - { id: 'hello', panels: {}, filters: [derivedFilter], viewMode: ViewMode.EDIT }, + { id: 'hello', panels: {}, filters: [derivedFilter] }, getFactory ); @@ -55,7 +55,6 @@ beforeEach(async () => { FilterableEmbeddable >(FILTERABLE_EMBEDDABLE, { id: '123', - viewMode: ViewMode.EDIT, }); if (isErrorEmbeddable(filterableEmbeddable)) { @@ -69,7 +68,7 @@ test('Removes the embeddable', async () => { const removePanelAction = new RemovePanelAction(); expect(container.getChild(embeddable.id)).toBeDefined(); - await removePanelAction.execute({ embeddable }); + removePanelAction.execute({ embeddable }); expect(container.getChild(embeddable.id)).toBeUndefined(); }); diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts index 2a61857d17f77..f8fc9d02992d4 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts @@ -19,9 +19,8 @@ import { i18n } from '@kbn/i18n'; import { ContainerInput, IContainer } from '../../../containers'; import { ViewMode } from '../../../types'; -import { Action } from '../../../actions'; +import { Action, ActionContext } from '../../../actions'; import { IncompatibleActionError } from '../../../errors'; -import { IEmbeddable } from '../../../embeddables'; export const REMOVE_PANEL_ACTION = 'deletePanel'; @@ -29,17 +28,13 @@ interface ExpandedPanelInput extends ContainerInput { expandedPanelId: string; } -interface ActionContext { - embeddable: IEmbeddable; -} - function hasExpandedPanelInput( container: IContainer ): container is IContainer<{}, ExpandedPanelInput> { return (container as IContainer<{}, ExpandedPanelInput>).getInput().expandedPanelId !== undefined; } -export class RemovePanelAction extends Action { +export class RemovePanelAction extends Action { public readonly type = REMOVE_PANEL_ACTION; constructor() { super(REMOVE_PANEL_ACTION); @@ -68,8 +63,8 @@ export class RemovePanelAction extends Action { ); } - public async execute({ embeddable }: ActionContext) { - if (!embeddable.parent || !(await this.isCompatible({ embeddable }))) { + public execute({ embeddable }: ActionContext) { + if (!embeddable.parent || !this.isCompatible({ embeddable })) { throw new IncompatibleActionError(); } embeddable.parent.removeEmbeddable(embeddable.id); diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/edit_mode_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/edit_mode_action.ts index f87ff835f59cc..d2e893887808b 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/edit_mode_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/edit_mode_action.ts @@ -18,15 +18,11 @@ */ import { ViewMode } from '../../types'; -import { Action } from '../../actions'; -import { IEmbeddable } from '../../embeddables'; +import { Action, ActionContext } from '../../actions'; export const EDIT_MODE_ACTION = 'EDIT_MODE_ACTION'; -interface ActionContext { - embeddable: IEmbeddable; -} -export class EditModeAction extends Action { +export class EditModeAction extends Action { public readonly type = EDIT_MODE_ACTION; constructor() { @@ -41,7 +37,7 @@ export class EditModeAction extends Action { return context.embeddable.getInput().viewMode === ViewMode.EDIT; } - async execute() { + execute() { return; } } diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/hello_world_action.tsx b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/hello_world_action.tsx index 356679b5e4501..bfd7ac6541f01 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/hello_world_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/hello_world_action.tsx @@ -35,7 +35,7 @@ export class HelloWorldAction extends Action { return 'Hello World Action!'; } - public async execute() { + public execute() { const flyoutSession = this.overlays.openFlyout( flyoutSession && flyoutSession.close()}> Hello World, I am a hello world action! diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/restricted_action.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/restricted_action.ts index 16aede6da18d1..950b843816c7a 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/restricted_action.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/restricted_action.ts @@ -17,15 +17,15 @@ * under the License. */ -import { Action } from '../../actions'; +import { Action, ActionContext } from '../../actions'; export const RESTRICTED_ACTION = 'RESTRICTED_ACTION'; -export class RestrictedAction extends Action { +export class RestrictedAction extends Action { public readonly type = RESTRICTED_ACTION; - private isCompatibleFn: (context: A) => boolean; - constructor(isCompatible: (context: A) => boolean) { + private isCompatibleFn: (context: ActionContext) => boolean; + constructor(isCompatible: (context: ActionContext) => boolean) { super(RESTRICTED_ACTION); this.isCompatibleFn = isCompatible; } @@ -34,9 +34,9 @@ export class RestrictedAction extends Action { return `I am only sometimes compatible`; } - async isCompatible(context: A) { + async isCompatible(context: ActionContext) { return this.isCompatibleFn(context); } - async execute() {} + execute() {} } diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/say_hello_action.tsx b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/say_hello_action.tsx index 1a884148498aa..e64d0e8cec58c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/say_hello_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/say_hello_action.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { Action } from '../../actions'; +import { Action, ActionContext } from '../../actions'; import { EmbeddableInput, Embeddable, EmbeddableOutput, IEmbeddable } from '../../embeddables'; import { IncompatibleActionError } from '../../errors'; @@ -36,12 +36,7 @@ export function hasFullNameOutput( ); } -interface ActionContext { - embeddable: Embeddable; - message?: string; -} - -export class SayHelloAction extends Action { +export class SayHelloAction extends Action { public readonly type = SAY_HELLO_ACTION; private sayHello: (name: string) => void; @@ -58,7 +53,9 @@ export class SayHelloAction extends Action { // Can use typescript generics to get compiler time warnings for immediate feedback if // the context is not compatible. - async isCompatible(context: ActionContext) { + async isCompatible( + context: ActionContext> + ) { // Option 1: only compatible with Greeting Embeddables. // return context.embeddable.type === CONTACT_CARD_EMBEDDABLE; @@ -66,15 +63,20 @@ export class SayHelloAction extends Action { return hasFullNameOutput(context.embeddable); } - async execute(context: ActionContext) { + async execute( + context: ActionContext< + Embeddable, + { message?: string } + > + ) { if (!(await this.isCompatible(context))) { throw new IncompatibleActionError(); } const greeting = `Hello, ${context.embeddable.getOutput().fullName}`; - if (context.message) { - this.sayHello(`${greeting}. ${context.message}`); + if (context.triggerContext && context.triggerContext.message) { + this.sayHello(`${greeting}. ${context.triggerContext.message}`); } else { this.sayHello(greeting); } diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/send_message_action.tsx b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/send_message_action.tsx index 0f54f80398f20..9ac8232725299 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/send_message_action.tsx +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/test_samples/actions/send_message_action.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { EuiFlyoutBody } from '@elastic/eui'; -import { Action, IncompatibleActionError } from '../..'; +import { Action, ActionContext, IncompatibleActionError } from '../..'; import { Embeddable, EmbeddableInput } from '../../embeddables'; import { GetMessageModal } from './get_message_modal'; import { FullNameEmbeddableOutput, hasFullNameOutput } from './say_hello_action'; @@ -26,10 +26,6 @@ import { CoreStart } from '../../../../../../../../../core/public'; export const SEND_MESSAGE_ACTION = 'SEND_MESSAGE_ACTION'; -interface ActionContext { - embeddable: Embeddable; - message: string; -} export class SendMessageAction extends Action { public readonly type = SEND_MESSAGE_ACTION; @@ -41,18 +37,28 @@ export class SendMessageAction extends Action { return 'Send message'; } - async isCompatible(context: ActionContext) { + async isCompatible( + context: ActionContext> + ) { return hasFullNameOutput(context.embeddable); } - async sendMessage(context: ActionContext, message: string) { + async sendMessage( + context: ActionContext>, + message: string + ) { const greeting = `Hello, ${context.embeddable.getOutput().fullName}`; const content = message ? `${greeting}. ${message}` : greeting; this.overlays.openFlyout({content}); } - async execute(context: ActionContext) { + async execute( + context: ActionContext< + Embeddable, + { message?: string } + > + ) { if (!(await this.isCompatible(context))) { throw new IncompatibleActionError(); } diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/types.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/types.ts index 925afe07ff539..9d10a0f059f3c 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/types.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/types.ts @@ -17,7 +17,9 @@ * under the License. */ -import { Action } from './actions'; +import { Action, ActionContext } from './actions'; +import { IEmbeddable } from './embeddables'; +import { IContainer } from './containers'; import { EmbeddableFactory } from './embeddables/embeddable_factory'; import { Adapters } from '../../../../../../../plugins/inspector/public'; @@ -53,10 +55,18 @@ export interface SavedObjectMetaData { showSavedObject?(savedObject: any): any; } -export type ExecuteTriggerActions = (triggerId: string, actionContext: A) => Promise; -export type GetActionsCompatibleWithTrigger = ( +export interface TriggerContext { + embeddable: IEmbeddable; + container?: IContainer; +} + +export type ExecuteTriggerActions = ( + triggerId: string, + actionContext: ActionContext +) => Promise; +export type GetActionsCompatibleWithTrigger = ( triggerId: string, - context: C + context: TriggerContext ) => Promise; export type GetEmbeddableFactory = (id: string) => EmbeddableFactory | undefined; export type GetEmbeddableFactories = () => IterableIterator; diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/apply_filter_action.test.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/apply_filter_action.test.ts index 6e5d2182b7063..7cd56290bf781 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/apply_filter_action.test.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/apply_filter_action.test.ts @@ -85,7 +85,7 @@ test('ApplyFilterAction applies the filter to the root of the container tree', a query: { match: { extension: { query: 'foo' } } }, }; - await applyFilterAction.execute({ embeddable, filters: [filter] }); + applyFilterAction.execute({ embeddable, triggerContext: { filters: [filter] } }); expect(root.getInput().filters.length).toBe(1); expect(node1.getInput().filters.length).toBe(1); expect(embeddable.getInput().filters.length).toBe(1); @@ -124,7 +124,6 @@ test('ApplyFilterAction is incompatible if the root container does not accept a throw new Error(); } - // @ts-ignore expect(await applyFilterAction.isCompatible({ embeddable })).toBe(false); }); @@ -161,7 +160,6 @@ test('trying to execute on incompatible context throws an error ', async () => { } async function check() { - // @ts-ignore await applyFilterAction.execute({ embeddable }); } await expect(check()).rejects.toThrow(Error); diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/execute_trigger_actions.test.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/execute_trigger_actions.test.ts index 1b5a80af52987..a2406a548b0cb 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/execute_trigger_actions.test.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/execute_trigger_actions.test.ts @@ -19,7 +19,7 @@ import { testPlugin, TestPluginReturn } from './test_plugin'; import { of } from './helpers'; -import { Action, openContextMenu, IEmbeddable } from '../lib'; +import { ActionContext, Action, openContextMenu } from '../lib'; import { ContactCardEmbeddable, CONTACT_USER_TRIGGER, @@ -31,11 +31,11 @@ jest.mock('../lib/context_menu_actions'); const executeFn = jest.fn(); const openContextMenuSpy = (openContextMenu as any) as jest.SpyInstance; -class TestAction extends Action { +class TestAction extends Action { public readonly type = 'testAction'; - public checkCompatibility: (context: A) => boolean; + public checkCompatibility: (context: ActionContext) => boolean; - constructor(id: string, checkCompatibility: (context: A) => boolean) { + constructor(id: string, checkCompatibility: (context: ActionContext) => boolean) { super(id); this.checkCompatibility = checkCompatibility; } @@ -44,11 +44,11 @@ class TestAction extends Action { return 'test'; } - async isCompatible(context: A) { + async isCompatible(context: ActionContext) { return this.checkCompatibility(context); } - async execute(context: unknown) { + execute(context: ActionContext) { executeFn(context); } } @@ -124,10 +124,7 @@ test('does not execute an incompatible action', async () => { title: 'My trigger', actionIds: ['test1'], }; - const action = new TestAction<{ embeddable: IEmbeddable }>( - 'test1', - ({ embeddable }) => embeddable.id === 'executeme' - ); + const action = new TestAction('test1', ({ embeddable }) => embeddable.id === 'executeme'); const embeddable = new ContactCardEmbeddable( { id: 'executeme', @@ -190,7 +187,7 @@ test('passes whole action context to isCompatible()', async () => { title: 'My trigger', actionIds: ['test'], }; - const action = new TestAction<{ triggerContext: any }>('test', ({ triggerContext }) => { + const action = new TestAction('test', ({ triggerContext }) => { expect(triggerContext).toEqual({ foo: 'bar', }); diff --git a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/get_trigger_compatible_actions.test.ts b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/get_trigger_compatible_actions.test.ts index f70bfcb080ae9..ba926458cce13 100644 --- a/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/get_trigger_compatible_actions.test.ts +++ b/src/legacy/core_plugins/embeddable_api/public/np_ready/public/tests/get_trigger_compatible_actions.test.ts @@ -22,7 +22,7 @@ import { HelloWorldAction } from '../lib/test_samples/actions/hello_world_action import { SayHelloAction } from '../lib/test_samples/actions/say_hello_action'; import { RestrictedAction } from '../lib/test_samples/actions/restricted_action'; import { EmptyEmbeddable } from '../lib/test_samples/embeddables/empty_embeddable'; -import { CONTEXT_MENU_TRIGGER, IEmbeddable } from '../lib'; +import { ActionContext, CONTEXT_MENU_TRIGGER } from '../lib'; import { of } from './helpers'; let action: SayHelloAction; @@ -73,7 +73,7 @@ test('getTriggerCompatibleActions returns attached actions', async () => { test('filters out actions not applicable based on the context', async () => { const { setup, doStart } = embeddables; - const restrictedAction = new RestrictedAction<{ embeddable: IEmbeddable }>(context => { + const restrictedAction = new RestrictedAction((context: ActionContext) => { return context.embeddable.id === 'accept'; }); diff --git a/src/legacy/core_plugins/kibana/common/field_formats/types/__tests__/string.js b/src/legacy/core_plugins/kibana/common/field_formats/types/__tests__/string.js index 659d040189891..04c0b9a045d38 100644 --- a/src/legacy/core_plugins/kibana/common/field_formats/types/__tests__/string.js +++ b/src/legacy/core_plugins/kibana/common/field_formats/types/__tests__/string.js @@ -70,11 +70,4 @@ describe('String Format', function () { expect(string.convert(value)).to.be(value); }); - it('decode a URL Param string', function () { - const string = new StringFormat({ - transform: 'urlparam' - }); - expect(string.convert('%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98')).to.be('안녕 키바나'); - }); - }); diff --git a/src/legacy/core_plugins/kibana/common/field_formats/types/string.js b/src/legacy/core_plugins/kibana/common/field_formats/types/string.js index 9440f740c7659..acab65058159d 100644 --- a/src/legacy/core_plugins/kibana/common/field_formats/types/string.js +++ b/src/legacy/core_plugins/kibana/common/field_formats/types/string.js @@ -26,8 +26,7 @@ const TRANSFORM_OPTIONS = [ { kind: 'upper', text: 'Upper Case' }, { kind: 'title', text: 'Title Case' }, { kind: 'short', text: 'Short Dots' }, - { kind: 'base64', text: 'Base64 Decode' }, - { kind: 'urlparam', text: 'URL Param Decode' } + { kind: 'base64', text: 'Base64 Decode' } ]; const DEFAULT_TRANSFORM_OPTION = false; @@ -58,7 +57,6 @@ export function createStringFormat(FieldFormat) { case 'title': return this._toTitleCase(val); case 'short': return shortenDottedString(val); case 'base64': return this._base64Decode(val); - case 'urlparam': return decodeURIComponent(val); default: return asPrettyString(val); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 1f65ccebb67d9..a0fe853484eee 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -36,7 +36,7 @@ import { import { KbnUrl } from 'ui/url/kbn_url'; import { Filter } from '@kbn/es-query'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data'; 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 5b4a4c99d403a..081ad3fa3a95d 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 @@ -33,7 +33,8 @@ import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { docTitle } from 'ui/doc_title/doc_title'; -import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; +// @ts-ignore +import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; @@ -603,7 +604,7 @@ export class DashboardAppController { * @return {Promise} * @resolved {String} - The id of the doc */ - function save(saveOptions: SaveOptions): Promise { + function save(saveOptions: SaveOptions): Promise<{ id?: string } | { error: Error }> { return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions) .then(function(id) { if (id) { @@ -694,7 +695,7 @@ export class DashboardAppController { isTitleDuplicateConfirmed, onTitleDuplicate, }; - return save(saveOptions).then((response: SaveResult) => { + return save(saveOptions).then((response: { id?: string } | { error: Error }) => { // If the save wasn't successful, put the original values back. if (!(response as { id: string }).id) { dashboardStateManager.setTitle(currentTitle); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index be76f498c8911..40e65f5683ee2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -23,8 +23,7 @@ import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; import { AppStateClass } from 'ui/state_management/app_state'; import { DashboardAppState } from './types'; -import { TimeRange } from 'src/plugins/data/public'; -import { Timefilter } from 'ui/timefilter'; +import { Timefilter, TimeRange } from 'ui/timefilter'; import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; describe('DashboardState', function() { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts index 707b5a0f5f5f5..93f487ba6d502 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts @@ -19,8 +19,7 @@ import _ from 'lodash'; import { AppState } from 'ui/state_management/app_state'; -import { Timefilter } from 'ui/timefilter'; -import { RefreshInterval } from 'src/plugins/data/public'; +import { Timefilter, RefreshInterval } from 'ui/timefilter'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index d8216361562e2..a8cd8ffcb77f9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -19,10 +19,14 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { + EuiLink, + EuiButton, + EuiEmptyPrompt, +} from '@elastic/eui'; import { TableListView } from './../../table_list_view'; @@ -33,7 +37,8 @@ export const EMPTY_FILTER = ''; // and not supporting server-side paging. // This component does not try to tackle these problems (yet) and is just feature matching the legacy component // TODO support server side sorting/paging once title and description are sortable on the server. -export class DashboardListing extends React.Component { +class DashboardListingUi extends React.Component { + constructor(props) { super(props); } @@ -49,15 +54,21 @@ export class DashboardListing extends React.Component { listingLimit={this.props.listingLimit} initialFilter={this.props.initialFilter} noItemsFragment={this.getNoItemsMessage()} - entityName={i18n.translate('kbn.dashboard.listing.table.entityName', { - defaultMessage: 'dashboard', - })} - entityNamePlural={i18n.translate('kbn.dashboard.listing.table.entityNamePlural', { - defaultMessage: 'dashboards', - })} - tableListTitle={i18n.translate('kbn.dashboard.listing.dashboardsTitle', { - defaultMessage: 'Dashboards', - })} + entityName={ + i18n.translate('kbn.dashboard.listing.table.entityName', { + defaultMessage: 'dashboard' + }) + } + entityNamePlural={ + i18n.translate('kbn.dashboard.listing.table.entityNamePlural', { + defaultMessage: 'dashboards' + }) + } + tableListTitle={ + i18n.translate('kbn.dashboard.listing.dashboardsTitle', { + defaultMessage: 'Dashboards' + }) + } /> ); } @@ -135,6 +146,7 @@ export class DashboardListing extends React.Component { />
); + } getTableColumns() { @@ -142,7 +154,7 @@ export class DashboardListing extends React.Component { { field: 'title', name: i18n.translate('kbn.dashboard.listing.table.titleColumnName', { - defaultMessage: 'Title', + defaultMessage: 'Title' }), sortable: true, render: (field, record) => ( @@ -152,22 +164,22 @@ export class DashboardListing extends React.Component { > {field} - ), + ) }, { field: 'description', name: i18n.translate('kbn.dashboard.listing.table.descriptionColumnName', { - defaultMessage: 'Description', + defaultMessage: 'Description' }), dataType: 'string', sortable: true, - }, + } ]; return tableColumns; } } -DashboardListing.propTypes = { +DashboardListingUi.propTypes = { createItem: PropTypes.func.isRequired, findItems: PropTypes.func.isRequired, deleteItems: PropTypes.func.isRequired, @@ -178,6 +190,8 @@ DashboardListing.propTypes = { initialFilter: PropTypes.string, }; -DashboardListing.defaultProps = { +DashboardListingUi.defaultProps = { initialFilter: EMPTY_FILTER, }; + +export const DashboardListing = injectI18n(DashboardListingUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js index 57de395525e1b..866cc7300310d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js @@ -17,54 +17,63 @@ * under the License. */ -jest.mock( - 'ui/notify', +jest.mock('ui/notify', () => ({ toastNotifications: { addWarning: () => {}, - }, - }), - { virtual: true } -); + } + }), { virtual: true }); -jest.mock( - 'lodash', +jest.mock('lodash', () => ({ ...require.requireActual('lodash'), // mock debounce to fire immediately with no internal timer - debounce: func => { + debounce: function (func) { function debounced(...args) { return func.apply(this, args); } return debounced; - }, - }), - { virtual: true } -); + } + }), { virtual: true }); import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardListing } from './dashboard_listing'; +import { + DashboardListing, +} from './dashboard_listing'; -const find = num => { +const find = (num) => { const hits = []; for (let i = 0; i < num; i++) { hits.push({ id: `dashboard${i}`, title: `dashboard${i} title`, - description: `dashboard${i} desc`, + description: `dashboard${i} desc` }); } return Promise.resolve({ total: num, - hits: hits, + hits: hits }); }; test('renders empty page in before initial fetch to avoid flickering', () => { - const component = shallow( - {}} + createItem={() => {}} + editItem={() => {}} + getViewUrl={() => {}} + listingLimit={1000} + hideWriteControls={false} + />); + expect(component).toMatchSnapshot(); +}); + +describe('after fetch', () => { + test('initialFilter', async () => { + const component = shallowWithIntl( {}} createItem={() => {}} @@ -72,25 +81,8 @@ test('renders empty page in before initial fetch to avoid flickering', () => { getViewUrl={() => {}} listingLimit={1000} hideWriteControls={false} - /> - ); - expect(component).toMatchSnapshot(); -}); - -describe('after fetch', () => { - test('initialFilter', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - getViewUrl={() => {}} - listingLimit={1000} - hideWriteControls={false} - initialFilter="my dashboard" - /> - ); + initialFilter="my dashboard" + />); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -101,17 +93,15 @@ describe('after fetch', () => { }); test('renders table rows', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - getViewUrl={() => {}} - listingLimit={1000} - hideWriteControls={false} - /> - ); + const component = shallowWithIntl( {}} + createItem={() => {}} + editItem={() => {}} + getViewUrl={() => {}} + listingLimit={1000} + hideWriteControls={false} + />); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -122,17 +112,15 @@ describe('after fetch', () => { }); test('renders call to action when no dashboards exist', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - getViewUrl={() => {}} - listingLimit={1} - hideWriteControls={false} - /> - ); + const component = shallowWithIntl( {}} + createItem={() => {}} + editItem={() => {}} + getViewUrl={() => {}} + listingLimit={1} + hideWriteControls={false} + />); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -143,17 +131,15 @@ describe('after fetch', () => { }); test('hideWriteControls', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - getViewUrl={() => {}} - listingLimit={1} - hideWriteControls={true} - /> - ); + const component = shallowWithIntl( {}} + createItem={() => {}} + editItem={() => {}} + getViewUrl={() => {}} + listingLimit={1} + hideWriteControls={true} + />); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -164,17 +150,15 @@ describe('after fetch', () => { }); test('renders warning when listingLimit is exceeded', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - getViewUrl={() => {}} - listingLimit={1} - hideWriteControls={false} - /> - ); + const component = shallowWithIntl( {}} + createItem={() => {}} + editItem={() => {}} + getViewUrl={() => {}} + listingLimit={1} + hideWriteControls={false} + />); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts index b1b5a57ca27dc..6c8bf3321b05a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import semver from 'semver'; import { GridData } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; -import uuid from 'uuid'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel630, @@ -124,17 +123,16 @@ function migratePre61PanelToLatest( const { columns, sort, row, col, size_x: sizeX, size_y: sizeY, ...rest } = panel; - const panelIndex = panel.panelIndex ? panel.panelIndex.toString() : uuid.v4(); return { ...rest, version, - panelIndex, + panelIndex: panel.panelIndex.toString(), gridData: { x: (col - 1) * PANEL_WIDTH_SCALE_FACTOR, y: (row - 1) * heightScaleFactor, w: sizeX ? sizeX * PANEL_WIDTH_SCALE_FACTOR : DEFAULT_PANEL_WIDTH, h: sizeY ? sizeY * heightScaleFactor : DEFAULT_PANEL_HEIGHT, - i: panelIndex, + i: panel.panelIndex.toString(), }, embeddableConfig, }; @@ -177,11 +175,10 @@ function migrate610PanelToLatest( : PANEL_HEIGHT_SCALE_FACTOR; const { columns, sort, ...rest } = panel; - const panelIndex = panel.panelIndex ? panel.panelIndex.toString() : uuid.v4(); return { ...rest, version, - panelIndex, + panelIndex: panel.panelIndex.toString(), gridData: { w: panel.gridData.w * PANEL_WIDTH_SCALE_FACTOR, h: panel.gridData.h * heightScaleFactor, @@ -215,11 +212,10 @@ function migrate620PanelToLatest( : PANEL_HEIGHT_SCALE_FACTOR; const { columns, sort, ...rest } = panel; - const panelIndex = panel.panelIndex ? panel.panelIndex.toString() : uuid.v4(); return { ...rest, version, - panelIndex, + panelIndex: panel.panelIndex.toString(), gridData: { w: panel.gridData.w * PANEL_WIDTH_SCALE_FACTOR, h: panel.gridData.h * heightScaleFactor, @@ -246,11 +242,10 @@ function migrate630PanelToLatest( } const { columns, sort, ...rest } = panel; - const panelIndex = panel.panelIndex ? panel.panelIndex.toString() : uuid.v4(); return { ...rest, version, - panelIndex, + panelIndex: panel.panelIndex.toString(), embeddableConfig, }; } @@ -261,11 +256,10 @@ function migrate640To720PanelsToLatest( panel: RawSavedDashboardPanel630, version: string ): RawSavedDashboardPanel730ToLatest { - const panelIndex = panel.panelIndex ? panel.panelIndex.toString() : uuid.v4(); return { ...panel, version, - panelIndex, + panelIndex: panel.panelIndex.toString(), }; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts index 6404b5bdbd63b..3ffa6c569cc4b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts @@ -106,38 +106,6 @@ test('dashboard migration 7.3.0 migrates filters to query on search source when expect(newDoc.attributes.uiStateJSON).toBeUndefined(); }); -// See https://github.com/elastic/kibana/issues/44639 - apparently this can happen. -test('dashboard migration works when panelsJSON is missing panelIndex', () => { - const doc: DashboardDoc700To720 = { - id: '1', - type: 'dashboard', - references: [], - attributes: { - description: '', - uiStateJSON: '{}', - title: 'fancy stuff', - timeRestore: true, - version: 1, - panelsJSON: - '[{"id":"funky-funky","type":"visualization","size_x":7,"size_y":5,"col":1,"row":1},{"id":"funky-funky2","type":"search","size_x":5,"size_y":5,"col":8,"row":1,"columns":["_source"],"sort":["@timestamp","desc"]}]', - optionsJSON: '{"darkTheme":false}', - kibanaSavedObjectMeta: { - searchSourceJSON: - '{"filter":[{"query":{"query_string":{"query":"user:spiderman","analyze_wildcard":true}}}]}', - }, - }, - }; - - const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockLogger); - const newDoc = migrations.dashboard['7.3.0'](doc700, mockLogger); - - const parsedSearchSource = JSON.parse(newDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON); - expect(parsedSearchSource.filter.length).toBe(0); - expect(parsedSearchSource.query.query).toBe('user:spiderman'); - - expect(newDoc.attributes.uiStateJSON).toBeUndefined(); -}); - test('dashboard migration 7.3.0 migrates panels', () => { const doc: DashboardDoc700To720 = { id: '1', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts index 7fba2f4003f31..17398a9e828f4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts @@ -129,7 +129,7 @@ export interface RawSavedDashboardPanelTo60 { readonly size_y?: number; readonly row: number; readonly col: number; - panelIndex?: number | string; // earlier versions allowed this to be number or string. Some very early versions seem to be missing this entirely + panelIndex: number | string; // earlier versions allowed this to be number or string readonly name: string; // This is where custom panel titles are stored prior to Embeddable API v2 diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts index 1231ca28ed014..68fd8f0a5a976 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts @@ -20,7 +20,7 @@ import { SearchSource } from 'ui/courier'; import { SavedObject } from 'ui/saved_objects/saved_object'; import moment from 'moment'; -import { RefreshInterval } from 'src/plugins/data/public'; +import { RefreshInterval } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js index ac3bf7ed2d794..b6e2856cfee74 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js @@ -19,48 +19,55 @@ import React from 'react'; import sinon from 'sinon'; -import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { + findTestSubject, +} from '@elastic/eui/lib/test'; -import { DashboardCloneModal } from './clone_modal'; +import { + DashboardCloneModal, +} from './clone_modal'; let onClone; let onClose; +let component; beforeEach(() => { onClone = sinon.spy(); onClose = sinon.spy(); }); -test('renders DashboardCloneModal', () => { - const component = shallowWithI18nProvider( - +function createComponent(creationMethod = mountWithIntl) { + component = creationMethod( + ); +} + +test('renders DashboardCloneModal', () => { + createComponent(shallowWithIntl); expect(component).toMatchSnapshot(); // eslint-disable-line }); test('onClone', () => { - const component = mountWithI18nProvider( - - ); + createComponent(); findTestSubject(component, 'cloneConfirmButton').simulate('click'); sinon.assert.calledWith(onClone, 'dash title'); sinon.assert.notCalled(onClose); }); test('onClose', () => { - const component = mountWithI18nProvider( - - ); + createComponent(); findTestSubject(component, 'cloneCancelButton').simulate('click'); sinon.assert.calledOnce(onClose); sinon.assert.notCalled(onClone); }); test('title', () => { - const component = mountWithI18nProvider( - - ); + createComponent(); const event = { target: { value: 'a' } }; component.find('input').simulate('change', event); findTestSubject(component, 'cloneConfirmButton').simulate('click'); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.tsx index e5e75e4b7d277..f216dcf4506e4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/clone_modal.tsx @@ -18,8 +18,7 @@ */ import React, { Fragment } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { injectI18n, FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import { EuiButton, @@ -44,6 +43,7 @@ interface Props { ) => Promise; onClose: () => void; title: string; + intl: InjectedIntl; } interface State { @@ -53,7 +53,7 @@ interface State { isLoading: boolean; } -export class DashboardCloneModal extends React.Component { +class DashboardCloneModalUi extends React.Component { private isMounted = false; constructor(props: Props) { @@ -117,12 +117,15 @@ export class DashboardCloneModal extends React.Component { @@ -212,3 +215,5 @@ export class DashboardCloneModal extends React.Component { ); } } + +export const DashboardCloneModal = injectI18n(DashboardCloneModalUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/options.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/options.tsx index af284e6f557cb..07c8c392bd805 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/options.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/options.tsx @@ -18,7 +18,7 @@ */ import React, { Component } from 'react'; -import { i18n } from '@kbn/i18n'; +import { injectI18n, InjectedIntl } from '@kbn/i18n/react'; import { EuiForm, EuiFormRow, EuiSwitch } from '@elastic/eui'; @@ -27,6 +27,7 @@ interface Props { onUseMarginsChange: (useMargins: boolean) => void; hidePanelTitles: boolean; onHidePanelTitlesChange: (hideTitles: boolean) => void; + intl: InjectedIntl; } interface State { @@ -34,7 +35,7 @@ interface State { hidePanelTitles: boolean; } -export class OptionsMenu extends Component { +class OptionsMenuUi extends Component { state = { useMargins: this.props.useMargins, hidePanelTitles: this.props.hidePanelTitles, @@ -61,12 +62,10 @@ export class OptionsMenu extends Component { { { ); } } + +export const OptionsMenu = injectI18n(OptionsMenuUi); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js index 153a049276cee..ceee75055fa90 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js @@ -18,20 +18,20 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardSaveModal } from './save_modal'; +import { + DashboardSaveModal, +} from './save_modal'; test('renders DashboardSaveModal', () => { - const component = shallowWithI18nProvider( - {}} - onClose={() => {}} - title="dash title" - description="dash description" - timeRestore={true} - showCopyOnSave={true} - /> - ); + const component = shallowWithIntl( {}} + onClose={() => {}} + title="dash title" + description="dash description" + timeRestore={true} + showCopyOnSave={true} + />); expect(component).toMatchSnapshot(); // eslint-disable-line }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx index 47455f04ba809..d889d4be16fc6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx @@ -18,7 +18,7 @@ */ import React, { Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { injectI18n, FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui'; @@ -46,6 +46,7 @@ interface Props { description: string; timeRestore: boolean; showCopyOnSave: boolean; + intl: InjectedIntl; } interface State { @@ -53,7 +54,7 @@ interface State { timeRestore: boolean; } -export class DashboardSaveModal extends React.Component { +class DashboardSaveModalUi extends React.Component { state: State = { description: this.props.description, timeRestore: this.props.timeRestore, @@ -151,3 +152,5 @@ export class DashboardSaveModal extends React.Component { ); } } + +export const DashboardSaveModal = injectI18n(DashboardSaveModalUi); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row/cell.html b/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row/cell.html index 77702ed606474..cea5712219653 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row/cell.html +++ b/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row/cell.html @@ -17,7 +17,6 @@ class="fa fa-search-plus kbnDocTableRowFilterButton" data-column="<%- column %>" tooltip-append-to-body="1" - data-test-subj="docTableCellFilter" tooltip="{{ ::'kbn.docTable.tableRow.filterForValueButtonTooltip' | i18n: {defaultMessage: 'Filter for value'} }}" aria-label="{{ ::'kbn.docTable.tableRow.filterForValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter for value'} }}" > @@ -26,7 +25,6 @@ ng-click="inlineFilter($event, '-')" class="fa fa-search-minus kbnDocTableRowFilterButton" data-column="<%- column %>" - data-test-subj="docTableCellFilterNegate" tooltip="{{ ::'kbn.docTable.tableRow.filterOutValueButtonTooltip' | i18n: {defaultMessage: 'Filter out value'} }}" aria-label="{{ ::'kbn.docTable.tableRow.filterOutValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter out value'} }}" tooltip-append-to-body="1" diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index e307d5da7ebe8..92fad1713177a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -35,8 +35,7 @@ import { Filter, FilterStateStore } from '@kbn/es-query'; import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; -import { timefilter, getTime } from 'ui/timefilter'; -import { TimeRange } from 'src/plugins/data/public'; +import { timefilter, getTime, TimeRange } from 'ui/timefilter'; import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; import { APPLY_FILTER_TRIGGER, @@ -257,7 +256,9 @@ export class SearchEmbeddable extends Embeddable await this.executeTriggerActions(APPLY_FILTER_TRIGGER, { embeddable: this, - filters, + triggerContext: { + filters, + }, }); }; } diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index 982e56731bacb..88dafaacf7357 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -22,7 +22,7 @@ import { capabilities } from 'ui/capabilities'; import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { EmbeddableFactory, diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts index a0e15e99b742d..981580079ae18 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts @@ -18,7 +18,7 @@ */ import { StaticIndexPattern } from 'ui/index_patterns'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; import { SavedSearch } from '../types'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js index 0155b10f60218..5dc9d8d4ee737 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js @@ -45,9 +45,6 @@ const names = { search: i18n.translate('kbn.management.settings.categoryNames.searchLabel', { defaultMessage: 'Search', }), - siem: i18n.translate('kbn.management.settings.categoryNames.siemLabel', { - defaultMessage: 'SIEM', - }), }; export function getCategoryName(category) { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index 6aa0a08d88cbb..d750010a132d6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -29,7 +29,7 @@ import { } from 'ui/visualize/loader/types'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { Filter } from '@kbn/es-query'; import { EmbeddableInput, diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index a370a7c409fcb..a77baef960af1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -41,6 +41,10 @@ import 'uiExports/visualize'; import { i18n } from '@kbn/i18n'; import { capabilities } from 'ui/capabilities'; +// @ts-ignore +import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +// @ts-ignore +import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import chrome from 'ui/chrome'; import { getVisualizeLoader } from 'ui/visualize/loader'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 4da6ed16a144c..d221dcfc5ecaf 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -30,13 +30,11 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { VisualizeListingTable } from './visualize_listing_table'; import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from 'plugins/visualizations'; import { i18n } from '@kbn/i18n'; const app = uiModules.get('app/visualize', ['ngRoute', 'react']); -app.directive('visualizeListingTable', reactDirective => - reactDirective(wrapInI18nContext(VisualizeListingTable)) -); +app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable))); app.directive('newVisModal', reactDirective => reactDirective(wrapInI18nContext(NewVisModal))); export function VisualizeListingController($injector, createNewVis) { @@ -46,7 +44,7 @@ export function VisualizeListingController($injector, createNewVis) { const savedObjectClient = Private(SavedObjectsClientProvider); this.visTypeRegistry = Private(VisTypesRegistryProvider); - this.visTypeAliases = setup.types.visTypeAliasRegistry.get(); + this.visTypeAliases = visualizations.types.visTypeAliasRegistry.get(); timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); @@ -83,16 +81,15 @@ export function VisualizeListingController($injector, createNewVis) { const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; const visualizationService = services.visualizations; - this.fetchItems = filter => { + this.fetchItems = (filter) => { const isLabsEnabled = config.get('visualize:enableLabs'); - return visualizationService - .findListItems(filter, config.get('savedObjects:listingLimit')) + return visualizationService.findListItems(filter, config.get('savedObjects:listingLimit')) .then(result => { this.totalItems = result.total; return { total: result.total, - hits: result.hits.filter(result => isLabsEnabled || result.type.stage !== 'experimental'), + hits: result.hits.filter(result => (isLabsEnabled || result.type.stage !== 'experimental')) }; }); }; @@ -101,27 +98,23 @@ export function VisualizeListingController($injector, createNewVis) { return Promise.all( selectedItems.map(item => { return savedObjectClient.delete(item.savedObjectType, item.id); - }) - ) - .then(() => { - chrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); - }) - .catch(error => { - toastNotifications.addError(error, { - title: i18n.translate('kbn.visualize.visualizeListingDeleteErrorTitle', { - defaultMessage: 'Error deleting visualization', - }), - }); + }), + ).then(() => { + chrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); + }).catch(error => { + toastNotifications.addError(error, { + title: i18n.translate('kbn.visualize.visualizeListingDeleteErrorTitle', { + defaultMessage: 'Error deleting visualization', + }), }); + }); }; - chrome.breadcrumbs.set([ - { - text: i18n.translate('kbn.visualize.visualizeListingBreadcrumbsTitle', { - defaultMessage: 'Visualize', - }), - }, - ]); + chrome.breadcrumbs.set([{ + text: i18n.translate('kbn.visualize.visualizeListingBreadcrumbsTitle', { + defaultMessage: 'Visualize', + }) + }]); this.listingLimit = config.get('savedObjects:listingLimit'); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js index 5f0a2be54e095..cfe5eeec3834f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js @@ -22,7 +22,7 @@ import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { uiModules } from 'ui/modules'; import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects'; import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from 'plugins/visualizations'; import { createVisualizeEditUrl } from '../visualize_constants'; import { findListItems } from './find_list_items'; @@ -32,18 +32,13 @@ const app = uiModules.get('app/visualize'); // edited by the object editor. savedObjectManagementRegistry.register({ service: 'savedVisualizations', - title: 'visualizations', + title: 'visualizations' }); app.service('savedVisualizations', function (SavedVis, Private, kbnUrl, chrome) { const visTypes = Private(VisTypesRegistryProvider); const savedObjectClient = Private(SavedObjectsClientProvider); - const saveVisualizationLoader = new SavedObjectLoader( - SavedVis, - kbnUrl, - chrome, - savedObjectClient - ); + const saveVisualizationLoader = new SavedObjectLoader(SavedVis, kbnUrl, chrome, savedObjectClient); saveVisualizationLoader.mapHitSource = function (source, id) { source.id = id; @@ -51,11 +46,8 @@ app.service('savedVisualizations', function (SavedVis, Private, kbnUrl, chrome) let typeName = source.typeName; if (source.visState) { - try { - typeName = JSON.parse(source.visState).type; - } catch (e) { - /* missing typename handled below */ - } // eslint-disable-line no-empty + try { typeName = JSON.parse(source.visState).type; } + catch (e) { /* missing typename handled below */ } // eslint-disable-line no-empty } if (!typeName || !visTypes.byName[typeName]) { @@ -86,7 +78,7 @@ app.service('savedVisualizations', function (SavedVis, Private, kbnUrl, chrome) size, mapSavedObjectApiHits: this.mapSavedObjectApiHits.bind(this), savedObjectsClient: this.savedObjectsClient, - visTypes: setup.types.visTypeAliasRegistry.get(), + visTypes: visualizations.types.visTypeAliasRegistry.get(), }); }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index f7bb0a8f45731..4039bf3dff7a5 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -24,7 +24,7 @@ import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { VisType } from 'ui/vis'; -import { VisTypeAlias } from '../../../../visualizations/public/np_ready/public'; +import { VisTypeAlias } from 'plugins/visualizations'; import { VisualizeConstants } from '../visualize_constants'; import { SearchSelection } from './search_selection'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx index 76fc84921627a..f2eb273af8f04 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx @@ -37,7 +37,7 @@ import { } from '@elastic/eui'; import { memoizeLast } from 'ui/utils/memoize'; import { VisType } from 'ui/vis'; -import { VisTypeAlias } from '../../../../../visualizations/public/np_ready/public'; +import { VisTypeAlias } from 'plugins/visualizations'; import { NewVisHelp } from './new_vis_help'; import { VisHelpText } from './vis_help_text'; import { VisTypeIcon } from './vis_type_icon'; diff --git a/src/legacy/core_plugins/metrics/common/set_is_reversed.js b/src/legacy/core_plugins/metrics/common/set_is_reversed.js index b633d004b9705..a5faed3d8a4e8 100644 --- a/src/legacy/core_plugins/metrics/common/set_is_reversed.js +++ b/src/legacy/core_plugins/metrics/common/set_is_reversed.js @@ -52,7 +52,7 @@ export const isBackgroundDark = (backgroundColor, currentTheme) => { const themeIsDark = isThemeDark(currentTheme); // If a background color doesn't exist or it inherits, pass back if it's a darktheme - if (!backgroundColor || backgroundColor === 'inherit') { + if (backgroundColor === undefined || backgroundColor === 'inherit') { return themeIsDark; } diff --git a/src/legacy/core_plugins/metrics/public/components/annotations_editor.js b/src/legacy/core_plugins/metrics/public/components/annotations_editor.js index 53ada137c803f..f517875a522d7 100644 --- a/src/legacy/core_plugins/metrics/public/components/annotations_editor.js +++ b/src/legacy/core_plugins/metrics/public/components/annotations_editor.js @@ -26,10 +26,11 @@ import { AddDeleteButtons } from './add_delete_buttons'; import { ColorPicker } from './color_picker'; import { FieldSelect } from './aggs/field_select'; import uuid from 'uuid'; -import { IconSelect } from './icon_select/icon_select'; +import { IconSelect } from './icon_select'; import { YesNo } from './yes_no'; import { QueryBarWrapper } from './query_bar_wrapper'; import { getDefaultQueryLanguage } from './lib/get_default_query_language'; + import { htmlIdGenerator, EuiFlexGroup, diff --git a/src/legacy/core_plugins/metrics/public/components/icon_select.js b/src/legacy/core_plugins/metrics/public/components/icon_select.js new file mode 100644 index 0000000000000..fcf1f38270949 --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/components/icon_select.js @@ -0,0 +1,127 @@ +/* + * 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 PropTypes from 'prop-types'; +import React from 'react'; +import { EuiComboBox } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +function renderOption(option) { + const icon = option.value; + const label = option.label; + return ( + + + ); +} + +export function IconSelect(props) { + const selectedIcon = props.icons.find(option => { + return props.value === option.value; + }); + return ( + + ); +} + +IconSelect.defaultProps = { + icons: [ + { + value: 'fa-asterisk', + label: i18n.translate('tsvb.iconSelect.asteriskLabel', { defaultMessage: 'Asterisk' }), + }, + { + value: 'fa-bell', + label: i18n.translate('tsvb.iconSelect.bellLabel', { defaultMessage: 'Bell' }), + }, + { + value: 'fa-bolt', + label: i18n.translate('tsvb.iconSelect.boltLabel', { defaultMessage: 'Bolt' }), + }, + { + value: 'fa-bomb', + label: i18n.translate('tsvb.iconSelect.bombLabel', { defaultMessage: 'Bomb' }), + }, + { + value: 'fa-bug', + label: i18n.translate('tsvb.iconSelect.bugLabel', { defaultMessage: 'Bug' }), + }, + { + value: 'fa-comment', + label: i18n.translate('tsvb.iconSelect.commentLabel', { defaultMessage: 'Comment' }), + }, + { + value: 'fa-exclamation-circle', + label: i18n.translate('tsvb.iconSelect.exclamationCircleLabel', { + defaultMessage: 'Exclamation Circle', + }), + }, + { + value: 'fa-exclamation-triangle', + label: i18n.translate('tsvb.iconSelect.exclamationTriangleLabel', { + defaultMessage: 'Exclamation Triangle', + }), + }, + { + value: 'fa-fire', + label: i18n.translate('tsvb.iconSelect.fireLabel', { defaultMessage: 'Fire' }), + }, + { + value: 'fa-flag', + label: i18n.translate('tsvb.iconSelect.flagLabel', { defaultMessage: 'Flag' }), + }, + { + value: 'fa-heart', + label: i18n.translate('tsvb.iconSelect.heartLabel', { defaultMessage: 'Heart' }), + }, + { + value: 'fa-map-marker', + label: i18n.translate('tsvb.iconSelect.mapMarkerLabel', { defaultMessage: 'Map Marker' }), + }, + { + value: 'fa-map-pin', + label: i18n.translate('tsvb.iconSelect.mapPinLabel', { defaultMessage: 'Map Pin' }), + }, + { + value: 'fa-star', + label: i18n.translate('tsvb.iconSelect.starLabel', { defaultMessage: 'Star' }), + }, + { + value: 'fa-tag', + label: i18n.translate('tsvb.iconSelect.tagLabel', { defaultMessage: 'Tag' }), + }, + ], +}; + +IconSelect.propTypes = { + icons: PropTypes.array, + id: PropTypes.string, + onChange: PropTypes.func, + value: PropTypes.string.isRequired, +}; diff --git a/src/legacy/core_plugins/metrics/public/components/icon_select/__snapshots__/icon_select.test.js.snap b/src/legacy/core_plugins/metrics/public/components/icon_select/__snapshots__/icon_select.test.js.snap deleted file mode 100644 index fd22bcafb8df4..0000000000000 --- a/src/legacy/core_plugins/metrics/public/components/icon_select/__snapshots__/icon_select.test.js.snap +++ /dev/null @@ -1,162 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.js should render and match a snapshot 1`] = ` - -`; - -exports[`src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.js should render and match a snapshot 1`] = ` - - - Comment - -`; - -exports[`src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.js ICONS should match and save an icons collection snapshot 1`] = ` -Array [ - Object { - "label": "Asterisk", - "value": "fa-asterisk", - }, - Object { - "label": "Bell", - "value": "fa-bell", - }, - Object { - "label": "Bolt", - "value": "fa-bolt", - }, - Object { - "label": "Comment", - "value": "fa-comment", - }, - Object { - "label": "Map Marker", - "value": "fa-map-marker", - }, - Object { - "label": "Map Pin", - "value": "fa-map-pin", - }, - Object { - "label": "Star", - "value": "fa-star", - }, - Object { - "label": "Tag", - "value": "fa-tag", - }, - Object { - "label": "Bomb", - "value": "fa-bomb", - }, - Object { - "label": "Bug", - "value": "fa-bug", - }, - Object { - "label": "Exclamation Circle", - "value": "fa-exclamation-circle", - }, - Object { - "label": "Exclamation Triangle", - "value": "fa-exclamation-triangle", - }, - Object { - "label": "Fire", - "value": "fa-fire", - }, - Object { - "label": "Flag", - "value": "fa-flag", - }, - Object { - "label": "Heart", - "value": "fa-heart", - }, -] -`; diff --git a/src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.js b/src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.js deleted file mode 100644 index 00a925496b928..0000000000000 --- a/src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 PropTypes from 'prop-types'; -import React from 'react'; -import { EuiComboBox, EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { ICON_TYPES_MAP } from '../../visualizations/constants/icons'; - -export const ICONS = [ - { - value: 'fa-asterisk', - label: i18n.translate('tsvb.iconSelect.asteriskLabel', { defaultMessage: 'Asterisk' }), - }, - { - value: 'fa-bell', - label: i18n.translate('tsvb.iconSelect.bellLabel', { defaultMessage: 'Bell' }), - }, - { - value: 'fa-bolt', - label: i18n.translate('tsvb.iconSelect.boltLabel', { defaultMessage: 'Bolt' }), - }, - { - value: 'fa-comment', - label: i18n.translate('tsvb.iconSelect.commentLabel', { defaultMessage: 'Comment' }), - }, - { - value: 'fa-map-marker', - label: i18n.translate('tsvb.iconSelect.mapMarkerLabel', { defaultMessage: 'Map Marker' }), - }, - { - value: 'fa-map-pin', - label: i18n.translate('tsvb.iconSelect.mapPinLabel', { defaultMessage: 'Map Pin' }), - }, - { - value: 'fa-star', - label: i18n.translate('tsvb.iconSelect.starLabel', { defaultMessage: 'Star' }), - }, - { - value: 'fa-tag', - label: i18n.translate('tsvb.iconSelect.tagLabel', { defaultMessage: 'Tag' }), - }, - { - value: 'fa-bomb', - label: i18n.translate('tsvb.iconSelect.bombLabel', { defaultMessage: 'Bomb' }), - }, - { - value: 'fa-bug', - label: i18n.translate('tsvb.iconSelect.bugLabel', { defaultMessage: 'Bug' }), - }, - { - value: 'fa-exclamation-circle', - label: i18n.translate('tsvb.iconSelect.exclamationCircleLabel', { - defaultMessage: 'Exclamation Circle', - }), - }, - { - value: 'fa-exclamation-triangle', - label: i18n.translate('tsvb.iconSelect.exclamationTriangleLabel', { - defaultMessage: 'Exclamation Triangle', - }), - }, - { - value: 'fa-fire', - label: i18n.translate('tsvb.iconSelect.fireLabel', { defaultMessage: 'Fire' }), - }, - { - value: 'fa-flag', - label: i18n.translate('tsvb.iconSelect.flagLabel', { defaultMessage: 'Flag' }), - }, - { - value: 'fa-heart', - label: i18n.translate('tsvb.iconSelect.heartLabel', { defaultMessage: 'Heart' }), - }, -]; - -export function IconView({ value: icon, label }) { - return ( - - - {` ${label}`} - - ); -} - -export function IconSelect({ value, onChange }) { - const selectedIcon = ICONS.find(option => value === option.value) || ICONS[0]; - - return ( - - ); -} - -IconSelect.propTypes = { - onChange: PropTypes.func.isRequired, - value: PropTypes.string.isRequired, -}; diff --git a/src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.test.js b/src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.test.js deleted file mode 100644 index 042fedac565db..0000000000000 --- a/src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.test.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 React from 'react'; -import { shallow } from 'enzyme'; -import { IconSelect, IconView, ICONS } from './icon_select'; - -describe('src/legacy/core_plugins/metrics/public/components/icon_select/icon_select.js', () => { - describe('', () => { - test('should render and match a snapshot', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); - }); - - test("should put the default value if the passed one does't match with icons collection", () => { - const wrapper = shallow(); - - expect(wrapper.prop('selectedOptions')).toEqual([ICONS[0]]); - }); - }); - - describe('', () => { - test('should render and match a snapshot', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); - }); - }); - - describe('ICONS', () => { - test('should match and save an icons collection snapshot', () => { - expect(ICONS).toMatchSnapshot(); - }); - }); -}); diff --git a/src/legacy/core_plugins/metrics/public/components/lib/__tests__/tick_formatter.js b/src/legacy/core_plugins/metrics/public/components/lib/__tests__/tick_formatter.js index f31af4e846305..bcca2f88ac2c4 100644 --- a/src/legacy/core_plugins/metrics/public/components/lib/__tests__/tick_formatter.js +++ b/src/legacy/core_plugins/metrics/public/components/lib/__tests__/tick_formatter.js @@ -18,11 +18,11 @@ */ import { expect } from 'chai'; -import { createTickFormatter } from '../tick_formatter'; +import { tickFormatter } from '../tick_formatter'; -describe('createTickFormatter(format, template)', () => { +describe('tickFormatter(format, template)', () => { it('returns a number with two decimal place by default', () => { - const fn = createTickFormatter(); + const fn = tickFormatter(); expect(fn(1.5556)).to.equal('1.56'); }); @@ -30,7 +30,7 @@ describe('createTickFormatter(format, template)', () => { const config = { 'format:percent:defaultPattern': '0.[00]%', }; - const fn = createTickFormatter('percent', null, key => config[key]); + const fn = tickFormatter('percent', null, key => config[key]); expect(fn(0.5556)).to.equal('55.56%'); }); @@ -38,12 +38,12 @@ describe('createTickFormatter(format, template)', () => { const config = { 'format:bytes:defaultPattern': '0.0b', }; - const fn = createTickFormatter('bytes', null, key => config[key]); + const fn = tickFormatter('bytes', null, key => config[key]); expect(fn(1500 ^ 10)).to.equal('1.5KB'); }); it('returns a custom formatted string with custom formatter', () => { - const fn = createTickFormatter('0.0a'); + const fn = tickFormatter('0.0a'); expect(fn(1500)).to.equal('1.5k'); }); @@ -51,22 +51,17 @@ describe('createTickFormatter(format, template)', () => { const config = { 'format:number:defaultLocale': 'fr', }; - const fn = createTickFormatter('0,0.0', null, key => config[key]); + const fn = tickFormatter('0,0.0', null, key => config[key]); expect(fn(1500)).to.equal('1 500,0'); }); it('returns a custom formatted string with custom formatter and template', () => { - const fn = createTickFormatter('0.0a', '{{value}}/s'); + const fn = tickFormatter('0.0a', '{{value}}/s'); expect(fn(1500)).to.equal('1.5k/s'); }); - it('returns "foo" if passed a string', () => { - const fn = createTickFormatter(); - expect(fn('foo')).to.equal('foo'); - }); - it('returns value if passed a bad formatter', () => { - const fn = createTickFormatter('102'); + const fn = tickFormatter('102'); expect(fn(100)).to.equal('100'); }); @@ -74,7 +69,7 @@ describe('createTickFormatter(format, template)', () => { const config = { 'format:number:defaultPattern': '0,0.[00]', }; - const fn = createTickFormatter('number', '{{value', key => config[key]); + const fn = tickFormatter('number', '{{value', key => config[key]); expect(fn(1.5556)).to.equal('1.56'); }); }); diff --git a/src/legacy/core_plugins/metrics/public/components/lib/charts.js b/src/legacy/core_plugins/metrics/public/components/lib/charts.js deleted file mode 100644 index e0e3cb1db21ce..0000000000000 --- a/src/legacy/core_plugins/metrics/public/components/lib/charts.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { uniq, map, size, flow } from 'lodash'; - -export const areFieldsDifferent = name => series => - flow( - uniq, - size - )(map(series, name)) > 1; diff --git a/src/legacy/core_plugins/metrics/public/components/lib/convert_series_to_vars.js b/src/legacy/core_plugins/metrics/public/components/lib/convert_series_to_vars.js index bcab9ec026d9a..7fdee5dbe845b 100644 --- a/src/legacy/core_plugins/metrics/public/components/lib/convert_series_to_vars.js +++ b/src/legacy/core_plugins/metrics/public/components/lib/convert_series_to_vars.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import { getLastValue } from '../../../common/get_last_value'; -import { createTickFormatter } from './tick_formatter'; +import { tickFormatter } from './tick_formatter'; import moment from 'moment'; export const convertSeriesToVars = (series, model, dateFormat = 'lll', getConfig = null) => { @@ -32,7 +32,7 @@ export const convertSeriesToVars = (series, model, dateFormat = 'lll', getConfig .filter(v => v) .join('.'); - const formatter = createTickFormatter( + const formatter = tickFormatter( seriesModel.formatter, seriesModel.value_template, getConfig diff --git a/src/legacy/core_plugins/metrics/public/components/lib/get_axis_label_string.js b/src/legacy/core_plugins/metrics/public/components/lib/get_axis_label_string.js index 6a8822beca120..4cd83e3fa2832 100644 --- a/src/legacy/core_plugins/metrics/public/components/lib/get_axis_label_string.js +++ b/src/legacy/core_plugins/metrics/public/components/lib/get_axis_label_string.js @@ -20,10 +20,6 @@ import { convertIntervalIntoUnit } from './get_interval'; import { i18n } from '@kbn/i18n'; export function getAxisLabelString(interval) { - if (!interval) { - return ''; - } - const convertedValue = convertIntervalIntoUnit(interval); if (convertedValue) { diff --git a/src/legacy/core_plugins/metrics/public/components/lib/new_series_fn.js b/src/legacy/core_plugins/metrics/public/components/lib/new_series_fn.js index 9d2398ed079a9..0041038b80cb9 100644 --- a/src/legacy/core_plugins/metrics/public/components/lib/new_series_fn.js +++ b/src/legacy/core_plugins/metrics/public/components/lib/new_series_fn.js @@ -20,7 +20,6 @@ import uuid from 'uuid'; import _ from 'lodash'; import { newMetricAggFn } from './new_metric_agg_fn'; -import { STACKED_OPTIONS } from '../../visualizations/constants'; export const newSeriesFn = (obj = {}) => { return _.assign( @@ -36,7 +35,7 @@ export const newSeriesFn = (obj = {}) => { line_width: 1, point_size: 1, fill: 0.5, - stacked: STACKED_OPTIONS.NONE, + stacked: 'none', }, obj ); diff --git a/src/legacy/core_plugins/metrics/public/components/lib/stacked.js b/src/legacy/core_plugins/metrics/public/components/lib/stacked.js deleted file mode 100644 index f1cfcfbc05ba3..0000000000000 --- a/src/legacy/core_plugins/metrics/public/components/lib/stacked.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export const isPercentDisabled = seriesQuantity => seriesQuantity < 2; diff --git a/src/legacy/core_plugins/metrics/public/components/lib/tick_formatter.js b/src/legacy/core_plugins/metrics/public/components/lib/tick_formatter.js index 0459d11c74ef0..f0a995fa7ef3b 100644 --- a/src/legacy/core_plugins/metrics/public/components/lib/tick_formatter.js +++ b/src/legacy/core_plugins/metrics/public/components/lib/tick_formatter.js @@ -22,7 +22,7 @@ import { isNumber } from 'lodash'; import { fieldFormats } from 'ui/registry/field_formats'; import { inputFormats, outputFormats, isDuration } from '../lib/durations'; -export const createTickFormatter = (format = '0,0.[00]', template, getConfig = null) => { +export const tickFormatter = (format = '0,0.[00]', template, getConfig = null) => { if (!template) template = '{{value}}'; const render = handlebars.compile(template, { knownHelpersOnly: true }); let formatter; diff --git a/src/legacy/core_plugins/metrics/public/components/markdown_editor.js b/src/legacy/core_plugins/metrics/public/components/markdown_editor.js index 4096fd6ee1134..9fb766f174c48 100644 --- a/src/legacy/core_plugins/metrics/public/components/markdown_editor.js +++ b/src/legacy/core_plugins/metrics/public/components/markdown_editor.js @@ -24,7 +24,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { createTickFormatter } from './lib/tick_formatter'; +import { tickFormatter } from './lib/tick_formatter'; import { convertSeriesToVars } from './lib/convert_series_to_vars'; import _ from 'lodash'; import 'brace/mode/markdown'; @@ -59,7 +59,7 @@ export class MarkdownEditor extends Component { const series = _.get(visData, `${model.id}.series`, []); const variables = convertSeriesToVars(series, model, dateFormat, this.props.getConfig); const rows = []; - const rawFormatter = createTickFormatter('0.[0000]', null, this.props.getConfig); + const rawFormatter = tickFormatter('0.[0000]', null, this.props.getConfig); const createPrimitiveRow = key => { const snippet = `{{ ${key} }}`; diff --git a/src/legacy/core_plugins/metrics/public/components/panel_config.js b/src/legacy/core_plugins/metrics/public/components/panel_config.js index d58c682660e01..d1487955887b6 100644 --- a/src/legacy/core_plugins/metrics/public/components/panel_config.js +++ b/src/legacy/core_plugins/metrics/public/components/panel_config.js @@ -57,7 +57,7 @@ export function PanelConfig(props) { return function cleanup() { visDataSubscription.unsubscribe(); }; - }, [model.id, props.visData$]); + }, [props.visData$]); const updateControlValidity = (controlKey, isControlValid) => { formValidationResults[controlKey] = isControlValid; diff --git a/src/legacy/core_plugins/metrics/public/components/series.js b/src/legacy/core_plugins/metrics/public/components/series.js index 1d397dd93121d..06cf533cb8fd1 100644 --- a/src/legacy/core_plugins/metrics/public/components/series.js +++ b/src/legacy/core_plugins/metrics/public/components/series.js @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { assign, get } from 'lodash'; +import { assign } from 'lodash'; import { TimeseriesSeries as timeseries } from './vis_types/timeseries/series'; import { MetricSeries as metric } from './vis_types/metric/series'; @@ -79,41 +79,30 @@ export class Series extends Component { return Boolean(Component) ? ( - {visData => { - const series = get(visData, `${panel.id}.series`, []); - const counter = {}; - const seriesQuantity = series.reduce((acc, value) => { - counter[value.seriesId] = counter[value.seriesId] + 1 || 1; - acc[value.seriesId] = counter[value.seriesId]; - return acc; - }, {}); - - return ( - - ); - }} + {visData => ( + + )} ) : ( ); } @@ -97,5 +96,4 @@ Split.propTypes = { model: PropTypes.object, onChange: PropTypes.func, panel: PropTypes.object, - seriesQuantity: PropTypes.object, }; diff --git a/src/legacy/core_plugins/metrics/public/components/splits/terms.js b/src/legacy/core_plugins/metrics/public/components/splits/terms.js index 90196fb6fef61..8eaef0754cad8 100644 --- a/src/legacy/core_plugins/metrics/public/components/splits/terms.js +++ b/src/legacy/core_plugins/metrics/public/components/splits/terms.js @@ -23,7 +23,6 @@ import { get, find } from 'lodash'; import { GroupBySelect } from './group_by_select'; import { createTextHandler } from '../lib/create_text_handler'; import { createSelectHandler } from '../lib/create_select_handler'; -import { isPercentDisabled } from '../lib/stacked'; import { FieldSelect } from '../aggs/field_select'; import { MetricSelect } from '../aggs/metric_select'; import { @@ -37,7 +36,6 @@ import { } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { FIELD_TYPES } from '../../../common/field_types'; -import { STACKED_OPTIONS } from '../../visualizations/constants'; const DEFAULTS = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' }; @@ -48,7 +46,6 @@ export const SplitByTermsUI = ({ model: seriesModel, fields, uiRestrictions, - seriesQuantity, }) => { const htmlId = htmlIdGenerator(); const handleTextChange = createTextHandler(onChange); @@ -89,14 +86,6 @@ export const SplitByTermsUI = ({ const selectedField = find(fields[indexPattern], ({ name }) => name === model.terms_field); const selectedFieldType = get(selectedField, 'type'); - if ( - seriesQuantity && - model.stacked === STACKED_OPTIONS.PERCENT && - isPercentDisabled(seriesQuantity[model.id]) - ) { - onChange({ ['stacked']: STACKED_OPTIONS.NONE }); - } - return (
@@ -229,7 +218,6 @@ SplitByTermsUI.propTypes = { indexPattern: PropTypes.string, fields: PropTypes.object, uiRestrictions: PropTypes.object, - seriesQuantity: PropTypes.object, }; export const SplitByTerms = injectI18n(SplitByTermsUI); diff --git a/src/legacy/core_plugins/metrics/public/components/splits/terms.test.js b/src/legacy/core_plugins/metrics/public/components/splits/terms.test.js index 4d322cd7b7e61..0daa083816c50 100644 --- a/src/legacy/core_plugins/metrics/public/components/splits/terms.test.js +++ b/src/legacy/core_plugins/metrics/public/components/splits/terms.test.js @@ -40,12 +40,8 @@ describe('src/legacy/core_plugins/metrics/public/components/splits/terms.test.js formatMessage: jest.fn(), }, model: { - id: 123, terms_field: 'OriginCityName', }, - seriesQuantity: { - id123: 123, - }, onChange: jest.fn(), indexPattern: 'kibana_sample_data_flights', fields: { diff --git a/src/legacy/core_plugins/metrics/public/components/svg/bomb_icon.js b/src/legacy/core_plugins/metrics/public/components/svg/bomb_icon.js deleted file mode 100644 index 865bd67ea9c35..0000000000000 --- a/src/legacy/core_plugins/metrics/public/components/svg/bomb_icon.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 React from 'react'; - -export const bombIcon = () => ( - - - -); diff --git a/src/legacy/core_plugins/metrics/public/components/svg/fire_icon.js b/src/legacy/core_plugins/metrics/public/components/svg/fire_icon.js deleted file mode 100644 index 9ec45907d4636..0000000000000 --- a/src/legacy/core_plugins/metrics/public/components/svg/fire_icon.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 React from 'react'; - -export const fireIcon = () => ( - - - -); diff --git a/src/legacy/core_plugins/metrics/public/components/vis_editor.js b/src/legacy/core_plugins/metrics/public/components/vis_editor.js index d6cbdcd60d2cf..cf8c4a02b9e58 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_editor.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_editor.js @@ -27,7 +27,7 @@ import { VisEditorVisualization } from './vis_editor_visualization'; import { Visualization } from './visualization'; import { VisPicker } from './vis_picker'; import { PanelConfig } from './panel_config'; -import { createBrushHandler } from '../lib/create_brush_handler'; +import { brushHandler } from '../lib/create_brush_handler'; import { fetchFields } from '../lib/fetch_fields'; import { extractIndexPatterns } from '../../common/extract_index_patterns'; @@ -51,7 +51,7 @@ export class VisEditor extends Component { visFields: props.visFields, extractedIndexPatterns: [''], }; - this.onBrush = createBrushHandler(props.vis.API.timeFilter); + this.onBrush = brushHandler(props.vis.API.timeFilter); this.visDataSubject = new Rx.BehaviorSubject(this.props.visData); this.visData$ = this.visDataSubject.asObservable().pipe(share()); diff --git a/src/legacy/core_plugins/metrics/public/components/vis_editor_visualization.js b/src/legacy/core_plugins/metrics/public/components/vis_editor_visualization.js index fd399e66bb149..e313719b3dd99 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_editor_visualization.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_editor_visualization.js @@ -31,7 +31,7 @@ import { } from './lib/get_interval'; import { PANEL_TYPES } from '../../common/panel_types'; -const MIN_CHART_HEIGHT = 300; +const MIN_CHART_HEIGHT = 250; class VisEditorVisualizationUI extends Component { constructor(props) { diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/_vis_types.scss b/src/legacy/core_plugins/metrics/public/components/vis_types/_vis_types.scss index 90c2007b1c94a..f4f9230b32dc2 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/_vis_types.scss +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/_vis_types.scss @@ -3,8 +3,4 @@ flex-direction: column; flex: 1 1 100%; padding: $euiSizeS; - - .tvbVisTimeSeries { - overflow: hidden; - } } diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/gauge/vis.js b/src/legacy/core_plugins/metrics/public/components/vis_types/gauge/vis.js index 5d6bb55f33db6..5363152f7e60c 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/gauge/vis.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/gauge/vis.js @@ -20,9 +20,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import { visWithSplits } from '../../vis_with_splits'; -import { createTickFormatter } from '../../lib/tick_formatter'; +import { tickFormatter } from '../../lib/tick_formatter'; import _, { get, isUndefined, assign, includes } from 'lodash'; -import { Gauge } from '../../../visualizations/views/gauge'; +import { Gauge } from '../../../visualizations/components/gauge'; import { getLastValue } from '../../../../common/get_last_value'; function getColors(props) { @@ -54,7 +54,7 @@ function GaugeVisualization(props) { const seriesDef = model.series.find(s => includes(row.id, s.id)); const newProps = {}; if (seriesDef) { - newProps.formatter = createTickFormatter( + newProps.formatter = tickFormatter( seriesDef.formatter, seriesDef.value_template, props.getConfig diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/metric/vis.js b/src/legacy/core_plugins/metrics/public/components/vis_types/metric/vis.js index f463a4494a189..6e3a7544397c2 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/metric/vis.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/metric/vis.js @@ -20,9 +20,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import { visWithSplits } from '../../vis_with_splits'; -import { createTickFormatter } from '../../lib/tick_formatter'; +import { tickFormatter } from '../../lib/tick_formatter'; import _, { get, isUndefined, assign, includes, pick } from 'lodash'; -import { Metric } from '../../../visualizations/views/metric'; +import { Metric } from '../../../visualizations/components/metric'; import { getLastValue } from '../../../../common/get_last_value'; import { isBackgroundInverted } from '../../../../common/set_is_reversed'; @@ -54,7 +54,7 @@ function MetricVisualization(props) { const seriesDef = model.series.find(s => includes(row.id, s.id)); const newProps = {}; if (seriesDef) { - newProps.formatter = createTickFormatter( + newProps.formatter = tickFormatter( seriesDef.formatter, seriesDef.value_template, props.getConfig diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/table/vis.js b/src/legacy/core_plugins/metrics/public/components/vis_types/table/vis.js index c9b02af3d37fe..adb92d4d880cb 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/table/vis.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/table/vis.js @@ -21,7 +21,7 @@ import _, { isArray, last, get } from 'lodash'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { fieldFormats } from 'ui/registry/field_formats'; -import { createTickFormatter } from '../../lib/tick_formatter'; +import { tickFormatter } from '../../lib/tick_formatter'; import { calculateLabel } from '../../../../common/calculate_label'; import { isSortable } from './is_sortable'; import { EuiToolTip, EuiIcon } from '@elastic/eui'; @@ -68,7 +68,7 @@ export class TableVis extends Component { .map(item => { const column = this.visibleSeries.find(c => c.id === item.id); if (!column) return null; - const formatter = createTickFormatter( + const formatter = tickFormatter( column.formatter, column.value_template, this.props.getConfig diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/config.js b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/config.js index 2daa2470bc611..352b01a832694 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/config.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/config.js @@ -41,9 +41,6 @@ import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { getDefaultQueryLanguage } from '../../lib/get_default_query_language'; import { QueryBarWrapper } from '../../query_bar_wrapper'; -import { isPercentDisabled } from '../../lib/stacked'; -import { STACKED_OPTIONS } from '../../../visualizations/constants/chart'; - export const TimeseriesConfig = injectI18n(function(props) { const handleSelectChange = createSelectHandler(props.onChange); const handleTextChange = createTextHandler(props.onChange); @@ -56,7 +53,7 @@ export const TimeseriesConfig = injectI18n(function(props) { split_color_mode: 'gradient', axis_min: '', axis_max: '', - stacked: STACKED_OPTIONS.NONE, + stacked: 'none', steps: 0, }; const model = { ...defaults, ...props.model }; @@ -65,23 +62,22 @@ export const TimeseriesConfig = injectI18n(function(props) { const stackedOptions = [ { label: intl.formatMessage({ id: 'tsvb.timeSeries.noneLabel', defaultMessage: 'None' }), - value: STACKED_OPTIONS.NONE, + value: 'none', }, { label: intl.formatMessage({ id: 'tsvb.timeSeries.stackedLabel', defaultMessage: 'Stacked' }), - value: STACKED_OPTIONS.STACKED, + value: 'stacked', }, { label: intl.formatMessage({ id: 'tsvb.timeSeries.stackedWithinSeriesLabel', defaultMessage: 'Stacked within series', }), - value: STACKED_OPTIONS.STACKED_WITHIN_SERIES, + value: 'stacked_within_series', }, { label: intl.formatMessage({ id: 'tsvb.timeSeries.percentLabel', defaultMessage: 'Percent' }), - value: STACKED_OPTIONS.PERCENT, - disabled: isPercentDisabled(props.seriesQuantity[model.id]), + value: 'percent', }, ]; const selectedStackedOption = stackedOptions.find(option => { @@ -134,7 +130,6 @@ export const TimeseriesConfig = injectI18n(function(props) { }); let type; - if (model.chart_type === 'line') { type = ( @@ -287,7 +282,7 @@ export const TimeseriesConfig = injectI18n(function(props) { } > @@ -534,5 +529,4 @@ TimeseriesConfig.propTypes = { model: PropTypes.object, onChange: PropTypes.func, indexPatternForQuery: PropTypes.string, - seriesQuantity: PropTypes.object, }; diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js index c0a0fb744ce39..43e1ac9f0eda5 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js @@ -51,7 +51,6 @@ const TimeseriesSeriesUI = injectI18n(function(props) { intl, name, uiRestrictions, - seriesQuantity, } = props; const defaults = { @@ -88,7 +87,6 @@ const TimeseriesSeriesUI = injectI18n(function(props) { panel={panel} model={model} uiRestrictions={uiRestrictions} - seriesQuantity={seriesQuantity} />
@@ -100,7 +98,6 @@ const TimeseriesSeriesUI = injectI18n(function(props) { model={model} onChange={props.onChange} indexPatternForQuery={props.indexPatternForQuery} - seriesQuantity={seriesQuantity} /> ); } @@ -214,7 +211,6 @@ TimeseriesSeriesUI.propTypes = { uiRestrictions: PropTypes.object, dragHandleProps: PropTypes.object, indexPatternForQuery: PropTypes.string, - seriesQuantity: PropTypes.object, }; export const TimeseriesSeries = injectI18n(TimeseriesSeriesUI); diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/vis.js b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/vis.js index 982aca8d3b813..12f2d4ef66e04 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/vis.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/vis.js @@ -19,90 +19,35 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import reactCSS from 'reactcss'; - -import { startsWith, get, cloneDeep, map } from 'lodash'; import { toastNotifications } from 'ui/notify'; -import { htmlIdGenerator } from '@elastic/eui'; -import { ScaleType } from '@elastic/charts'; -import { createTickFormatter } from '../../lib/tick_formatter'; -import { TimeSeries } from '../../../visualizations/views/timeseries'; +import { tickFormatter } from '../../lib/tick_formatter'; +import _ from 'lodash'; +import { Timeseries } from '../../../visualizations/components/timeseries'; import { MarkdownSimple } from '../../../../../kibana_react/public'; import { replaceVars } from '../../lib/replace_vars'; import { getAxisLabelString } from '../../lib/get_axis_label_string'; import { getInterval } from '../../lib/get_interval'; -import { areFieldsDifferent } from '../../lib/charts'; import { createXaxisFormatter } from '../../lib/create_xaxis_formatter'; -import { isBackgroundDark } from '../../../../common/set_is_reversed'; -import { STACKED_OPTIONS } from '../../../visualizations/constants'; + +function hasSeparateAxis(row) { + return row.separate_axis; +} export class TimeseriesVisualization extends Component { - static propTypes = { - model: PropTypes.object, - onBrush: PropTypes.func, - visData: PropTypes.object, - dateFormat: PropTypes.string, - getConfig: PropTypes.func, + getInterval = () => { + const { visData, model } = this.props; + + return getInterval(visData, model); }; - xAxisFormatter = interval => val => { + xaxisFormatter = val => { const { scaledDataFormat, dateFormat } = this.props.visData; - - if (!scaledDataFormat || !dateFormat) { - return val; - } - - const formatter = createXaxisFormatter(interval, scaledDataFormat, dateFormat); - + if (!scaledDataFormat || !dateFormat) return val; + const formatter = createXaxisFormatter(this.getInterval(), scaledDataFormat, dateFormat); return formatter(val); }; - yAxisStackedByPercentFormatter = val => { - const n = Number(val) * 100; - - return `${(Number.isNaN(n) ? 0 : n).toFixed(0)}%`; - }; - - applyDocTo = template => doc => { - const vars = replaceVars(template, null, doc); - - if (vars instanceof Error) { - this.showToastNotification = vars.error.caused_by; - - return template; - } - - return vars; - }; - - static getYAxisDomain = model => { - const axisMin = get(model, 'axis_min', '').toString(); - const axisMax = get(model, 'axis_max', '').toString(); - - return { - min: axisMin.length ? Number(axisMin) : undefined, - max: axisMax.length ? Number(axisMax) : undefined, - }; - }; - - static addYAxis = (yAxis, { id, groupId, position, tickFormatter, domain, hide }) => { - yAxis.push({ - id, - groupId, - position, - tickFormatter, - domain, - hide, - }); - }; - - static getAxisScaleType = model => - get(model, 'axis_scale') === 'log' ? ScaleType.Log : ScaleType.Linear; - - static getTickFormatter = (model, getConfig) => - createTickFormatter(get(model, 'formatter'), get(model, 'value_template'), getConfig); - componentDidUpdate() { if ( this.showToastNotification && @@ -126,129 +71,181 @@ export class TimeseriesVisualization extends Component { } } - prepareAnnotations = () => { - const { model, visData } = this.props; - - return map(model.annotations, ({ id, color, icon, template }) => { - const annotationData = get(visData, `${model.id}.annotations.${id}`, []); - const applyDocToTemplate = this.applyDocTo(template); - - return { - id, - color, - icon, - data: annotationData.map(({ docs, ...rest }) => ({ - ...rest, - docs: docs.map(applyDocToTemplate), - })), - }; - }); - }; - render() { - const { model, visData, onBrush } = this.props; - const styles = reactCSS({ - default: { - tvbVis: { - backgroundColor: get(model, 'background_color'), - }, - }, - }); - const series = get(visData, `${model.id}.series`, []); - const interval = getInterval(visData, model); - const yAxisIdGenerator = htmlIdGenerator('yaxis'); - const mainAxisGroupId = yAxisIdGenerator('main_group'); + const { backgroundColor, model, visData } = this.props; + const series = _.get(visData, `${model.id}.series`, []); + let annotations; - const seriesModel = model.series.filter(s => !s.hidden).map(s => cloneDeep(s)); - const enableHistogramMode = areFieldsDifferent('chart_type')(seriesModel); - const firstSeries = seriesModel.find(s => s.formatter && !s.separate_axis); + this.showToastNotification = null; - const mainAxisScaleType = TimeseriesVisualization.getAxisScaleType(model); - const mainAxisDomain = TimeseriesVisualization.getYAxisDomain(model); - const tickFormatter = TimeseriesVisualization.getTickFormatter( - firstSeries, + if (model.annotations && Array.isArray(model.annotations)) { + annotations = model.annotations.map(annotation => { + const data = _.get(visData, `${model.id}.annotations.${annotation.id}`, []).map(item => [ + item.key, + item.docs, + ]); + return { + id: annotation.id, + color: annotation.color, + icon: annotation.icon, + series: data.map(s => { + return [ + s[0], + s[1].map(doc => { + const vars = replaceVars(annotation.template, null, doc); + + if (vars instanceof Error) { + this.showToastNotification = vars.error.caused_by; + + return annotation.template; + } + + return vars; + }), + ]; + }), + }; + }); + } + const seriesModel = model.series.map(s => _.cloneDeep(s)); + const firstSeries = seriesModel.find(s => s.formatter && !s.separate_axis); + const formatter = tickFormatter( + _.get(firstSeries, 'formatter'), + _.get(firstSeries, 'value_template'), this.props.getConfig ); - const yAxis = []; - let mainDomainAdded = false; - this.showToastNotification = null; + const mainAxis = { + position: model.axis_position, + tickFormatter: formatter, + axisFormatter: _.get(firstSeries, 'formatter', 'number'), + axisFormatterTemplate: _.get(firstSeries, 'value_template'), + }; - seriesModel.forEach(seriesGroup => { - const isStackedWithinSeries = seriesGroup.stacked === STACKED_OPTIONS.STACKED_WITHIN_SERIES; - const hasSeparateAxis = Boolean(seriesGroup.separate_axis); - const groupId = hasSeparateAxis || isStackedWithinSeries ? seriesGroup.id : mainAxisGroupId; - const domain = hasSeparateAxis - ? TimeseriesVisualization.getYAxisDomain(seriesGroup) - : undefined; - const isCustomDomain = groupId !== mainAxisGroupId; - const seriesGroupTickFormatter = TimeseriesVisualization.getTickFormatter( - seriesGroup, - this.props.getConfig - ); - const yScaleType = hasSeparateAxis - ? TimeseriesVisualization.getAxisScaleType(seriesGroup) - : mainAxisScaleType; - - if (seriesGroup.stacked === STACKED_OPTIONS.PERCENT) { - seriesGroup.separate_axis = true; - seriesGroup.axisFormatter = 'percent'; - seriesGroup.axis_min = seriesGroup.axis_min || 0; - seriesGroup.axis_max = seriesGroup.axis_max || 1; - seriesGroup.axis_position = model.axis_position; - } + if (model.axis_min) mainAxis.min = model.axis_min; + if (model.axis_max) mainAxis.max = model.axis_max; + if (model.axis_scale === 'log') { + mainAxis.mode = 'log'; + mainAxis.transform = value => (value > 0 ? Math.log(value) / Math.LN10 : null); + mainAxis.inverseTransform = value => Math.pow(10, value); + } + + const yaxes = [mainAxis]; + seriesModel.forEach(s => { series - .filter(r => startsWith(r.id, seriesGroup.id)) - .forEach(seriesDataRow => { - seriesDataRow.tickFormatter = seriesGroupTickFormatter; - seriesDataRow.groupId = groupId; - seriesDataRow.yScaleType = yScaleType; - seriesDataRow.hideInLegend = Boolean(seriesGroup.hide_in_legend); - seriesDataRow.useDefaultGroupDomain = !isCustomDomain; - }); - - if (isCustomDomain) { - TimeseriesVisualization.addYAxis(yAxis, { - domain, - groupId, - id: yAxisIdGenerator(seriesGroup.id), - position: seriesGroup.axis_position, - hide: isStackedWithinSeries, - tickFormatter: - seriesGroup.stacked === STACKED_OPTIONS.PERCENT - ? this.yAxisStackedByPercentFormatter - : seriesGroupTickFormatter, - }); - } else if (!mainDomainAdded) { - TimeseriesVisualization.addYAxis(yAxis, { - tickFormatter, - id: yAxisIdGenerator('main'), - groupId: mainAxisGroupId, - position: model.axis_position, - domain: mainAxisDomain, - }); - - mainDomainAdded = true; + .filter(r => _.startsWith(r.id, s.id)) + .forEach( + r => + (r.tickFormatter = tickFormatter(s.formatter, s.value_template, this.props.getConfig)) + ); + + if (s.hide_in_legend) { + series.filter(r => _.startsWith(r.id, s.id)).forEach(r => delete r.label); + } + if (s.stacked !== 'none') { + series + .filter(r => _.startsWith(r.id, s.id)) + .forEach(row => { + row.data = row.data.map(point => { + if (!point[1]) return [point[0], 0]; + return point; + }); + }); + } + if (s.stacked === 'percent') { + s.separate_axis = true; + s.axisFormatter = 'percent'; + s.axis_min = 0; + s.axis_max = 1; + s.axis_position = model.axis_position; + const seriesData = series.filter(r => _.startsWith(r.id, s.id)); + const first = seriesData[0]; + if (first) { + first.data.forEach((row, index) => { + const rowSum = seriesData.reduce((acc, item) => { + return item.data[index][1] + acc; + }, 0); + seriesData.forEach(item => { + item.data[index][1] = (rowSum && item.data[index][1] / rowSum) || 0; + }); + }); + } } }); + const interval = this.getInterval(); + + let axisCount = 1; + if (seriesModel.some(hasSeparateAxis)) { + seriesModel.forEach(row => { + if (row.separate_axis) { + axisCount++; + + const formatter = tickFormatter(row.formatter, row.value_template, this.props.getConfig); + + const yaxis = { + alignTicksWithAxis: 1, + position: row.axis_position, + tickFormatter: formatter, + axisFormatter: row.axis_formatter, + axisFormatterTemplate: row.value_template, + }; + + if (row.axis_min != null) yaxis.min = row.axis_min; + if (row.axis_max != null) yaxis.max = row.axis_max; + + yaxes.push(yaxis); + + // Assign axis and formatter to each series + series + .filter(r => _.startsWith(r.id, row.id)) + .forEach(r => { + r.yaxis = axisCount; + }); + } + }); + } + + const panelBackgroundColor = model.background_color || backgroundColor; + const style = { backgroundColor: panelBackgroundColor }; + + const params = { + dateFormat: this.props.dateFormat, + crosshair: true, + tickFormatter: formatter, + legendPosition: model.legend_position || 'right', + backgroundColor: panelBackgroundColor, + series, + annotations, + yaxes, + showGrid: Boolean(model.show_grid), + legend: Boolean(model.show_legend), + xAxisFormatter: this.xaxisFormatter, + onBrush: ranges => { + if (this.props.onBrush) this.props.onBrush(ranges); + }, + }; + + if (interval) { + params.xaxisLabel = getAxisLabelString(interval); + } + return ( -
- +
+
); } } + +TimeseriesVisualization.propTypes = { + backgroundColor: PropTypes.string, + className: PropTypes.string, + model: PropTypes.object, + onBrush: PropTypes.func, + onChange: PropTypes.func, + visData: PropTypes.object, + dateFormat: PropTypes.string, + getConfig: PropTypes.func, +}; diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/top_n/vis.js b/src/legacy/core_plugins/metrics/public/components/vis_types/top_n/vis.js index 7d09f33acdecc..57de7f018f134 100644 --- a/src/legacy/core_plugins/metrics/public/components/vis_types/top_n/vis.js +++ b/src/legacy/core_plugins/metrics/public/components/vis_types/top_n/vis.js @@ -17,8 +17,8 @@ * under the License. */ -import { createTickFormatter } from '../../lib/tick_formatter'; -import { TopN } from '../../../visualizations/views/top_n'; +import { tickFormatter } from '../../lib/tick_formatter'; +import { TopN } from '../../../visualizations/components/top_n'; import { getLastValue } from '../../../../common/get_last_value'; import { isBackgroundInverted } from '../../../../common/set_is_reversed'; import { replaceVars } from '../../lib/replace_vars'; @@ -54,7 +54,7 @@ export function TopNVisualization(props) { const id = first(item.id.split(/:/)); const seriesConfig = model.series.find(s => s.id === id); if (seriesConfig) { - const tickFormatter = createTickFormatter( + const formatter = tickFormatter( seriesConfig.formatter, seriesConfig.value_template, props.getConfig @@ -73,7 +73,7 @@ export function TopNVisualization(props) { return { ...item, color, - tickFormatter, + tickFormatter: formatter, }; } return item; diff --git a/src/legacy/core_plugins/metrics/public/index.scss b/src/legacy/core_plugins/metrics/public/index.scss index 86fbfb52dbe64..5083c5156be23 100644 --- a/src/legacy/core_plugins/metrics/public/index.scss +++ b/src/legacy/core_plugins/metrics/public/index.scss @@ -17,4 +17,4 @@ @import './components/index'; // Visualizations -@import './visualizations/views/index'; +@import './visualizations/components/index'; diff --git a/src/legacy/core_plugins/metrics/public/legacy.ts b/src/legacy/core_plugins/metrics/public/legacy.ts index 8dc24503772db..49420e4b51273 100644 --- a/src/legacy/core_plugins/metrics/public/legacy.ts +++ b/src/legacy/core_plugins/metrics/public/legacy.ts @@ -20,12 +20,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { MetricsPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, }; diff --git a/src/legacy/core_plugins/metrics/public/lib/create_brush_handler.test.js b/src/legacy/core_plugins/metrics/public/lib/__tests__/create_brush_handler.test.js similarity index 65% rename from src/legacy/core_plugins/metrics/public/lib/create_brush_handler.test.js rename to src/legacy/core_plugins/metrics/public/lib/__tests__/create_brush_handler.test.js index 76d88b6b8dfb9..f78ed52ea52a4 100644 --- a/src/legacy/core_plugins/metrics/public/lib/create_brush_handler.test.js +++ b/src/legacy/core_plugins/metrics/public/lib/__tests__/create_brush_handler.test.js @@ -17,12 +17,14 @@ * under the License. */ -import { createBrushHandler } from './create_brush_handler'; +import { brushHandler } from '../create_brush_handler'; import moment from 'moment'; +import { expect } from 'chai'; describe('brushHandler', () => { let mockTimefilter; let onBrush; + let range; beforeEach(() => { mockTimefilter = { @@ -31,15 +33,14 @@ describe('brushHandler', () => { this.time = time; }, }; - onBrush = createBrushHandler(mockTimefilter); + onBrush = brushHandler(mockTimefilter); }); - it('returns brushHandler() that updates timefilter', () => { - const from = '2017-01-01T00:00:00Z'; - const to = '2017-01-01T00:10:00Z'; - onBrush(from, to); - expect(mockTimefilter.time.from).toEqual(moment(from).toISOString()); - expect(mockTimefilter.time.to).toEqual(moment(to).toISOString()); - expect(mockTimefilter.time.mode).toEqual('absolute'); + test('returns brushHandler() that updates timefilter', () => { + range = { xaxis: { from: '2017-01-01T00:00:00Z', to: '2017-01-01T00:10:00Z' } }; + onBrush(range); + expect(mockTimefilter.time.from).to.equal(moment(range.xaxis.from).toISOString()); + expect(mockTimefilter.time.to).to.equal(moment(range.xaxis.to).toISOString()); + expect(mockTimefilter.time.mode).to.equal('absolute'); }); }); diff --git a/src/legacy/core_plugins/metrics/public/lib/create_brush_handler.js b/src/legacy/core_plugins/metrics/public/lib/create_brush_handler.js index 0a7b10ff1aabb..4bd3bd641e9a6 100644 --- a/src/legacy/core_plugins/metrics/public/lib/create_brush_handler.js +++ b/src/legacy/core_plugins/metrics/public/lib/create_brush_handler.js @@ -19,12 +19,10 @@ import moment from 'moment'; -const TIME_MODE = 'absolute'; - -export const createBrushHandler = timefilter => (from, to) => { +export const brushHandler = timefilter => ranges => { timefilter.setTime({ - from: moment(from).toISOString(), - to: moment(to).toISOString(), - mode: TIME_MODE, + from: moment(ranges.xaxis.from).toISOString(), + to: moment(ranges.xaxis.to).toISOString(), + mode: 'absolute', }); }; diff --git a/src/legacy/core_plugins/metrics/public/metrics_type.ts b/src/legacy/core_plugins/metrics/public/metrics_type.ts index 00f54a90b3b0f..35182fbe25265 100644 --- a/src/legacy/core_plugins/metrics/public/metrics_type.ts +++ b/src/legacy/core_plugins/metrics/public/metrics_type.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; // @ts-ignore import { createMetricsRequestHandler } from './request_handler'; diff --git a/src/legacy/core_plugins/metrics/public/plugin.ts b/src/legacy/core_plugins/metrics/public/plugin.ts index 5c103a3ae4b08..e1eca37970604 100644 --- a/src/legacy/core_plugins/metrics/public/plugin.ts +++ b/src/legacy/core_plugins/metrics/public/plugin.ts @@ -18,7 +18,7 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricsFn } from './metrics_fn'; import { createMetricsTypeDefinition } from './metrics_type'; @@ -39,7 +39,7 @@ export class MetricsPlugin implements Plugin, void> { public async setup(core: CoreSetup, { data, visualizations }: MetricsPluginSetupDependencies) { data.expressions.registerFunction(createMetricsFn); - visualizations.types.registerVisualization(createMetricsTypeDefinition); + visualizations.types.VisTypesRegistryProvider.register(createMetricsTypeDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/_annotation.scss b/src/legacy/core_plugins/metrics/public/visualizations/components/_annotation.scss similarity index 100% rename from src/legacy/core_plugins/metrics/public/visualizations/views/_annotation.scss rename to src/legacy/core_plugins/metrics/public/visualizations/components/_annotation.scss diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/_gauge.scss b/src/legacy/core_plugins/metrics/public/visualizations/components/_gauge.scss similarity index 100% rename from src/legacy/core_plugins/metrics/public/visualizations/views/_gauge.scss rename to src/legacy/core_plugins/metrics/public/visualizations/components/_gauge.scss diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/_index.scss b/src/legacy/core_plugins/metrics/public/visualizations/components/_index.scss similarity index 62% rename from src/legacy/core_plugins/metrics/public/visualizations/views/_index.scss rename to src/legacy/core_plugins/metrics/public/visualizations/components/_index.scss index ddd5604801806..c5ce0ff8c8513 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/_index.scss +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/_index.scss @@ -1,5 +1,6 @@ @import './annotation'; @import './gauge'; @import './metric'; - +@import './legend'; +@import './timeseries_chart'; @import './top_n'; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/_legend.scss b/src/legacy/core_plugins/metrics/public/visualizations/components/_legend.scss new file mode 100644 index 0000000000000..429e7c682f21e --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/_legend.scss @@ -0,0 +1,92 @@ +// LEGEND + +.tvbLegend { + @include euiFontSizeXS; + display: flex; + width: 200px; + padding: $euiSizeXS 0; + overflow: auto; +} + +.tvbLegend__toggle { + align-self: flex-start; + color: $tvbValueColor; + + .tvbVisTimeSeries--reversed & { + color: $tvbValueColorReversed; + } +} + +.tvbLegend__series { + flex-grow: 1; +} + +.tvbLegend__item { + cursor: pointer; + padding: $euiSizeXS; + border-bottom: 1px solid $tvbLineColor; + display: flex; + max-width: 170px; + + &.disabled { + opacity: .5; + } + + &:first-child { + border-top: 1px solid $tvbLineColor; + } + + .tvbVisTimeSeries--reversed & { + border-color: $tvbLineColorReversed; + } +} + +.tvbLegend__button { + text-align: left; + display: flex; + width: 100%; +} + +.tvbLegend__itemLabel { + @include euiTextTruncate; + flex-grow: 1; + + span { + color: $euiTextColor; + margin-left: $euiSizeXS; + + .tvbVisTimeSeries--reversed & { + color: $tvbTextColorReversed; + } + } +} + +.tvbLegend__itemValue { + font-weight: $euiFontWeightSemiBold; + color: $tvbValueColor; + margin-left: $euiSizeXS; + + .tvbVisTimeSeries--reversed & { + color: $tvbValueColorReversed; + } +} + +.tvbLegend--horizontal { + width: auto; + display: flex; + + .tvbLegend__series { + display: flex; + flex-wrap: wrap; + } + + .tvbLegend__item { + max-width: inherit; + margin-right: $euiSizeM; + border: none; + } + + .tvbLegend__itemLabel { + flex: 0 1 auto; + } +} diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/_metric.scss b/src/legacy/core_plugins/metrics/public/visualizations/components/_metric.scss similarity index 100% rename from src/legacy/core_plugins/metrics/public/visualizations/views/_metric.scss rename to src/legacy/core_plugins/metrics/public/visualizations/components/_metric.scss diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/_timeseries_chart.scss b/src/legacy/core_plugins/metrics/public/visualizations/components/_timeseries_chart.scss new file mode 100644 index 0000000000000..7bda2cc78e05b --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/_timeseries_chart.scss @@ -0,0 +1,132 @@ +@import '@elastic/eui/src/components/tool_tip/variables'; +@import '@elastic/eui/src/components/tool_tip/mixins'; + +.tvbVisTimeSeries { + position: relative; + display: flex; + flex-direction: column; + flex: 1 0 auto; +} + +.tvbVisTimeSeries__content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + flex: 1 0 auto; + // TODO: Remove once tooltips are portaled + overflow: visible; // Ensures the tooltip doesn't cause scrollbars +} + +.tvbVisTimeSeries__visualization { + cursor: crosshair; + display: flex; + flex-direction: column; + flex: 1 0 auto; + position: relative; + + > .tvbVisTimeSeries__container { + min-width: 1px; + width: 100%; + height: 100%; + } +} + +.tvbVisTimeSeries__container { + @include euiFontSizeXS; + position: relative; + display: flex; + flex-direction: column; + flex: 1 0 auto; +} + +.tvbVisTimeSeries__axisLabel { + font-weight: $euiFontWeightBold; + color: $tvbTextColor; + text-align: center; + min-height: 1.2em; + + &.tvbVisTimeSeries__axisLabel--reversed { + color: $tvbTextColorReversed; + } +} + +// TOOLTIP + +// EUITODO: Use EuiTooltip or somehow portal the current one +.tvbTooltip__container { + pointer-events: none; + position: absolute; + z-index: $euiZLevel9; + display: flex; + align-items: center; + padding: 0 $euiSizeS; + transform: translate(0, -50%); +} + +.tvbTooltip__container--right { + flex-direction: row-reverse; +} + +.tvbTooltip { + @include euiToolTipStyle; + @include euiFontSizeXS; + padding: $euiSizeS; +} + +.tvbTooltip__caret { + $tempArrowSize: $euiSizeM; + width: $tempArrowSize; + height: $tempArrowSize; + border-radius: $euiBorderRadius / 2; + background-color: tintOrShade($euiColorFullShade, 25%, 90%); + transform-origin: center; + transform: rotateZ(45deg); + + .tvbTooltip__container--left & { + margin-right: (($tempArrowSize/2) + 1px) * -1; + } + + .tvbTooltip__container--right & { + margin-left: (($tempArrowSize/2) + 1px) * -1; + } +} + +.tvbTooltip__item { + display: flex; +} + +/** + * 1. Ensure tvbTooltip__label text wraps nicely. + * 2. Create consistent space between the dot icon and the label. + */ +.tvbTooltip__labelContainer { + display: flex; + flex-wrap: wrap; + flex-grow: 1; + min-width: 1px; /* 1 */ + margin-left: $euiSizeXS; /* 2 */ +} + +/** + * 1. Ensure text wraps nicely. + */ +.tvbTooltip__label { + flex-grow: 1; + margin-right: $euiSizeXS; + word-wrap: break-word; /* 1 */ + overflow-wrap: break-word; /* 1 */ + min-width: 1px; /* 1 */ +} + +.tvbTooltip__icon, +.tvbTooltip__value { + flex-shrink: 0; +} + +.tvbTooltip__timestamp { + color: transparentize($euiColorGhost, .3); + white-space: nowrap; +} diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/_top_n.scss b/src/legacy/core_plugins/metrics/public/visualizations/components/_top_n.scss similarity index 91% rename from src/legacy/core_plugins/metrics/public/visualizations/views/_top_n.scss rename to src/legacy/core_plugins/metrics/public/visualizations/components/_top_n.scss index fb6be95dba2a2..6392e437bc0a6 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/_top_n.scss +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/_top_n.scss @@ -35,12 +35,7 @@ } .tvbVisTopN__innerBar { - position: relative; - - > div { - width: 100%; - min-height: $euiSize; - } + min-height: $euiSize; } .tvbVisTopN--reversed { diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/annotation.js b/src/legacy/core_plugins/metrics/public/visualizations/components/annotation.js similarity index 100% rename from src/legacy/core_plugins/metrics/public/visualizations/views/annotation.js rename to src/legacy/core_plugins/metrics/public/visualizations/components/annotation.js diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/flot_chart.js b/src/legacy/core_plugins/metrics/public/visualizations/components/flot_chart.js new file mode 100644 index 0000000000000..bb900ee3f7b38 --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/flot_chart.js @@ -0,0 +1,346 @@ +/* + * 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 React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { findDOMNode } from 'react-dom'; +import _ from 'lodash'; +import $ from 'ui/flot-charts'; +import { eventBus } from '../lib/events'; +import { Resize } from './resize'; +import { calculateBarWidth } from '../lib/calculate_bar_width'; +import { calculateFillColor } from '../lib/calculate_fill_color'; +import { COLORS } from '../lib/colors'; + +export class FlotChart extends Component { + constructor(props) { + super(props); + this.handleResize = this.handleResize.bind(this); + } + + shouldComponentUpdate(props) { + if (!this.plot) return true; + if (props.reversed !== this.props.reversed) { + return true; + } + + // if the grid changes we need to re-render + if (props.showGrid !== this.props.showGrid) return true; + + if (props.yaxes && this.props.yaxes) { + // We need to rerender if the axis change + const valuesChanged = props.yaxes.some((axis, i) => { + if (this.props.yaxes[i]) { + return ( + axis.position !== this.props.yaxes[i].position || + axis.max !== this.props.yaxes[i].max || + axis.min !== this.props.yaxes[i].min || + axis.axisFormatter !== this.props.yaxes[i].axisFormatter || + axis.mode !== this.props.yaxes[i].mode || + axis.axisFormatterTemplate !== this.props.yaxes[i].axisFormatterTemplate + ); + } + }); + if (props.yaxes.length !== this.props.yaxes.length || valuesChanged) { + return true; + } + } + return false; + } + + shutdownChart() { + if (!this.plot) return; + $(this.target).off('plothover', this.props.plothover); + if (this.props.onMouseOver) $(this.target).off('plothover', this.handleMouseOver); + if (this.props.onMouseLeave) $(this.target).off('mouseleave', this.handleMouseLeave); + if (this.props.onBrush) $(this.target).off('plotselected', this.brushChart); + this.plot.shutdown(); + if (this.props.crosshair) { + $(this.target).off('plothover', this.handlePlotover); + eventBus.off('thorPlotover', this.handleThorPlotover); + eventBus.off('thorPlotleave', this.handleThorPlotleave); + } + } + + componentWillUnmount() { + this.shutdownChart(); + } + + filterByShow(show) { + if (show) { + return metric => { + return show.some(id => _.startsWith(id, metric.id)); + }; + } + return () => true; + } + + componentWillReceiveProps(newProps) { + if (this.plot) { + const { series } = newProps; + const options = this.plot.getOptions(); + _.set(options, 'series.bars.barWidth', calculateBarWidth(series)); + _.set(options, 'xaxes[0].ticks', this.calculateTicks()); + this.plot.setData(this.calculateData(series, newProps.show)); + this.plot.setupGrid(); + this.plot.draw(); + if (!_.isEqual(this.props.series, newProps.series)) this.handleDraw(this.plot); + } else { + this.renderChart(); + } + } + + componentDidMount() { + this.renderChart(); + } + + componentDidUpdate() { + this.shutdownChart(); + this.renderChart(); + } + + calculateData(data, show) { + return _(data) + .filter(this.filterByShow(show)) + .map(set => { + if (_.isPlainObject(set)) { + return { + ...set, + lines: this.computeColor(set.lines, set.color), + bars: this.computeColor(set.bars, set.color), + }; + } + return { + color: '#990000', + data: set, + }; + }) + .reverse() + .value(); + } + + computeColor(style, color) { + if (style && style.show) { + const { fill, fillColor } = calculateFillColor(color, style.fill); + return { + ...style, + fill, + fillColor, + }; + } + return style; + } + + handleDraw(plot) { + if (this.props.onDraw) this.props.onDraw(plot); + } + + getOptions(props) { + const yaxes = props.yaxes || [{}]; + + const lineColor = COLORS.lineColor; + const textColor = props.reversed ? COLORS.textColorReversed : COLORS.textColor; + + const borderWidth = { bottom: 1, top: 0, left: 0, right: 0 }; + + if (yaxes.some(y => y.position === 'left')) borderWidth.left = 1; + if (yaxes.some(y => y.position === 'right')) borderWidth.right = 1; + + if (props.showGrid) { + borderWidth.top = 1; + borderWidth.left = 1; + borderWidth.right = 1; + } + + const opts = { + legend: { show: false }, + yaxes: yaxes.map(axis => { + axis.tickLength = props.showGrid ? null : 0; + return axis; + }), + yaxis: { + color: lineColor, + font: { color: textColor, size: 11 }, + tickFormatter: props.tickFormatter, + }, + xaxis: { + tickLength: props.showGrid ? null : 0, + color: lineColor, + timezone: 'browser', + mode: 'time', + font: { color: textColor, size: 11 }, + ticks: this.calculateTicks(), + }, + series: { + shadowSize: 0, + }, + grid: { + margin: 0, + borderWidth, + borderColor: lineColor, + hoverable: true, + mouseActiveRadius: 200, + }, + }; + + if (props.crosshair) { + _.set(opts, 'crosshair', { + mode: 'x', + color: '#C66', + lineWidth: 1, + }); + } + + if (props.onBrush) { + _.set(opts, 'selection', { mode: 'x', color: textColor }); + } + + if (props.xAxisFormatter) { + _.set(opts, 'xaxis.tickFormatter', props.xAxisFormatter); + } + + _.set(opts, 'series.bars.barWidth', calculateBarWidth(props.series)); + return _.assign(opts, props.options); + } + + calculateTicks() { + const sample = this.props.xAxisFormatter(new Date()); + const tickLetterWidth = 7; + const tickPadding = 45; + const ticks = Math.floor( + this.target.clientWidth / (sample.length * tickLetterWidth + tickPadding) + ); + return ticks; + } + + handleResize() { + const resize = findDOMNode(this.resize); + if (!this.rendered) { + this.renderChart(); + return; + } + + if (resize && resize.clientHeight > 0 && resize.clientHeight > 0) { + if (!this.plot) return; + const options = this.plot.getOptions(); + _.set(options, 'xaxes[0].ticks', this.calculateTicks()); + this.plot.resize(); + this.plot.setupGrid(); + this.plot.draw(); + this.handleDraw(this.plot); + } + } + + renderChart() { + const resize = findDOMNode(this.resize); + + if (resize.clientWidth > 0 && resize.clientHeight > 0) { + this.rendered = true; + const { series } = this.props; + const data = this.calculateData(series, this.props.show); + + this.plot = $.plot(this.target, data, this.getOptions(this.props)); + this.handleDraw(this.plot); + + _.defer(() => this.handleResize()); + + this.handleMouseOver = (...args) => { + if (this.props.onMouseOver) this.props.onMouseOver(...args, this.plot); + }; + + this.handleMouseLeave = (...args) => { + if (this.props.onMouseLeave) this.props.onMouseLeave(...args, this.plot); + }; + + $(this.target).on('plothover', this.handleMouseOver); + $(this.target).on('mouseleave', this.handleMouseLeave); + + if (this.props.crosshair) { + this.handleThorPlotover = (e, pos, item, originalPlot) => { + if (this.plot !== originalPlot) { + this.plot.setCrosshair({ x: _.get(pos, 'x') }); + this.props.plothover(e, pos, item); + } + }; + + this.handlePlotover = (e, pos, item) => + eventBus.trigger('thorPlotover', [pos, item, this.plot]); + this.handlePlotleave = () => eventBus.trigger('thorPlotleave'); + this.handleThorPlotleave = e => { + if (this.plot) this.plot.clearCrosshair(); + if (this.props.plothover) this.props.plothover(e); + }; + + $(this.target).on('plothover', this.handlePlotover); + $(this.target).on('mouseleave', this.handlePlotleave); + eventBus.on('thorPlotover', this.handleThorPlotover); + eventBus.on('thorPlotleave', this.handleThorPlotleave); + } + + if (_.isFunction(this.props.plothover)) { + $(this.target).bind('plothover', this.props.plothover); + } + + $(this.target).on('mouseleave', () => { + eventBus.trigger('thorPlotleave'); + }); + + if (_.isFunction(this.props.onBrush)) { + this.brushChart = (e, ranges) => { + this.props.onBrush(ranges); + this.plot.clearSelection(); + }; + + $(this.target).on('plotselected', this.brushChart); + } + } + } + + render() { + return ( + (this.resize = el)} + className="tvbVisTimeSeries__container" + > +
(this.target = el)} className="tvbVisTimeSeries__container" /> + + ); + } +} + +FlotChart.defaultProps = { + showGrid: true, +}; + +FlotChart.propTypes = { + crosshair: PropTypes.bool, + onBrush: PropTypes.func, + onPlotCreate: PropTypes.func, + onMouseOver: PropTypes.func, + onMouseLeave: PropTypes.func, + options: PropTypes.object, + plothover: PropTypes.func, + reversed: PropTypes.bool, + series: PropTypes.array, + show: PropTypes.array, + tickFormatter: PropTypes.func, + showGrid: PropTypes.bool, + yaxes: PropTypes.array, +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/gauge.js b/src/legacy/core_plugins/metrics/public/visualizations/components/gauge.js similarity index 100% rename from src/legacy/core_plugins/metrics/public/visualizations/views/gauge.js rename to src/legacy/core_plugins/metrics/public/visualizations/components/gauge.js diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/gauge_vis.js b/src/legacy/core_plugins/metrics/public/visualizations/components/gauge_vis.js similarity index 98% rename from src/legacy/core_plugins/metrics/public/visualizations/views/gauge_vis.js rename to src/legacy/core_plugins/metrics/public/visualizations/components/gauge_vis.js index aa4ac99243397..28f00bddd4e57 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/gauge_vis.js +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/gauge_vis.js @@ -22,7 +22,7 @@ import React, { Component } from 'react'; import _ from 'lodash'; import reactcss from 'reactcss'; import { calculateCoordinates } from '../lib/calculate_coordinates'; -import { COLORS } from '../constants/chart'; +import { COLORS } from '../lib/colors'; export class GaugeVis extends Component { constructor(props) { @@ -118,7 +118,7 @@ export class GaugeVis extends Component { cx: 60, cy: 60, fill: 'rgba(0,0,0,0)', - stroke: COLORS.LINE_COLOR, + stroke: COLORS.lineColor, strokeDasharray: `${sliceSize * size} ${size}`, strokeWidth: this.props.innerLine, }, diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/horizontal_legend.js b/src/legacy/core_plugins/metrics/public/visualizations/components/horizontal_legend.js new file mode 100644 index 0000000000000..3bdb904d8d9c1 --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/horizontal_legend.js @@ -0,0 +1,80 @@ +/* + * 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 PropTypes from 'prop-types'; +import React from 'react'; +import { createLegendSeries } from '../lib/create_legend_series'; +import reactcss from 'reactcss'; +import { htmlIdGenerator, EuiButtonIcon } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; + +export const HorizontalLegend = injectI18n(function(props) { + const rows = props.series.map(createLegendSeries(props)); + const htmlId = htmlIdGenerator(); + const styles = reactcss( + { + hideLegend: { + legend: { + display: 'none', + }, + }, + }, + { hideLegend: !props.showLegend } + ); + + let legendToggleIcon = 'arrowDown'; + if (!props.showLegend) { + legendToggleIcon = 'arrowUp'; + } + return ( +
+ +
+ {rows} +
+
+ ); +}); + +HorizontalLegend.propTypes = { + legendPosition: PropTypes.string, + onClick: PropTypes.func, + onToggle: PropTypes.func, + series: PropTypes.array, + showLegend: PropTypes.bool, + seriesValues: PropTypes.object, + seriesFilter: PropTypes.array, + tickFormatter: PropTypes.func, +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/constants/chart.js b/src/legacy/core_plugins/metrics/public/visualizations/components/legend.js similarity index 57% rename from src/legacy/core_plugins/metrics/public/visualizations/constants/chart.js rename to src/legacy/core_plugins/metrics/public/visualizations/components/legend.js index 2e7eae438de12..38e3bd3d05921 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/constants/chart.js +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/legend.js @@ -17,25 +17,25 @@ * under the License. */ -export const COLORS = { - LINE_COLOR: 'rgba(105,112,125,0.2)', - TEXT_COLOR: 'rgba(0,0,0,0.4)', - TEXT_COLOR_REVERSED: 'rgba(255,255,255,0.5)', - VALUE_COLOR: 'rgba(0,0,0,0.7)', - VALUE_COLOR_REVERSED: 'rgba(255,255,255,0.8)', -}; - -export const GRID_LINE_CONFIG = { - stroke: 'rgba(125,125,125,0.1)', -}; +import PropTypes from 'prop-types'; +import React from 'react'; +import { VerticalLegend } from './vertical_legend'; +import { HorizontalLegend } from './horizontal_legend'; -export const X_ACCESSOR_INDEX = 0; -export const STACK_ACCESSORS = [0]; -export const Y_ACCESSOR_INDEXES = [1]; +export function Legend(props) { + if (props.legendPosition === 'bottom') { + return ; + } + return ; +} -export const STACKED_OPTIONS = { - NONE: 'none', - PERCENT: 'percent', - STACKED: 'stacked', - STACKED_WITHIN_SERIES: 'stacked_within_series', +Legend.propTypes = { + legendPosition: PropTypes.string, + onClick: PropTypes.func, + onToggle: PropTypes.func, + series: PropTypes.array, + showLegend: PropTypes.bool, + seriesValues: PropTypes.object, + seriesFilter: PropTypes.array, + tickFormatter: PropTypes.func, }; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/metric.js b/src/legacy/core_plugins/metrics/public/visualizations/components/metric.js similarity index 100% rename from src/legacy/core_plugins/metrics/public/visualizations/views/metric.js rename to src/legacy/core_plugins/metrics/public/visualizations/components/metric.js diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/resize.js b/src/legacy/core_plugins/metrics/public/visualizations/components/resize.js new file mode 100644 index 0000000000000..e6aa9831cf1a8 --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/resize.js @@ -0,0 +1,81 @@ +/* + * 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 PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { findDOMNode } from 'react-dom'; + +export class Resize extends Component { + constructor(props) { + super(props); + this.state = {}; + this.handleResize = this.handleResize.bind(this); + } + + checkSize() { + const el = findDOMNode(this.el); + if (!el) return; + this.timeout = setTimeout(() => { + const { currentHeight, currentWidth } = this.state; + if ( + currentHeight !== el.parentNode.clientHeight || + currentWidth !== el.parentNode.clientWidth + ) { + this.setState({ + currentWidth: el.parentNode.clientWidth, + currentHeight: el.parentNode.clientHeight, + }); + this.handleResize(); + } + clearTimeout(this.timeout); + this.checkSize(); + }, this.props.frequency); + } + + componentDidMount() { + this.checkSize(); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + handleResize() { + if (this.props.onResize) this.props.onResize(); + } + + render() { + const style = this.props.style || {}; + const className = this.props.className || ''; + return ( +
(this.el = el)}> + {this.props.children} +
+ ); + } +} + +Resize.defaultProps = { + frequency: 500, +}; + +Resize.propTypes = { + frequency: PropTypes.number, + onResize: PropTypes.func, +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/timeseries.js b/src/legacy/core_plugins/metrics/public/visualizations/components/timeseries.js new file mode 100644 index 0000000000000..2fe676efa58aa --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/timeseries.js @@ -0,0 +1,201 @@ +/* + * 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 PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import classNames from 'classnames'; +import _ from 'lodash'; +import { getLastValue } from '../../../common/get_last_value'; +import { isBackgroundInverted } from '../../../common/set_is_reversed'; +import { TimeseriesChart } from './timeseries_chart'; +import { Legend } from './legend'; +import { eventBus } from '../lib/events'; +import reactcss from 'reactcss'; + +export class Timeseries extends Component { + constructor(props) { + super(props); + const values = this.getLastValues(props); + this.state = { + showLegend: props.legend != null ? props.legend : true, + values: values || {}, + show: _.keys(values) || [], + ignoreLegendUpdates: false, + ignoreVisibilityUpdates: false, + }; + this.toggleFilter = this.toggleFilter.bind(this); + this.handleHideClick = this.handleHideClick.bind(this); + this.plothover = this.plothover.bind(this); + } + + filterLegend(id) { + if (!_.has(this.state.values, id)) return []; + const notAllShown = _.keys(this.state.values).length !== this.state.show.length; + const isCurrentlyShown = _.includes(this.state.show, id); + const show = []; + if (notAllShown && isCurrentlyShown) { + this.setState({ ignoreVisibilityUpdates: false, show: Object.keys(this.state.values) }); + } else { + show.push(id); + this.setState({ ignoreVisibilityUpdates: true, show: [id] }); + } + return show; + } + + toggleFilter(event, id) { + const show = this.filterLegend(id); + if (_.isFunction(this.props.onFilter)) { + this.props.onFilter(show); + } + eventBus.trigger('toggleFilter', id, this); + } + + getLastValues(props) { + const values = {}; + props.series.forEach(row => { + // we need a valid identifier + if (!row.id) row.id = row.label; + values[row.id] = getLastValue(row.data); + }); + return values; + } + + updateLegend(pos, item) { + const values = {}; + if (pos) { + this.props.series.forEach(row => { + if (row.data && Array.isArray(row.data)) { + if ( + item && + row.data[item.dataIndex] && + row.data[item.dataIndex][0] === item.datapoint[0] + ) { + values[row.id] = row.data[item.dataIndex][1]; + } else { + let closest; + for (let i = 0; i < row.data.length; i++) { + closest = i; + if (row.data[i] && pos.x < row.data[i][0]) break; + } + if (!row.data[closest]) return (values[row.id] = null); + const [, value] = row.data[closest]; + values[row.id] = (value != null && value) || null; + } + } + }); + } else { + _.assign(values, this.getLastValues(this.props)); + } + + this.setState({ values }); + } + + componentWillReceiveProps(props) { + if (props.legend !== this.props.legend) this.setState({ showLegend: props.legend }); + if (!this.state.ignoreLegendUpdates) { + const values = this.getLastValues(props); + const currentKeys = _.keys(this.state.values); + const keys = _.keys(values); + const diff = _.difference(keys, currentKeys); + const nextState = { values: values }; + if (diff.length && !this.state.ignoreVisibilityUpdates) { + nextState.show = keys; + } + this.setState(nextState); + } + } + + plothover(event, pos, item) { + this.updateLegend(pos, item); + } + + handleHideClick() { + this.setState({ showLegend: !this.state.showLegend }); + } + + render() { + const classes = classNames('tvbVisTimeSeries', { + 'tvbVisTimeSeries--reversed': isBackgroundInverted(this.props.backgroundColor), + }); + + const styles = reactcss( + { + bottomLegend: { + content: { + flexDirection: 'column', + }, + }, + }, + { bottomLegend: this.props.legendPosition === 'bottom' } + ); + return ( +
+
+
+ +
+ +
+
+ ); + } +} + +Timeseries.defaultProps = { + legend: true, + showGrid: true, +}; + +Timeseries.propTypes = { + legend: PropTypes.bool, + legendPosition: PropTypes.string, + onFilter: PropTypes.func, + series: PropTypes.array, + annotations: PropTypes.array, + backgroundColor: PropTypes.string, + options: PropTypes.object, + tickFormatter: PropTypes.func, + showGrid: PropTypes.bool, + xaxisLabel: PropTypes.string, + dateFormat: PropTypes.string, +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/timeseries_chart.js b/src/legacy/core_plugins/metrics/public/visualizations/components/timeseries_chart.js new file mode 100644 index 0000000000000..aa00c93ccf4b6 --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/timeseries_chart.js @@ -0,0 +1,229 @@ +/* + * 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 PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import classNames from 'classnames'; +import { isBackgroundInverted, isBackgroundDark } from '../../../common/set_is_reversed'; +import moment from 'moment'; +import reactcss from 'reactcss'; +import { FlotChart } from './flot_chart'; +import { Annotation } from './annotation'; +import { EuiIcon } from '@elastic/eui'; + +export function scaleUp(value) { + return window.devicePixelRatio * value; +} + +export function scaleDown(value) { + return value / window.devicePixelRatio; +} + +export class TimeseriesChart extends Component { + constructor(props) { + super(props); + this.state = { + annotations: [], + showTooltip: false, + mouseHoverTimer: false, + }; + this.handleMouseLeave = this.handleMouseLeave.bind(this); + this.handleMouseOver = this.handleMouseOver.bind(this); + this.renderAnnotations = this.renderAnnotations.bind(this); + this.handleDraw = this.handleDraw.bind(this); + } + + calculateLeftRight(item, plot) { + const canvas = plot.getCanvas(); + const point = plot.pointOffset({ x: item.datapoint[0], y: item.datapoint[1] }); + const edge = (scaleUp(point.left) + 10) / canvas.width; + let right; + let left; + if (edge > 0.5) { + right = scaleDown(canvas.width) - point.left; + left = null; + } else { + right = null; + left = point.left; + } + return [left, right]; + } + + handleDraw(plot) { + if (!plot || !this.props.annotations) return; + const annotations = this.props.annotations.reduce((acc, anno) => { + return acc.concat( + anno.series.map(series => { + return { + series, + plot, + key: `${anno.id}-${series[0]}`, + icon: anno.icon, + color: anno.color, + }; + }) + ); + }, []); + this.setState({ annotations }); + } + + handleMouseOver(e, pos, item, plot) { + if (typeof this.state.mouseHoverTimer === 'number') { + window.clearTimeout(this.state.mouseHoverTimer); + } + + if (item) { + const plotOffset = plot.getPlotOffset(); + const point = plot.pointOffset({ x: item.datapoint[0], y: item.datapoint[1] }); + const [left, right] = this.calculateLeftRight(item, plot); + const top = point.top; + this.setState({ + showTooltip: true, + item, + left, + right, + top: top + 10, + bottom: plotOffset.bottom, + }); + } + } + + handleMouseLeave() { + this.state.mouseHoverTimer = window.setTimeout(() => { + this.setState({ showTooltip: false }); + }, 250); + } + + renderAnnotations(annotation) { + return ( + + ); + } + + render() { + const { item, right, top, left } = this.state; + const { series } = this.props; + let tooltip; + + const styles = reactcss( + { + showTooltip: { + tooltipContainer: { + top: top - 8, + left, + right, + }, + }, + hideTooltip: { + tooltipContainer: { display: 'none' }, + }, + }, + { + showTooltip: this.state.showTooltip, + hideTooltip: !this.state.showTooltip, + } + ); + + if (item) { + const metric = series.find(r => r.id === item.series.id); + const formatter = (metric && metric.tickFormatter) || this.props.tickFormatter || (v => v); + const value = item.datapoint[2] ? item.datapoint[1] - item.datapoint[2] : item.datapoint[1]; + tooltip = ( +
+ +
+
+ {moment(item.datapoint[0]).format(this.props.dateFormat)} +
+
+ +
+
{item.series.label}
+
{formatter(value)}
+
+
+
+
+ ); + } + + const params = { + crosshair: this.props.crosshair, + onPlotCreate: this.handlePlotCreate, + onBrush: this.props.onBrush, + onMouseLeave: this.handleMouseLeave, + onMouseOver: this.handleMouseOver, + onDraw: this.handleDraw, + options: this.props.options, + plothover: this.props.plothover, + reversed: isBackgroundDark(this.props.backgroundColor), + series: this.props.series, + annotations: this.props.annotations, + showGrid: this.props.showGrid, + show: this.props.show, + tickFormatter: this.props.tickFormatter, + yaxes: this.props.yaxes, + xAxisFormatter: this.props.xAxisFormatter, + }; + + const annotations = this.state.annotations.map(this.renderAnnotations); + const axisLabelClass = classNames('tvbVisTimeSeries__axisLabel', { + 'tvbVisTimeSeries__axisLabel--reversed': isBackgroundInverted(this.props.backgroundColor), + }); + + return ( +
(this.container = el)} className="tvbVisTimeSeries__container"> + {tooltip} + {annotations} + +
{this.props.xaxisLabel}
+
+ ); + } +} + +TimeseriesChart.defaultProps = { + showGrid: true, + dateFormat: 'll LTS', +}; + +TimeseriesChart.propTypes = { + crosshair: PropTypes.bool, + onBrush: PropTypes.func, + options: PropTypes.object, + plothover: PropTypes.func, + backgroundColor: PropTypes.string, + series: PropTypes.array, + annotations: PropTypes.array, + show: PropTypes.array, + tickFormatter: PropTypes.func, + yaxes: PropTypes.array, + showGrid: PropTypes.bool, + xaxisLabel: PropTypes.string, + dateFormat: PropTypes.string, +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/top_n.js b/src/legacy/core_plugins/metrics/public/visualizations/components/top_n.js similarity index 63% rename from src/legacy/core_plugins/metrics/public/visualizations/views/top_n.js rename to src/legacy/core_plugins/metrics/public/visualizations/components/top_n.js index 5734d101a09d2..ea8f795b7620d 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/top_n.js +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/top_n.js @@ -22,12 +22,6 @@ import React, { Component } from 'react'; import { getLastValue } from '../../../common/get_last_value'; import reactcss from 'reactcss'; -const RENDER_MODES = { - POSITIVE: 'positive', - NEGATIVE: 'negative', - MIXED: 'mixed', -}; - export class TopN extends Component { constructor(props) { super(props); @@ -65,61 +59,20 @@ export class TopN extends Component { }; } - static getRenderMode = (min, max) => { - if (min >= 0) { - return RENDER_MODES.POSITIVE; - } else if (max < 0) { - return RENDER_MODES.NEGATIVE; - } - return RENDER_MODES.MIXED; - }; - - static calcInnerBarStyles = (renderMode, isPositive) => { - if (renderMode === RENDER_MODES.MIXED) { - return { - [isPositive ? 'marginLeft' : 'marginRight']: '50%', - }; - } - return {}; - }; - - static calcInnerBarDivStyles = (item, width, isPositive) => { - return { - backgroundColor: item.color, - width: width + '%', - float: isPositive ? 'left' : 'right', - }; - }; - - static calcDomain = (renderMode, min, max) => { - if (renderMode === RENDER_MODES.MIXED) { - return Math.max(max, Math.abs(min)); - } else if (renderMode === RENDER_MODES.NEGATIVE) { - return Math.abs(min); - } - - return max; - }; - - renderRow({ min, max }) { + renderRow(maxValue) { return item => { - const renderMode = TopN.getRenderMode(min, max); const key = `${item.id || item.label}`; const lastValue = getLastValue(item.data); const formatter = item.tickFormatter || this.props.tickFormatter; - const isPositiveValue = lastValue >= 0; - - const intervalLength = TopN.calcDomain(renderMode, min, max); - const width = 100 * (Math.abs(lastValue) / intervalLength); - + const value = formatter(lastValue); + const width = `${100 * (lastValue / maxValue)}%`; + const backgroundColor = item.color; const styles = reactcss( { default: { innerBar: { - ...TopN.calcInnerBarStyles(renderMode, isPositiveValue), - }, - innerBarValue: { - ...TopN.calcInnerBarDivStyles(item, width, isPositiveValue), + width, + backgroundColor, }, label: { maxWidth: this.state.labelMaxWidth, @@ -139,12 +92,10 @@ export class TopN extends Component { {item.label} -
-
-
+
- {formatter(lastValue)} + {value} ); @@ -153,20 +104,12 @@ export class TopN extends Component { render() { if (!this.props.series) return null; + const maxValue = this.props.series.reduce((max, series) => { + const lastValue = getLastValue(series.data); + return lastValue > max ? lastValue : max; + }, 0); - const intervalSettings = this.props.series.reduce( - (acc, series, index) => { - const value = getLastValue(series.data); - - return { - min: !index || value < acc.min ? value : acc.min, - max: !index || value > acc.max ? value : acc.max, - }; - }, - { min: undefined, max: undefined } - ); - - const rows = this.props.series.map(this.renderRow(intervalSettings)); + const rows = this.props.series.map(this.renderRow(maxValue)); let className = 'tvbVisTopN'; if (this.props.reversed) { className += ' tvbVisTopN--reversed'; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/components/vertical_legend.js b/src/legacy/core_plugins/metrics/public/visualizations/components/vertical_legend.js new file mode 100644 index 0000000000000..47c3c357d6f1b --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/components/vertical_legend.js @@ -0,0 +1,91 @@ +/* + * 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 PropTypes from 'prop-types'; +import React from 'react'; +import { createLegendSeries } from '../lib/create_legend_series'; +import reactcss from 'reactcss'; +import { htmlIdGenerator, EuiButtonIcon } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; + +export const VerticalLegend = injectI18n(function(props) { + const rows = props.series.map(createLegendSeries(props)); + const htmlId = htmlIdGenerator(); + const hideLegend = !props.showLegend; + const leftLegend = props.legendPosition === 'left'; + + const styles = reactcss( + { + default: { + legend: { width: 200 }, + }, + leftLegend: { + legend: { order: '-1' }, + control: { order: '2' }, + }, + hideLegend: { + legend: { width: 24 }, + series: { display: 'none' }, + }, + }, + { hideLegend, leftLegend } + ); + + const openIcon = leftLegend ? 'arrowRight' : 'arrowLeft'; + const closeIcon = leftLegend ? 'arrowLeft' : 'arrowRight'; + const legendToggleIcon = hideLegend ? `${openIcon}` : `${closeIcon}`; + + return ( +
+ + +
+ {rows} +
+
+ ); +}); + +VerticalLegend.propTypes = { + legendPosition: PropTypes.string, + onClick: PropTypes.func, + onToggle: PropTypes.func, + series: PropTypes.array, + showLegend: PropTypes.bool, + seriesValues: PropTypes.object, + seriesFilter: PropTypes.array, + tickFormatter: PropTypes.func, +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/constants/icons.js b/src/legacy/core_plugins/metrics/public/visualizations/constants/icons.js deleted file mode 100644 index 4c55a9dfbd387..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/constants/icons.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { bombIcon } from '../../components/svg/bomb_icon'; -import { fireIcon } from '../../components/svg/fire_icon'; - -export const ICON_NAMES = { - ASTERISK: 'fa-asterisk', - BELL: 'fa-bell', - BOLT: 'fa-bolt', - BOMB: 'fa-bomb', - BUG: 'fa-bug', - COMMENT: 'fa-comment', - EXCLAMATION_CIRCLE: 'fa-exclamation-circle', - EXCLAMATION_TRIANGLE: 'fa-exclamation-triangle', - FIRE: 'fa-fire', - FLAG: 'fa-flag', - HEART: 'fa-heart', - MAP_MARKER: 'fa-map-marker', - MAP_PIN: 'fa-map-pin', - STAR: 'fa-star', - TAG: 'fa-tag', -}; - -export const ICON_TYPES_MAP = { - [ICON_NAMES.ASTERISK]: 'asterisk', - [ICON_NAMES.BELL]: 'bell', - [ICON_NAMES.BOLT]: 'bolt', - [ICON_NAMES.BOMB]: bombIcon, - [ICON_NAMES.BUG]: 'bug', - [ICON_NAMES.COMMENT]: 'editorComment', - [ICON_NAMES.EXCLAMATION_CIRCLE]: 'alert', // TODO: Change as an exclamation mark is added - [ICON_NAMES.EXCLAMATION_TRIANGLE]: 'alert', - [ICON_NAMES.FIRE]: fireIcon, - [ICON_NAMES.FLAG]: 'flag', - [ICON_NAMES.HEART]: 'heart', - [ICON_NAMES.MAP_MARKER]: 'mapMarker', - [ICON_NAMES.MAP_PIN]: 'pinFilled', - [ICON_NAMES.STAR]: 'starFilled', - [ICON_NAMES.TAG]: 'tag', -}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/lib/active_cursor.js b/src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/calcualte_bar_width.test.js similarity index 62% rename from src/legacy/core_plugins/metrics/public/visualizations/lib/active_cursor.js rename to src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/calcualte_bar_width.test.js index 427ced4dc3f2a..bf5fd0174e96b 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/lib/active_cursor.js +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/calcualte_bar_width.test.js @@ -17,9 +17,17 @@ * under the License. */ -// TODO: Remove bus when action/triggers are available with LegacyPluginApi or metric is converted to Embeddable -import $ from 'jquery'; +import { expect } from 'chai'; +import { calculateBarWidth } from '../calculate_bar_width'; -export const ACTIVE_CURSOR = 'ACTIVE_CURSOR'; +describe('calculateBarWidth(series, divisor, multiplier)', () => { + it('returns default bar width', () => { + const series = [{ data: [[100, 100], [200, 100]] }]; + expect(calculateBarWidth(series)).to.equal(70); + }); -export const eventBus = $({}); + it('returns custom bar width', () => { + const series = [{ data: [[100, 100], [200, 100]] }]; + expect(calculateBarWidth(series, 2)).to.equal(200); + }); +}); diff --git a/src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/calculate_fill_color.test.js b/src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/calculate_fill_color.test.js new file mode 100644 index 0000000000000..c43c0a16f24dc --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/calculate_fill_color.test.js @@ -0,0 +1,56 @@ +/* + * 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 { expect } from 'chai'; +import { calculateFillColor } from '../calculate_fill_color'; + +describe('calculateFillColor(color, fill)', () => { + it('should return "fill" and "fillColor" properties', () => { + const color = 'rgb(255,0,0)'; + const fill = 1; + const data = calculateFillColor(color, fill); + + expect(data.fill).to.be.true; + expect(data.fillColor).to.be.a('string'); + }); + + it('should set "fill" property to false in case of 0 opacity', () => { + const color = 'rgb(255, 0, 0)'; + const fill = 0; + const data = calculateFillColor(color, fill); + + expect(data.fill).to.be.false; + }); + + it('should return the opacity less than 1', () => { + const color = 'rgba(255, 0, 0, 0.9)'; + const fill = 10; + const data = calculateFillColor(color, fill); + + expect(data.fillColor).to.equal('rgba(255, 0, 0, 0.9)'); + }); + + it('should sum fill and color opacity', () => { + const color = 'rgba(255, 0, 0, 0.5)'; + const fill = 0.5; + const data = calculateFillColor(color, fill); + + expect(data.fillColor).to.equal('rgba(255, 0, 0, 0.25)'); + }); +}); diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js b/src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/get_value_by.test.js similarity index 59% rename from src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js rename to src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/get_value_by.test.js index cb59bef63681b..6acbb1196d123 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/__tests__/get_value_by.test.js @@ -17,29 +17,20 @@ * under the License. */ -export const CurveType = { - CURVE_CARDINAL: 0, - CURVE_NATURAL: 1, - CURVE_MONOTONE_X: 2, - CURVE_MONOTONE_Y: 3, - CURVE_BASIS: 4, - CURVE_CATMULL_ROM: 5, - CURVE_STEP: 6, - CURVE_STEP_AFTER: 7, - CURVE_STEP_BEFORE: 8, - LINEAR: 9, -}; +import { getValueBy } from '../get_value_by'; +import { expect } from 'chai'; -export const ScaleType = { - Linear: 'linear', - Ordinal: 'ordinal', - Log: 'log', - Sqrt: 'sqrt', - Time: 'time', -}; - -export const getSpecId = x => `id:${x}`; -export const getGroupId = x => `groupId:${x}`; - -export const BarSeries = () => null; -export const AreaSeries = () => null; +describe('getValueBy(fn, data)', () => { + it("returns max for getValueBy('max', data) ", () => { + const data = [[0, 5], [1, 3], [2, 4], [3, 6], [4, 5]]; + expect(getValueBy('max', data)).to.equal(6); + }); + it('returns 0 if data is not array', () => { + const data = '1'; + expect(getValueBy('max', data)).to.equal(0); + }); + it('returns value if data is number', () => { + const data = 1; + expect(getValueBy('max', data)).to.equal(1); + }); +}); diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/stack_format.js b/src/legacy/core_plugins/metrics/public/visualizations/lib/calculate_bar_width.js similarity index 65% rename from src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/stack_format.js rename to src/legacy/core_plugins/metrics/public/visualizations/lib/calculate_bar_width.js index 20c655c995a5e..a58246c446882 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/stack_format.js +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/calculate_bar_width.js @@ -17,15 +17,15 @@ * under the License. */ -import { STACK_ACCESSORS, STACKED_OPTIONS } from '../../../constants'; - -export const getStackAccessors = stack => { - switch (stack) { - case STACKED_OPTIONS.STACKED: - case STACKED_OPTIONS.STACKED_WITHIN_SERIES: - case STACKED_OPTIONS.PERCENT: - return STACK_ACCESSORS; - default: - return undefined; +import _ from 'lodash'; +// bar sizes are measured in milliseconds so this assumes that the different +// between timestamps is in milliseconds. A normal bar size is 70% which gives +// enough spacing for the bar. +export const calculateBarWidth = (series, multiplier = 0.7) => { + const first = _.first(series); + try { + return (first.data[1][0] - first.data[0][0]) * multiplier; + } catch (e) { + return 1000; // 1000 ms } }; diff --git a/src/legacy/ui/field_formats/converters/custom.ts b/src/legacy/core_plugins/metrics/public/visualizations/lib/calculate_fill_color.js similarity index 69% rename from src/legacy/ui/field_formats/converters/custom.ts rename to src/legacy/core_plugins/metrics/public/visualizations/lib/calculate_fill_color.js index bc9b421127228..878cc518ef384 100644 --- a/src/legacy/ui/field_formats/converters/custom.ts +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/calculate_fill_color.js @@ -17,16 +17,16 @@ * under the License. */ -import { FieldFormat } from '../field_format'; -import { FieldFormatConvert } from '../types'; +import Color from 'color'; -const ID = 'custom'; +export const calculateFillColor = (color, fill = 1) => { + const initialColor = new Color(color).rgb(); -export const createCustomFieldFormat = (convert: FieldFormatConvert) => - class CustomFieldFormat extends FieldFormat { - static id = ID; + const opacity = Math.min(Number(fill), 1) * initialColor.valpha; + const [r, g, b] = initialColor.color; - public get _convert() { - return convert; - } + return { + fill: opacity > 0, + fillColor: new Color([r, g, b, Number(opacity.toFixed(2))]).string(), }; +}; diff --git a/src/legacy/core_plugins/data/public/timefilter/index.ts b/src/legacy/core_plugins/metrics/public/visualizations/lib/colors.js similarity index 78% rename from src/legacy/core_plugins/data/public/timefilter/index.ts rename to src/legacy/core_plugins/metrics/public/visualizations/lib/colors.js index da57bca126b0f..d3c7e2191f996 100644 --- a/src/legacy/core_plugins/data/public/timefilter/index.ts +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/colors.js @@ -17,6 +17,10 @@ * under the License. */ -export { Timefilter } from './timefilter'; -export { TimeHistory } from './time_history'; -export { getTime } from './get_time'; +export const COLORS = { + lineColor: 'rgba(105,112,125,0.2)', + textColor: 'rgba(0,0,0,0.4)', + textColorReversed: 'rgba(255,255,255,0.5)', + valueColor: 'rgba(0,0,0,0.7)', + valueColorReversed: 'rgba(255,255,255,0.8)', +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/lib/create_legend_series.js b/src/legacy/core_plugins/metrics/public/visualizations/lib/create_legend_series.js new file mode 100644 index 0000000000000..1e4e35f8a366a --- /dev/null +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/create_legend_series.js @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import _ from 'lodash'; +import { EuiIcon } from '@elastic/eui'; + +export const createLegendSeries = props => (row, index = 0) => { + function tickFormatter(value) { + if (_.isFunction(props.tickFormatter)) return props.tickFormatter(value); + return value; + } + const key = `tvbLegend__item${row.id}${index}`; + const formatter = row.tickFormatter || tickFormatter; + const value = formatter(props.seriesValues[row.id]); + const classes = ['tvbLegend__item']; + + if (!_.includes(props.seriesFilter, row.id)) classes.push('disabled'); + if (row.label == null || row.legend === false) + return
; + return ( +
+ +
+ ); +}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/constants/index.js b/src/legacy/core_plugins/metrics/public/visualizations/lib/events.js similarity index 93% rename from src/legacy/core_plugins/metrics/public/visualizations/constants/index.js rename to src/legacy/core_plugins/metrics/public/visualizations/lib/events.js index 0264b0bcfa9e0..3bfcfa72032fd 100644 --- a/src/legacy/core_plugins/metrics/public/visualizations/constants/index.js +++ b/src/legacy/core_plugins/metrics/public/visualizations/lib/events.js @@ -17,5 +17,6 @@ * under the License. */ -export * from './chart'; -export * from './icons'; +import $ from 'jquery'; + +export const eventBus = $({}); diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap deleted file mode 100644 index 822de4cef0813..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap +++ /dev/null @@ -1,63 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.js should render and match a snapshot 1`] = ` - "rgb(0, 156, 224)", - } - } - data={ - Array [ - Array [ - 1556917200000, - 7, - ], - Array [ - 1557003600000, - 9, - ], - ] - } - enableHistogramMode={true} - groupId="groupId:yaxis_main_group" - hideInLegend={false} - histogramModeAlignment="center" - id="id:61ca57f1-469d-11e7-af02-69e470af7417:Rome" - name="Rome" - stackAsPercentage={false} - timeZone="local" - xAccessor={0} - xScaleType="time" - yAccessors={ - Array [ - 1, - ] - } - yScaleType="linear" -/> -`; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap deleted file mode 100644 index 78133f2dda7cc..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.js should render and match a snapshot 1`] = ` - "rgb(0, 156, 224)", - } - } - data={ - Array [ - Array [ - 1556917200000, - 7, - ], - Array [ - 1557003600000, - 9, - ], - ] - } - enableHistogramMode={true} - groupId="groupId:yaxis_main_group" - hideInLegend={false} - histogramModeAlignment="center" - id="id:61ca57f1-469d-11e7-af02-69e470af7417:Rome" - name="Rome" - stackAsPercentage={false} - timeZone="local" - xAccessor={0} - xScaleType="time" - yAccessors={ - Array [ - 1, - ] - } - yScaleType="linear" -/> -`; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.js deleted file mode 100644 index 536064139e6ea..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 React from 'react'; -import { getSpecId, getGroupId, ScaleType, AreaSeries } from '@elastic/charts'; -import { getSeriesColors, getAreaStyles } from '../utils/series_styles'; -import { ChartsEntities } from '../model/charts'; -import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants'; - -export function AreaSeriesDecorator({ - seriesId, - seriesGroupId, - name, - data, - hideInLegend, - lines, - color, - stackAccessors, - stackAsPercentage, - points, - xScaleType, - yScaleType, - timeZone, - enableHistogramMode, - useDefaultGroupDomain, - sortIndex, -}) { - const id = getSpecId(seriesId); - const groupId = getGroupId(seriesGroupId); - const customSeriesColors = getSeriesColors(color, id); - const areaSeriesStyle = getAreaStyles({ points, lines, color }); - - const seriesSettings = { - id, - name, - groupId, - data, - customSeriesColors, - hideInLegend, - xAccessor: X_ACCESSOR_INDEX, - yAccessors: Y_ACCESSOR_INDEXES, - stackAccessors, - stackAsPercentage, - xScaleType, - yScaleType, - timeZone, - enableHistogramMode, - useDefaultGroupDomain, - sortIndex, - ...areaSeriesStyle, - }; - - if (enableHistogramMode) { - seriesSettings.histogramModeAlignment = 'center'; - } - - return ; -} - -AreaSeriesDecorator.propTypes = ChartsEntities.AreaChart; - -AreaSeriesDecorator.defaultProps = { - yScaleType: ScaleType.Linear, - xScaleType: ScaleType.Time, -}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.test.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.test.js deleted file mode 100644 index f58abc7f1f724..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.test.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 React from 'react'; -import { shallow } from 'enzyme'; -import { AreaSeriesDecorator } from './area_decorator'; - -describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.js', () => { - let props; - - beforeEach(() => { - props = { - lines: { - fill: 1, - lineWidth: 2, - show: true, - steps: false, - }, - points: { - lineWidth: 5, - radius: 1, - show: false, - }, - color: 'rgb(0, 156, 224)', - data: [[1556917200000, 7], [1557003600000, 9]], - hideInLegend: false, - stackAsPercentage: false, - seriesId: '61ca57f1-469d-11e7-af02-69e470af7417:Rome', - seriesGroupId: 'yaxis_main_group', - name: 'Rome', - stack: false, - timeZone: 'local', - enableHistogramMode: true, - }; - }); - - describe('', () => { - test('should render and match a snapshot', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); - }); - }); -}); diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.js deleted file mode 100644 index 3dbe04dca06b8..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 React from 'react'; -import { getSpecId, getGroupId, ScaleType, BarSeries } from '@elastic/charts'; -import { getSeriesColors, getBarStyles } from '../utils/series_styles'; -import { ChartsEntities } from '../model/charts'; -import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants'; - -export function BarSeriesDecorator({ - seriesId, - seriesGroupId, - name, - data, - hideInLegend, - bars, - color, - stackAccessors, - stackAsPercentage, - xScaleType, - yScaleType, - timeZone, - enableHistogramMode, - useDefaultGroupDomain, - sortIndex, -}) { - const id = getSpecId(seriesId); - const groupId = getGroupId(seriesGroupId); - const customSeriesColors = getSeriesColors(color, id); - const barSeriesStyle = getBarStyles(bars, color); - - const seriesSettings = { - id, - name, - groupId, - data, - customSeriesColors, - hideInLegend, - xAccessor: X_ACCESSOR_INDEX, - yAccessors: Y_ACCESSOR_INDEXES, - stackAccessors, - stackAsPercentage, - xScaleType, - yScaleType, - timeZone, - enableHistogramMode, - useDefaultGroupDomain, - sortIndex, - ...barSeriesStyle, - }; - - if (enableHistogramMode) { - seriesSettings.histogramModeAlignment = 'center'; - } - - return ; -} - -BarSeriesDecorator.propTypes = ChartsEntities.BarChart; - -BarSeriesDecorator.defaultProps = { - yScaleType: ScaleType.Linear, - xScaleType: ScaleType.Time, -}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.test.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.test.js deleted file mode 100644 index 8432814870fbf..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.test.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 React from 'react'; -import { shallow } from 'enzyme'; -import { BarSeriesDecorator } from './bar_decorator'; - -describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/bar_decorator.js', () => { - let props; - - beforeEach(() => { - props = { - bars: { show: true, fill: 0.5, lineWidth: 2 }, - color: 'rgb(0, 156, 224)', - data: [[1556917200000, 7], [1557003600000, 9]], - hideInLegend: false, - stackAsPercentage: false, - seriesId: '61ca57f1-469d-11e7-af02-69e470af7417:Rome', - seriesGroupId: 'yaxis_main_group', - name: 'Rome', - stack: false, - timeZone: 'local', - enableHistogramMode: true, - }; - }); - - describe('', () => { - test('should render and match a snapshot', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchSnapshot(); - }); - }); -}); diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/index.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/index.js deleted file mode 100644 index a02ea83e5104b..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/index.js +++ /dev/null @@ -1,257 +0,0 @@ -/* - * 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 React, { useEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; - -import { - Axis, - Chart, - Position, - Settings, - getAxisId, - getGroupId, - DARK_THEME, - LIGHT_THEME, - getAnnotationId, - AnnotationDomainTypes, - LineAnnotation, - TooltipType, -} from '@elastic/charts'; -import { EuiIcon } from '@elastic/eui'; - -import { timezoneProvider } from 'ui/vis/lib/timezone'; -import { eventBus, ACTIVE_CURSOR } from '../../lib/active_cursor'; -import chrome from 'ui/chrome'; -import { GRID_LINE_CONFIG, ICON_TYPES_MAP, STACKED_OPTIONS } from '../../constants'; -import { AreaSeriesDecorator } from './decorators/area_decorator'; -import { BarSeriesDecorator } from './decorators/bar_decorator'; -import { getStackAccessors } from './utils/stack_format'; - -const generateAnnotationData = (values, formatter) => - values.map(({ key, docs }) => ({ - dataValue: key, - details: docs[0], - header: formatter({ - value: key, - }), - })); - -const decorateFormatter = formatter => ({ value }) => formatter(value); - -const handleCursorUpdate = cursor => { - eventBus.trigger(ACTIVE_CURSOR, cursor); -}; - -export const TimeSeries = ({ - isDarkMode, - showGrid, - legend, - legendPosition, - xAxisLabel, - series, - yAxis, - onBrush, - xAxisFormatter, - annotations, - enableHistogramMode, -}) => { - const chartRef = useRef(); - const updateCursor = (_, cursor) => { - if (chartRef.current) { - chartRef.current.dispatchExternalCursorEvent(cursor); - } - }; - - useEffect(() => { - eventBus.on(ACTIVE_CURSOR, updateCursor); - - return () => { - eventBus.off(ACTIVE_CURSOR, undefined, updateCursor); - }; - }, []); // eslint-disable-line - - const tooltipFormatter = decorateFormatter(xAxisFormatter); - const uiSettings = chrome.getUiSettingsClient(); - const timeZone = timezoneProvider(uiSettings)(); - const hasBarChart = series.some(({ bars }) => bars.show); - - return ( - - - - {annotations.map(({ id, data, icon, color }) => { - const dataValues = generateAnnotationData(data, tooltipFormatter); - const style = { line: { stroke: color } }; - - return ( - } - hideLinesTooltips={true} - style={style} - /> - ); - })} - - {series.map( - ( - { - id, - label, - bars, - lines, - data, - hideInLegend, - xScaleType, - yScaleType, - groupId, - color, - stack, - points, - useDefaultGroupDomain, - }, - sortIndex - ) => { - const stackAccessors = getStackAccessors(stack); - const isPercentage = stack === STACKED_OPTIONS.PERCENT; - const key = `${id}-${label}`; - - if (bars.show) { - return ( - - ); - } - - if (lines.show) { - return ( - - ); - } - - return null; - } - )} - - {yAxis.map(({ id, groupId, position, tickFormatter, domain, hide }) => ( - - ))} - - - - ); -}; - -TimeSeries.defaultProps = { - showGrid: true, - legend: true, - legendPosition: 'right', -}; - -TimeSeries.propTypes = { - isDarkMode: PropTypes.bool, - showGrid: PropTypes.bool, - legend: PropTypes.bool, - legendPosition: PropTypes.string, - xAxisLabel: PropTypes.string, - series: PropTypes.array, - yAxis: PropTypes.array, - onBrush: PropTypes.func, - xAxisFormatter: PropTypes.func, - annotations: PropTypes.array, - enableHistogramMode: PropTypes.bool.isRequired, -}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap deleted file mode 100644 index 541265c05057a..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/__snapshots__/charts.test.js.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.js ChartsEntities should match a snapshot of ChartsEntities 1`] = ` -Object { - "AreaChart": Object { - "color": [Function], - "data": [Function], - "enableHistogramMode": [Function], - "hideInLegend": [Function], - "lines": [Function], - "name": [Function], - "points": [Function], - "seriesGroupId": [Function], - "seriesId": [Function], - "sortIndex": [Function], - "stackAccessors": [Function], - "stackAsPercentage": [Function], - "timeZone": [Function], - "useDefaultGroupDomain": [Function], - "xScaleType": [Function], - "yScaleType": [Function], - }, - "BarChart": Object { - "bars": [Function], - "color": [Function], - "data": [Function], - "enableHistogramMode": [Function], - "hideInLegend": [Function], - "name": [Function], - "seriesGroupId": [Function], - "seriesId": [Function], - "sortIndex": [Function], - "stackAccessors": [Function], - "stackAsPercentage": [Function], - "timeZone": [Function], - "useDefaultGroupDomain": [Function], - "xScaleType": [Function], - "yScaleType": [Function], - }, -} -`; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.js deleted file mode 100644 index b14b84dcd1fe4..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/model/charts.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 PropTypes from 'prop-types'; - -const Chart = { - seriesId: PropTypes.string.isRequired, - seriesGroupId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - /** - * @example - * [[1556917200000, 6], [1556231200000, 16]] - */ - data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, - hideInLegend: PropTypes.bool.isRequired, - color: PropTypes.string.isRequired, - stackAsPercentage: PropTypes.bool.isRequired, - stackAccessors: PropTypes.arrayOf(PropTypes.number), - xScaleType: PropTypes.string, - yScaleType: PropTypes.string, - timeZone: PropTypes.string.isRequired, - enableHistogramMode: PropTypes.bool.isRequired, - useDefaultGroupDomain: PropTypes.bool, - sortIndex: PropTypes.number, -}; - -const BarChart = { - ...Chart, - bars: PropTypes.shape({ - fill: PropTypes.number, - lineWidth: PropTypes.number, - show: PropTypes.boolean, - }).isRequired, -}; - -const AreaChart = { - ...Chart, - lines: PropTypes.shape({ - fill: PropTypes.number, - lineWidth: PropTypes.number, - show: PropTypes.bool, - steps: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), - }).isRequired, - points: PropTypes.shape({ - lineWidth: PropTypes.number, - radius: PropTypes.number, - show: PropTypes.bool, - }).isRequired, -}; - -export const ChartsEntities = { - BarChart, - AreaChart, -}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap deleted file mode 100644 index 607580d1f37f8..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/__snapshots__/series_styles.test.js.snap +++ /dev/null @@ -1,90 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js getAreaStyles() should match a snapshot 1`] = ` -Object { - "areaSeriesStyle": Object { - "area": Object { - "fill": "rgb(224, 0, 221)", - "opacity": 0, - "visible": true, - }, - "line": Object { - "stroke": "rgb(224, 0, 221)", - "strokeWidth": 1, - "visible": true, - }, - "point": Object { - "radius": 1, - "stroke": "rgb(224, 0, 221)", - "strokeWidth": 1, - "visible": true, - }, - }, - "curve": 6, -} -`; - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js getAreaStyles() should set default values if points, lines and color are empty 1`] = ` -Object { - "areaSeriesStyle": Object { - "area": Object { - "fill": "", - "opacity": undefined, - "visible": false, - }, - "line": Object { - "stroke": "", - "strokeWidth": 0, - "visible": false, - }, - "point": Object { - "radius": 0.5, - "stroke": "#000", - "strokeWidth": 5, - "visible": false, - }, - }, - "curve": 9, -} -`; - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js getBarStyles() should match a snapshot 1`] = ` -Object { - "barSeriesStyle": Object { - "rect": Object { - "fill": "rgb(224, 0, 221)", - "opacity": 0.5, - }, - "rectBorder": Object { - "stroke": "rgb(224, 0, 221)", - "strokeWidth": 2, - "visible": true, - }, - }, -} -`; - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js getBarStyles() should set default values if bars and colors are empty 1`] = ` -Object { - "barSeriesStyle": Object { - "rect": Object { - "fill": "#000", - "opacity": 1, - }, - "rectBorder": Object { - "stroke": "#000", - "strokeWidth": 0, - "visible": true, - }, - }, -} -`; - -exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js getSeriesColors() should match a snapshot 1`] = ` -Map { - Object { - "colorValues": Array [], - "specId": "IT", - } => "rgb(224, 0, 221)", -} -`; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js deleted file mode 100644 index 63be14790c6c5..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { CurveType } from '@elastic/charts'; - -const DEFAULT_COLOR = '#000'; - -export const getAreaStyles = ({ points, lines, color }) => ({ - areaSeriesStyle: { - line: { - stroke: color, - strokeWidth: Number(lines.lineWidth) || 0, - visible: Boolean(lines.show && lines.lineWidth), - }, - area: { - fill: color, - opacity: lines.fill <= 0 ? 0 : lines.fill, - visible: Boolean(lines.show), - }, - point: { - radius: points.radius || 0.5, - stroke: color || DEFAULT_COLOR, - strokeWidth: points.lineWidth || 5, - visible: points.lineWidth > 0 && Boolean(points.show), - }, - }, - curve: lines.steps ? CurveType.CURVE_STEP : CurveType.LINEAR, -}); - -export const getBarStyles = ({ show = true, lineWidth = 0, fill = 1 }, color) => ({ - barSeriesStyle: { - rectBorder: { - stroke: color || DEFAULT_COLOR, - strokeWidth: lineWidth, - visible: show, - }, - rect: { - fill: color || DEFAULT_COLOR, - opacity: fill, - }, - }, -}); - -export const getSeriesColors = (color, specId) => { - const map = new Map(); - const seriesColorsValues = { specId, colorValues: [] }; - - map.set(seriesColorsValues, color); - - return map; -}; diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.test.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.test.js deleted file mode 100644 index ac0a7610f2660..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.test.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 { getBarStyles, getSeriesColors, getAreaStyles } from './series_styles'; - -describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js', () => { - let bars; - let color; - let specId; - let points; - let lines; - - beforeEach(() => { - bars = { - fill: 0.5, - lineWidth: 2, - show: true, - }; - color = 'rgb(224, 0, 221)'; - specId = 'IT'; - points = { - lineWidth: 1, - show: true, - radius: 1, - }; - lines = { - fill: 0, - lineWidth: 1, - show: true, - steps: true, - }; - }); - - describe('getBarStyles()', () => { - test('should match a snapshot', () => { - expect(getBarStyles(bars, color)).toMatchSnapshot(); - }); - - test('should set default values if bars and colors are empty', () => { - bars = {}; - color = ''; - - expect(getBarStyles(bars, color)).toMatchSnapshot(); - }); - }); - - describe('getSeriesColors()', () => { - test('should match a snapshot', () => { - expect(getSeriesColors(color, specId)).toMatchSnapshot(); - }); - }); - - describe('getAreaStyles()', () => { - test('should match a snapshot', () => { - expect(getAreaStyles({ points, lines, color })).toMatchSnapshot(); - }); - - test('should set default values if points, lines and color are empty', () => { - points = {}; - lines = {}; - color = ''; - - expect(getAreaStyles({ points, lines, color })).toMatchSnapshot(); - }); - }); -}); diff --git a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/stack_format.test.js b/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/stack_format.test.js deleted file mode 100644 index aecdb9324d958..0000000000000 --- a/src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/stack_format.test.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { getStackAccessors } from './stack_format'; -import { X_ACCESSOR_INDEX, STACKED_OPTIONS } from '../../../constants'; - -describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/stack_format.js', () => { - describe('getStackAccessors()', () => { - test('should return an accessor if the stack is stacked', () => { - expect(getStackAccessors(STACKED_OPTIONS.STACKED)).toEqual([X_ACCESSOR_INDEX]); - }); - - test('should return an accessor if the stack is percent', () => { - expect(getStackAccessors(STACKED_OPTIONS.PERCENT)).toEqual([X_ACCESSOR_INDEX]); - }); - - test('should return undefined if the stack does not match with STACKED and PERCENT', () => { - expect(getStackAccessors(STACKED_OPTIONS.NONE)).toBeUndefined(); - }); - }); -}); diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/__tests__/helpers/get_default_decoration.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/__tests__/helpers/get_default_decoration.js index 5cc94dda6d21a..c9c4f24fa566c 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/__tests__/helpers/get_default_decoration.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/__tests__/helpers/get_default_decoration.js @@ -22,34 +22,35 @@ import { getDefaultDecoration } from '../../helpers/get_default_decoration'; describe('getDefaultDecoration', () => { describe('stack option', () => { - it('should set a stack option to none', () => { + it('should set a stack option to false', () => { const series = { id: 'test_id', - stacked: 'none', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'none'); + expect(getDefaultDecoration(series)).to.have.property('stack', false); + + series.stacked = 'none'; + expect(getDefaultDecoration(series)).to.have.property('stack', false); }); - it('should set a stack option to stacked/percent', () => { + it('should set a stack option to true', () => { const series = { stacked: 'stacked', id: 'test_id', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'stacked'); + expect(getDefaultDecoration(series)).to.have.property('stack', true); series.stacked = 'percent'; - - expect(getDefaultDecoration(series)).to.have.property('stack', 'percent'); + expect(getDefaultDecoration(series)).to.have.property('stack', true); }); - it('should set a stack option to stacked_within_series', () => { + it('should set a stack option to be series id', () => { const series = { stacked: 'stacked_within_series', id: 'test_id', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'stacked_within_series'); + expect(getDefaultDecoration(series)).to.have.property('stack', series.id); }); }); diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_default_decoration.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_default_decoration.js index b63eb5488a755..2f3b8959d5d68 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_default_decoration.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_default_decoration.js @@ -21,10 +21,21 @@ export const getDefaultDecoration = series => { const pointSize = series.point_size != null ? Number(series.point_size) : Number(series.line_width); const showPoints = series.chart_type === 'line' && pointSize !== 0; + let stack; + switch (series.stacked) { + case 'stacked': + case 'percent': + stack = true; + break; + case 'stacked_within_series': + stack = series.id; + break; + default: + stack = false; + } return { - seriesId: series.id, - stack: series.stacked, + stack, lines: { show: series.chart_type === 'line' && series.line_width !== 0, fill: Number(series.fill), diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/series_agg.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/series_agg.js index 17dbde16e306e..2bdcdf0fdabd3 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/series_agg.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/series_agg.js @@ -109,7 +109,6 @@ describe('seriesAgg(resp, panel, series)', () => { color: '#F00', label: 'Total CPU', stack: false, - seriesId: 'test', lines: { show: true, fill: 0, lineWidth: 1, steps: false }, points: { show: true, radius: 1, lineWidth: 1 }, bars: { fill: 0, lineWidth: 1, show: false }, diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js index f8c98adc5d023..af24eb1be81c4 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js @@ -98,7 +98,6 @@ describe('stdSibling(resp, panel, series)', () => { label: 'Overall Std. Deviation of Average of cpu', color: 'rgb(255, 0, 0)', stack: false, - seriesId: 'test', lines: { show: true, fill: 0, lineWidth: 1, steps: false }, points: { show: true, radius: 1, lineWidth: 1 }, bars: { fill: 0, lineWidth: 1, show: false }, diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js index b0b40f8ff704e..a5c2296bd8b99 100644 --- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -39,7 +39,7 @@ import afterdatachangePng from './afterdatachange.png'; import afterdatachangeandresizePng from './afterdatachangeandresize.png'; import aftercolorchangePng from './aftercolorchange.png'; import changestartupPng from './changestartup.png'; -import { setup } from '../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../../visualizations/public'; import { createRegionMapVisualization } from '../region_map_visualization'; import { createRegionMapTypeDefinition } from '../region_map_type'; @@ -48,6 +48,7 @@ const THRESHOLD = 0.45; const PIXEL_DIFF = 96; describe('RegionMapsVisualizationTests', function () { + let domNode; let RegionMapsVisualization; let Vis; @@ -60,87 +61,83 @@ describe('RegionMapsVisualizationTests', function () { const _makeJsonAjaxCallOld = ChoroplethLayer.prototype._makeJsonAjaxCall; const dummyTableGroup = { - columns: [ - { - id: 'col-0', - aggConfig: { - id: '2', - enabled: true, - type: 'terms', - schema: 'segment', - params: { field: 'geo.dest', size: 5, order: 'desc', orderBy: '1' }, - }, - title: 'geo.dest: Descending', - }, - { - id: 'col-1', - aggConfig: { id: '1', enabled: true, type: 'count', schema: 'metric', params: {} }, - title: 'Count', - }, - ], + columns: [{ + 'id': 'col-0', + 'aggConfig': { + 'id': '2', + 'enabled': true, + 'type': 'terms', + 'schema': 'segment', + 'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' } + }, 'title': 'geo.dest: Descending' + }, { + 'id': 'col-1', + 'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} }, + 'title': 'Count' + }], rows: [ { 'col-0': 'CN', 'col-1': 26 }, { 'col-0': 'IN', 'col-1': 17 }, { 'col-0': 'US', 'col-1': 6 }, { 'col-0': 'DE', 'col-1': 4 }, - { 'col-0': 'BR', 'col-1': 3 }, - ], + { 'col-0': 'BR', 'col-1': 3 } + ] }; beforeEach(ngMock.module('kibana')); let getManifestStub; - beforeEach( - ngMock.inject((Private, $injector) => { - const serviceSettings = $injector.get('serviceSettings'); - const uiSettings = $injector.get('config'); - const regionmapsConfig = { - includeElasticMapsService: true, - layers: [], - }; - - dependencies = { - serviceSettings, - $injector, - regionmapsConfig, - uiSettings, - }; - - setup.types.registerVisualization(() => createRegionMapTypeDefinition(dependencies)); - - Vis = Private(visModule.VisProvider); - RegionMapsVisualization = createRegionMapVisualization(dependencies); - indexPattern = Private(LogstashIndexPatternStubProvider); - - ChoroplethLayer.prototype._makeJsonAjaxCall = async function () { - //simulate network call - return new Promise(resolve => { - setTimeout(() => { - resolve(worldJson); - }, 10); - }); - }; - - getManifestStub = serviceSettings.__debugStubManifestCalls(async url => { - //simulate network calls - if (url.startsWith('https://foobar')) { - return EMS_CATALOGUE; - } else if (url.startsWith('https://tiles.foobar')) { - return EMS_TILES; - } else if (url.startsWith('https://files.foobar')) { - return EMS_FILES; - } else if (url.startsWith('https://raster-style.foobar')) { - if (url.includes('osm-bright-desaturated')) { - return EMS_STYLE_ROAD_MAP_DESATURATED; - } else if (url.includes('osm-bright')) { - return EMS_STYLE_ROAD_MAP_BRIGHT; - } else if (url.includes('dark-matter')) { - return EMS_STYLE_DARK_MAP; - } - } + beforeEach(ngMock.inject((Private, $injector) => { + const serviceSettings = $injector.get('serviceSettings'); + const uiSettings = $injector.get('config'); + const regionmapsConfig = { + includeElasticMapsService: true, + layers: [] + }; + + dependencies = { + serviceSettings, + $injector, + regionmapsConfig, + uiSettings + }; + + visualizations.types.VisTypesRegistryProvider.register(() => + createRegionMapTypeDefinition(dependencies) + ); + + Vis = Private(visModule.VisProvider); + RegionMapsVisualization = createRegionMapVisualization(dependencies); + indexPattern = Private(LogstashIndexPatternStubProvider); + + ChoroplethLayer.prototype._makeJsonAjaxCall = async function () { + //simulate network call + return new Promise((resolve)=> { + setTimeout(() => { + resolve(worldJson); + }, 10); }); - }) - ); + }; + + getManifestStub = serviceSettings.__debugStubManifestCalls(async (url) => { + //simulate network calls + if (url.startsWith('https://foobar')) { + return EMS_CATALOGUE; + } else if (url.startsWith('https://tiles.foobar')) { + return EMS_TILES; + } else if (url.startsWith('https://files.foobar')) { + return EMS_FILES; + } else if (url.startsWith('https://raster-style.foobar')) { + if (url.includes('osm-bright-desaturated')) { + return EMS_STYLE_ROAD_MAP_DESATURATED; + } else if (url.includes('osm-bright')) { + return EMS_STYLE_ROAD_MAP_BRIGHT; + } else if (url.includes('dark-matter')) { + return EMS_STYLE_DARK_MAP; + } + } + }); + })); afterEach(function () { ChoroplethLayer.prototype._makeJsonAjaxCall = _makeJsonAjaxCallOld; @@ -148,13 +145,15 @@ describe('RegionMapsVisualizationTests', function () { }); describe('RegionMapVisualization - basics', function () { + beforeEach(async function () { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); + vis = new Vis(indexPattern, { - type: 'region_map', + type: 'region_map' }); vis.params.bucket = { @@ -164,25 +163,19 @@ describe('RegionMapsVisualizationTests', function () { accessor: 1, }; - vis.params.selectedJoinField = { name: 'iso2', description: 'Two letter abbreviation' }; + vis.params.selectedJoinField = { 'name': 'iso2', 'description': 'Two letter abbreviation' }; vis.params.selectedLayer = { - attribution: - '

Made with NaturalEarth | Elastic Maps Service

', - name: 'World Countries', - format: 'geojson', - url: - 'https://vector-staging.maps.elastic.co/blob/5715999101812736?elastic_tile_service_tos=agree&my_app_version=7.0.0-alpha1', - fields: [ - { name: 'iso2', description: 'Two letter abbreviation' }, - { - name: 'iso3', - description: 'Three letter abbreviation', - }, - { name: 'name', description: 'Country name' }, - ], - created_at: '2017-07-31T16:00:19.996450', - id: 5715999101812736, - layerId: 'elastic_maps_service.World Countries', + 'attribution': '

Made with NaturalEarth | Elastic Maps Service

', + 'name': 'World Countries', + 'format': 'geojson', + 'url': 'https://vector-staging.maps.elastic.co/blob/5715999101812736?elastic_tile_service_tos=agree&my_app_version=7.0.0-alpha1', + 'fields': [{ 'name': 'iso2', 'description': 'Two letter abbreviation' }, { + 'name': 'iso3', + 'description': 'Three letter abbreviation' + }, { 'name': 'name', 'description': 'Country name' }], + 'created_at': '2017-07-31T16:00:19.996450', + 'id': 5715999101812736, + 'layerId': 'elastic_maps_service.World Countries' }; }); @@ -191,6 +184,7 @@ describe('RegionMapsVisualizationTests', function () { imageComparator.destroy(); }); + it('should instantiate at zoom level 2', async function () { const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, vis.params, { @@ -198,7 +192,7 @@ describe('RegionMapsVisualizationTests', function () { params: true, aggs: true, data: true, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(initialPng); regionMapsVisualization.destroy(); @@ -212,33 +206,35 @@ describe('RegionMapsVisualizationTests', function () { params: true, aggs: true, data: true, - uiState: false, + uiState: false }); //this will actually create an empty image - vis.params.selectedJoinField = { name: 'iso3', description: 'Three letter abbreviation' }; - vis.params.isDisplayWarning = false; //so we don't get notifications + vis.params.selectedJoinField = { 'name': 'iso3', 'description': 'Three letter abbreviation' }; + vis.params.isDisplayWarning = false;//so we don't get notifications await regionMapsVisualization.render(dummyTableGroup, vis.params, { resize: false, params: true, aggs: false, data: false, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(toiso3Png); regionMapsVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); + }); it('should resize', async function () { + const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, vis.params, { resize: false, params: true, aggs: true, data: true, - uiState: false, + uiState: false }); domNode.style.width = '256px'; @@ -248,7 +244,7 @@ describe('RegionMapsVisualizationTests', function () { params: false, aggs: false, data: false, - uiState: false, + uiState: false }); const mismatchedPixelsAfterFirstResize = await compareImage(afterresizePng); @@ -259,7 +255,7 @@ describe('RegionMapsVisualizationTests', function () { params: false, aggs: false, data: false, - uiState: false, + uiState: false }); const mismatchedPixelsAfterSecondResize = await compareImage(initialPng); @@ -269,31 +265,32 @@ describe('RegionMapsVisualizationTests', function () { }); it('should redo data', async function () { + const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, vis.params, { resize: false, params: true, aggs: true, data: true, - uiState: false, + uiState: false }); const newTableGroup = _.cloneDeep(dummyTableGroup); - newTableGroup.rows.pop(); //remove one shape + newTableGroup.rows.pop();//remove one shape await regionMapsVisualization.render(newTableGroup, vis.params, { resize: false, params: false, aggs: false, data: true, - uiState: false, + uiState: false }); const mismatchedPixelsAfterDataChange = await compareImage(afterdatachangePng); const anotherTableGroup = _.cloneDeep(newTableGroup); - anotherTableGroup.rows.pop(); //remove one shape + anotherTableGroup.rows.pop();//remove one shape domNode.style.width = '412px'; domNode.style.height = '112px'; await regionMapsVisualization.render(anotherTableGroup, vis.params, { @@ -301,11 +298,9 @@ describe('RegionMapsVisualizationTests', function () { params: false, aggs: false, data: true, - uiState: false, + uiState: false }); - const mismatchedPixelsAfterDataChangeAndResize = await compareImage( - afterdatachangeandresizePng - ); + const mismatchedPixelsAfterDataChangeAndResize = await compareImage(afterdatachangeandresizePng); regionMapsVisualization.destroy(); expect(mismatchedPixelsAfterDataChange).to.be.lessThan(PIXEL_DIFF); @@ -319,18 +314,18 @@ describe('RegionMapsVisualizationTests', function () { params: true, aggs: true, data: true, - uiState: false, + uiState: false }); const newTableGroup = _.cloneDeep(dummyTableGroup); - newTableGroup.rows.pop(); //remove one shape + newTableGroup.rows.pop();//remove one shape vis.params.colorSchema = 'Blues'; await regionMapsVisualization.render(newTableGroup, vis.params, { resize: false, params: true, aggs: false, data: true, - uiState: false, + uiState: false }); const mismatchedPixelsAfterDataAndColorChange = await compareImage(aftercolorchangePng); @@ -347,7 +342,7 @@ describe('RegionMapsVisualizationTests', function () { params: true, aggs: true, data: true, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(changestartupPng); @@ -355,6 +350,7 @@ describe('RegionMapsVisualizationTests', function () { expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); }); + }); async function compareImage(expectedImageSource) { diff --git a/src/legacy/core_plugins/region_map/public/legacy.ts b/src/legacy/core_plugins/region_map/public/legacy.ts index 7adbc2117d7ee..5e01e7ee56e2f 100644 --- a/src/legacy/core_plugins/region_map/public/legacy.ts +++ b/src/legacy/core_plugins/region_map/public/legacy.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { RegionMapPluginSetupDependencies, RegionMapsConfig } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; @@ -30,7 +30,7 @@ const regionmapsConfig = npSetup.core.injectedMetadata.getInjectedVar( ) as RegionMapsConfig; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/region_map/public/plugin.ts b/src/legacy/core_plugins/region_map/public/plugin.ts index 4afee7269c578..995a96b7a19c7 100644 --- a/src/legacy/core_plugins/region_map/public/plugin.ts +++ b/src/legacy/core_plugins/region_map/public/plugin.ts @@ -24,7 +24,7 @@ import { UiSettingsClientContract, } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; @@ -70,7 +70,7 @@ export class RegionMapPlugin implements Plugin, void> { data.expressions.registerFunction(createRegionMapFn); - visualizations.types.registerVisualization(() => + visualizations.types.VisTypesRegistryProvider.register(() => createRegionMapTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/region_map/public/region_map_type.js b/src/legacy/core_plugins/region_map/public/region_map_type.js index 9b28aa96eccbc..10aaeb1060bc0 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_type.js +++ b/src/legacy/core_plugins/region_map/public/region_map_type.js @@ -25,7 +25,7 @@ import { createRegionMapVisualization } from './region_map_visualization'; import { Status } from 'ui/vis/update_status'; import { RegionMapOptions } from './components/region_map_options'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; // TODO: reference to TILE_MAP plugin should be removed import { ORIGIN } from '../../tile_map/common/origin'; @@ -40,11 +40,8 @@ export function createRegionMapTypeDefinition(dependencies) { return visFactory.createBaseVisualization({ name: 'region_map', title: i18n.translate('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }), - description: i18n.translate('regionMap.mapVis.regionMapDescription', { - defaultMessage: - 'Show metrics on a thematic map. Use one of the \ -provided base maps, or add your own. Darker colors represent higher values.', - }), + description: i18n.translate('regionMap.mapVis.regionMapDescription', { defaultMessage: 'Show metrics on a thematic map. Use one of the \ +provided base maps, or add your own. Darker colors represent higher values.' }), icon: 'visMapRegion', visConfig: { defaults: { @@ -59,59 +56,45 @@ provided base maps, or add your own. Darker colors represent higher values.', mapZoom: 2, mapCenter: [0, 0], outlineWeight: 1, - showAllShapes: true, //still under consideration - }, + showAllShapes: true//still under consideration + } }, requiresUpdateStatus: [Status.AGGS, Status.PARAMS, Status.RESIZE, Status.DATA, Status.UI_STATE], visualization, editorConfig: { - optionsTemplate: props => ( - + ( - ), + />), collections: { colorSchemas, vectorLayers, - tmsLayers: [], + tmsLayers: [] }, schemas: new Schemas([ { group: 'metrics', name: 'metric', - title: i18n.translate('regionMap.mapVis.regionMapEditorConfig.schemas.metricTitle', { - defaultMessage: 'Value', - }), + title: i18n.translate('regionMap.mapVis.regionMapEditorConfig.schemas.metricTitle', { defaultMessage: 'Value' }), min: 1, max: 1, - aggFilter: [ - 'count', - 'avg', - 'sum', - 'min', - 'max', - 'cardinality', - 'top_hits', - 'sum_bucket', - 'min_bucket', - 'max_bucket', - 'avg_bucket', - ], - defaults: [{ schema: 'metric', type: 'count' }], + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits', + 'sum_bucket', 'min_bucket', 'max_bucket', 'avg_bucket'], + defaults: [ + { schema: 'metric', type: 'count' } + ] }, { group: 'buckets', name: 'segment', - title: i18n.translate('regionMap.mapVis.regionMapEditorConfig.schemas.segmentTitle', { - defaultMessage: 'Shape field', - }), + title: i18n.translate('regionMap.mapVis.regionMapEditorConfig.schemas.segmentTitle', { defaultMessage: 'Shape field' }), min: 1, max: 1, - aggFilter: ['terms'], - }, - ]), - }, + aggFilter: ['terms'] + } + ]) + } }); } diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index 5f2abd8c9e083..53b627d39595e 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -85,7 +85,6 @@ const coreSystem = new CoreSystem({ injectedMetadata: { version: '1.2.3', buildNumber: 1234, - legacyMode: true, legacyMetadata: { nav: [], version: '1.2.3', diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index e32df04a583c2..6b3bdd6c8f993 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -33,7 +33,7 @@ import EMS_TILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_ import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_bright'; import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated'; import EMS_STYLE_DARK_MAP from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_dark'; -import { setup } from '../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../../visualizations/public'; import { createTileMapVisualization } from '../tile_map_visualization'; import { createTileMapTypeDefinition } from '../tile_map_type'; @@ -62,6 +62,7 @@ const THRESHOLD = 0.45; const PIXEL_DIFF = 64; describe('CoordinateMapsVisualizationTest', function () { + let domNode; let CoordinateMapsVisualization; let Vis; @@ -71,57 +72,59 @@ describe('CoordinateMapsVisualizationTest', function () { let imageComparator; + let getManifestStub; beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject((Private, $injector) => { - const serviceSettings = $injector.get('serviceSettings'); - const uiSettings = $injector.get('config'); - - dependencies = { - serviceSettings, - uiSettings, - $injector, - }; - - setup.types.registerVisualization(() => createTileMapTypeDefinition(dependencies)); - - Vis = Private(visModule.VisProvider); - CoordinateMapsVisualization = createTileMapVisualization(dependencies); - indexPattern = Private(LogstashIndexPatternStubProvider); - - getManifestStub = serviceSettings.__debugStubManifestCalls(async url => { - //simulate network calls - if (url.startsWith('https://foobar')) { - return EMS_CATALOGUE; - } else if (url.startsWith('https://tiles.foobar')) { - return EMS_TILES; - } else if (url.startsWith('https://files.foobar')) { - return EMS_FILES; - } else if (url.startsWith('https://raster-style.foobar')) { - if (url.includes('osm-bright-desaturated')) { - return EMS_STYLE_ROAD_MAP_DESATURATED; - } else if (url.includes('osm-bright')) { - return EMS_STYLE_ROAD_MAP_BRIGHT; - } else if (url.includes('dark-matter')) { - return EMS_STYLE_DARK_MAP; - } + beforeEach(ngMock.inject((Private, $injector) => { + const serviceSettings = $injector.get('serviceSettings'); + const uiSettings = $injector.get('config'); + + dependencies = { + serviceSettings, + uiSettings, + $injector + }; + + visualizations.types.VisTypesRegistryProvider.register(() => + createTileMapTypeDefinition(dependencies) + ); + + Vis = Private(visModule.VisProvider); + CoordinateMapsVisualization = createTileMapVisualization(dependencies); + indexPattern = Private(LogstashIndexPatternStubProvider); + + getManifestStub = serviceSettings.__debugStubManifestCalls(async (url) => { + //simulate network calls + if (url.startsWith('https://foobar')) { + return EMS_CATALOGUE; + } else if (url.startsWith('https://tiles.foobar')) { + return EMS_TILES; + } else if (url.startsWith('https://files.foobar')) { + return EMS_FILES; + } else if (url.startsWith('https://raster-style.foobar')) { + if (url.includes('osm-bright-desaturated')) { + return EMS_STYLE_ROAD_MAP_DESATURATED; + } else if (url.includes('osm-bright')) { + return EMS_STYLE_ROAD_MAP_BRIGHT; + } else if (url.includes('dark-matter')) { + return EMS_STYLE_DARK_MAP; } - }); - }) - ); + } + }); + })); afterEach(() => { getManifestStub.removeStub(); }); describe('CoordinateMapsVisualization - basics', function () { + beforeEach(async function () { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); vis = new Vis(indexPattern, { - type: 'tile_map', + type: 'tile_map' }); vis.params = { mapType: 'Scaled Circle Markers', @@ -133,33 +136,32 @@ describe('CoordinateMapsVisualizationTest', function () { mapCenter: [0, 0], }; const mockAggs = { - byType: type => { + byType: (type) => { return mockAggs.aggs.find(agg => agg.type.type === type); }, aggs: [ { type: { - type: 'metrics', + type: 'metrics' }, - fieldFormatter: x => { + fieldFormatter: (x) => { return x; }, makeLabel: () => { return 'foobar'; - }, - }, - { + } + }, { type: { - type: 'buckets', + type: 'buckets' }, - params: { useGeoCentroid: true }, - }, - ], + params: { useGeoCentroid: true } + }] }; vis.getAggConfig = function () { return mockAggs; }; vis.aggs = mockAggs; + }); afterEach(function () { @@ -174,7 +176,7 @@ describe('CoordinateMapsVisualizationTest', function () { params: true, aggs: true, data: true, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(initial, 0); @@ -182,38 +184,43 @@ describe('CoordinateMapsVisualizationTest', function () { expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); }); + it('should toggle to Heatmap OK', async function () { + const coordinateMapVisualization = new CoordinateMapsVisualization(domNode, vis); await coordinateMapVisualization.render(dummyESResponse, vis.params, { resize: false, params: true, aggs: true, data: true, - uiState: false, + uiState: false }); + vis.params.mapType = 'Heatmap'; await coordinateMapVisualization.render(dummyESResponse, vis.params, { resize: false, params: true, aggs: false, data: false, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(heatmapRaw, 1); coordinateMapVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); + }); it('should toggle back&forth OK between mapTypes', async function () { + const coordinateMapVisualization = new CoordinateMapsVisualization(domNode, vis); await coordinateMapVisualization.render(dummyESResponse, vis.params, { resize: false, params: true, aggs: true, data: true, - uiState: false, + uiState: false }); vis.params.mapType = 'Heatmap'; @@ -222,7 +229,7 @@ describe('CoordinateMapsVisualizationTest', function () { params: true, aggs: false, data: false, - uiState: false, + uiState: false }); vis.params.mapType = 'Scaled Circle Markers'; @@ -231,48 +238,54 @@ describe('CoordinateMapsVisualizationTest', function () { params: true, aggs: false, data: false, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(initial, 0); coordinateMapVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); + }); it('should toggle to different color schema ok', async function () { + const coordinateMapVisualization = new CoordinateMapsVisualization(domNode, vis); await coordinateMapVisualization.render(dummyESResponse, vis.params, { resize: false, params: true, aggs: true, data: true, - uiState: false, + uiState: false }); + vis.params.colorSchema = 'Blues'; await coordinateMapVisualization.render(dummyESResponse, vis.params, { resize: false, params: true, aggs: false, data: false, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(blues, 0); coordinateMapVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); + }); it('should toggle to different color schema and maptypes ok', async function () { + const coordinateMapVisualization = new CoordinateMapsVisualization(domNode, vis); await coordinateMapVisualization.render(dummyESResponse, vis.params, { resize: false, params: true, aggs: true, data: true, - uiState: false, + uiState: false }); + vis.params.colorSchema = 'Greens'; vis.params.mapType = 'Shaded Geohash Grid'; await coordinateMapVisualization.render(dummyESResponse, vis.params, { @@ -280,21 +293,26 @@ describe('CoordinateMapsVisualizationTest', function () { params: true, aggs: false, data: false, - uiState: false, + uiState: false }); const mismatchedPixels = await compareImage(shadedGeohashGrid, 0); coordinateMapVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); + }); + + }); + async function compareImage(expectedImageSource, index) { const elementList = domNode.querySelectorAll('canvas'); const firstCanvasOnMap = elementList[index]; return imageComparator.compareImage(firstCanvasOnMap, expectedImageSource, THRESHOLD); } + function setupDOM(width, height) { domNode = document.createElement('div'); domNode.style.top = '0'; @@ -311,4 +329,6 @@ describe('CoordinateMapsVisualizationTest', function () { domNode.innerHTML = ''; document.body.removeChild(domNode); } + }); + diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index 096266502417d..d0cff4013b0e7 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -31,7 +31,6 @@ import { } from '../../../kbn_vislib_vis_types/public/components'; import { WmsOptions } from './wms_options'; import { TileMapVisParams } from '../types'; -import { MapTypes } from '../map_types'; export type TileMapOptionsProps = { serviceSettings: ServiceSettings } & VisOptionsProps< TileMapVisParams @@ -59,7 +58,17 @@ function TileMapOptions(props: TileMapOptionsProps) { setValue={setValue} /> - {stateParams.mapType === MapTypes.Heatmap ? ( + + + {stateParams.mapType === 'Heatmap' && ( - ) : ( - )} diff --git a/src/legacy/core_plugins/tile_map/public/geohash_layer.js b/src/legacy/core_plugins/tile_map/public/geohash_layer.js index 287f4bb3b15ae..5ed00ee942f2a 100644 --- a/src/legacy/core_plugins/tile_map/public/geohash_layer.js +++ b/src/legacy/core_plugins/tile_map/public/geohash_layer.js @@ -26,7 +26,6 @@ import { HeatmapMarkers } from './markers/heatmap'; import { ScaledCirclesMarkers } from './markers/scaled_circles'; import { ShadedCirclesMarkers } from './markers/shaded_circles'; import { GeohashGridMarkers } from './markers/geohash_grid'; -import { MapTypes } from './map_types'; export class GeohashLayer extends KibanaMapLayer { @@ -55,19 +54,19 @@ export class GeohashLayer extends KibanaMapLayer { colorRamp: this._geohashOptions.colorRamp }; switch (this._geohashOptions.mapType) { - case MapTypes.ScaledCircleMarkers: + case 'Scaled Circle Markers': this._geohashMarkers = new ScaledCirclesMarkers(this._featureCollection, this._featureCollectionMetaData, markerOptions, this._zoom, this._kibanaMap); break; - case MapTypes.ShadedCircleMarkers: + case 'Shaded Circle Markers': this._geohashMarkers = new ShadedCirclesMarkers(this._featureCollection, this._featureCollectionMetaData, markerOptions, this._zoom, this._kibanaMap); break; - case MapTypes.ShadedGeohashGrid: + case 'Shaded Geohash Grid': this._geohashMarkers = new GeohashGridMarkers(this._featureCollection, this._featureCollectionMetaData, markerOptions, this._zoom, this._kibanaMap); break; - case MapTypes.Heatmap: + case 'Heatmap': let radius = 15; if (this._featureCollectionMetaData.geohashGridDimensionsAtEquator) { diff --git a/src/legacy/core_plugins/tile_map/public/legacy.ts b/src/legacy/core_plugins/tile_map/public/legacy.ts index cf2e702cd09b6..3237ab4373720 100644 --- a/src/legacy/core_plugins/tile_map/public/legacy.ts +++ b/src/legacy/core_plugins/tile_map/public/legacy.ts @@ -20,13 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { TileMapPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/tile_map/public/map_types.ts b/src/legacy/core_plugins/tile_map/public/map_types.ts deleted file mode 100644 index d4240fc8a579a..0000000000000 --- a/src/legacy/core_plugins/tile_map/public/map_types.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 enum MapTypes { - ScaledCircleMarkers = 'Scaled Circle Markers', - ShadedCircleMarkers = 'Shaded Circle Markers', - ShadedGeohashGrid = 'Shaded Geohash Grid', - Heatmap = 'Heatmap', -} diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/legacy/core_plugins/tile_map/public/plugin.ts index 1309c37a4c73a..f5ce0df297125 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/plugin.ts @@ -24,7 +24,7 @@ import { UiSettingsClientContract, } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; @@ -64,7 +64,7 @@ export class TileMapPlugin implements Plugin, void> { data.expressions.registerFunction(() => createTileMapFn(visualizationDependencies)); - visualizations.types.registerVisualization(() => + visualizations.types.VisTypesRegistryProvider.register(() => createTileMapTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index d96d8ad9be786..05dbe0fa9f9df 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -27,9 +27,8 @@ import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; import { createTileMapVisualization } from './tile_map_visualization'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; import { TileMapOptions } from './components/tile_map_options'; -import { MapTypes } from './map_types'; export function createTileMapTypeDefinition(dependencies) { const CoordinateMapsVisualization = createTileMapVisualization(dependencies); @@ -65,53 +64,48 @@ export function createTileMapTypeDefinition(dependencies) { editorConfig: { collections: { colorSchemas, - legendPositions: [ - { - value: 'bottomleft', - text: i18n.translate('tileMap.vis.editorConfig.legendPositions.bottomLeftText', { - defaultMessage: 'Bottom left', - }), - }, - { - value: 'bottomright', - text: i18n.translate('tileMap.vis.editorConfig.legendPositions.bottomRightText', { - defaultMessage: 'Bottom right', - }), - }, - { - value: 'topleft', - text: i18n.translate('tileMap.vis.editorConfig.legendPositions.topLeftText', { - defaultMessage: 'Top left', - }), - }, - { - value: 'topright', - text: i18n.translate('tileMap.vis.editorConfig.legendPositions.topRightText', { - defaultMessage: 'Top right', - }), - }, - ], + legendPositions: [{ + value: 'bottomleft', + text: i18n.translate('tileMap.vis.editorConfig.legendPositions.bottomLeftText', { + defaultMessage: 'Bottom left', + }), + }, { + value: 'bottomright', + text: i18n.translate('tileMap.vis.editorConfig.legendPositions.bottomRightText', { + defaultMessage: 'Bottom right', + }), + }, { + value: 'topleft', + text: i18n.translate('tileMap.vis.editorConfig.legendPositions.topLeftText', { + defaultMessage: 'Top left', + }), + }, { + value: 'topright', + text: i18n.translate('tileMap.vis.editorConfig.legendPositions.topRightText', { + defaultMessage: 'Top right', + }), + }], mapTypes: [ { - value: MapTypes.ScaledCircleMarkers, + value: 'Scaled Circle Markers', text: i18n.translate('tileMap.vis.editorConfig.mapTypes.scaledCircleMarkersText', { defaultMessage: 'Scaled circle markers', }), }, { - value: MapTypes.ShadedCircleMarkers, + value: 'Shaded Circle Markers', text: i18n.translate('tileMap.vis.editorConfig.mapTypes.shadedCircleMarkersText', { defaultMessage: 'Shaded circle markers', }), }, { - value: MapTypes.ShadedGeohashGrid, + value: 'Shaded Geohash Grid', text: i18n.translate('tileMap.vis.editorConfig.mapTypes.shadedGeohashGridText', { defaultMessage: 'Shaded geohash grid', }), }, { - value: MapTypes.Heatmap, + value: 'Heatmap', text: i18n.translate('tileMap.vis.editorConfig.mapTypes.heatmapText', { defaultMessage: 'Heatmap', }), @@ -119,7 +113,7 @@ export function createTileMapTypeDefinition(dependencies) { ], tmsLayers: [], }, - optionsTemplate: props => , + optionsTemplate: (props) => , schemas: new Schemas([ { group: 'metrics', @@ -130,7 +124,9 @@ export function createTileMapTypeDefinition(dependencies) { min: 1, max: 1, aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], - defaults: [{ schema: 'metric', type: 'count' }], + defaults: [ + { schema: 'metric', type: 'count' }, + ], }, { group: 'buckets', diff --git a/src/legacy/core_plugins/tile_map/public/types.ts b/src/legacy/core_plugins/tile_map/public/types.ts index 5f1c3f9b03c9e..dbaa80217879a 100644 --- a/src/legacy/core_plugins/tile_map/public/types.ts +++ b/src/legacy/core_plugins/tile_map/public/types.ts @@ -18,7 +18,6 @@ */ import { TmsLayer } from 'ui/vis/map/service_settings'; -import { MapTypes } from './map_types'; export interface WMSOptions { selectedTmsLayer?: TmsLayer; @@ -36,7 +35,7 @@ export interface WMSOptions { export interface TileMapVisParams { colorSchema: string; - mapType: MapTypes; + mapType: 'Scaled Circle Markers' | 'Shaded Circle Markers' | 'Shaded geohash grid' | 'Heatmap'; isDesaturated: boolean; addTooltip: boolean; heatClusterSize: number; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts b/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts index 539b4e1bdfb45..769fc72b4c661 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts @@ -20,12 +20,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { MarkdownPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, }; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts index 2f1a46f1d8f3f..7b2f8f6c236b2 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { visFactory, DefaultEditorSize } from '../../visualizations/public/np_ready/public'; +import { visFactory, DefaultEditorSize } from '../../visualizations/public'; import { MarkdownVisWrapper } from './markdown_vis_controller'; import { MarkdownOptions } from './markdown_options'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts index 5f9c82ca89290..286b233280382 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { markdownVis } from './markdown_vis'; import { createMarkdownVisFn } from './markdown_fn'; @@ -39,7 +39,7 @@ export class MarkdownPlugin implements Plugin { } public setup(core: CoreSetup, { data, visualizations }: MarkdownPluginSetupDependencies) { - visualizations.types.registerVisualization(() => markdownVis); + visualizations.types.VisTypesRegistryProvider.register(() => markdownVis); data.expressions.registerFunction(createMarkdownVisFn); } diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js index ce77c64d77651..7adf7007e8603 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js +++ b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js @@ -24,7 +24,7 @@ import expect from '@kbn/expect'; import { VisProvider } from 'ui/vis'; import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { setup as setupVisualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../../visualizations/public'; import { createMetricVisTypeDefinition } from '../metric_vis_type'; describe('metric_vis - createMetricVisTypeDefinition', () => { @@ -32,52 +32,48 @@ describe('metric_vis - createMetricVisTypeDefinition', () => { let vis; beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(Private => { - setup = () => { - const Vis = Private(VisProvider); - const metricVisType = createMetricVisTypeDefinition(); - - setupVisualizations.types.registerVisualization(() => metricVisType); - - const indexPattern = Private(LogstashIndexPatternStubProvider); - - indexPattern.stubSetFieldFormat('ip', 'url', { - urlTemplate: 'http://ip.info?address={{value}}', - labelTemplate: 'ip[{{value}}]', - }); - - vis = new Vis(indexPattern, { - type: 'metric', - aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }], - }); - - vis.params.dimensions = { - metrics: [ - { - accessor: 0, - format: { - id: 'url', - params: { - urlTemplate: 'http://ip.info?address={{value}}', - labelTemplate: 'ip[{{value}}]', - }, - }, - }, - ], - }; - - const el = document.createElement('div'); - const Controller = metricVisType.visualization; - const controller = new Controller(el, vis); - const render = esResponse => { - controller.render(esResponse, vis.params); - }; - - return { el, render }; + beforeEach(ngMock.inject((Private) => { + setup = () => { + const Vis = Private(VisProvider); + const metricVisType = createMetricVisTypeDefinition(); + + visualizations.types.VisTypesRegistryProvider.register(() => + metricVisType + ); + + const indexPattern = Private(LogstashIndexPatternStubProvider); + + indexPattern.stubSetFieldFormat('ip', 'url', { + urlTemplate: 'http://ip.info?address={{value}}', + labelTemplate: 'ip[{{value}}]' + }); + + vis = new Vis(indexPattern, { + type: 'metric', + aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }], + }); + + vis.params.dimensions = { + metrics: [{ + accessor: 0, format: { + id: 'url', params: { + urlTemplate: 'http://ip.info?address={{value}}', + labelTemplate: 'ip[{{value}}]' + } + } + }] + }; + + const el = document.createElement('div'); + const Controller = metricVisType.visualization; + const controller = new Controller(el, vis); + const render = (esResponse) => { + controller.render(esResponse, vis.params); }; - }) - ); + + return { el, render }; + }; + })); it('renders html value from field formatter', () => { const { el, render } = setup(); @@ -85,14 +81,12 @@ describe('metric_vis - createMetricVisTypeDefinition', () => { const ip = '235.195.237.208'; render({ columns: [{ id: 'col-0', name: 'ip' }], - rows: [{ 'col-0': ip }], + rows: [{ 'col-0': ip }] }); const $link = $(el) .find('a[href]') - .filter(function () { - return this.href.includes('ip.info'); - }); + .filter(function () { return this.href.includes('ip.info'); }); expect($link).to.have.length(1); expect($link.text()).to.be(`ip[${ip}]`); diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts index 2eea4c70309de..4ab399977d7b1 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts @@ -20,13 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { MetricVisPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index eba61edeeb84a..76e88888ef7f2 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -26,7 +26,7 @@ import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; // @ts-ignore import { MetricVisComponent } from './components/metric_vis_controller'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; export const createMetricVisTypeDefinition = () => { return visFactory.createReactVisualization({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts index 66a3bb007e379..d99df03fcc560 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { LegacyDependenciesPlugin } from './shim'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; // @ts-ignore @@ -48,7 +48,7 @@ export class MetricVisPlugin implements Plugin { __LEGACY.setup(); data.expressions.registerFunction(createMetricVisFn); - visualizations.types.registerVisualization(createMetricVisTypeDefinition); + visualizations.types.VisTypesRegistryProvider.register(createMetricVisTypeDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js index 9110c8dcb8486..4253efd4689cf 100644 --- a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js +++ b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js @@ -28,7 +28,7 @@ import { AppStateProvider } from 'ui/state_management/app_state'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { createTableVisTypeDefinition } from '../table_vis_type'; -import { setup as setupVisualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../../visualizations/public'; describe('Table Vis - Controller', async function () { let $rootScope; @@ -44,73 +44,68 @@ describe('Table Vis - Controller', async function () { let legacyDependencies; beforeEach(ngMock.module('kibana', 'kibana/table_vis')); - beforeEach( - ngMock.inject(function ($injector) { - Private = $injector.get('Private'); - legacyDependencies = { - // eslint-disable-next-line new-cap - createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, - }; - - setupVisualizations.types.registerVisualization(() => - createTableVisTypeDefinition(legacyDependencies) - ); - - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - fixtures = require('fixtures/fake_hierarchical_data'); - AppState = Private(AppStateProvider); - Vis = Private(VisProvider); - tableAggResponse = legacyResponseHandlerProvider().handler; - }) - ); + beforeEach(ngMock.inject(function ($injector) { + Private = $injector.get('Private'); + legacyDependencies = { + // eslint-disable-next-line new-cap + createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization + }; + + visualizations.types.VisTypesRegistryProvider.register(() => + createTableVisTypeDefinition(legacyDependencies) + ); + + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); + fixtures = require('fixtures/fake_hierarchical_data'); + AppState = Private(AppStateProvider); + Vis = Private(VisProvider); + tableAggResponse = legacyResponseHandlerProvider().handler; + })); function OneRangeVis(params) { - return new Vis(Private(FixturesStubbedLogstashIndexPatternProvider), { - type: 'table', - params: params || {}, - aggs: [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }], - }, - }, - ], - }); + return new Vis( + Private(FixturesStubbedLogstashIndexPatternProvider), + { + type: 'table', + params: params || {}, + aggs: [ + { type: 'count', schema: 'metric' }, + { + type: 'range', + schema: 'bucket', + params: { + field: 'bytes', + ranges: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 } + ] + } + } + ] + } + ); } const dimensions = { - buckets: [ - { - accessor: 0, - }, - ], - metrics: [ - { - accessor: 1, - format: { id: 'range' }, - }, - ], + buckets: [{ + accessor: 0, + }], metrics: [{ + accessor: 1, + format: { id: 'range' }, + }] }; // basically a parameterized beforeEach function initController(vis) { - vis.aggs.aggs.forEach(function (agg, i) { - agg.id = 'agg_' + (i + 1); - }); + vis.aggs.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); tabifiedResponse = tabifyAggResponse(vis.aggs, fixtures.oneRangeBucket); $rootScope.vis = vis; $rootScope.visParams = vis.params; $rootScope.uiState = new AppState({ uiState: {} }).makeStateful('uiState'); $rootScope.renderComplete = () => {}; - $rootScope.newScope = function (scope) { - $scope = scope; - }; + $rootScope.newScope = function (scope) { $scope = scope; }; $el = $('
') .attr('ng-controller', 'KbnTableVisController') @@ -162,7 +157,7 @@ describe('Table Vis - Controller', async function () { it('sets the sort on the scope when it is passed as a vis param', async function () { const sortObj = { columnIndex: 1, - direction: 'asc', + direction: 'asc' }; const vis = new OneRangeVis({ sort: sortObj }); initController(vis); @@ -186,6 +181,7 @@ describe('Table Vis - Controller', async function () { }); it('passes partialRows:true to tabify based on the vis params', function () { + const vis = new OneRangeVis({ showPartialRows: true }); initController(vis); @@ -193,6 +189,7 @@ describe('Table Vis - Controller', async function () { }); it('passes partialRows:false to tabify based on the vis params', function () { + const vis = new OneRangeVis({ showPartialRows: false }); initController(vis); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js index 8521ee729f313..b0476ae224866 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js @@ -31,9 +31,10 @@ import { round } from 'lodash'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { createTableVisTypeDefinition } from '../../table_vis_type'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../../../visualizations/public'; describe('Table Vis - AggTable Directive', function () { + let $rootScope; let $compile; let Vis; @@ -50,21 +51,19 @@ describe('Table Vis - AggTable Directive', function () { const vis2 = new Vis(indexPattern, { type: 'table', params: { - showMetricsAtAllLevels: true, + showMetricsAtAllLevels: true }, aggs: [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'bucket', params: { field: 'extension' } }, { type: 'terms', schema: 'bucket', params: { field: 'geo.src' } }, - { type: 'terms', schema: 'bucket', params: { field: 'machine.os' } }, - ], + { type: 'terms', schema: 'bucket', params: { field: 'machine.os' } } + ] }); vis2.aggs.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); - tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, fixtures.threeTermBuckets, { - metricsAtAllLevels: true, - }); + tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, fixtures.threeTermBuckets, { metricsAtAllLevels: true }); const vis3 = new Vis(indexPattern, { type: 'table', @@ -72,54 +71,41 @@ describe('Table Vis - AggTable Directive', function () { { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'min', schema: 'metric', params: { field: '@timestamp' } }, { type: 'terms', schema: 'bucket', params: { field: 'extension' } }, - { - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: 'd' }, - }, - { - type: 'derivative', - schema: 'metric', - params: { metricAgg: 'custom', customMetric: { id: '5-orderAgg', type: 'count' } }, - }, - { - type: 'top_hits', - schema: 'metric', - params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 }, - }, - ], + { type: 'date_histogram', schema: 'bucket', params: { field: '@timestamp', interval: 'd' } }, + { type: 'derivative', schema: 'metric', + params: { metricAgg: 'custom', customMetric: { id: '5-orderAgg', type: 'count' } } }, + { type: 'top_hits', schema: 'metric', params: { field: 'bytes', aggregate: { val: 'min' }, size: 1 } } + ] }); vis3.aggs.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); - tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative = tabifyAggResponse( - vis3.aggs, - fixtures.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative - ); + tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative = + tabifyAggResponse(vis3.aggs, fixtures.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative); }; beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function ($injector, Private, config) { - legacyDependencies = { - // eslint-disable-next-line new-cap - createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, - }; + beforeEach(ngMock.inject(function ($injector, Private, config) { + legacyDependencies = { + // eslint-disable-next-line new-cap + createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization + }; - setup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); + visualizations.types.VisTypesRegistryProvider.register(() => + createTableVisTypeDefinition(legacyDependencies) + ); - tableAggResponse = legacyResponseHandlerProvider().handler; - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - Vis = Private(VisProvider); - settings = config; + tableAggResponse = legacyResponseHandlerProvider().handler; + indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + Vis = Private(VisProvider); + settings = config; - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); - init(); - }) - ); + init(); + })); let $scope; beforeEach(function () { @@ -129,16 +115,12 @@ describe('Table Vis - AggTable Directive', function () { $scope.$destroy(); }); + it('renders a simple response properly', async function () { - $scope.dimensions = { - metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], - buckets: [], - }; + $scope.dimensions = { metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], buckets: [] }; $scope.table = (await tableAggResponse(tabifiedData.metricOnly, $scope.dimensions)).tables[0]; - const $el = $compile('')( - $scope - ); + const $el = $compile('')($scope); $scope.$digest(); expect($el.find('tbody').length).to.be(1); @@ -149,9 +131,7 @@ describe('Table Vis - AggTable Directive', function () { it('renders nothing if the table is empty', function () { $scope.dimensions = {}; $scope.table = null; - const $el = $compile('')( - $scope - ); + const $el = $compile('')($scope); $scope.$digest(); expect($el.find('tbody').length).to.be(0); @@ -159,21 +139,10 @@ describe('Table Vis - AggTable Directive', function () { it('renders a complex response properly', async function () { $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 2, params: {} }, - { accessor: 4, params: {} }, - ], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], + buckets: [{ accessor: 0, params: {} }, { accessor: 2, params: {} }, { accessor: 4, params: {} }], + metrics: [{ accessor: 1, params: {} }, { accessor: 3, params: {} }, { accessor: 5, params: {} }] }; - $scope.table = (await tableAggResponse( - tabifiedData.threeTermBuckets, - $scope.dimensions - )).tables[0]; + $scope.table = (await tableAggResponse(tabifiedData.threeTermBuckets, $scope.dimensions)).tables[0]; const $el = $(''); $compile($el)($scope); $scope.$digest(); @@ -196,9 +165,7 @@ describe('Table Vis - AggTable Directive', function () { expect($cells.length).to.be(6); const txts = $cells.map(function () { - return $(this) - .text() - .trim(); + return $(this).text().trim(); }); // two character country code @@ -217,6 +184,7 @@ describe('Table Vis - AggTable Directive', function () { describe('renders totals row', function () { async function totalsRowTest(totalFunc, expected) { + function setDefaultTimezone() { moment.tz.setDefault(settings.get('dateFormat:tz')); } @@ -229,18 +197,15 @@ describe('Table Vis - AggTable Directive', function () { buckets: [ { accessor: 0, params: {} }, { accessor: 1, format: { id: 'date', params: { pattern: 'YYYY-MM-DD' } } }, - ], - metrics: [ + ], metrics: [ { accessor: 2, format: { id: 'number' } }, { accessor: 3, format: { id: 'date' } }, { accessor: 4, format: { id: 'number' } }, { accessor: 5, format: { id: 'number' } }, - ], + ] }; const response = await tableAggResponse( - tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative, - $scope.dimensions - ); + tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative, $scope.dimensions); $scope.table = response.tables[0]; $scope.showTotal = true; $scope.totalFunc = totalFunc; @@ -261,11 +226,7 @@ describe('Table Vis - AggTable Directive', function () { expect($cells.length).to.be(6); for (let i = 0; i < 6; i++) { - expect( - $($cells[i]) - .text() - .trim() - ).to.be(expected[i]); + expect($($cells[i]).text().trim()).to.be(expected[i]); } settings.set('dateFormat:tz', oldTimezoneSetting); off(); @@ -280,7 +241,7 @@ describe('Table Vis - AggTable Directive', function () { '9,283', 'Sep 28, 2014 @ 00:00:00.000', '1', - '11', + '11' ]); }); it('as max', async function () { @@ -290,22 +251,34 @@ describe('Table Vis - AggTable Directive', function () { '220,943', 'Oct 3, 2014 @ 00:00:00.000', '239', - '837', + '837' ]); }); it('as avg', async function () { - await totalsRowTest('avg', ['', '', '87,221.5', '', '64.667', '206.833']); + await totalsRowTest('avg', [ + '', + '', + '87,221.5', + '', + '64.667', + '206.833' + ]); }); it('as sum', async function () { - await totalsRowTest('sum', ['', '', '1,569,987', '', '1,164', '3,723']); + await totalsRowTest('sum', [ + '', + '', + '1,569,987', + '', + '1,164', + '3,723' + ]); }); }); describe('aggTable.toCsv()', function () { it('escapes rows and columns properly', function () { - const $el = $compile('')( - $scope - ); + const $el = $compile('')($scope); $scope.$digest(); const $tableScope = $el.isolateScope(); @@ -316,35 +289,25 @@ describe('Table Vis - AggTable Directive', function () { { id: 'b', name: 'two' }, { id: 'c', name: 'with double-quotes(")' }, ], - rows: [{ a: 1, b: 2, c: '"foobar"' }], + rows: [ + { a: 1, b: 2, c: '"foobar"' }, + ], }; expect(aggTable.toCsv()).to.be( - 'one,two,"with double-quotes("")"' + '\r\n' + '1,2,"""foobar"""' + '\r\n' + 'one,two,"with double-quotes("")"' + '\r\n' + + '1,2,"""foobar"""' + '\r\n' ); }); it('exports rows and columns properly', async function () { $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 2, params: {} }, - { accessor: 4, params: {} }, - ], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], + buckets: [{ accessor: 0, params: {} }, { accessor: 2, params: {} }, { accessor: 4, params: {} }], + metrics: [{ accessor: 1, params: {} }, { accessor: 3, params: {} }, { accessor: 5, params: {} }] }; - $scope.table = (await tableAggResponse( - tabifiedData.threeTermBuckets, - $scope.dimensions - )).tables[0]; + $scope.table = (await tableAggResponse(tabifiedData.threeTermBuckets, $scope.dimensions)).tables[0]; - const $el = $compile('')( - $scope - ); + const $el = $compile('')($scope); $scope.$digest(); const $tableScope = $el.isolateScope(); @@ -353,56 +316,30 @@ describe('Table Vis - AggTable Directive', function () { const raw = aggTable.toCsv(false); expect(raw).to.be( - '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + - '\r\n' + - 'png,IT,win,412032,9299,0' + - '\r\n' + - 'png,IT,mac,412032,9299,9299' + - '\r\n' + - 'png,US,linux,412032,8293,3992' + - '\r\n' + - 'png,US,mac,412032,8293,3029' + - '\r\n' + - 'css,MX,win,412032,9299,4992' + - '\r\n' + - 'css,MX,mac,412032,9299,5892' + - '\r\n' + - 'css,US,linux,412032,8293,3992' + - '\r\n' + - 'css,US,mac,412032,8293,3029' + - '\r\n' + - 'html,CN,win,412032,9299,4992' + - '\r\n' + - 'html,CN,mac,412032,9299,5892' + - '\r\n' + - 'html,FR,win,412032,8293,3992' + - '\r\n' + - 'html,FR,mac,412032,8293,3029' + - '\r\n' + '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + '\r\n' + + 'png,IT,win,412032,9299,0' + '\r\n' + + 'png,IT,mac,412032,9299,9299' + '\r\n' + + 'png,US,linux,412032,8293,3992' + '\r\n' + + 'png,US,mac,412032,8293,3029' + '\r\n' + + 'css,MX,win,412032,9299,4992' + '\r\n' + + 'css,MX,mac,412032,9299,5892' + '\r\n' + + 'css,US,linux,412032,8293,3992' + '\r\n' + + 'css,US,mac,412032,8293,3029' + '\r\n' + + 'html,CN,win,412032,9299,4992' + '\r\n' + + 'html,CN,mac,412032,9299,5892' + '\r\n' + + 'html,FR,win,412032,8293,3992' + '\r\n' + + 'html,FR,mac,412032,8293,3029' + '\r\n' ); }); it('exports formatted rows and columns properly', async function () { $scope.dimensions = { - buckets: [ - { accessor: 0, params: {} }, - { accessor: 2, params: {} }, - { accessor: 4, params: {} }, - ], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], + buckets: [{ accessor: 0, params: {} }, { accessor: 2, params: {} }, { accessor: 4, params: {} }], + metrics: [{ accessor: 1, params: {} }, { accessor: 3, params: {} }, { accessor: 5, params: {} }] }; - $scope.table = (await tableAggResponse( - tabifiedData.threeTermBuckets, - $scope.dimensions - )).tables[0]; + $scope.table = (await tableAggResponse(tabifiedData.threeTermBuckets, $scope.dimensions)).tables[0]; - const $el = $compile('')( - $scope - ); + const $el = $compile('')($scope); $scope.$digest(); const $tableScope = $el.isolateScope(); @@ -414,32 +351,19 @@ describe('Table Vis - AggTable Directive', function () { const formatted = aggTable.toCsv(true); expect(formatted).to.be( - '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + - '\r\n' + - '"png_formatted",IT,win,412032,9299,0' + - '\r\n' + - '"png_formatted",IT,mac,412032,9299,9299' + - '\r\n' + - '"png_formatted",US,linux,412032,8293,3992' + - '\r\n' + - '"png_formatted",US,mac,412032,8293,3029' + - '\r\n' + - '"css_formatted",MX,win,412032,9299,4992' + - '\r\n' + - '"css_formatted",MX,mac,412032,9299,5892' + - '\r\n' + - '"css_formatted",US,linux,412032,8293,3992' + - '\r\n' + - '"css_formatted",US,mac,412032,8293,3029' + - '\r\n' + - '"html_formatted",CN,win,412032,9299,4992' + - '\r\n' + - '"html_formatted",CN,mac,412032,9299,5892' + - '\r\n' + - '"html_formatted",FR,win,412032,8293,3992' + - '\r\n' + - '"html_formatted",FR,mac,412032,8293,3029' + - '\r\n' + '"extension: Descending","Average bytes","geo.src: Descending","Average bytes","machine.os: Descending","Average bytes"' + '\r\n' + + '"png_formatted",IT,win,412032,9299,0' + '\r\n' + + '"png_formatted",IT,mac,412032,9299,9299' + '\r\n' + + '"png_formatted",US,linux,412032,8293,3992' + '\r\n' + + '"png_formatted",US,mac,412032,8293,3029' + '\r\n' + + '"css_formatted",MX,win,412032,9299,4992' + '\r\n' + + '"css_formatted",MX,mac,412032,9299,5892' + '\r\n' + + '"css_formatted",US,linux,412032,8293,3992' + '\r\n' + + '"css_formatted",US,mac,412032,8293,3029' + '\r\n' + + '"html_formatted",CN,win,412032,9299,4992' + '\r\n' + + '"html_formatted",CN,mac,412032,9299,5892' + '\r\n' + + '"html_formatted",FR,win,412032,8293,3992' + '\r\n' + + '"html_formatted",FR,mac,412032,8293,3029' + '\r\n' ); }); }); @@ -528,7 +452,9 @@ describe('Table Vis - AggTable Directive', function () { { id: 'b', name: 'two' }, { id: 'c', name: 'with double-quotes(")' }, ], - rows: [{ a: 1, b: 2, c: '"foobar"' }], + rows: [ + { a: 1, b: 2, c: '"foobar"' }, + ], }; aggTable.csv.filename = 'somefilename.csv'; @@ -538,26 +464,25 @@ describe('Table Vis - AggTable Directive', function () { const call = saveAs.getCall(0); expect(call.args[0]).to.be.a(FakeBlob); expect(call.args[0].slices).to.eql([ - 'one,two,"with double-quotes("")"' + '\r\n' + '1,2,"""foobar"""' + '\r\n', + 'one,two,"with double-quotes("")"' + '\r\n' + + '1,2,"""foobar"""' + '\r\n' ]); expect(call.args[0].opts).to.eql({ - type: 'text/plain;charset=utf-8', + type: 'text/plain;charset=utf-8' }); expect(call.args[1]).to.be('somefilename.csv'); }); it('should use the export-title attribute', function () { const expected = 'export file name'; - const $el = $compile( - `` - )($scope); + const $el = $compile(``)($scope); $scope.$digest(); const $tableScope = $el.isolateScope(); const aggTable = $tableScope.aggTable; $tableScope.table = { columns: [], - rows: [], + rows: [] }; $tableScope.exportTitle = expected; $scope.$digest(); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js index 7998a92a4759f..5b9974d6ff4d5 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js @@ -28,7 +28,7 @@ import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { createTableVisTypeDefinition } from '../../table_vis_type'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../../../visualizations/public'; describe('Table Vis - AggTableGroup Directive', function () { let $rootScope; @@ -49,8 +49,8 @@ describe('Table Vis - AggTableGroup Directive', function () { { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'split', params: { field: 'extension' } }, { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } }, - ], + { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } + ] }); vis2.aggs.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); @@ -59,25 +59,25 @@ describe('Table Vis - AggTableGroup Directive', function () { }; beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function ($injector, Private) { - legacyDependencies = { - // eslint-disable-next-line new-cap - createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, - }; + beforeEach(ngMock.inject(function ($injector, Private) { + legacyDependencies = { + // eslint-disable-next-line new-cap + createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization + }; - setup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); + visualizations.types.VisTypesRegistryProvider.register(() => + createTableVisTypeDefinition(legacyDependencies) + ); - tableAggResponse = legacyResponseHandlerProvider().handler; - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - Vis = Private(VisProvider); + tableAggResponse = legacyResponseHandlerProvider().handler; + indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + Vis = Private(VisProvider); - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); - init(); - }) - ); + init(); + })); let $scope; beforeEach(function () { @@ -88,18 +88,13 @@ describe('Table Vis - AggTableGroup Directive', function () { }); it('renders a simple split response properly', async function () { - $scope.dimensions = { - metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], - buckets: [], - }; + $scope.dimensions = { metrics: [{ accessor: 0, format: { id: 'number' }, params: {} }], buckets: [] }; $scope.group = await tableAggResponse(tabifiedData.metricOnly, $scope.dimensions); $scope.sort = { columnIndex: null, - direction: null, + direction: null }; - const $el = $( - '' - ); + const $el = $(''); $compile($el)($scope); $scope.$digest(); @@ -109,12 +104,10 @@ describe('Table Vis - AggTableGroup Directive', function () { }); it('renders nothing if the table list is empty', function () { - const $el = $( - '' - ); + const $el = $(''); $scope.group = { - tables: [], + tables: [] }; $compile($el)($scope); @@ -128,19 +121,10 @@ describe('Table Vis - AggTableGroup Directive', function () { $scope.dimensions = { splitRow: [{ accessor: 0, params: {} }], buckets: [{ accessor: 2, params: {} }, { accessor: 4, params: {} }], - metrics: [ - { accessor: 1, params: {} }, - { accessor: 3, params: {} }, - { accessor: 5, params: {} }, - ], + metrics: [{ accessor: 1, params: {} }, { accessor: 3, params: {} }, { accessor: 5, params: {} }] }; - const group = ($scope.group = await tableAggResponse( - tabifiedData.threeTermBuckets, - $scope.dimensions - )); - const $el = $( - '' - ); + const group = $scope.group = await tableAggResponse(tabifiedData.threeTermBuckets, $scope.dimensions); + const $el = $(''); $compile($el)($scope); $scope.$digest(); diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy.ts b/src/legacy/core_plugins/vis_type_table/public/legacy.ts index 8139a70552c48..fded5690a362d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_table/public/legacy.ts @@ -22,11 +22,11 @@ import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; import { TablePluginSetupDependencies } from './plugin'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { LegacyDependenciesPlugin } from './shim'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/legacy/core_plugins/vis_type_table/public/plugin.ts index 6a39e8079a9fd..21b6e21d6d639 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/plugin.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; import { @@ -63,7 +63,7 @@ export class TableVisPlugin implements Plugin, void> { data.expressions.registerFunction(createTableVisFn); - visualizations.types.registerVisualization(() => + visualizations.types.VisTypesRegistryProvider.register(() => createTableVisTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts index 6a5e06b6e6978..01b2d99ce509c 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts @@ -20,12 +20,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { TagCloudPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, }; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts index e13e9896e3940..e7f633b4af377 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createTagCloudFn } from './tag_cloud_fn'; import { createTagCloudTypeDefinition } from './tag_cloud_type'; @@ -40,7 +40,7 @@ export class TagCloudPlugin implements Plugin { public setup(core: CoreSetup, { data, visualizations }: TagCloudPluginSetupDependencies) { data.expressions.registerFunction(createTagCloudFn); - visualizations.types.registerVisualization(createTagCloudTypeDefinition); + visualizations.types.VisTypesRegistryProvider.register(createTagCloudTypeDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts index 421821d93b045..0b4d90522cc44 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts @@ -23,7 +23,7 @@ import { Status } from 'ui/vis/update_status'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { TagCloudOptions } from './components/tag_cloud_options'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; // @ts-ignore import { TagCloudVisualization } from './components/tag_cloud_visualization'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js index 012f144983e98..681f4486a02d7 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js @@ -41,10 +41,10 @@ import vegaMapImage256 from './vega_map_image_256.png'; import { VegaParser } from '../data_model/vega_parser'; import { SearchCache } from '../data_model/search_cache'; -import { setup } from '../../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../../visualizations/public'; import { createVegaTypeDefinition } from '../vega_type'; -const THRESHOLD = 0.1; +const THRESHOLD = 0.10; const PIXEL_DIFF = 30; describe('VegaVisualizations', () => { @@ -57,24 +57,22 @@ describe('VegaVisualizations', () => { let vegaVisualizationDependencies; beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject((Private, $injector) => { - vegaVisualizationDependencies = { - es: $injector.get('es'), - serviceSettings: $injector.get('serviceSettings'), - uiSettings: $injector.get('config'), - }; + beforeEach(ngMock.inject((Private, $injector) => { + vegaVisualizationDependencies = { + es: $injector.get('es'), + serviceSettings: $injector.get('serviceSettings'), + uiSettings: $injector.get('config'), + }; - setup.types.registerVisualization(() => - createVegaTypeDefinition(vegaVisualizationDependencies) - ); + visualizations.types.VisTypesRegistryProvider.register(() => + createVegaTypeDefinition(vegaVisualizationDependencies) + ); - Vis = Private(visModule.VisProvider); + Vis = Private(visModule.VisProvider); - VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); - indexPattern = Private(LogstashIndexPatternStubProvider); - }) - ); + VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); + indexPattern = Private(LogstashIndexPatternStubProvider); + })); describe('VegaVisualization - basics', () => { beforeEach(async function () { @@ -107,12 +105,15 @@ describe('VegaVisualizations', () => { await vegaVis.render(vegaParser, vis.params, { resize: true }); const mismatchedPixels2 = await compareImage(vegaliteImage256); expect(mismatchedPixels2).to.be.lessThan(PIXEL_DIFF); + } finally { vegaVis.destroy(); } + }); it('should show vega graph', async function () { + let vegaVis; try { vegaVis = new VegaVisualization(domNode, vis); @@ -126,16 +127,20 @@ describe('VegaVisualizations', () => { } finally { vegaVis.destroy(); } + }); it('should show vegatooltip on mouseover over a vega graph', async () => { + let vegaVis; try { + vegaVis = new VegaVisualization(domNode, vis); const vegaParser = new VegaParser(vegaTooltipGraph, new SearchCache()); await vegaParser.parseAsync(); await vegaVis.render(vegaParser, vis.params, { data: true }); + const $el = $(domNode); const offset = $el.offset(); @@ -155,24 +160,27 @@ describe('VegaVisualizations', () => { expect(tooltip).to.be.ok(); expect(tooltip.innerHTML).to.be( '

This is a long title

' + - '' + - '' + - '' + - '
fieldA:value of fld1
fld2:42
' - ); + '' + + '' + + '' + + '
fieldA:value of fld1
fld2:42
'); vegaVis.destroy(); tooltip = document.getElementById('vega-kibana-tooltip'); expect(tooltip).to.not.be.ok(); + } finally { vegaVis.destroy(); } + }); it('should show vega blank rectangle on top of a map (vegamap)', async () => { + let vegaVis; try { + vegaVis = new VegaVisualization(domNode, vis); const vegaParser = new VegaParser(vegaMapGraph, new SearchCache()); await vegaParser.parseAsync(); @@ -183,17 +191,19 @@ describe('VegaVisualizations', () => { await vegaVis.render(vegaParser, vis.params, { data: true }); const mismatchedPixels = await compareImage(vegaMapImage256); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); + } finally { vegaVis.destroy(); } + }); it('should add a small subpixel value to the height of the canvas to avoid getting it set to 0', async () => { let vegaVis; try { + vegaVis = new VegaVisualization(domNode, vis); - const vegaParser = new VegaParser( - `{ + const vegaParser = new VegaParser(`{ "$schema": "https://vega.github.io/schema/vega/v3.json", "marks": [ { @@ -212,9 +222,7 @@ describe('VegaVisualizations', () => { } } ] - }`, - new SearchCache() - ); + }`, new SearchCache()); await vegaParser.parseAsync(); domNode.style.width = '256px'; @@ -232,8 +240,10 @@ describe('VegaVisualizations', () => { vegaVis.destroy(); } }); + }); + async function compareImage(expectedImageSource) { const elementList = domNode.querySelectorAll('canvas'); expect(elementList.length).to.equal(1); @@ -257,4 +267,5 @@ describe('VegaVisualizations', () => { domNode.innerHTML = ''; document.body.removeChild(domNode); } + }); diff --git a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts index 15cf97beb5717..5136046b31a97 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts @@ -20,13 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { visualizations } from '../../visualizations/public'; import { VegaPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index b2a6fade883ca..039ef49cb2289 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -25,7 +25,7 @@ import { } from '../../../../core/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createVegaFn } from './vega_fn'; import { createVegaTypeDefinition } from './vega_type'; @@ -61,7 +61,7 @@ export class VegaPlugin implements Plugin, void> { data.expressions.registerFunction(() => createVegaFn(visualizationDependencies)); - visualizations.types.registerVisualization(() => + visualizations.types.VisTypesRegistryProvider.register(() => createVegaTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts index 22a71bd999d54..a930e369c3e1c 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts @@ -17,8 +17,7 @@ * under the License. */ import { Filter } from '@kbn/es-query'; -import { timefilter } from 'ui/timefilter'; -import { TimeRange } from 'src/plugins/data/public'; +import { timefilter, TimeRange } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index d1f04c794e3c6..6ffcd8867ffea 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -25,7 +25,7 @@ import { DefaultEditorSize } from 'ui/vis/editor_size'; import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; import vegaEditorTemplate from './vega_editor_template.html'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; import { VegaVisualizationDependencies } from './plugin'; import { createVegaRequestHandler } from './vega_request_handler'; diff --git a/src/legacy/core_plugins/visualizations/index.ts b/src/legacy/core_plugins/visualizations/index.ts index 3642071667f48..bb9ef1588bdc2 100644 --- a/src/legacy/core_plugins/visualizations/index.ts +++ b/src/legacy/core_plugins/visualizations/index.ts @@ -25,7 +25,7 @@ export default function VisualizationsPlugin(kibana: any) { const config: Legacy.PluginSpecOptions = { id: 'visualizations', require: ['data'], - publicDir: resolve(__dirname, 'public/np_ready/public'), + publicDir: resolve(__dirname, 'public'), config: (Joi: any) => { return Joi.object({ enabled: Joi.boolean().default(true), diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts b/src/legacy/core_plugins/visualizations/public/filters/filters_service.ts similarity index 87% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts rename to src/legacy/core_plugins/visualizations/public/filters/filters_service.ts index 51709f365dbbd..60c26d7cbdc1d 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts +++ b/src/legacy/core_plugins/visualizations/public/filters/filters_service.ts @@ -17,10 +17,8 @@ * under the License. */ -interface SetupDependecies { - VisFiltersProvider: any; - createFilter: any; -} +// @ts-ignore +import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters'; /** * Vis Filters Service @@ -28,7 +26,7 @@ interface SetupDependecies { * @internal */ export class FiltersService { - public setup({ VisFiltersProvider, createFilter }: SetupDependecies) { + public setup() { return { VisFiltersProvider, createFilter, diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts b/src/legacy/core_plugins/visualizations/public/filters/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts rename to src/legacy/core_plugins/visualizations/public/filters/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/index.ts similarity index 53% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts rename to src/legacy/core_plugins/visualizations/public/index.ts index d38acaa3cf3f2..10fef3112335b 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/index.ts @@ -17,11 +17,43 @@ * under the License. */ -import { PluginInitializerContext } from 'src/core/public'; -import { VisualizationsPublicPlugin, Setup } from './plugin'; +import { FiltersService, FiltersSetup } from './filters'; +import { TypesService, TypesSetup } from './types'; + +class VisualizationsPlugin { + private readonly filters: FiltersService; + private readonly types: TypesService; + + constructor() { + this.filters = new FiltersService(); + this.types = new TypesService(); + } + + public setup() { + return { + filters: this.filters.setup(), + types: this.types.setup(), + }; + } + + public stop() { + this.filters.stop(); + this.types.stop(); + } +} + +/** + * We export visualizations here so that users importing from 'plugins/visualizations' + * will automatically receive the response value of the `setup` contract, mimicking + * the data that will eventually be injected by the new platform. + */ +export const visualizations = new VisualizationsPlugin().setup(); /** @public */ -export type VisualizationsSetup = Setup; +export interface VisualizationsSetup { + filters: FiltersSetup; + types: TypesSetup; +} /** @public types */ export { @@ -37,9 +69,3 @@ export { VisTypesRegistry, Status, } from './types'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new VisualizationsPublicPlugin(initializerContext); -} - -export { VisualizationsPublicPlugin as Plugin }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json b/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json deleted file mode 100644 index 8ecf3dfce6e94..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "visualizations", - "version": "kibana", - "requiredPlugins": [ - ], - "server": false, - "ui": true - } - \ No newline at end of file diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts deleted file mode 100644 index 04a49294bd0c6..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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. - */ -/* eslint-disable @kbn/eslint/no-restricted-paths */ -import { npSetup, npStart } from 'ui/new_platform'; -// @ts-ignore -import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters'; -// @ts-ignore -import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -// @ts-ignore -import { VisProvider as Vis } from 'ui/vis/index.js'; -// @ts-ignore -import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ - -import { visTypeAliasRegistry } from './types/vis_type_alias_registry'; - -import { plugin } from '.'; - -const pluginInstance = plugin({} as any); - -export const setup = pluginInstance.setup(npSetup.core, { - __LEGACY: { - VisFiltersProvider, - createFilter, - - Vis, - VisFactoryProvider, - VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, - }, -}); -export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts deleted file mode 100644 index df5e4d25dedcc..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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. - */ - -/* eslint-disable @kbn/eslint/no-restricted-paths */ -jest.mock('ui/vis/vis_filters'); -jest.mock('ui/vis/default_feedback_message'); -jest.mock('ui/vis/index.js'); -jest.mock('ui/vis/vis_factory'); -jest.mock('ui/registry/vis_types'); -// @ts-ignore -import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters'; -// @ts-ignore -import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -// @ts-ignore -import { VisProvider as Vis } from 'ui/vis/index.js'; -// @ts-ignore -import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ -jest.mock('./types/vis_type_alias_registry'); -import { visTypeAliasRegistry } from './types/vis_type_alias_registry'; - -import { Plugin } from '.'; -import { coreMock } from '../../../../../../core/public/mocks'; - -export type Setup = jest.Mocked>; -export type Start = jest.Mocked>; - -const createSetupContract = (): Setup => ({ - filters: { - VisFiltersProvider: jest.fn(), - createFilter: jest.fn(), - }, - types: { - Vis, - VisFactoryProvider: jest.fn(), - registerVisualization: jest.fn(), - defaultFeedbackMessage, - visTypeAliasRegistry: { - add: jest.fn(), - get: jest.fn(), - }, - }, -}); - -const createStartContract = (): Start => {}; - -const createInstance = () => { - const plugin = new Plugin({} as any); - - const setup = plugin.setup(coreMock.createSetup(), { - __LEGACY: { - VisFiltersProvider, - createFilter, - - Vis, - VisFactoryProvider, - VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, - }, - }); - const doStart = () => plugin.start(coreMock.createStart()); - - return { - plugin, - setup, - doStart, - }; -}; - -export const visualizationsPluginMock = { - createSetupContract, - createStartContract, - createInstance, -}; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts deleted file mode 100644 index abf5974b77532..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; - -import { FiltersService, FiltersSetup } from './filters'; -import { TypesService, TypesSetup } from './types'; -import { VisTypeAliasRegistry } from './types/vis_type_alias_registry'; - -interface SetupDependencies { - __LEGACY: { - VisFiltersProvider: any; - createFilter: any; - - Vis: any; - VisFactoryProvider: any; - VisTypesRegistryProvider: any; - defaultFeedbackMessage: any; - visTypeAliasRegistry: VisTypeAliasRegistry; - }; -} - -export interface Setup { - filters: FiltersSetup; - types: TypesSetup; -} - -export type Start = void; - -export class VisualizationsPublicPlugin implements Plugin { - private readonly filters: FiltersService; - private readonly types: TypesService; - - constructor(initializerContext: PluginInitializerContext) { - this.filters = new FiltersService(); - this.types = new TypesService(); - } - - public setup(core: CoreSetup, { __LEGACY }: SetupDependencies) { - const { - VisFiltersProvider, - createFilter, - Vis, - VisFactoryProvider, - VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, - } = __LEGACY; - - return { - filters: this.filters.setup({ - VisFiltersProvider, - createFilter, - }), - types: this.types.setup({ - Vis, - VisFactoryProvider, - VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, - }), - }; - } - - public start(core: CoreStart) { - // Do nothing yet... - } - - public stop() { - this.filters.stop(); - this.types.stop(); - } -} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/index.ts b/src/legacy/core_plugins/visualizations/public/types/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/index.ts rename to src/legacy/core_plugins/visualizations/public/types/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts b/src/legacy/core_plugins/visualizations/public/types/types_service.ts similarity index 72% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts rename to src/legacy/core_plugins/visualizations/public/types/types_service.ts index 28574917ba1bb..cb5328812b406 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts +++ b/src/legacy/core_plugins/visualizations/public/types/types_service.ts @@ -17,23 +17,18 @@ * under the License. */ -/* eslint-disable @kbn/eslint/no-restricted-paths */ // @ts-ignore -import { visFactory } from 'ui/vis/vis_factory'; +import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; +// @ts-ignore +import { VisProvider as Vis } from 'ui/vis/index.js'; +// @ts-ignore +import { VisFactoryProvider, visFactory } from 'ui/vis/vis_factory'; // @ts-ignore import { DefaultEditorSize } from 'ui/vis/editor_size'; +import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import * as types from 'ui/vis/vis'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ -import { VisTypeAliasRegistry, VisTypeAlias } from './vis_type_alias_registry'; - -interface SetupDependencies { - Vis: any; - VisFactoryProvider: any; - VisTypesRegistryProvider: any; - defaultFeedbackMessage: any; - visTypeAliasRegistry: VisTypeAliasRegistry; -} +import { visTypeAliasRegistry, VisTypeAlias } from './vis_type_alias_registry'; /** * Vis Types Service @@ -41,19 +36,11 @@ interface SetupDependencies { * @internal */ export class TypesService { - public setup({ - Vis, - VisFactoryProvider, - VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, - }: SetupDependencies) { + public setup() { return { Vis, VisFactoryProvider, - registerVisualization: (registerFn: () => any) => { - VisTypesRegistryProvider.register(registerFn); - }, + VisTypesRegistryProvider, defaultFeedbackMessage, // make default in base vis type, or move? visTypeAliasRegistry, }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts b/src/legacy/core_plugins/visualizations/public/types/vis_type_alias_registry.ts similarity index 90% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts rename to src/legacy/core_plugins/visualizations/public/types/vis_type_alias_registry.ts index eb84f93a0d3ba..91040cf966567 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts +++ b/src/legacy/core_plugins/visualizations/public/types/vis_type_alias_registry.ts @@ -52,12 +52,7 @@ export interface VisTypeAlias { const registry: VisTypeAlias[] = []; -export interface VisTypeAliasRegistry { - get: () => VisTypeAlias[]; - add: (newVisTypeAlias: VisTypeAlias) => void; -} - -export const visTypeAliasRegistry: VisTypeAliasRegistry = { +export const visTypeAliasRegistry = { get: () => [...registry], add: (newVisTypeAlias: VisTypeAlias) => { if (registry.find(visTypeAlias => visTypeAlias.name === newVisTypeAlias.name)) { diff --git a/src/legacy/ui/field_formats/__tests__/field_format.js b/src/legacy/ui/field_formats/__tests__/field_format.js new file mode 100644 index 0000000000000..bf8ff9f33b18c --- /dev/null +++ b/src/legacy/ui/field_formats/__tests__/field_format.js @@ -0,0 +1,180 @@ +/* + * 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 _ from 'lodash'; +import expect from '@kbn/expect'; +import { asPrettyString } from '../../../core_plugins/kibana/common/utils/as_pretty_string'; +import { FieldFormat } from '../field_format'; + +describe('FieldFormat class', function () { + + let TestFormat; + + beforeEach(function () { + TestFormat = class _TestFormat extends FieldFormat { + static id = 'test-format'; + static title = 'Test Format'; + _convert(val) { + return asPrettyString(val); + } + }; + }); + + describe('params', function () { + it('accepts its params via the constructor', function () { + const f = new TestFormat({ foo: 'bar' }); + expect(f.param('foo')).to.be('bar'); + }); + + it('allows reading a clone of the params', function () { + const params = { foo: 'bar' }; + const f = new TestFormat(params); + const output = f.params(); + expect(output).to.eql(params); + expect(output).to.not.be(params); + }); + }); + + describe('type', function () { + it('links the constructor class to instances as the `type`', function () { + const f = new TestFormat(); + expect(f.type).to.be(TestFormat); + }); + }); + + describe('toJSON', function () { + it('serializes to a version a basic id and param pair', function () { + const f = new TestFormat({ foo: 'bar' }); + const ser = JSON.parse(JSON.stringify(f)); + expect(ser).to.eql({ id: 'test-format', params: { foo: 'bar' } }); + }); + + it('removes param values that match the defaults', function () { + TestFormat.prototype.getParamDefaults = function () { + return { foo: 'bar' }; + }; + + const f = new TestFormat({ foo: 'bar', baz: 'bar' }); + const ser = JSON.parse(JSON.stringify(f)); + expect(ser.params).to.eql({ baz: 'bar' }); + }); + + it('removes the params entirely if they are empty', function () { + const f = new TestFormat(); + const ser = JSON.parse(JSON.stringify(f)); + expect(ser).to.not.have.property('params'); + }); + }); + + describe('converters', function () { + describe('#getConverterFor', function () { + it('returns a converter for a specific content type', function () { + const f = new TestFormat(); + expect(f.getConverterFor('html')()).to.be.a('string'); + expect(f.getConverterFor('text')()).to.be.a('string'); + }); + }); + + describe('#_convert, the instance method or methods used to format values', function () { + it('can be a function, which gets converted to a text and html converter', function () { + TestFormat.prototype._convert = function () { + return 'formatted'; + }; + + const f = new TestFormat(); + const text = f.getConverterFor('text'); + const html = f.getConverterFor('html'); + expect(text).to.not.be(html); + expect(text('formatted')).to.be('formatted'); + expect(html('formatted')).to.be('formatted'); + }); + + it('can be an object, with separate text and html converter', function () { + TestFormat.prototype._convert = { + text: _.constant('formatted text'), + html: _.constant('formatted html'), + }; + + const f = new TestFormat(); + const text = f.getConverterFor('text'); + const html = f.getConverterFor('html'); + expect(text).to.not.be(html); + expect(text('formatted text')).to.be('formatted text'); + expect(html('formatted html')).to.be('formatted html'); + }); + + it('does not escape the output of the text converter', function () { + TestFormat.prototype._convert = _.constant(''); + const f = new TestFormat(); + expect(f.convert('', 'text')).to.contain('<'); + }); + + it('does escape the output of the text converter if used in an html context', function () { + TestFormat.prototype._convert = _.constant(''); + const f = new TestFormat(); + expect(_.trimRight(_.trimLeft(f.convert('', 'html'), ''), '')) + .to.not.contain('<'); + }); + + it('does not escape the output of an html specific converter', function () { + TestFormat.prototype._convert = { + text: _.constant(''), + html: _.constant(''), + }; + + const f = new TestFormat(); + expect(f.convert('', 'text')).to.be(''); + expect(f.convert('', 'html')).to.be(''); + }); + }); + + describe('#convert', function () { + it('formats a value, defaulting to text content type', function () { + TestFormat.prototype._convert = { + text: _.constant('text'), + html: _.constant('html'), + }; + + const f = new TestFormat(); + expect(f.convert('val')).to.be('text'); + }); + + it('formats a value as html, when specified via second param', function () { + TestFormat.prototype._convert = { + text: _.constant('text'), + html: _.constant('html'), + }; + + const f = new TestFormat(); + expect(f.convert('val', 'html')).to.be('html'); + }); + + it('formats a value as " - " when no value is specified', function () { + const f = new TestFormat(); + expect(f.convert()).to.be(' - '); + }); + + it('formats a list of values as text', function () { + const f = new TestFormat(); + expect(f.convert(['one', 'two', 'three'])).to.be('["one","two","three"]'); + }); + }); + + }); +}); diff --git a/src/legacy/ui/field_formats/__tests__/field_formats_mixin.js b/src/legacy/ui/field_formats/__tests__/field_formats_mixin.js new file mode 100644 index 0000000000000..8f513fe28c98b --- /dev/null +++ b/src/legacy/ui/field_formats/__tests__/field_formats_mixin.js @@ -0,0 +1,83 @@ +/* + * 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 expect from '@kbn/expect'; +import sinon from 'sinon'; + +import { FieldFormat } from '../field_format'; +import * as FieldFormatsServiceNS from '../field_formats_service'; +import { fieldFormatsMixin } from '../field_formats_mixin'; + +describe('server.registerFieldFormat(createFormat)', () => { + const sandbox = sinon.createSandbox(); + + let registerFieldFormat; + let fieldFormatServiceFactory; + const serverMock = { decorate() {} }; + beforeEach(async () => { + sandbox.stub(serverMock); + await fieldFormatsMixin({}, serverMock); + [[,, fieldFormatServiceFactory], [,, registerFieldFormat]] = serverMock.decorate.args; + }); + + afterEach(() => sandbox.restore()); + + it('throws if createFormat is not a function', () => { + expect(() => registerFieldFormat()).to.throwError(error => { + expect(error.message).to.match(/createFormat is not a function/i); + }); + }); + + it('calls the createFormat() function with the FieldFormat class', () => { + const createFormat = sinon.stub(); + registerFieldFormat(createFormat); + sinon.assert.calledOnce(createFormat); + sinon.assert.calledWithExactly(createFormat, sinon.match.same(FieldFormat)); + }); + + it('passes the returned class to the FieldFormatsService', async () => { + const { FieldFormatsService: ActualFFS } = FieldFormatsServiceNS; + sandbox.stub(FieldFormatsServiceNS, 'FieldFormatsService').callsFake((...args) => { + return new ActualFFS(...args); + }); + + const { FieldFormatsService } = FieldFormatsServiceNS; + class FooFormat { + static id = 'foo' + } + registerFieldFormat(() => FooFormat); + + const fieldFormats = await fieldFormatServiceFactory({ + getAll: () => ({}), + getDefaults: () => ({}) + }); + + sinon.assert.calledOnce(FieldFormatsService); + sinon.assert.calledWithExactly( + FieldFormatsService, + // array of fieldFormat classes + [sinon.match.same(FooFormat)], + // getConfig() function + sinon.match.func + ); + + const format = fieldFormats.getInstance({ id: 'foo' }); + expect(format).to.be.a(FooFormat); + }); +}); diff --git a/src/legacy/ui/field_formats/__tests__/field_formats_service.js b/src/legacy/ui/field_formats/__tests__/field_formats_service.js new file mode 100644 index 0000000000000..9ae80462c2a07 --- /dev/null +++ b/src/legacy/ui/field_formats/__tests__/field_formats_service.js @@ -0,0 +1,52 @@ +/* + * 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 expect from '@kbn/expect'; +import { FieldFormat } from '../field_format'; +import { FieldFormatsService } from '../field_formats_service'; +import { createNumberFormat } from '../../../core_plugins/kibana/common/field_formats/types/number'; + +describe('FieldFormatsService', function () { + + const config = {}; + config['format:defaultTypeMap'] = { + 'number': { 'id': 'number', 'params': {} }, + '_default_': { 'id': 'string', 'params': {} } + }; + config['format:number:defaultPattern'] = '0,0.[000]'; + const getConfig = (key) => config[key]; + const fieldFormatClasses = [createNumberFormat(FieldFormat)]; + + let fieldFormats; + beforeEach(function () { + fieldFormats = new FieldFormatsService(fieldFormatClasses, getConfig); + }); + + it('FieldFormats are accessible via getType method', function () { + const Type = fieldFormats.getType('number'); + expect(Type.id).to.be('number'); + }); + + it('getDefaultInstance returns default FieldFormat instance for fieldType', function () { + const instance = fieldFormats.getDefaultInstance('number', getConfig); + expect(instance.type.id).to.be('number'); + expect(instance.convert('0.33333')).to.be('0.333'); + }); + +}); diff --git a/src/legacy/ui/field_formats/content_types.js b/src/legacy/ui/field_formats/content_types.js new file mode 100644 index 0000000000000..39a5cac7b9027 --- /dev/null +++ b/src/legacy/ui/field_formats/content_types.js @@ -0,0 +1,84 @@ +/* + * 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 _ from 'lodash'; +import { asPrettyString } from '../../core_plugins/kibana/common/utils/as_pretty_string'; +import { getHighlightHtml } from '../../core_plugins/kibana/common/highlight/highlight_html'; + +const types = { + html: function (format, convert) { + function recurse(value, field, hit, meta) { + if (value == null) { + return asPrettyString(value); + } + + if (!value || typeof value.map !== 'function') { + return convert.call(format, value, field, hit, meta); + } + + const subVals = value.map(v => { + return recurse(v, field, hit, meta); + }); + const useMultiLine = subVals.some(sub => { + return sub.indexOf('\n') > -1; + }); + + return subVals.join(',' + (useMultiLine ? '\n' : ' ')); + } + + return function (...args) { + return `${recurse(...args)}`; + }; + }, + + text: function (format, convert) { + return function recurse(value) { + if (!value || typeof value.map !== 'function') { + return convert.call(format, value); + } + + // format a list of values. In text contexts we just use JSON encoding + return JSON.stringify(value.map(recurse)); + }; + } +}; + +function fallbackText(value) { + return asPrettyString(value); +} + +function fallbackHtml(value, field, hit) { + const formatted = _.escape(this.convert(value, 'text')); + + if (!hit || !hit.highlight || !hit.highlight[field.name]) { + return formatted; + } else { + return getHighlightHtml(formatted, hit.highlight[field.name]); + } +} + +export function contentTypesSetup(format) { + const src = format._convert || {}; + const converters = format._convert = {}; + + converters.text = types.text(format, src.text || fallbackText); + converters.html = types.html(format, src.html || fallbackHtml); + + return format._convert; +} diff --git a/src/legacy/ui/field_formats/content_types/html_content_type.ts b/src/legacy/ui/field_formats/content_types/html_content_type.ts deleted file mode 100644 index 21cdb21d16895..0000000000000 --- a/src/legacy/ui/field_formats/content_types/html_content_type.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 { escape, isFunction } from 'lodash'; -import { FieldFormatConvert, IFieldFormat, HtmlConventTypeConvert } from '../types'; - -// @ts-ignore -import { asPrettyString } from '../../../core_plugins/kibana/common/utils/as_pretty_string'; -// @ts-ignore -import { getHighlightHtml } from '../../../core_plugins/kibana/common/highlight/highlight_html'; - -const CONTEXT_TYPE = 'html'; - -const getConvertFn = ( - format: IFieldFormat, - fieldFormatConvert: FieldFormatConvert -): HtmlConventTypeConvert => { - const fallbackHtml: HtmlConventTypeConvert = (value, field, hit) => { - const formatted = escape(format.convert(value, 'text')); - - return !field || !hit || !hit.highlight || !hit.highlight[field.name] - ? formatted - : getHighlightHtml(formatted, hit.highlight[field.name]); - }; - - return (fieldFormatConvert[CONTEXT_TYPE] || fallbackHtml) as HtmlConventTypeConvert; -}; - -export const setup = ( - format: IFieldFormat, - fieldFormatConvert: FieldFormatConvert -): FieldFormatConvert => { - const convert = getConvertFn(format, fieldFormatConvert); - - const recurse: HtmlConventTypeConvert = (value, field, hit, meta) => { - if (value == null) { - return asPrettyString(value); - } - - if (!value || !isFunction(value.map)) { - return convert.call(format, value, field, hit, meta); - } - - const subValues = value.map((v: any) => { - return recurse(v, field, hit, meta); - }); - const useMultiLine = subValues.some((sub: any) => { - return sub.indexOf('\n') > -1; - }); - - return subValues.join(',' + (useMultiLine ? '\n' : ' ')); - }; - - const wrap: HtmlConventTypeConvert = (value, field, hit, meta) => { - return `${recurse(value, field, hit, meta)}`; - }; - - return { - [CONTEXT_TYPE]: wrap, - }; -}; diff --git a/src/legacy/ui/field_formats/content_types/index.ts b/src/legacy/ui/field_formats/content_types/index.ts deleted file mode 100644 index b5d98a7bc8393..0000000000000 --- a/src/legacy/ui/field_formats/content_types/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { setup as textContentTypeSetup } from './text_content_type'; -export { setup as htmlContentTypeSetup } from './html_content_type'; diff --git a/src/legacy/ui/field_formats/content_types/text_content_type.ts b/src/legacy/ui/field_formats/content_types/text_content_type.ts deleted file mode 100644 index 0a6983f8f1a87..0000000000000 --- a/src/legacy/ui/field_formats/content_types/text_content_type.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { isFunction } from 'lodash'; -import { IFieldFormat, FieldFormatConvert, TextContextTypeConvert } from '../types'; - -// @ts-ignore -import { asPrettyString } from '../../../core_plugins/kibana/common/utils/as_pretty_string'; - -const CONTEXT_TYPE = 'text'; - -const getConvertFn = (fieldFormatConvert: FieldFormatConvert): TextContextTypeConvert => - (fieldFormatConvert[CONTEXT_TYPE] || asPrettyString) as TextContextTypeConvert; - -export const setup = ( - format: IFieldFormat, - fieldFormatConvert: FieldFormatConvert -): FieldFormatConvert => { - const convert = getConvertFn(fieldFormatConvert); - - const recurse: TextContextTypeConvert = value => { - if (!value || !isFunction(value.map)) { - return convert.call(format, value); - } - - // format a list of values. In text contexts we just use JSON encoding - return JSON.stringify(value.map(recurse)); - }; - - return { [CONTEXT_TYPE]: recurse }; -}; diff --git a/src/legacy/ui/field_formats/field_format.js b/src/legacy/ui/field_formats/field_format.js new file mode 100644 index 0000000000000..21e4946a86b48 --- /dev/null +++ b/src/legacy/ui/field_formats/field_format.js @@ -0,0 +1,123 @@ +/* + * 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 _ from 'lodash'; +import { contentTypesSetup } from './content_types'; + +export function FieldFormat(params) { + // give the constructor a more appropriate name + this.type = this.constructor; + + // keep the params and defaults separate + this._params = params || {}; + + // one content type, so assume text + if (_.isFunction(this._convert)) { + this._convert = { text: this._convert }; + } + + contentTypesSetup(this); +} + +FieldFormat.from = function (converter) { + class FieldFormatFromConverter extends FieldFormat {} + FieldFormatFromConverter.prototype._convert = converter; + return FieldFormatFromConverter; +}; + +/** + * Convert a raw value to a formated string + * @param {any} value + * @param {string} [contentType=text] - optional content type, the only two contentTypes + * currently supported are "html" and "text", which helps + * formatters adjust to different contexts + * @return {string} - the formatted string, which is assumed to be html, safe for + * injecting into the DOM or a DOM attribute + */ +FieldFormat.prototype.convert = function (value, contentType) { + return this.getConverterFor(contentType)(value); +}; + +/** + * Get a convert function that is bound to a specific contentType + * @param {string} [contentType=text] + * @return {function} - a bound converter function + */ +FieldFormat.prototype.getConverterFor = function (contentType) { + return this._convert[contentType || 'text']; +}; + +/** + * Get parameter defaults + * @return {object} - parameter defaults + */ +FieldFormat.prototype.getParamDefaults = function () { + return {}; +}; + +/** + * Get the value of a param. This value may be a default value. + * + * @param {string} name - the param name to fetch + * @return {any} + */ +FieldFormat.prototype.param = function (name) { + const val = this._params[name]; + if (val || val === false || val === 0) { + // truthy, false, or 0 are fine + // '', NaN, null, undefined, etc are not + return val; + } + + return this.getParamDefaults()[name]; +}; + +/** + * Get all of the params in a single object + * @return {object} + */ +FieldFormat.prototype.params = function () { + return _.cloneDeep(_.defaults({}, this._params, this.getParamDefaults())); +}; + +/** + * serialize this format to a simple POJO, with only the params + * that are not default + * + * @return {object} + */ +FieldFormat.prototype.toJSON = function () { + const type = this.type; + const defaults = this.getParamDefaults(); + + let params = _.transform(this._params, function (uniqParams, val, param) { + if (val !== defaults[param]) { + uniqParams[param] = val; + } + }, {}); + + if (!_.size(params)) { + params = undefined; + } + + return { + id: type.id, + params: params + }; +}; diff --git a/src/legacy/ui/field_formats/field_format.test.ts b/src/legacy/ui/field_formats/field_format.test.ts deleted file mode 100644 index 05912d5b5f4a8..0000000000000 --- a/src/legacy/ui/field_formats/field_format.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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 { constant, trimRight, trimLeft, get } from 'lodash'; -import { FieldFormat } from './field_format'; -import { FieldFormatConvert } from './types'; - -// @ts-ignore -import { asPrettyString } from '../../core_plugins/kibana/common/utils/as_pretty_string'; - -const getTestFormat = ( - _convert: FieldFormatConvert = { - text: (val: string) => asPrettyString(val), - }, - _params?: any -) => - new (class TestFormat extends FieldFormat { - static id = 'test-format'; - static title = 'Test Format'; - - public get _convert() { - return _convert; - } - })(_params); - -describe('FieldFormat class', () => { - describe('params', () => { - test('accepts its params via the constructor', () => { - const f = getTestFormat(undefined, { foo: 'bar' }); - const fooParam = f.param('foo'); - - expect(fooParam).toBe('bar'); - }); - - test('allows reading a clone of the params', () => { - const params = { foo: 'bar' }; - const f = getTestFormat(undefined, params); - const output = f.params(); - - expect(output).toEqual(params); - expect(output).not.toBe(params); - }); - }); - - describe('type', () => { - test('links the constructor class to instances as the `type`', () => { - const f = getTestFormat(); - - expect(get(f.type, 'id')).toBe('test-format'); - expect(get(f.type, 'title')).toBe('Test Format'); - }); - }); - - describe('toJSON', () => { - it('serializes to a version a basic id and param pair', () => { - const f = getTestFormat(undefined, { foo: 'bar' }); - const ser = JSON.parse(JSON.stringify(f)); - - expect(ser).toEqual({ id: 'test-format', params: { foo: 'bar' } }); - }); - - it('removes the params entirely if they are empty', () => { - const f = getTestFormat(); - const ser = JSON.parse(JSON.stringify(f)); - - expect(ser).not.toHaveProperty('params'); - }); - }); - - describe('converters', () => { - describe('#getConverterFor', () => { - it('returns a converter for a specific content type', () => { - const f = getTestFormat(); - const htmlConverter = f.getConverterFor('html'); - const textConverter = f.getConverterFor('text'); - - expect(htmlConverter && typeof htmlConverter('')).toBe('string'); - expect(textConverter && typeof textConverter('')).toBe('string'); - }); - }); - - describe('#_convert, the instance method or methods used to format values', () => { - it('can be a function, which gets converted to a text and html converter', () => { - const f = getTestFormat({ - text: () => 'formatted', - }); - const text = f.getConverterFor('text'); - const html = f.getConverterFor('html'); - - expect(text).not.toBe(html); - expect(text && text('formatted')).toBe('formatted'); - expect(html && html('formatted')).toBe('formatted'); - }); - - it('can be an object, with separate text and html converter', () => { - const f = getTestFormat({ - text: constant('formatted text'), - html: constant('formatted html'), - }); - const text = f.getConverterFor('text'); - const html = f.getConverterFor('html'); - - expect(text).not.toBe(html); - expect(text && text('formatted text')).toBe('formatted text'); - expect(html && html('formatted html')).toBe('formatted html'); - }); - - it('does not escape the output of the text converter', () => { - const f = getTestFormat({ - text: constant(''), - }); - - expect(f.convert('', 'text')).toContain('<'); - }); - - it('does escape the output of the text converter if used in an html context', () => { - const f = getTestFormat({ - text: constant(''), - }); - - const expected = trimRight( - trimLeft(f.convert('', 'html'), ''), - '' - ); - - expect(expected).not.toContain('<'); - }); - - it('does not escape the output of an html specific converter', function() { - const f = getTestFormat({ - text: constant(''), - html: constant(''), - }); - expect(f.convert('', 'text')).toBe(''); - expect(f.convert('', 'html')).toBe(''); - }); - }); - - describe('#convert', () => { - it('formats a value, defaulting to text content type', () => { - const f = getTestFormat({ - text: constant('text'), - html: constant('html'), - }); - - expect(f.convert('val')).toBe('text'); - }); - - it('formats a value as html, when specified via second param', () => { - const f = getTestFormat({ - text: constant('text'), - html: constant('html'), - }); - - expect(f.convert('val', 'html')).toBe('html'); - }); - - it('formats a value as " - " when no value is specified', () => { - const f = getTestFormat(); - - expect(f.convert(undefined)).toBe(' - '); - }); - - it('formats a list of values as text', () => { - const f = getTestFormat(); - - expect(f.convert(['one', 'two', 'three'])).toBe('["one","two","three"]'); - }); - }); - }); -}); diff --git a/src/legacy/ui/field_formats/field_format.ts b/src/legacy/ui/field_formats/field_format.ts deleted file mode 100644 index 226631660b8a0..0000000000000 --- a/src/legacy/ui/field_formats/field_format.ts +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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 { isFunction, transform, size, cloneDeep, get, defaults } from 'lodash'; -import { createCustomFieldFormat } from './converters/custom'; -import { ContentType, FieldFormatConvert, FieldFormatConvertFunction } from './types'; - -import { htmlContentTypeSetup, textContentTypeSetup } from './content_types'; - -const DEFAULT_CONTEXT_TYPE = 'text'; - -export abstract class FieldFormat { - /** - * @property {string} - Field Format Id - * @static - * @public - */ - static id: string; - /** - * @property {string} - Field Format Title - * @static - * @public - */ - static title: string; - - /** - * @property {string} - Field Format Type - * @private - */ - static fieldType: string; - - /** - * @property {FieldFormatConvert} - * @private - */ - _convert: FieldFormatConvert = FieldFormat.setupContentType(this, get(this, '_convert', {})); - - /** - * @property {Function} - ref to child class - * @private - */ - type: any = this.constructor; - - constructor(public _params: any = {}) {} - - /** - * Convert a raw value to a formatted string - * @param {any} value - * @param {string} [contentType=text] - optional content type, the only two contentTypes - * currently supported are "html" and "text", which helps - * formatters adjust to different contexts - * @return {string} - the formatted string, which is assumed to be html, safe for - * injecting into the DOM or a DOM attribute - * @public - */ - convert(value: any, contentType: ContentType = DEFAULT_CONTEXT_TYPE): string { - const converter = this.getConverterFor(contentType); - - if (converter) { - return converter.call(this, value); - } - - return value; - } - - /** - * Get a convert function that is bound to a specific contentType - * @param {string} [contentType=text] - * @return {function} - a bound converter function - * @public - */ - getConverterFor( - contentType: ContentType = DEFAULT_CONTEXT_TYPE - ): FieldFormatConvertFunction | null { - if (this._convert) { - return this._convert[contentType]; - } - - return null; - } - - /** - * Get parameter defaults - * @return {object} - parameter defaults - * @public - */ - getParamDefaults(): Record { - return {}; - } - - /** - * Get the value of a param. This value may be a default value. - * - * @param {string} name - the param name to fetch - * @return {any} - * @public - */ - param(name: string): any { - const val = get(this._params, name); - - if (val || val === false || val === 0) { - // truthy, false, or 0 are fine - // '', NaN, null, undefined, etc are not - return val; - } - - return get(this.getParamDefaults(), name); - } - - /** - * Get all of the params in a single object - * @return {object} - * @public - */ - params(): Record { - return cloneDeep(defaults({}, this._params, this.getParamDefaults())); - } - - /** - * Serialize this format to a simple POJO, with only the params - * that are not default - * - * @return {object} - * @public - */ - toJSON() { - const id = get(this.type, 'id'); - const defaultsParams = this.getParamDefaults() || {}; - - const params = transform( - this._params, - (uniqParams, val, param) => { - if (param && val !== get(defaultsParams, param)) { - uniqParams[param] = val; - } - }, - {} - ); - - return { - id, - params: size(params) ? params : undefined, - }; - } - - static from(convertFn: FieldFormatConvertFunction) { - return createCustomFieldFormat(FieldFormat.toConvertObject(convertFn)); - } - - private static setupContentType( - fieldFormat: IFieldFormat, - convert: FieldFormatConvert | FieldFormatConvertFunction - ): FieldFormatConvert { - const convertObject = FieldFormat.toConvertObject(convert); - - return { - ...textContentTypeSetup(fieldFormat, convertObject), - ...htmlContentTypeSetup(fieldFormat, convertObject), - }; - } - - private static toConvertObject( - convert: FieldFormatConvert | FieldFormatConvertFunction - ): FieldFormatConvert { - if (isFunction(convert)) { - return { - [DEFAULT_CONTEXT_TYPE]: convert, - }; - } - return convert; - } -} - -export type FieldFormatConvert = { [key: string]: Function } | FieldFormatConvertFunction; -export type IFieldFormat = PublicMethodsOf; diff --git a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts b/src/legacy/ui/field_formats/field_formats_mixin.js similarity index 72% rename from src/legacy/ui/field_formats/mixin/field_formats_mixin.ts rename to src/legacy/ui/field_formats/field_formats_mixin.js index 6f20ab8cdf51f..76c72102ebff7 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts +++ b/src/legacy/ui/field_formats/field_formats_mixin.js @@ -17,29 +17,27 @@ * under the License. */ -import { has } from 'lodash'; -import { Legacy } from 'kibana'; +import _ from 'lodash'; import { FieldFormatsService } from './field_formats_service'; -import { FieldFormat } from '../field_format'; +import { FieldFormat } from './field_format'; -export function fieldFormatsMixin(kbnServer: any, server: Legacy.Server) { - const fieldFormatClasses: FieldFormat[] = []; +export function fieldFormatsMixin(kbnServer, server) { + const fieldFormatClasses = []; // for use outside of the request context, for special cases - server.decorate('server', 'fieldFormatServiceFactory', async function(uiSettings) { + server.decorate('server', 'fieldFormatServiceFactory', async function (uiSettings) { const uiConfigs = await uiSettings.getAll(); const uiSettingDefaults = await uiSettings.getDefaults(); - Object.keys(uiSettingDefaults).forEach(key => { - if (has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') { + Object.keys(uiSettingDefaults).forEach((key) => { + if (_.has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') { uiConfigs[key] = JSON.parse(uiConfigs[key]); } }); - const getConfig = (key: string) => uiConfigs[key]; - + const getConfig = (key) => uiConfigs[key]; return new FieldFormatsService(fieldFormatClasses, getConfig); }); - server.decorate('server', 'registerFieldFormat', createFormat => { + server.decorate('server', 'registerFieldFormat', (createFormat) => { fieldFormatClasses.push(createFormat(FieldFormat)); }); } diff --git a/src/legacy/ui/field_formats/mixin/field_formats_service.ts b/src/legacy/ui/field_formats/field_formats_service.js similarity index 66% rename from src/legacy/ui/field_formats/mixin/field_formats_service.ts rename to src/legacy/ui/field_formats/field_formats_service.js index 779157f5c418b..9584f78046c35 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_service.ts +++ b/src/legacy/ui/field_formats/field_formats_service.js @@ -17,20 +17,11 @@ * under the License. */ -import { indexBy, Dictionary } from 'lodash'; -import { FieldFormat } from '../field_format'; - -interface FieldFormatConfig { - id: string; - params?: Record; -} +import _ from 'lodash'; export class FieldFormatsService { - getConfig: any; - _fieldFormats: Dictionary; - - constructor(fieldFormatClasses: FieldFormat[], getConfig: Function) { - this._fieldFormats = indexBy(fieldFormatClasses, 'id'); + constructor(fieldFormatClasses, getConfig) { + this._fieldFormats = _.indexBy(fieldFormatClasses, 'id'); this.getConfig = getConfig; } @@ -39,9 +30,9 @@ export class FieldFormatsService { * using the format:defaultTypeMap config map * * @param {String} fieldType - the field type - * @return {FieldFormatConfig} + * @return {String} */ - getDefaultConfig(fieldType: string): FieldFormatConfig { + getDefaultConfig(fieldType) { const defaultMap = this.getConfig('format:defaultTypeMap'); return defaultMap[fieldType] || defaultMap._default_; } @@ -52,19 +43,21 @@ export class FieldFormatsService { * @param {String} fieldType * @return {FieldFormat} */ - getDefaultInstance(fieldType: string): FieldFormat { - return this.getInstance(this.getDefaultConfig(fieldType)); + getDefaultInstance(fieldType) { + const conf = this.getDefaultConfig(fieldType); + const FieldFormat = this._fieldFormats[conf.id]; + return new FieldFormat(conf.params, this.getConfig); } /** * Get the fieldFormat instance for a field format configuration. * - * @param {FieldFormatConfig} field format config + * @param {Object} conf:id, conf:params * @return {FieldFormat} */ - getInstance(conf: FieldFormatConfig): FieldFormat { - // @ts-ignore - return new this._fieldFormats[conf.id](conf.params, this.getConfig); + getInstance(conf) { + const FieldFormat = this._fieldFormats[conf.id]; + return new FieldFormat(conf.params, this.getConfig); } /** @@ -73,7 +66,7 @@ export class FieldFormatsService { * @param {String} fieldFormatId - the FieldFormat id * @return {FieldFormat} */ - getType(fieldFormatId: string): any { + getType(fieldFormatId) { return this._fieldFormats[fieldFormatId]; } } diff --git a/src/legacy/ui/field_formats/index.ts b/src/legacy/ui/field_formats/index.js similarity index 92% rename from src/legacy/ui/field_formats/index.ts rename to src/legacy/ui/field_formats/index.js index cb15ba7a6b0ae..1bbad4800a363 100644 --- a/src/legacy/ui/field_formats/index.ts +++ b/src/legacy/ui/field_formats/index.js @@ -17,5 +17,5 @@ * under the License. */ -export { fieldFormatsMixin } from './mixin/field_formats_mixin'; +export { fieldFormatsMixin } from './field_formats_mixin'; export { FieldFormat } from './field_format'; diff --git a/src/legacy/ui/field_formats/mixin/field_formats_service.test.ts b/src/legacy/ui/field_formats/mixin/field_formats_service.test.ts deleted file mode 100644 index 32f1579510c64..0000000000000 --- a/src/legacy/ui/field_formats/mixin/field_formats_service.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { FieldFormat } from '../field_format'; -import { FieldFormatsService } from './field_formats_service'; - -// @ts-ignore -import { createNumberFormat } from '../../../core_plugins/kibana/common/field_formats/types/number'; - -const getConfig = (key: string) => { - switch (key) { - case 'format:defaultTypeMap': - return { - number: { id: 'number', params: {} }, - _default_: { id: 'string', params: {} }, - }; - case 'format:number:defaultPattern': - return '0,0.[000]'; - } -}; - -describe('FieldFormatsService', function() { - let fieldFormatsService: FieldFormatsService; - - beforeEach(function() { - const fieldFormatClasses = [createNumberFormat(FieldFormat)]; - - fieldFormatsService = new FieldFormatsService(fieldFormatClasses, getConfig); - }); - - test('FieldFormats are accessible via getType method', function() { - const Type = fieldFormatsService.getType('number'); - - expect(Type.id).toBe('number'); - }); - - test('getDefaultInstance returns default FieldFormat instance for fieldType', function() { - const instance = fieldFormatsService.getDefaultInstance('number'); - - expect(instance.type.id).toBe('number'); - expect(instance.convert('0.33333')).toBe('0.333'); - }); -}); diff --git a/src/legacy/ui/field_formats/types.ts b/src/legacy/ui/field_formats/types.ts deleted file mode 100644 index f5238422020c2..0000000000000 --- a/src/legacy/ui/field_formats/types.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { Field } from '../../core_plugins/data/public/index_patterns'; - -/** @public **/ -export type ContentType = 'html' | 'text'; - -/** @public **/ -export { IFieldFormat } from './field_format'; - -/** @internal **/ -export type HtmlConventTypeConvert = ( - value: any, - field?: Field, - hit?: Record, - meta?: any -) => string; - -/** @internal **/ -export type TextContextTypeConvert = (value: any) => string; - -/** @internal **/ -export type FieldFormatConvertFunction = HtmlConventTypeConvert | TextContextTypeConvert; - -/** @internal **/ -export interface FieldFormatConvert { - [key: string]: FieldFormatConvertFunction; -} diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index e7d85b8cc3f8e..1c3a9c006acfd 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -19,7 +19,6 @@ @import './exit_full_screen/index'; @import './field_editor/index'; @import './notify/index'; -@import './saved_objects/index'; @import './share/index'; @import './style_compile/index'; diff --git a/src/legacy/ui/public/chrome/chrome.js b/src/legacy/ui/public/chrome/chrome.js index a5a0521013a6e..8f58da9107673 100644 --- a/src/legacy/ui/public/chrome/chrome.js +++ b/src/legacy/ui/public/chrome/chrome.js @@ -95,6 +95,7 @@ const waitForBootstrap = new Promise(resolve => { document.body.setAttribute('id', `${internals.app.id}-app`); chrome.setupAngular(); + // targetDomElement.setAttribute('id', 'kibana-body'); targetDomElement.setAttribute('kbn-chrome', 'true'); targetDomElement.setAttribute('ng-class', '{ \'hidden-chrome\': !chrome.getVisible() }'); targetDomElement.className = 'app-wrapper'; diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.js b/src/legacy/ui/public/chrome/directives/kbn_chrome.js index 755cb8b42d363..d81a1ceb5f288 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js @@ -77,21 +77,15 @@ export function kbnChromeProvider(chrome, internals) { // Non-scope based code (e.g., React) // Banners - const bannerListContainer = document.getElementById('globalBannerList'); - // Banners not supported in New Platform yet - // https://github.com/elastic/kibana/issues/41986 - if (bannerListContainer) { - ReactDOM.render( - - - , - bannerListContainer - ); - } - + ReactDOM.render( + + + , + document.getElementById('globalBannerList') + ); return chrome; } diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/__snapshots__/string.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/__snapshots__/string.test.js.snap index 5561fbec68b3b..1b48a8e6a7250 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/__snapshots__/string.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/__snapshots__/string.test.js.snap @@ -58,10 +58,6 @@ exports[`StringFormatEditor should render normally 1`] = ` "input": "SGVsbG8gd29ybGQ=", "output": "SGVSBG8GD29YBGQ=", }, - Object { - "input": "%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98", - "output": "%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98", - }, ] } /> diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/string.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/string.js index 9aacc7899afee..8d3c1d9fca1c8 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/string.js +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/string/string.js @@ -44,8 +44,7 @@ export class StringFormatEditor extends DefaultFormatEditor { 'STAY CALM!', 'com.organizations.project.ClassName', 'hostname.net', - 'SGVsbG8gd29ybGQ=', - '%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98' + 'SGVsbG8gd29ybGQ=' ]; } diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 28d57e9f8e8c9..1e22003b32833 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -33,7 +33,7 @@ import * as Rx from 'rxjs'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CoreStart, LegacyCoreStart } from 'kibana/public'; +import { InternalCoreStart } from 'kibana/public'; import { fatalError } from 'ui/notify'; import { capabilities } from 'ui/capabilities'; @@ -77,7 +77,7 @@ export const configureAppAngularModule = (angularModule: IModule) => { .run($setupUrlOverflowHandling(newPlatform)); }; -const getEsUrl = (newPlatform: CoreStart) => { +const getEsUrl = (newPlatform: InternalCoreStart) => { const a = document.createElement('a'); a.href = newPlatform.http.basePath.prepend('/elasticsearch'); const protocolPort = /https/.test(a.protocol) ? 443 : 80; @@ -90,7 +90,7 @@ const getEsUrl = (newPlatform: CoreStart) => { }; }; -const setupCompileProvider = (newPlatform: LegacyCoreStart) => ( +const setupCompileProvider = (newPlatform: InternalCoreStart) => ( $compileProvider: ICompileProvider ) => { if (!newPlatform.injectedMetadata.getLegacyMetadata().devMode) { @@ -98,7 +98,7 @@ const setupCompileProvider = (newPlatform: LegacyCoreStart) => ( } }; -const setupLocationProvider = (newPlatform: CoreStart) => ( +const setupLocationProvider = (newPlatform: InternalCoreStart) => ( $locationProvider: ILocationProvider ) => { $locationProvider.html5Mode({ @@ -110,7 +110,7 @@ const setupLocationProvider = (newPlatform: CoreStart) => ( $locationProvider.hashPrefix(''); }; -export const $setupXsrfRequestInterceptor = (newPlatform: LegacyCoreStart) => { +export const $setupXsrfRequestInterceptor = (newPlatform: InternalCoreStart) => { const version = newPlatform.injectedMetadata.getLegacyMetadata().version; // Configure jQuery prefilter @@ -145,7 +145,7 @@ export const $setupXsrfRequestInterceptor = (newPlatform: LegacyCoreStart) => { * @param {HttpService} $http * @return {undefined} */ -const capture$httpLoadingCount = (newPlatform: CoreStart) => ( +const capture$httpLoadingCount = (newPlatform: InternalCoreStart) => ( $rootScope: IRootScopeService, $http: IHttpService ) => { @@ -166,7 +166,7 @@ const capture$httpLoadingCount = (newPlatform: CoreStart) => ( * lets us integrate with the angular router so that we can automatically clear * the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly */ -const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( +const $setupBreadcrumbsAutoClear = (newPlatform: InternalCoreStart) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -213,7 +213,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( * lets us integrate with the angular router so that we can automatically clear * the badge if we switch to a Kibana app that does not use the badge correctly */ -const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( +const $setupBadgeAutoClear = (newPlatform: InternalCoreStart) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -253,7 +253,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( * the helpExtension if we switch to a Kibana app that does not set its own * helpExtension */ -const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( +const $setupHelpExtensionAutoClear = (newPlatform: InternalCoreStart) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -285,7 +285,7 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( }); }; -const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( +const $setupUrlOverflowHandling = (newPlatform: InternalCoreStart) => ( $location: ILocationService, $rootScope: IRootScopeService, Private: any, diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 4f55349e3efe2..5e0eb2feeb450 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { LegacyCoreSetup, LegacyCoreStart } from '../../../../core/public'; +import { InternalCoreSetup, InternalCoreStart } from '../../../../core/public'; import { Plugin as DataPlugin } from '../../../../plugins/data/public'; import { Setup as InspectorSetup, @@ -34,12 +34,12 @@ export interface PluginsStart { } export const npSetup = { - core: (null as unknown) as LegacyCoreSetup, + core: (null as unknown) as InternalCoreSetup, plugins: {} as PluginsSetup, }; export const npStart = { - core: (null as unknown) as LegacyCoreStart, + core: (null as unknown) as InternalCoreStart, plugins: {} as PluginsStart, }; @@ -48,18 +48,18 @@ export const npStart = { * @internal */ export function __reset__() { - npSetup.core = (null as unknown) as LegacyCoreSetup; + npSetup.core = (null as unknown) as InternalCoreSetup; npSetup.plugins = {} as any; - npStart.core = (null as unknown) as LegacyCoreStart; + npStart.core = (null as unknown) as InternalCoreStart; npStart.plugins = {} as any; } -export function __setup__(coreSetup: LegacyCoreSetup, plugins: PluginsSetup) { +export function __setup__(coreSetup: InternalCoreSetup, plugins: PluginsSetup) { npSetup.core = coreSetup; npSetup.plugins = plugins; } -export function __start__(coreStart: LegacyCoreStart, plugins: PluginsStart) { +export function __start__(coreStart: InternalCoreStart, plugins: PluginsStart) { npStart.core = coreStart; npStart.plugins = plugins; } diff --git a/src/legacy/ui/public/saved_objects/_index.scss b/src/legacy/ui/public/saved_objects/_index.scss deleted file mode 100644 index 50a192b6a7b17..0000000000000 --- a/src/legacy/ui/public/saved_objects/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import '../../../../plugins/kibana_react/public/saved_objects/index'; diff --git a/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx b/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.js similarity index 52% rename from src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx rename to src/legacy/ui/public/saved_objects/show_saved_object_save_modal.js index 6aea3c72e0c34..bd2fbba3aa145 100644 --- a/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx +++ b/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.js @@ -21,25 +21,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nContext } from 'ui/i18n'; -/** - * Represents the result of trying to persist the saved object. - * Contains `error` prop if something unexpected happened (e.g. network error). - * Contains an `id` if persisting was successful. If `id` and - * `error` are undefined, persisting was not successful, but the - * modal can still recover (e.g. the name of the saved object was already taken). - */ -export type SaveResult = { id?: string } | { error: Error }; - -function isSuccess(result: SaveResult): result is { id?: string } { - return 'id' in result; -} - -interface MinimalSaveModalProps { - onSave: (...args: any[]) => Promise; - onClose: () => void; -} - -export function showSaveModal(saveModal: React.ReactElement) { +export function showSaveModal(saveModal) { const container = document.createElement('div'); const closeModal = () => { ReactDOM.unmountComponentAtNode(container); @@ -48,19 +30,21 @@ export function showSaveModal(saveModal: React.ReactElement { - const response = await onSave(...args); - // close modal if we either hit an error or the saved object got an id - if (Boolean(isSuccess(response) ? response.id : response.error)) { - closeModal(); - } - return response; + const onSaveConfirmed = (...args) => { + onSave(...args).then(({ id, error }) => { + if (id || error) { + closeModal(); + } + }); }; document.body.appendChild(container); - const element = React.cloneElement(saveModal, { - onSave: onSaveConfirmed, - onClose: closeModal, - }); + const element = React.cloneElement( + saveModal, + { + onSave: onSaveConfirmed, + onClose: closeModal + } + ); ReactDOM.render({element}, container); } diff --git a/src/legacy/core_plugins/data/public/timefilter/get_time.test.ts b/src/legacy/ui/public/timefilter/get_time.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/get_time.test.ts rename to src/legacy/ui/public/timefilter/get_time.test.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/get_time.ts b/src/legacy/ui/public/timefilter/get_time.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/get_time.ts rename to src/legacy/ui/public/timefilter/get_time.ts diff --git a/src/legacy/ui/public/timefilter/index.ts b/src/legacy/ui/public/timefilter/index.ts index 34f2a367a217c..5f5fcf6b19c7f 100644 --- a/src/legacy/ui/public/timefilter/index.ts +++ b/src/legacy/ui/public/timefilter/index.ts @@ -17,16 +17,8 @@ * under the License. */ -import uiRoutes from 'ui/routes'; -import { registerTimefilterWithGlobalState, getTimefilterConfig } from './setup_router'; -import { Timefilter, TimeHistory } from '../../../core_plugins/data/public/timefilter'; +export { TimeRange, RefreshInterval } from '../../../../plugins/data/public'; -const config = getTimefilterConfig(); - -export { Timefilter, TimeHistory, getTime } from '../../../core_plugins/data/public/timefilter'; -export const timeHistory = new TimeHistory(); -export const timefilter = new Timefilter(config, timeHistory); - -uiRoutes.addSetupWork((globalState, $rootScope) => { - return registerTimefilterWithGlobalState(timefilter, globalState, $rootScope); -}); +export { timefilter, Timefilter, registerTimefilterWithGlobalState } from './timefilter'; +export { timeHistory, TimeHistory } from './time_history'; +export { getTime } from './get_time'; diff --git a/src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.test.ts b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.test.ts rename to src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.ts b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/lib/diff_time_picker_vals.ts rename to src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts diff --git a/src/legacy/core_plugins/data/public/timefilter/lib/parse_querystring.ts b/src/legacy/ui/public/timefilter/lib/parse_querystring.ts similarity index 100% rename from src/legacy/core_plugins/data/public/timefilter/lib/parse_querystring.ts rename to src/legacy/ui/public/timefilter/lib/parse_querystring.ts diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts deleted file mode 100644 index cbd03df455da3..0000000000000 --- a/src/legacy/ui/public/timefilter/setup_router.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 _ from 'lodash'; -import { IScope } from 'angular'; -import moment from 'moment'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import chrome from 'ui/chrome'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; -import { Timefilter } from '../../../core_plugins/data/public/timefilter'; - -// TODO -// remove everything underneath once globalState is no longer an angular service -// and listener can be registered without angular. -function convertISO8601(stringTime: string): string { - const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true); - return obj.isValid() ? obj.toString() : stringTime; -} - -export function getTimefilterConfig() { - const settings = chrome.getUiSettingsClient(); - return { - timeDefaults: settings.get('timepicker:timeDefaults'), - refreshIntervalDefaults: settings.get('timepicker:refreshIntervalDefaults'), - }; -} - -// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter -// and require it to be executed to properly function. -// This function is exposed for applications that do not use uiRoutes like APM -// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter -export const registerTimefilterWithGlobalState = _.once( - (timefilter: Timefilter, globalState: any, $rootScope: IScope) => { - // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. - const config = getTimefilterConfig(); - timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); - timefilter.setRefreshInterval( - _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) - ); - - globalState.on('fetch_with_changes', () => { - // clone and default to {} in one - const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); - const newRefreshInterval: RefreshInterval = _.defaults( - {}, - globalState.refreshInterval, - config.refreshIntervalDefaults - ); - - if (newTime) { - if (newTime.to) newTime.to = convertISO8601(newTime.to); - if (newTime.from) newTime.from = convertISO8601(newTime.from); - } - - timefilter.setTime(newTime); - timefilter.setRefreshInterval(newRefreshInterval); - }); - - const updateGlobalStateWithTime = () => { - globalState.time = timefilter.getTime(); - globalState.refreshInterval = timefilter.getRefreshInterval(); - globalState.save(); - }; - - subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { - next: updateGlobalStateWithTime, - }); - - subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { - next: updateGlobalStateWithTime, - }); - } -); diff --git a/src/legacy/core_plugins/data/public/timefilter/time_history.ts b/src/legacy/ui/public/timefilter/time_history.ts similarity index 94% rename from src/legacy/core_plugins/data/public/timefilter/time_history.ts rename to src/legacy/ui/public/timefilter/time_history.ts index 98e759fb27a9d..66957ec5987c9 100644 --- a/src/legacy/core_plugins/data/public/timefilter/time_history.ts +++ b/src/legacy/ui/public/timefilter/time_history.ts @@ -19,7 +19,7 @@ import moment from 'moment'; import { TimeRange } from 'src/plugins/data/public'; -import { PersistedLog } from 'ui/persisted_log'; +import { PersistedLog } from '../persisted_log'; export class TimeHistory { private history: PersistedLog; @@ -52,3 +52,5 @@ export class TimeHistory { return this.history.get(); } } + +export const timeHistory = new TimeHistory(); diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter.test.mocks.ts b/src/legacy/ui/public/timefilter/timefilter.test.mocks.ts similarity index 93% rename from src/legacy/core_plugins/data/public/timefilter/timefilter.test.mocks.ts rename to src/legacy/ui/public/timefilter/timefilter.test.mocks.ts index 7354916c3fc35..f6d50167e45db 100644 --- a/src/legacy/core_plugins/data/public/timefilter/timefilter.test.mocks.ts +++ b/src/legacy/ui/public/timefilter/timefilter.test.mocks.ts @@ -17,7 +17,7 @@ * under the License. */ -import { chromeServiceMock } from '../../../../../core/public/mocks'; +import { chromeServiceMock } from '../../../../core/public/mocks'; jest.doMock('ui/new_platform', () => ({ npStart: { diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter.test.ts b/src/legacy/ui/public/timefilter/timefilter.test.ts similarity index 91% rename from src/legacy/core_plugins/data/public/timefilter/timefilter.test.ts rename to src/legacy/ui/public/timefilter/timefilter.test.ts index 47896189aa080..f8885d842ef69 100644 --- a/src/legacy/core_plugins/data/public/timefilter/timefilter.test.ts +++ b/src/legacy/ui/public/timefilter/timefilter.test.ts @@ -19,38 +19,46 @@ import './timefilter.test.mocks'; -jest.mock('ui/chrome', () => ({ - getBasePath: () => `/some/base/path`, - getUiSettingsClient: () => { - return { - get: (key: string) => { - switch (key) { - case 'timepicker:timeDefaults': - return { from: 'now-15m', to: 'now' }; - case 'timepicker:refreshIntervalDefaults': - return { pause: false, value: 0 }; - default: - throw new Error(`Unexpected config key: ${key}`); - } - }, - }; - }, -})); - -jest.mock('./lib/parse_querystring', () => ({ - parseQueryString: () => { - return { - // Can not access local variable from within a mock - // @ts-ignore - forceNow: global.nowTime, - }; - }, -})); +jest.mock( + 'ui/chrome', + () => ({ + getBasePath: () => `/some/base/path`, + getUiSettingsClient: () => { + return { + get: (key: string) => { + switch (key) { + case 'timepicker:timeDefaults': + return { from: 'now-15m', to: 'now' }; + case 'timepicker:refreshIntervalDefaults': + return { pause: false, value: 0 }; + default: + throw new Error(`Unexpected config key: ${key}`); + } + }, + }; + }, + }), + { virtual: true } +); + +jest.mock( + 'ui/timefilter/lib/parse_querystring', + () => ({ + parseQueryString: () => { + return { + // Can not access local variable from within a mock + // @ts-ignore + forceNow: global.nowTime, + }; + }, + }), + { virtual: true } +); import sinon from 'sinon'; import expect from '@kbn/expect'; import moment from 'moment'; -import { timefilter } from 'ui/timefilter'; +import { timefilter } from './timefilter'; import { Subscription } from 'rxjs'; import { TimeRange, RefreshInterval } from 'src/plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter.ts b/src/legacy/ui/public/timefilter/timefilter.ts similarity index 69% rename from src/legacy/core_plugins/data/public/timefilter/timefilter.ts rename to src/legacy/ui/public/timefilter/timefilter.ts index b07060018f9d7..729575a833e37 100644 --- a/src/legacy/core_plugins/data/public/timefilter/timefilter.ts +++ b/src/legacy/ui/public/timefilter/timefilter.ts @@ -20,18 +20,18 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; import moment, { Moment } from 'moment'; +import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +import chrome from 'ui/chrome'; +import { UiSettingsClientContract } from 'src/core/public'; import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; import { IndexPattern } from 'src/legacy/core_plugins/data/public'; -import { TimeHistory } from './time_history'; +import { IScope } from 'angular'; +import { timeHistory } from './time_history'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; +import uiRoutes from '../routes'; import { parseQueryString } from './lib/parse_querystring'; import { calculateBounds, getTime } from './get_time'; -export interface TimefilterConfig { - timeDefaults: TimeRange; - refreshIntervalDefaults: RefreshInterval; -} - // Timefilter accepts moment input but always returns string output export type InputTimeRange = | TimeRange @@ -53,15 +53,13 @@ export class Timefilter { private _time: TimeRange; private _refreshInterval!: RefreshInterval; - private _history: TimeHistory; public isTimeRangeSelectorEnabled: boolean = false; public isAutoRefreshSelectorEnabled: boolean = false; - constructor(config: TimefilterConfig, timeHistory: TimeHistory) { - this._history = timeHistory; - this._time = config.timeDefaults; - this.setRefreshInterval(config.refreshIntervalDefaults); + constructor(uiSettings: UiSettingsClientContract) { + this._time = uiSettings.get('timepicker:timeDefaults'); + this.setRefreshInterval(uiSettings.get('timepicker:refreshIntervalDefaults')); } getEnabledUpdated$ = () => { @@ -108,7 +106,7 @@ export class Timefilter { from: newTime.from, to: newTime.to, }; - this._history.add(this._time); + timeHistory.add(this._time); this.timeUpdate$.next(); this.fetch$.next(); } @@ -223,3 +221,64 @@ export class Timefilter { this.autoRefreshFetch$.next(); }; } + +export const timefilter = new Timefilter(chrome.getUiSettingsClient()); + +// TODO +// remove everything underneath once globalState is no longer an angular service +// and listener can be registered without angular. +function convertISO8601(stringTime: string): string { + const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true); + return obj.isValid() ? obj.toString() : stringTime; +} + +// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter +// and require it to be executed to properly function. +// This function is exposed for applications that do not use uiRoutes like APM +// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter +export const registerTimefilterWithGlobalState = _.once((globalState: any, $rootScope: IScope) => { + const uiSettings = chrome.getUiSettingsClient(); + const timeDefaults = uiSettings.get('timepicker:timeDefaults'); + const refreshIntervalDefaults = uiSettings.get('timepicker:refreshIntervalDefaults'); + + timefilter.setTime(_.defaults(globalState.time || {}, timeDefaults)); + timefilter.setRefreshInterval( + _.defaults(globalState.refreshInterval || {}, refreshIntervalDefaults) + ); + + globalState.on('fetch_with_changes', () => { + // clone and default to {} in one + const newTime: TimeRange = _.defaults({}, globalState.time, timeDefaults); + const newRefreshInterval: RefreshInterval = _.defaults( + {}, + globalState.refreshInterval, + refreshIntervalDefaults + ); + + if (newTime) { + if (newTime.to) newTime.to = convertISO8601(newTime.to); + if (newTime.from) newTime.from = convertISO8601(newTime.from); + } + + timefilter.setTime(newTime); + timefilter.setRefreshInterval(newRefreshInterval); + }); + + const updateGlobalStateWithTime = () => { + globalState.time = timefilter.getTime(); + globalState.refreshInterval = timefilter.getRefreshInterval(); + globalState.save(); + }; + + subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { + next: updateGlobalStateWithTime, + }); + + subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { + next: updateGlobalStateWithTime, + }); +}); + +uiRoutes.addSetupWork((globalState, $rootScope) => { + return registerTimefilterWithGlobalState(globalState, $rootScope); +}); diff --git a/src/legacy/ui/public/vis/agg_configs.ts b/src/legacy/ui/public/vis/agg_configs.ts index e16784bb6e2f2..e3edbef40c654 100644 --- a/src/legacy/ui/public/vis/agg_configs.ts +++ b/src/legacy/ui/public/vis/agg_configs.ts @@ -27,8 +27,8 @@ */ import _ from 'lodash'; -import { TimeRange } from 'src/plugins/data/public'; import { Schemas } from '../visualize/loader/pipeline_helpers/build_pipeline'; +import { TimeRange } from '../timefilter'; import { Schema } from '../vis/editors/default/schemas'; import { AggConfig, AggConfigOptions } from './agg_config'; import { AggGroupNames } from './editors/default/agg_groups'; diff --git a/src/legacy/ui/public/vis/request_handlers/courier.js b/src/legacy/ui/public/vis/request_handlers/courier.js index b6f05f6228ea3..cdd5158c5701d 100644 --- a/src/legacy/ui/public/vis/request_handlers/courier.js +++ b/src/legacy/ui/public/vis/request_handlers/courier.js @@ -24,7 +24,7 @@ import { calculateObjectHash } from '../lib/calculate_object_hash'; import { getRequestInspectorStats, getResponseInspectorStats } from '../../courier/utils/courier_inspector_utils'; import { tabifyAggResponse } from '../../agg_response/tabify/tabify'; import { buildTabularInspectorData } from '../../inspector/build_tabular_inspector_data'; -import { getTime } from '../../timefilter'; +import { getTime } from '../../timefilter/get_time'; const CourierRequestHandlerProvider = function () { diff --git a/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts b/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts index 03751e189210a..213f1e6252a6d 100644 --- a/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts +++ b/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; import { AggConfigs } from 'ui/vis/agg_configs'; diff --git a/src/legacy/ui/public/visualize/loader/types.ts b/src/legacy/ui/public/visualize/loader/types.ts index bb1113d212261..595a0649ec5ef 100644 --- a/src/legacy/ui/public/visualize/loader/types.ts +++ b/src/legacy/ui/public/visualize/loader/types.ts @@ -18,7 +18,7 @@ */ import { Filter } from '@kbn/es-query'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { SavedObject } from 'ui/saved_objects/saved_object'; diff --git a/src/legacy/ui/ui_bundles/ui_bundles_controller.js b/src/legacy/ui/ui_bundles/ui_bundles_controller.js index 5e9ae0abbb183..a4521268ea121 100644 --- a/src/legacy/ui/ui_bundles/ui_bundles_controller.js +++ b/src/legacy/ui/ui_bundles/ui_bundles_controller.js @@ -25,7 +25,6 @@ import { existsSync } from 'fs'; import del from 'del'; import { makeRe } from 'minimatch'; import mkdirp from 'mkdirp'; -import jsonStableStringify from 'json-stable-stringify'; import { IS_KIBANA_DISTRIBUTABLE } from '../../utils'; @@ -49,21 +48,6 @@ function getWebpackAliases(pluginSpecs) { }, {}); } -function sortAllArrays(input) { - if (Array.isArray(input)) { - return input - .map(i => sortAllArrays(i)) - .sort((a, b) => typeof a === 'string' && typeof b === 'string' ? a.localeCompare(b) : 0); - } - - if (typeof input === 'object') { - return Object.entries(input) - .map(([key, value]) => [key, sortAllArrays(value)]); - } - - return input; -} - export class UiBundlesController { constructor(kbnServer) { const { config, uiApps, uiExports, pluginSpecs } = kbnServer; @@ -75,7 +59,9 @@ export class UiBundlesController { sourceMaps: config.get('optimize.sourceMaps'), kbnVersion: config.get('pkg.version'), buildNum: config.get('pkg.buildNum'), - appExtensions: sortAllArrays(uiExports.appExtensions), + plugins: pluginSpecs + .map(spec => spec.getId()) + .sort((a, b) => a.localeCompare(b)) }; this._filter = makeRe(config.get('optimize.bundleFilter') || '*', { @@ -95,13 +81,6 @@ export class UiBundlesController { this._postLoaders = []; this._bundles = []; - // create a bundle for core-only with no modules - this.add({ - id: 'core', - modules: [], - template: appEntryTemplate - }); - // create a bundle for each uiApp for (const uiApp of uiApps) { this.add({ @@ -164,9 +143,7 @@ export class UiBundlesController { } getContext() { - return jsonStableStringify(this._context, { - space: ' ' - }); + return JSON.stringify(this._context, null, ' '); } resolvePath(...args) { diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 7e6609c4b5487..47d13184bfd0a 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -102,7 +102,9 @@ export function uiRenderMixin(kbnServer, server, config) { async handler(request, h) { const { id } = request.params; const app = server.getUiAppById(id) || server.getHiddenUiAppById(id); - const isCore = !app; + if (!app) { + throw Boom.notFound(`Unknown app: ${id}`); + } const uiSettings = request.getUiSettingsService(); const darkMode = !authEnabled || request.auth.isAuthenticated @@ -128,9 +130,7 @@ export function uiRenderMixin(kbnServer, server, config) { ), `${regularBundlePath}/${darkMode ? 'dark' : 'light'}_theme.style.css`, `${regularBundlePath}/commons.style.css`, - ...( - !isCore ? [`${regularBundlePath}/${app.getId()}.style.css`] : [] - ), + `${regularBundlePath}/${app.getId()}.style.css`, ...kbnServer.uiExports.styleSheetPaths .filter(path => ( path.theme === '*' || path.theme === (darkMode ? 'dark' : 'light') @@ -145,7 +145,7 @@ export function uiRenderMixin(kbnServer, server, config) { const bootstrap = new AppBootstrap({ templateData: { - appId: isCore ? 'core' : app.getId(), + appId: app.getId(), regularBundlePath, dllBundlePath, styleSheetPaths, @@ -164,11 +164,12 @@ export function uiRenderMixin(kbnServer, server, config) { }); server.route({ - path: '/app/{id}/{any*}', + path: '/app/{id}', method: 'GET', async handler(req, h) { const id = req.params.id; const app = server.getUiAppById(id); + if (!app) throw Boom.notFound('Unknown app ' + id); try { if (kbnServer.status.isGreen()) { @@ -182,15 +183,9 @@ export function uiRenderMixin(kbnServer, server, config) { } }); - async function getUiSettings({ request, includeUserProvidedConfig }) { + async function getLegacyKibanaPayload({ app, translations, request, includeUserProvidedConfig }) { const uiSettings = request.getUiSettingsService(); - return props({ - defaults: uiSettings.getDefaults(), - user: includeUserProvidedConfig && uiSettings.getUserProvided() - }); - } - async function getLegacyKibanaPayload({ app, translations, request, includeUserProvidedConfig }) { return { app, translations, @@ -203,15 +198,16 @@ export function uiRenderMixin(kbnServer, server, config) { basePath: request.getBasePath(), serverName: config.get('server.name'), devMode: config.get('env.dev'), - uiSettings: await getUiSettings({ request, includeUserProvidedConfig }), + uiSettings: await props({ + defaults: uiSettings.getDefaults(), + user: includeUserProvidedConfig && uiSettings.getUserProvided() + }) }; } async function renderApp({ app, h, includeUserProvidedConfig = true, injectedVarsOverrides = {} }) { const request = h.request; const basePath = request.getBasePath(); - const uiSettings = await getUiSettings({ request, includeUserProvidedConfig }); - app = app || { getId: () => 'core' }; const legacyMetadata = await getLegacyKibanaPayload({ app, @@ -232,14 +228,13 @@ export function uiRenderMixin(kbnServer, server, config) { bootstrapScriptUrl: `${basePath}/bundles/app/${app.getId()}/bootstrap.js`, i18n: (id, options) => i18n.translate(id, options), locale: i18n.getLocale(), - darkMode: get(uiSettings.user, ['theme:darkMode', 'userValue'], false), + darkMode: get(legacyMetadata.uiSettings.user, ['theme:darkMode', 'userValue'], false), injectedMetadata: { version: kbnServer.version, buildNumber: config.get('pkg.buildNum'), branch: config.get('pkg.branch'), basePath, - legacyMode: app.getId() !== 'core', i18n: { translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`, }, @@ -250,7 +245,7 @@ export function uiRenderMixin(kbnServer, server, config) { request, mergeVariables( injectedVarsOverrides, - app ? await server.getInjectedUiAppVars(app.getId()) : {}, + await server.getInjectedUiAppVars(app.getId()), defaultInjectedVars, ), ), diff --git a/src/legacy/ui/ui_render/views/ui_app.pug b/src/legacy/ui/ui_render/views/ui_app.pug index 95b321e09b500..5bbcc51e7745c 100644 --- a/src/legacy/ui/ui_render/views/ui_app.pug +++ b/src/legacy/ui/ui_render/views/ui_app.pug @@ -114,7 +114,7 @@ block content } } - .kibanaWelcomeView(id="kbn_loading_message", style="display: none;", data-test-subj="kbnLoadingMessage") + .kibanaWelcomeView(id="kbn_loading_message", style="display: none;") .kibanaLoaderWrap .kibanaLoader .kibanaWelcomeLogoCircle diff --git a/src/plugins/data/common/expressions/expression_types/kibana_context.ts b/src/plugins/data/common/expressions/expression_types/kibana_context.ts index 0a3e8a4db87f6..5ee3f7abbbdb0 100644 --- a/src/plugins/data/common/expressions/expression_types/kibana_context.ts +++ b/src/plugins/data/common/expressions/expression_types/kibana_context.ts @@ -18,8 +18,8 @@ */ import { Filter } from '@kbn/es-query'; -import { TimeRange } from 'src/plugins/data/public'; import { Query } from '../../query/types'; +import { TimeRange } from '../../timefilter/types'; const name = 'kibana_context'; diff --git a/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap b/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap index 5bf5c8be05ed8..47b4a5219068f 100644 --- a/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap +++ b/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap @@ -6,7 +6,7 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = ` onSubmit={[Function]} > - Save + diff --git a/src/plugins/kibana_react/public/saved_objects/_index.scss b/src/plugins/kibana_react/public/saved_objects/_index.scss deleted file mode 100644 index 6c773c7f777be..0000000000000 --- a/src/plugins/kibana_react/public/saved_objects/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './saved_object_save_modal'; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.scss b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.scss deleted file mode 100644 index b8758f692b7f7..0000000000000 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.scss +++ /dev/null @@ -1,3 +0,0 @@ -.kbnSavedObjectSaveModal { - width: $euiSizeXXL * 10; -} \ No newline at end of file diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx index 4d4e429417085..27da5d90646b3 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx @@ -35,9 +35,8 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -export interface OnSaveProps { +interface OnSaveProps { newTitle: string; newCopyOnSave: boolean; isTitleDuplicateConfirmed: boolean; @@ -73,14 +72,14 @@ export class SavedObjectSaveModal extends React.Component { }; public render() { - const { isTitleDuplicateConfirmed, hasTitleDuplicate, title } = this.state; + const { isTitleDuplicateConfirmed, hasTitleDuplicate, title, isLoading } = this.state; return (
@@ -137,7 +136,22 @@ export class SavedObjectSaveModal extends React.Component { /> - {this.renderConfirmButton()} + + {this.props.confirmButtonLabel ? ( + this.props.confirmButtonLabel + ) : ( + + )} +
@@ -190,34 +204,6 @@ export class SavedObjectSaveModal extends React.Component { this.saveSavedObject(); }; - private renderConfirmButton = () => { - const { isLoading, title, hasTitleDuplicate } = this.state; - - let confirmLabel: string | React.ReactNode = hasTitleDuplicate - ? i18n.translate('kibana-react.savedObjects.saveModal.confirmSaveButtonLabel', { - defaultMessage: 'Confirm save', - }) - : i18n.translate('kibana-react.savedObjects.saveModal.saveButtonLabel', { - defaultMessage: 'Save', - }); - - if (this.props.confirmButtonLabel) { - confirmLabel = this.props.confirmButtonLabel; - } - - return ( - - {confirmLabel} - - ); - }; - private renderDuplicateTitleCallout = () => { if (!this.state.hasTitleDuplicate) { return; @@ -244,14 +230,10 @@ export class SavedObjectSaveModal extends React.Component { objectType: this.props.objectType, confirmSaveLabel: ( - {this.props.confirmButtonLabel - ? this.props.confirmButtonLabel - : i18n.translate( - 'kibana-react.savedObjects.saveModal.duplicateTitleDescription.confirmSaveText', - { - defaultMessage: 'Confirm save', - } - )} + ), }} diff --git a/src/test_utils/public/enzyme_helpers.tsx b/src/test_utils/public/enzyme_helpers.tsx index 43ec49c5c1404..17027bd8dce48 100644 --- a/src/test_utils/public/enzyme_helpers.tsx +++ b/src/test_utils/public/enzyme_helpers.tsx @@ -128,15 +128,3 @@ export function renderWithIntl( } export const nextTick = () => new Promise(res => process.nextTick(res)); - -export function shallowWithI18nProvider(child: ReactElement) { - const wrapped = shallow({child}); - const name = typeof child.type === 'string' ? child.type : child.type.name; - return wrapped.find(name).dive(); -} - -export function mountWithI18nProvider(child: ReactElement) { - const wrapped = mount({child}); - const name = typeof child.type === 'string' ? child.type : child.type.name; - return wrapped.find(name); -} diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js index a48393e11b19c..312066000c63c 100644 --- a/test/functional/apps/dashboard/dashboard_filter_bar.js +++ b/test/functional/apps/dashboard/dashboard_filter_bar.js @@ -133,10 +133,10 @@ export default function ({ getService, getPageObjects }) { await PageObjects.dashboard.setTimepickerInDataRange(); }); - it('are added when a cell magnifying glass is clicked', async function () { - await dashboardAddPanel.addSavedSearch('Rendering-Test:-saved-search'); + it('are added when pie chart legend item is clicked', async function () { + await dashboardAddPanel.addVisualization('Rendering Test: pie'); await PageObjects.dashboard.waitForRenderComplete(); - await testSubjects.click('docTableCellFilter'); + await pieChart.filterByLegendItem('4,886'); const filterCount = await filterBar.getFilterCount(); expect(filterCount).to.equal(1); diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index 862ed8c87d347..15b444cb74151 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -74,6 +74,7 @@ export default function ({ getService, getPageObjects }) { it('tsvb time series shows no data message', async () => { expect(await testSubjects.exists('noTSVBDataMessage')).to.be(true); + await dashboardExpect.tsvbTimeSeriesLegendCount(0); }); it('metric value shows no data', async () => { @@ -133,6 +134,11 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.goalAndGuageLabelsExist(['0', '0%']); }); + it('tsvb time series shows no data message', async () => { + expect(await testSubjects.exists('noTSVBDataMessage')).to.be(true); + await dashboardExpect.tsvbTimeSeriesLegendCount(0); + }); + it('metric value shows no data', async () => { await dashboardExpect.metricValuesExist(['-']); }); @@ -189,6 +195,11 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.goalAndGuageLabelsExist(['39.958%', '7,544']); }); + it('tsvb time series', async () => { + expect(await testSubjects.exists('noTSVBDataMessage')).to.be(false); + await dashboardExpect.tsvbTimeSeriesLegendCount(10); + }); + it('metric value', async () => { await dashboardExpect.metricValuesExist(['101']); }); diff --git a/test/functional/apps/dashboard/dashboard_grid.js b/test/functional/apps/dashboard/dashboard_grid.js index a0c22ca85b91b..958d61176ded2 100644 --- a/test/functional/apps/dashboard/dashboard_grid.js +++ b/test/functional/apps/dashboard/dashboard_grid.js @@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['dashboard']); describe('dashboard grid', function () { + this.tags(['skipFirefox']); before(async () => { await PageObjects.dashboard.gotoDashboardLandingPage(); diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js index e21964495e46e..831622716f381 100644 --- a/test/functional/apps/dashboard/embeddable_rendering.js +++ b/test/functional/apps/dashboard/embeddable_rendering.js @@ -50,6 +50,7 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.tagCloudWithValuesFound(['CN', 'IN', 'US', 'BR', 'ID']); // TODO add test for 'region map viz' // TODO add test for 'tsvb gauge' viz + await dashboardExpect.tsvbTimeSeriesLegendCount(1); // TODO add test for 'geo map' viz // This tests the presence of the two input control embeddables await dashboardExpect.inputControlItemCount(5); @@ -85,6 +86,7 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.tsvbMetricValuesExist(['0']); await dashboardExpect.tsvbMarkdownWithValuesExists(['Hi Avg last bytes: 0']); await dashboardExpect.tsvbTableCellCount(0); + await dashboardExpect.tsvbTimeSeriesLegendCount(1); await dashboardExpect.tsvbTopNValuesExist(['0']); await dashboardExpect.vegaTextsDoNotExist(['5,000']); }; diff --git a/test/functional/apps/dashboard/full_screen_mode.js b/test/functional/apps/dashboard/full_screen_mode.js index 2a534a66d43e4..1f935e9b9aa1b 100644 --- a/test/functional/apps/dashboard/full_screen_mode.js +++ b/test/functional/apps/dashboard/full_screen_mode.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const retry = getService('retry'); + const browser = getService('browser'); const dashboardPanelActions = getService('dashboardPanelActions'); const PageObjects = getPageObjects(['dashboard', 'common']); @@ -68,7 +69,7 @@ export default function ({ getService, getPageObjects }) { it('exits when the text button is clicked on', async () => { const logoButton = await PageObjects.dashboard.getExitFullScreenLogoButton(); - await logoButton.moveMouseTo(); + await browser.moveMouseTo(logoButton); await PageObjects.dashboard.clickExitFullScreenTextButton(); await retry.try(async () => { diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index c29542e6a4035..3cb5d0a71cce5 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -160,7 +160,7 @@ export default function ({ getService, getPageObjects }) { const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); expect(Math.round(newDurationHours)).to.be(3); - const rowData = await PageObjects.discover.getDocTableField(1); + const rowData = await PageObjects.discover.getDocTableIndex(1); expect(rowData).to.have.string('Sep 20, 2015 @ 02:56:02.323'); }); diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js index 4d4031b4e489b..c9b444e501789 100644 --- a/test/functional/apps/management/index.js +++ b/test/functional/apps/management/index.js @@ -33,7 +33,7 @@ export default function ({ getService, loadTestFile }) { }); describe('', function () { - this.tags('ciGroup7'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./_create_index_pattern_wizard')); loadTestFile(require.resolve('./_index_pattern_create_delete')); @@ -45,7 +45,7 @@ export default function ({ getService, loadTestFile }) { }); describe('', function () { - this.tags('ciGroup8'); + this.tags('ciGroup2'); loadTestFile(require.resolve('./_index_pattern_filter')); loadTestFile(require.resolve('./_scripted_fields_filter')); diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index fa79190a5bf94..0a2400a367a76 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -75,7 +75,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await visualBuilder.changePanelPreview(); await visualBuilder.cloneSeries(); - const legend = await visualBuilder.getLegendItems(); + const legend = await visualBuilder.getLegentItems(); const series = await visualBuilder.getSeries(); expect(legend.length).to.be(2); expect(series.length).to.be(2); @@ -108,7 +108,8 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { expect(actualCount).to.be(expectedLegendValue); }); - it.skip('should show the correct count in the legend with "Human readable" duration formatter', async () => { + // FLAKY: https://github.com/elastic/kibana/issues/40458 + it('should show the correct count in the legend with "Human readable" duration formatter', async () => { await visualBuilder.clickSeriesOption(); await visualBuilder.changeDataFormatter('Duration'); await visualBuilder.setDurationFormatterSettings({ to: 'Human readable' }); @@ -126,8 +127,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { expect(actualCountMin).to.be('3 hours'); }); - // --reversed class is not implemented in @elastic\chart - describe.skip('Dark mode', () => { + describe('Dark mode', () => { before(async () => { await kibanaServer.uiSettings.update({ 'theme:darkMode': true, diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 2a13b6fea9158..68a00b29d107e 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -40,7 +40,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { }); describe('', function() { - this.tags('ciGroup9'); + this.tags('ciGroup3'); loadTestFile(require.resolve('./_embedding_chart')); loadTestFile(require.resolve('./_chart_types')); @@ -50,7 +50,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { }); describe('', function() { - this.tags('ciGroup10'); + this.tags('ciGroup4'); loadTestFile(require.resolve('./_inspector')); loadTestFile(require.resolve('./_experimental_vis')); @@ -62,7 +62,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { }); describe('', function() { - this.tags('ciGroup11'); + this.tags('ciGroup5'); loadTestFile(require.resolve('./_line_chart')); loadTestFile(require.resolve('./_pie_chart')); @@ -76,7 +76,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { }); describe('', function() { - this.tags('ciGroup12'); + this.tags('ciGroup6'); loadTestFile(require.resolve('./_tag_cloud')); loadTestFile(require.resolve('./_tile_map')); diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.js index fc47a53a092e8..651d82608961a 100644 --- a/test/functional/page_objects/common_page.js +++ b/test/functional/page_objects/common_page.js @@ -151,22 +151,11 @@ export function CommonPageProvider({ getService, getPageObjects }) { navigateToApp(appName, { basePath = '', shouldLoginIfPrompted = true, shouldAcceptAlert = true, hash = '' } = {}) { const self = this; - - let appUrl; - if (config.has(['apps', appName])) { - // Legacy applications - const appConfig = config.get(['apps', appName]); - appUrl = getUrl.noAuth(config.get('servers.kibana'), { - pathname: `${basePath}${appConfig.pathname}`, - hash: hash || appConfig.hash, - }); - } else { - appUrl = getUrl.noAuth(config.get('servers.kibana'), { - pathname: `${basePath}/app/${appName}`, - hash - }); - } - + const appConfig = config.get(['apps', appName]); + const appUrl = getUrl.noAuth(config.get('servers.kibana'), { + pathname: `${basePath}${appConfig.pathname}`, + hash: hash || appConfig.hash, + }); log.debug('navigating to ' + appName + ' url: ' + appUrl); function navigateTo(url) { @@ -370,7 +359,7 @@ export function CommonPageProvider({ getService, getPageObjects }) { throw new Error('Toast is not visible yet'); } }); - await toast.moveMouseTo(); + await browser.moveMouseTo(toast); const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText(); log.debug(title); await find.clickByCssSelector('.euiToast__closeButton'); @@ -381,7 +370,7 @@ export function CommonPageProvider({ getService, getPageObjects }) { const toasts = await find.allByCssSelector('.euiToast'); for (const toastElement of toasts) { try { - await toastElement.moveMouseTo(); + await browser.moveMouseTo(toastElement); const closeBtn = await toastElement.findByCssSelector('.euiToast__closeButton'); await closeBtn.click(); } catch (err) { diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js index dfe8dc1071d46..6954bed438478 100644 --- a/test/functional/page_objects/discover_page.js +++ b/test/functional/page_objects/discover_page.js @@ -193,13 +193,6 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { return await row.getVisibleText(); } - async getDocTableField(index) { - const field = await find.byCssSelector( - `tr.kbnDocTable__row:nth-child(${index}) > [data-test-subj='docTableField']` - ); - return await field.getVisibleText(); - } - async clickDocSortDown() { await find.clickByCssSelector('.fa-sort-down'); } diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 5f34e5c4f8637..c64d623979313 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -67,14 +67,11 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro } public async checkTimeSeriesChartIsPresent() { - const isPresent = await find.existsByCssSelector('.tvbVisTimeSeries'); - if (!isPresent) { - throw new Error(`TimeSeries chart is not loaded`); - } + await testSubjects.existOrFail('timeseriesChart'); } public async checkTimeSeriesLegendIsPresent() { - const isPresent = await find.existsByCssSelector('.echLegend'); + const isPresent = await find.existsByCssSelector('.tvbLegend'); if (!isPresent) { throw new Error(`TimeSeries legend is not loaded`); } @@ -242,7 +239,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro formatter: 'Bytes' | 'Number' | 'Percent' | 'Duration' | 'Custom' ) { const formatterEl = await find.byCssSelector('[id$="row"] .euiComboBox'); - await comboBox.setElement(formatterEl, formatter, { clickWithMouse: true }); + await comboBox.setElement(formatterEl, formatter); } /** @@ -263,11 +260,11 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro }) { if (from) { const fromCombobox = await find.byCssSelector('[id$="from-row"] .euiComboBox'); - await comboBox.setElement(fromCombobox, from, { clickWithMouse: true }); + await comboBox.setElement(fromCombobox, from); } if (to) { const toCombobox = await find.byCssSelector('[id$="to-row"] .euiComboBox'); - await comboBox.setElement(toCombobox, to, { clickWithMouse: true }); + await comboBox.setElement(toCombobox, to); } if (decimalPlaces) { const decimalPlacesInput = await find.byCssSelector('[id$="decimal"]'); @@ -294,11 +291,9 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro await el.type(value); } - public async getRhythmChartLegendValue(nth = 0) { + public async getRhythmChartLegendValue() { await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - const metricValue = (await find.allByCssSelector( - `.echLegendItem .echLegendItem__displayValue` - ))[nth]; + const metricValue = await find.byCssSelector('.tvbLegend__itemValue'); await metricValue.moveMouseTo(); return await metricValue.getVisibleText(); } @@ -452,7 +447,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro public async clickColorPicker(): Promise { const picker = await find.byCssSelector('.tvbColorPicker button'); - await picker.clickMouseButton(); + await browser.clickMouseButton(picker); } public async setBackgroundColor(colorHex: string): Promise { @@ -507,8 +502,8 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro await PageObjects.visualize.waitForRenderingCount(prevRenderingCount + 1); } - public async getLegendItems(): Promise { - return await find.allByCssSelector('.echLegendItem'); + public async getLegentItems(): Promise { + return await testSubjects.findAll('tsvbLegendItem'); } public async getSeries(): Promise { diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index d02638d37aa49..db6656deed7cb 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -1187,7 +1187,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli await retry.try(async () => { const table = await testSubjects.find('tableVis'); const cell = await table.findByCssSelector(`tbody tr:nth-child(${row}) td:nth-child(${column})`); - await cell.moveMouseTo(); + await browser.moveMouseTo(cell); const filterBtn = await testSubjects.findDescendant('filterForCellValue', cell); await filterBtn.click(); }); diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index ccd32590e941c..d52be4b90c043 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -155,26 +155,49 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { } /** - * Moves the remote environment’s mouse cursor to the specified point {x, y} which is - * offset to browser page top left corner. + * Moves the remote environment’s mouse cursor to the specified element or relative + * position. * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#move * - * @param {x: number, y: number} point on browser page + * @param {WebElementWrapper} element Optional + * @param {number} xOffset Optional + * @param {number} yOffset Optional * @return {Promise} */ - public async moveMouseTo(point: { x: number; y: number }): Promise { + public async moveMouseTo(element: any, xOffset: number, yOffset: number): Promise; + public async moveMouseTo(element: WebElementWrapper): Promise; + public async moveMouseTo( + element: WebElementWrapper, + xOffset?: number, + yOffset?: number + ): Promise { if (this.isW3CEnabled) { + // Workaround for scrolling bug in W3C mode: move pointer to { x: 0, y: 0 } + // https://github.com/mozilla/geckodriver/issues/776 await this.getActions() .move({ x: 0, y: 0 }) .perform(); - await this.getActions() - .move({ x: point.x, y: point.y, origin: 'pointer' }) - .perform(); + if (element instanceof WebElementWrapper) { + await this.getActions() + .move({ x: xOffset || 10, y: yOffset || 10, origin: element._webElement }) + .perform(); + } else { + await this.getActions() + .move({ origin: { x: xOffset, y: yOffset } }) + .perform(); + } } else { - await this.getActions() - .pause(this.getActions().mouse) - .move({ x: point.x, y: point.y, origin: 'pointer' }) - .perform(); + if (element instanceof WebElementWrapper) { + await this.getActions() + .pause(this.getActions().mouse) + .move({ origin: element._webElement }) + .perform(); + } else { + await this.getActions() + .pause(this.getActions().mouse) + .move({ origin: { x: xOffset, y: yOffset } }) + .perform(); + } } } @@ -190,47 +213,70 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { from: { offset: { x: any; y: any }; location: any }, to: { offset: { x: any; y: any }; location: any } ) { + // tslint:disable-next-line:variable-name + let _from; + // tslint:disable-next-line:variable-name + let _to; + // tslint:disable-next-line:variable-name + const _fromOffset = from.offset + ? { x: from.offset.x || 0, y: from.offset.y || 0 } + : { x: 0, y: 0 }; + // tslint:disable-next-line:variable-name + const _toOffset = to.offset ? { x: to.offset.x || 0, y: to.offset.y || 0 } : { x: 0, y: 0 }; + // tslint:disable-next-line:variable-name + const _convertPointW3C = async (point: any, offset: { x: any; y: any }) => { + if (point.location instanceof WebElementWrapper) { + const position = await point.location.getPosition(); + return { + x: Math.round(position.x + offset.x), + y: Math.round(position.y + offset.y), + }; + } else { + return { + x: Math.round(point.location.x + offset.x), + y: Math.round(point.location.y + offset.y), + }; + } + }; + // tslint:disable-next-line:variable-name + const _convertPoint = (point: any) => { + return point.location instanceof WebElementWrapper + ? point.location._webElement + : point.location; + }; + if (this.isW3CEnabled) { - // The offset should be specified in pixels relative to the center of the element's bounding box - const getW3CPoint = (data: any) => { - if (!data.offset) { - data.offset = {}; - } - return data.location instanceof WebElementWrapper - ? { x: data.offset.x || 0, y: data.offset.y || 0, origin: data.location._webElement } - : { x: data.location.x, y: data.location.y, origin: 'pointer' }; - }; + // tslint:disable-next-line:variable-name + _from = await _convertPointW3C(from, _fromOffset); + // tslint:disable-next-line:variable-name + _to = await _convertPointW3C(to, _toOffset); + // tslint:disable-next-line:variable-name + const _offset = { x: _to.x - _from.x, y: _to.y - _from.y }; - const startPoint = getW3CPoint(from); - const endPoint = getW3CPoint(to); - await this.getActions() - .move({ x: 0, y: 0 }) - .perform(); return await this.getActions() - .move(startPoint) + .move({ x: _from.x, y: _from.y, origin: 'pointer' }) .press() - .move(endPoint) + .move({ x: _offset.x, y: _offset.y, origin: 'pointer' }) .release() .perform(); } else { - // The offset should be specified in pixels relative to the top-left corner of the element's bounding box - const getOffset: any = (offset: { x: number; y: number }) => - offset ? { x: offset.x || 0, y: offset.y || 0 } : { x: 0, y: 0 }; - - if (from.location instanceof WebElementWrapper === false) { - throw new Error('Dragging point should be WebElementWrapper instance'); - } else if (typeof to.location.x === 'number') { + // until Chromedriver is not supporting W3C Webdriver Actions API + // tslint:disable-next-line:variable-name + _from = _convertPoint(from); + // tslint:disable-next-line:variable-name + _to = _convertPoint(to); + if (from.location instanceof WebElementWrapper && typeof to.location.x === 'number') { return await this.getActions() - .move({ origin: from.location._webElement }) + .move({ origin: _from }) .press() - .move({ x: to.location.x, y: to.location.y, origin: 'pointer' }) + .move({ x: _to.x, y: _to.y, origin: 'pointer' }) .release() .perform(); } else { return await new LegacyActionSequence(driver) - .mouseMove(from.location._webElement, getOffset(from.offset)) + .mouseMove(_from, _fromOffset) .mouseDown() - .mouseMove(to.location._webElement, getOffset(to.offset)) + .mouseMove(_to, _toOffset) .mouseUp() .perform(); } @@ -274,29 +320,34 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { } /** - * Moves the remote environment’s mouse cursor to the specified point {x, y} which is - * offset to browser page top left corner. - * Then adds an action for left-click (down/up) with the mouse. + * Inserts an action for moving the mouse x and y pixels relative to the specified origin. + * The origin may be defined as the mouse's current position, the viewport, or the center + * of a specific WebElement. Then adds an action for left-click (down/up) with the mouse. * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#click * - * @param {x: number, y: number} point on browser page + * @param {WebElementWrapper} element Optional + * @param {number} xOffset Optional + * @param {number} yOffset Optional * @return {Promise} */ - public async clickMouseButton(point: { x: number; y: number }): Promise { - if (this.isW3CEnabled) { + public async clickMouseButton(element: any, xOffset: number, yOffset: number): Promise; + public async clickMouseButton(element: WebElementWrapper): Promise; + public async clickMouseButton(...args: unknown[]): Promise { + const arg0 = args[0]; + if (arg0 instanceof WebElementWrapper) { await this.getActions() - .move({ x: 0, y: 0 }) - .perform(); - await this.getActions() - .move({ x: point.x, y: point.y, origin: 'pointer' }) + .pause(this.getActions().mouse) + .move({ origin: arg0._webElement }) .click() .perform(); - } else { + } else if (isNaN(args[1] as number) || isNaN(args[2] as number) === false) { await this.getActions() .pause(this.getActions().mouse) - .move({ x: point.x, y: point.y, origin: 'pointer' }) + .move({ origin: { x: args[1], y: args[2] } }) .click() .perform(); + } else { + throw new Error('Element or coordinates should be provided'); } } @@ -327,10 +378,16 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { * @param {WebElementWrapper} element * @return {Promise} */ - public async doubleClick(): Promise { - await this.getActions() - .doubleClick() - .perform(); + public async doubleClick(element?: WebElementWrapper): Promise { + if (element instanceof WebElementWrapper) { + await this.getActions() + .doubleClick(element._webElement) + .perform(); + } else { + await this.getActions() + .doubleClick() + .perform(); + } } /** diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index 5ecd8cd883c8a..b8267f0b4cbe3 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -46,21 +46,13 @@ export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderCont await this.setElement(comboBox, value); } - private async clickOption(isMouseClick: boolean, element: WebElementWrapper) { - return isMouseClick ? await element.clickMouseButton() : await element.click(); - } - /** * set value inside combobox element * * @param comboBoxElement * @param value */ - public async setElement( - comboBoxElement: WebElementWrapper, - value: string, - options = { clickWithMouse: false } - ): Promise { + public async setElement(comboBoxElement: WebElementWrapper, value: string): Promise { log.debug(`comboBox.setElement, value: ${value}`); const isOptionSelected = await this.isOptionSelected(comboBoxElement, value); @@ -73,22 +65,21 @@ export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderCont await this.openOptionsList(comboBoxElement); if (value !== undefined) { - const selectOptions = await find.allByCssSelector( + const options = await find.allByCssSelector( `.euiFilterSelectItem[title^="${value.toString().trim()}"]`, WAIT_FOR_EXISTS_TIME ); - if (selectOptions.length > 0) { - await this.clickOption(options.clickWithMouse, selectOptions[0]); + if (options.length > 0) { + await options[0].click(); } else { // if it doesn't find the item which text starts with value, it will choose the first option - const firstOption = await find.byCssSelector('.euiFilterSelectItem'); - await this.clickOption(options.clickWithMouse, firstOption); + await find.clickByCssSelector('.euiFilterSelectItem'); } } else { - const firstOption = await find.byCssSelector('.euiFilterSelectItem'); - await this.clickOption(options.clickWithMouse, firstOption); + await find.clickByCssSelector('.euiFilterSelectItem'); } + await this.closeOptionsList(comboBoxElement); } @@ -250,11 +241,11 @@ export function ComboBoxProvider({ getService, getPageObjects }: FtrProviderCont value: string ): Promise { log.debug(`comboBox.isOptionSelected, value: ${value}`); - const $ = await comboBoxElement.parseDomContent(); - const selectedOptions = $('.euiComboBoxPill') - .toArray() - .map(option => $(option).text()); - return selectedOptions.length === 1 && selectedOptions[0] === value; + const selectedOptions = await comboBoxElement.findAllByClassName( + 'euiComboBoxPill', + WAIT_FOR_EXISTS_TIME + ); + return selectedOptions.length === 1 && (await selectedOptions[0].getVisibleText()) === value; } } diff --git a/test/functional/services/dashboard/expectations.js b/test/functional/services/dashboard/expectations.js index abafe89c40941..b6bede32b769c 100644 --- a/test/functional/services/dashboard/expectations.js +++ b/test/functional/services/dashboard/expectations.js @@ -62,6 +62,14 @@ export function DashboardExpectProvider({ getService, getPageObjects }) { }); } + async tsvbTimeSeriesLegendCount(expectedCount) { + log.debug(`DashboardExpect.tsvbTimeSeriesLegendCount(${expectedCount})`); + await retry.try(async () => { + const tsvbLegendItems = await testSubjects.findAll('tsvbLegendItem', findTimeout); + expect(tsvbLegendItems.length).to.be(expectedCount); + }); + } + async fieldSuggestions(expectedFields) { log.debug(`DashboardExpect.fieldSuggestions(${expectedFields})`); const fields = await filterBar.getFilterEditorFields(); diff --git a/test/functional/services/dashboard/panel_actions.js b/test/functional/services/dashboard/panel_actions.js index b7327f4af6d17..051074eb9b3d0 100644 --- a/test/functional/services/dashboard/panel_actions.js +++ b/test/functional/services/dashboard/panel_actions.js @@ -26,6 +26,7 @@ const OPEN_INSPECTOR_TEST_SUBJ = 'embeddablePanelAction-openInspector'; export function DashboardPanelActionsProvider({ getService, getPageObjects }) { const log = getService('log'); + const browser = getService('browser'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['header', 'common']); @@ -44,7 +45,7 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { async toggleContextMenu(parent) { log.debug('toggleContextMenu'); - await (parent ? parent.moveMouseTo() : testSubjects.moveMouseTo('dashboardPanelTitle')); + await (parent ? browser.moveMouseTo(parent) : testSubjects.moveMouseTo('dashboardPanelTitle')); const toggleMenuItem = await this.findContextMenu(parent); await toggleMenuItem.click(); } diff --git a/test/functional/services/inspector.js b/test/functional/services/inspector.js index d7c3109251aaf..9c25ebea48b4f 100644 --- a/test/functional/services/inspector.js +++ b/test/functional/services/inspector.js @@ -22,6 +22,7 @@ import expect from '@kbn/expect'; export function InspectorProvider({ getService }) { const log = getService('log'); const retry = getService('retry'); + const browser = getService('browser'); const renderable = getService('renderable'); const flyout = getService('flyout'); const testSubjects = getService('testSubjects'); @@ -131,7 +132,7 @@ export function InspectorProvider({ getService }) { await retry.try(async () => { const table = await testSubjects.find('inspectorTable'); const cell = await table.findByCssSelector(`tbody tr:nth-child(${row}) td:nth-child(${column})`); - await cell.moveMouseTo(); + await browser.moveMouseTo(cell); const filterBtn = await testSubjects.findDescendant('filterForInspectorCellValue', cell); await filterBtn.click(); }); @@ -142,7 +143,7 @@ export function InspectorProvider({ getService }) { await retry.try(async () => { const table = await testSubjects.find('inspectorTable'); const cell = await table.findByCssSelector(`tbody tr:nth-child(${row}) td:nth-child(${column})`); - await cell.moveMouseTo(); + await browser.moveMouseTo(cell); const filterBtn = await testSubjects.findDescendant('filterOutInspectorCellValue', cell); await filterBtn.click(); }); 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 65478c4e5ccd2..b05485618da01 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 @@ -58,7 +58,6 @@ export class WebElementWrapper { private Keys: IKey = this.webDriver.Key; private driver: WebDriver = this.webDriver.driver; public LegacyAction: any = this.webDriver.LegacyActionSequence; - public isW3CEnabled: boolean = (this.webDriver.driver as any).executor_.w3c === true; public static create( webElement: WebElement | WebElementWrapper, @@ -150,12 +149,6 @@ export class WebElementWrapper { } } - private getActions(): any { - return this.isW3CEnabled - ? (this.driver as any).actions() - : (this.driver as any).actions({ bridge: true }); - } - /** * Returns whether or not the element would be visible to an actual user. This means * that the following types of elements are considered to be not displayed: @@ -409,81 +402,29 @@ export class WebElementWrapper { } /** - * Moves the remote environment’s mouse cursor to the current element with optional offset + * Moves the remote environment’s mouse cursor to the current element * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#move - * @param { xOffset: 0, yOffset: 0 } options - * @return {Promise} - */ - public async moveMouseTo(options = { xOffset: 0, yOffset: 0 }) { - await this.retryCall(async function moveMouseTo(wrapper) { - await wrapper.scrollIntoViewIfNecessary(); - if (wrapper.isW3CEnabled) { - await wrapper - .getActions() - .move({ x: 0, y: 0 }) - .perform(); - await wrapper - .getActions() - .move({ x: options.xOffset, y: options.yOffset, origin: wrapper._webElement }) - .perform(); - } else { - await wrapper - .getActions() - .pause(wrapper.getActions().mouse) - .move({ x: options.xOffset, y: options.yOffset, origin: wrapper._webElement }) - .perform(); - } - }); - } - - /** - * Inserts an action for moving the mouse to element center, unless optional offset is provided. - * Then adds an action for left-click (down/up) with the mouse. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#click * - * @param { xOffset: 0, yOffset: 0 } options Optional * @return {Promise} */ - public async clickMouseButton(options = { xOffset: 0, yOffset: 0 }): Promise { - await this.retryCall(async function clickMouseButton(wrapper) { + public async moveMouseTo() { + await this.retryCall(async function moveMouseTo(wrapper) { await wrapper.scrollIntoViewIfNecessary(); - if (wrapper.isW3CEnabled) { - await wrapper - .getActions() - .move({ x: 0, y: 0 }) - .perform(); - await wrapper - .getActions() - .move({ x: options.xOffset, y: options.yOffset, origin: wrapper._webElement }) - .click() - .perform(); + if (wrapper.browserType === Browsers.Firefox) { + const actions = (wrapper.driver as any).actions(); + await actions.move({ x: 0, y: 0 }).perform(); + await actions.move({ x: 10, y: 10, origin: wrapper._webElement }).perform(); } else { - await wrapper - .getActions() - .pause(wrapper.getActions().mouse) - .move({ x: options.xOffset, y: options.yOffset, origin: wrapper._webElement }) - .click() + const mouse = (wrapper.driver.actions() as any).mouse(); + const actions = (wrapper.driver as any).actions({ bridge: true }); + await actions + .pause(mouse) + .move({ origin: wrapper._webElement }) .perform(); } }); } - /** - * Inserts action for performing a double left-click with the mouse. - * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html#doubleClick - * @param {WebElementWrapper} element - * @return {Promise} - */ - public async doubleClick(): Promise { - await this.retryCall(async function clickMouseButton(wrapper) { - await wrapper.scrollIntoViewIfNecessary(); - await wrapper - .getActions() - .doubleClick(wrapper._webElement) - .perform(); - }); - } - /** * Gets the first element inside this element matching the given CSS selector. * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebElement.html#findElement diff --git a/test/functional/services/saved_query_management_component.js b/test/functional/services/saved_query_management_component.js index 346b470f0e5d2..86c565f739b5a 100644 --- a/test/functional/services/saved_query_management_component.js +++ b/test/functional/services/saved_query_management_component.js @@ -27,12 +27,14 @@ export function SavedQueryManagementComponentProvider({ getService }) { class SavedQueryManagementComponent { async saveNewQuery(name, description, includeFilters, includeTimeFilter) { - await this.openSaveCurrentQueryModal(); + await this.openSavedQueryManagementComponent(); + await testSubjects.click('saved-query-management-save-button'); await this.submitSaveQueryForm(name, description, includeFilters, includeTimeFilter); } async saveNewQueryWithNameError(name) { - await this.openSaveCurrentQueryModal(); + await this.openSavedQueryManagementComponent(); + await testSubjects.click('saved-query-management-save-button'); if (name) { await testSubjects.setValue('saveQueryFormTitle', name); } @@ -135,15 +137,6 @@ export function SavedQueryManagementComponentProvider({ getService }) { await testSubjects.click('saved-query-management-popover-button'); } - async openSaveCurrentQueryModal() { - await this.openSavedQueryManagementComponent(); - - await retry.try(async () => { - await testSubjects.click('saved-query-management-save-button'); - await testSubjects.existOrFail('saveQueryForm'); - }); - } - async saveNewQueryMissingOrFail() { await this.openSavedQueryManagementComponent(); await testSubjects.missingOrFail('saved-query-management-save-button'); diff --git a/test/functional/services/test_subjects.ts b/test/functional/services/test_subjects.ts index aaad4f7c238ff..538870ca8268e 100644 --- a/test/functional/services/test_subjects.ts +++ b/test/functional/services/test_subjects.ts @@ -30,6 +30,7 @@ interface ExistsOptions { export function TestSubjectsProvider({ getService }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); + const browser = getService('browser'); const find = getService('find'); const config = getService('config'); @@ -95,7 +96,7 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { log.debug(`TestSubjects.doubleClick(${selector})`); const element = await this.find(selector, timeout); await element.moveMouseTo(); - await element.doubleClick(); + await browser.doubleClick(element); }); } diff --git a/test/mocha_decorations.d.ts b/test/mocha_decorations.d.ts index 4645faf3d5fe8..f6fca538a2159 100644 --- a/test/mocha_decorations.d.ts +++ b/test/mocha_decorations.d.ts @@ -26,12 +26,12 @@ type Tags = | 'ciGroup4' | 'ciGroup5' | 'ciGroup6' - | 'ciGroup7' - | 'ciGroup8' - | 'ciGroup9' - | 'ciGroup10' - | 'ciGroup11' - | 'ciGroup12'; + | 'ciGroup1' + | 'ciGroup2' + | 'ciGroup3' + | 'ciGroup4' + | 'ciGroup5' + | 'ciGroup6'; // We need to use the namespace here to match the Mocha definition // eslint-disable-next-line @typescript-eslint/no-namespace diff --git a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx deleted file mode 100644 index 5d464cf0405d0..0000000000000 --- a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 React from 'react'; -import ReactDOM from 'react-dom'; -import { BrowserRouter as Router, Route, withRouter, RouteComponentProps } from 'react-router-dom'; - -import { - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiPageHeader, - EuiPageHeaderSection, - EuiPageSideBar, - EuiTitle, - EuiSideNav, -} from '@elastic/eui'; - -import { AppMountContext, AppMountParameters } from 'kibana/public'; - -const Home = () => ( - - - - -

Welcome to Foo!

-
-
-
- - - - -

Bar home page section title

-
-
-
- Wow what a home page this is! -
-
-); - -const PageA = () => ( - - - - -

Page A

-
-
-
- - - - -

Page A section title

-
-
-
- Page A's content goes here -
-
-); - -type NavProps = RouteComponentProps & { - navigateToApp: AppMountContext['core']['application']['navigateToApp']; -}; -const Nav = withRouter(({ history, navigateToApp }: NavProps) => ( - history.push('/'), - 'data-test-subj': 'fooNavHome', - }, - { - id: 'page-a', - name: 'Page A', - onClick: () => history.push('/page-a'), - 'data-test-subj': 'fooNavPageA', - }, - { - id: 'linktobar', - name: 'Open Bar / Page B', - onClick: () => navigateToApp('bar', { path: 'page-b?query=here', state: 'foo!!' }), - 'data-test-subj': 'fooNavBarPageB', - }, - ], - }, - ]} - /> -)); - -const FooApp = ({ basename, context }: { basename: string; context: AppMountContext }) => ( - - - -
- } - titleSize="s" -/> -`; - exports[`NoServicesMessage status: success and historicalDataFound: false 1`] = ` { @@ -76,7 +78,7 @@ export function AddSettingsFlyout({ } }, [serviceName], - { preservePreviousData: false } + { preservePreviousResponse: false } ); const isSampleRateValid = transactionSampleRateRt diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/ErrorCountBadge.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/ErrorCountBadge.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/StickyTransactionProperties.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.tsx similarity index 99% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/StickyTransactionProperties.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.tsx index aa2eddf2a18a0..b74d2480b8e63 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/StickyTransactionProperties.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/StickyTransactionProperties.tsx @@ -30,7 +30,7 @@ import { fontSize } from '../../../../style/variables'; interface Props { transaction: Transaction; totalDuration?: number; - errorCount: number; + errorCount?: number; } const ErrorTitle = styled.span` diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/ServiceLegends.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/ServiceLegends.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/ServiceLegends.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/ServiceLegends.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/ResponsiveFlyout.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/index.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/SpanFlyout/index.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/WaterfallItem.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/WaterfallItem.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap similarity index 96% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap index 6f61f62167638..6c9e5d5a2be6c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap @@ -3,27 +3,6 @@ exports[`waterfall_helpers getWaterfall should return full waterfall 1`] = ` Object { "duration": 49660, - "entryTransaction": Object { - "processor": Object { - "event": "transaction", - }, - "service": Object { - "name": "opbeans-node", - }, - "timestamp": Object { - "us": 1549324795784006, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "duration": Object { - "us": 49660, - }, - "id": "myTransactionId1", - "name": "GET /api", - }, - }, "errorCountByTransactionId": Object { "myTransactionId1": 2, "myTransactionId2": 3, @@ -541,30 +520,6 @@ Object { exports[`waterfall_helpers getWaterfall should return partial waterfall 1`] = ` Object { "duration": 8634, - "entryTransaction": Object { - "parent": Object { - "id": "mySpanIdD", - }, - "processor": Object { - "event": "transaction", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "timestamp": Object { - "us": 1549324795823304, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "duration": Object { - "us": 8634, - }, - "id": "myTransactionId2", - "name": "Api::ProductsController#index", - }, - }, "errorCountByTransactionId": Object { "myTransactionId1": 2, "myTransactionId2": 3, diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/spans.json b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/spans.json similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/spans.json rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/spans.json diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/transaction.json b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/transaction.json similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/transaction.json rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/mock_responses/transaction.json diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts similarity index 97% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index cc7697c1d8964..7835d47468b7f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -30,7 +30,6 @@ interface IWaterfallGroup { } export interface IWaterfall { - entryTransaction?: Transaction; traceRoot?: Transaction; traceRootDuration?: number; @@ -225,8 +224,7 @@ function createGetTransactionById(itemsById: IWaterfallIndex) { } const item = itemsById[id]; - const isTransaction = idx(item, _ => _.docType) === 'transaction'; - if (isTransaction) { + if (idx(item, _ => _.docType) === 'transaction') { return (item as IWaterfallItemTransaction).transaction; } }; @@ -279,10 +277,8 @@ export function getWaterfall( const services = getServices(orderedItems); const getTransactionById = createGetTransactionById(itemsById); const serviceColors = getServiceColors(services); - const entryTransaction = getTransactionById(entryTransactionId); return { - entryTransaction, traceRoot, traceRootDuration, duration, diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.test.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/get_agent_marks.test.ts similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.test.ts rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/get_agent_marks.test.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/get_agent_marks.ts similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.ts rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/get_agent_marks.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx similarity index 100% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx similarity index 83% rename from x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx rename to x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx index 698980f2faa74..02d03ecd1fdf9 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx @@ -11,8 +11,7 @@ import { EuiPanel, EuiSpacer, EuiTitle, - EuiToolTip, - EuiEmptyPrompt + EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Location } from 'history'; @@ -25,7 +24,6 @@ import { TransactionActionMenu } from '../../../shared/TransactionActionMenu/Tra import { StickyTransactionProperties } from './StickyTransactionProperties'; import { TransactionTabs } from './TransactionTabs'; import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; -import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; function MaybeViewTraceLink({ transaction, @@ -103,40 +101,20 @@ function MaybeViewTraceLink({ } interface Props { + transaction: ITransaction; urlParams: IUrlParams; location: Location; waterfall: IWaterfall; exceedsMax: boolean; - isLoading: boolean; } -export const WaterfallWithSummmary: React.SFC = ({ +export const Transaction: React.SFC = ({ + transaction, urlParams, location, waterfall, - exceedsMax, - isLoading + exceedsMax }) => { - const { entryTransaction } = waterfall; - if (!entryTransaction) { - const content = isLoading ? ( - - ) : ( - - {i18n.translate('xpack.apm.transactionDetails.traceNotFound', { - defaultMessage: 'The selected trace cannot be found' - })} -
- } - titleSize="s" - /> - ); - - return {content}; - } - return ( @@ -153,10 +131,10 @@ export const WaterfallWithSummmary: React.SFC = ({ - + @@ -165,14 +143,14 @@ export const WaterfallWithSummmary: React.SFC = ({ - + {transaction && ( + + )}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx index 8060231b32599..d9ca32f78f4dc 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx @@ -15,7 +15,7 @@ import { DiscoverErrorLink } from '../DiscoverErrorLink'; import { DiscoverSpanLink } from '../DiscoverSpanLink'; import { DiscoverTransactionLink } from '../DiscoverTransactionLink'; import * as kibanaCore from '../../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; +import { InternalCoreStart } from 'src/core/public'; jest.mock('ui/kfetch'); @@ -32,7 +32,7 @@ beforeAll(() => { prepend: (path: string) => `/basepath${path}` } } - } as unknown) as LegacyCoreStart; + } as unknown) as InternalCoreStart; jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx index 4f96f529c471c..d5518f6a96195 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { getRenderedHref } from '../../../utils/testHelpers'; import { InfraLink } from './InfraLink'; import * as kibanaCore from '../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; +import { InternalCoreStart } from 'src/core/public'; const coreMock = ({ http: { @@ -17,7 +17,7 @@ const coreMock = ({ prepend: (path: string) => `/basepath${path}` } } -} as unknown) as LegacyCoreStart; +} as unknown) as InternalCoreStart; jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx index 521d62205311d..c01d198b65b5a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { getRenderedHref } from '../../../utils/testHelpers'; import { KibanaLink } from './KibanaLink'; import * as kibanaCore from '../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; +import { InternalCoreStart } from 'src/core/public'; describe('KibanaLink', () => { beforeEach(() => { @@ -19,7 +19,7 @@ describe('KibanaLink', () => { prepend: (path: string) => `/basepath${path}` } } - } as unknown) as LegacyCoreStart; + } as unknown) as InternalCoreStart; jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx index aaf27e75ce93b..524a2d225c84c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { getRenderedHref } from '../../../../utils/testHelpers'; import { MLJobLink } from './MLJobLink'; import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; +import { InternalCoreStart } from 'src/core/public'; describe('MLJobLink', () => { beforeEach(() => { @@ -19,7 +19,7 @@ describe('MLJobLink', () => { prepend: (path: string) => `/basepath${path}` } } - } as unknown) as LegacyCoreStart; + } as unknown) as InternalCoreStart; spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx index a6ae4a82cda20..73f8bb2c7a213 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx @@ -10,7 +10,7 @@ import { getRenderedHref } from '../../../../utils/testHelpers'; import { MLLink } from './MLLink'; import * as savedObjects from '../../../../services/rest/savedObjects'; import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; +import { InternalCoreStart } from 'src/core/public'; jest.mock('ui/kfetch'); @@ -20,7 +20,7 @@ const coreMock = ({ prepend: (path: string) => `/basepath${path}` } } -} as unknown) as LegacyCoreStart; +} as unknown) as InternalCoreStart; jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index 7b711c3fd09fd..8442f300aa0fc 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -13,7 +13,7 @@ import * as Transactions from './mockData'; import * as apmIndexPatternHooks from '../../../../hooks/useAPMIndexPattern'; import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; import { ISavedObject } from '../../../../services/rest/savedObjects'; -import { LegacyCoreStart } from 'src/core/public'; +import { InternalCoreStart } from 'src/core/public'; jest.mock('ui/kfetch'); @@ -35,7 +35,7 @@ describe('TransactionActionMenu component', () => { prepend: (path: string) => `/basepath${path}` } } - } as unknown) as LegacyCoreStart; + } as unknown) as InternalCoreStart; jest .spyOn(apmIndexPatternHooks, 'useAPMIndexPattern') diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx index fb45d96026f1f..426cc3b500447 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/index.tsx @@ -17,6 +17,7 @@ import { useTransactionBreakdown } from '../../../hooks/useTransactionBreakdown' import { TransactionBreakdownHeader } from './TransactionBreakdownHeader'; import { TransactionBreakdownKpiList } from './TransactionBreakdownKpiList'; import { TransactionBreakdownGraph } from './TransactionBreakdownGraph'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; const NoTransactionsTitle = styled.span` font-weight: bold; @@ -33,6 +34,8 @@ const TransactionBreakdown: React.FC<{ receivedDataDuringLifetime } = useTransactionBreakdown(); + const loading = status === FETCH_STATUS.LOADING || status === undefined; + const { kpis, timeseries } = data; const hasHits = kpis.length > 0; @@ -58,7 +61,7 @@ const TransactionBreakdown: React.FC<{ ) : ( - status === 'success' && ( + !loading && ( <> diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/StaticPlot.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/StaticPlot.js index 5a1cdc185ac4f..57e83ef62c44b 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/StaticPlot.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/StaticPlot.js @@ -27,6 +27,8 @@ import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue // see https://github.com/uber/react-vis/issues/1214 const getNull = d => isValidCoordinateValue(d.y) && !isNaN(d.y); +const X_TICK_TOTAL = 7; + class StaticPlot extends PureComponent { getVisSeries(series, plotValues) { return series @@ -139,23 +141,12 @@ class StaticPlot extends PureComponent { } render() { - const { - width, - series, - tickFormatX, - tickFormatY, - plotValues, - noHits - } = this.props; + const { series, tickFormatX, tickFormatY, plotValues, noHits } = this.props; const { yTickValues } = plotValues; - // approximate number of x-axis ticks based on the width of the plot. There should by approx 1 tick per 100px - // d3 will determine the exact number of ticks based on the selected range - const xTickTotal = Math.floor(width / 100); - return ( - + {noHits ? (
{ onHover={onHover} onMouseLeave={onMouseLeave} onSelectionEnd={onSelectionEnd} - width={800} + width={100} tickFormatX={x => x.getTime()} // Avoid timezone issues in snapshots /> ); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap index aeef8ed995e92..ad3b3d25fa4c6 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap @@ -5836,7 +5836,7 @@ Array [ style={ Object { "height": "256px", - "width": "800px", + "width": "100px", } } > @@ -5850,7 +5850,7 @@ Array [ onMouseLeave={[Function]} onMouseMove={[Function]} onWheel={[Function]} - width={800} + width={100} > @@ -5895,7 +5895,7 @@ Array [ @@ -6213,7 +6213,7 @@ Array [ onMouseLeave={[Function]} onMouseMove={[Function]} onWheel={[Function]} - width={800} + width={100} />
diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/index.tsx b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/index.tsx index 58057b2a9a201..a26c2797135d3 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/index.tsx +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/index.tsx @@ -22,21 +22,24 @@ import { LocalUIFilterName } from '../../../server/lib/ui_filters/local_ui_filters/config'; import { pickKeys } from '../../utils/pickKeys'; -import { useDeepObjectIdentity } from '../../hooks/useDeepObjectIdentity'; interface TimeRange { rangeFrom: string; rangeTo: string; } -function useUiFilters(params: IUrlParams): UIFilters { - const { kuery, environment, ...urlParams } = params; - const localUiFilters = mapValues( - pickKeys(urlParams, ...localUIFilterNames), - val => (val ? val.split(',') : []) - ) as Partial>; - - return useDeepObjectIdentity({ kuery, environment, ...localUiFilters }); +function useUiFilters( + params: Pick +): UIFilters { + return useMemo(() => { + const { kuery, environment, ...localUIFilters } = params; + const mappedLocalFilters = mapValues( + pickKeys(localUIFilters, ...localUIFilterNames), + val => (val ? val.split(',') : []) + ) as Partial>; + + return { kuery, environment, ...mappedLocalFilters }; + }, [params]); } const defaultRefresh = (time: TimeRange) => {}; diff --git a/x-pack/legacy/plugins/apm/public/hooks/useDeepObjectIdentity.ts b/x-pack/legacy/plugins/apm/public/hooks/useDeepObjectIdentity.ts deleted file mode 100644 index 3bbda44f31ff6..0000000000000 --- a/x-pack/legacy/plugins/apm/public/hooks/useDeepObjectIdentity.ts +++ /dev/null @@ -1,19 +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 { useRef } from 'react'; -import { isEqual } from 'lodash'; - -// preserve object identity if it is deeply equal to the previous instance of it -export function useDeepObjectIdentity(value: T) { - const valueRef = useRef(value); - - // update ref if object has changed. Else return the original object and discard the new one - if (!isEqual(valueRef.current, value)) { - valueRef.current = value; - } - - return valueRef.current; -} diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx index 24d4cbc067d74..6078a2026dca1 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx @@ -15,59 +15,37 @@ import { KFetchError } from '../../../../../../src/legacy/ui/public/kfetch/kfetc export enum FETCH_STATUS { LOADING = 'loading', SUCCESS = 'success', - FAILURE = 'failure', - PENDING = 'pending' + FAILURE = 'failure' } -interface Result { - data: Data; - status: FETCH_STATUS; - error?: Error; -} - -export function useFetcher( - fn: () => Promise | TState | undefined, - fnDeps: any[], - options?: { - preservePreviousData?: boolean; - } -): Result & { refresh: () => void }; - -// To avoid infinite rescursion when infering the type of `TState` `initialState` must be given if `prevResult` is consumed -export function useFetcher( - fn: (prevResult: Result) => Promise | TState | undefined, - fnDeps: any[], - options: { - preservePreviousData?: boolean; - initialState: TState; - } -): Result & { refresh: () => void }; +type Fetcher = (...args: any[]) => any; +type GetReturn = Exclude< + ReturnType, + undefined +> extends Promise + ? TReturn + : ReturnType; -export function useFetcher( - fn: Function, +export function useFetcher( + fn: TFetcher, fnDeps: any[], - options: { - preservePreviousData?: boolean; - initialState?: unknown; - } = {} + options: { preservePreviousResponse?: boolean } = {} ) { - const { preservePreviousData = true } = options; + const { preservePreviousResponse = true } = options; const id = useComponentId(); const { dispatchStatus } = useContext(LoadingIndicatorContext); - const [result, setResult] = useState>({ - data: options.initialState, - status: FETCH_STATUS.PENDING - }); + const [result, setResult] = useState<{ + data?: GetReturn; + status?: FETCH_STATUS; + error?: Error; + }>({}); const [counter, setCounter] = useState(0); useEffect(() => { let didCancel = false; async function doFetch() { - const promise = fn(result); - // if `fn` doesn't return a promise it is a signal that data fetching was not initiated. - // This can happen if the data fetching is conditional (based on certain inputs). - // In these cases it is not desirable to invoke the global loading spinner, or change the status to success + const promise = fn(); if (!promise) { return; } @@ -75,7 +53,7 @@ export function useFetcher( dispatchStatus({ id, isLoading: true }); setResult(prevResult => ({ - data: preservePreviousData ? prevResult.data : undefined, // preserve data from previous state while loading next state + data: preservePreviousResponse ? prevResult.data : undefined, // preserve data from previous state while loading next state status: FETCH_STATUS.LOADING, error: undefined })); @@ -135,7 +113,7 @@ export function useFetcher( }, [ counter, id, - preservePreviousData, + preservePreviousResponse, dispatchStatus, ...fnDeps /* eslint-enable react-hooks/exhaustive-deps */ diff --git a/x-pack/legacy/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/legacy/plugins/apm/public/hooks/useTransactionDistribution.ts index d91238109c233..3964af19bcf6b 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useTransactionDistribution.ts @@ -8,10 +8,9 @@ import { IUrlParams } from '../context/UrlParamsContext/types'; import { useFetcher } from './useFetcher'; import { useUiFilters } from '../context/UrlParamsContext'; import { callApmApi } from '../services/rest/callApmApi'; -import { TransactionDistributionAPIResponse } from '../../server/lib/transactions/distribution'; const INITIAL_DATA = { - buckets: [] as TransactionDistributionAPIResponse['buckets'], + buckets: [], totalHits: 0, bucketSize: 0 }; @@ -49,9 +48,16 @@ export function useTransactionDistribution(urlParams: IUrlParams) { } }); } - // the histogram should not be refetched if the transactionId or traceId changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [serviceName, start, end, transactionType, transactionName, uiFilters]); + }, [ + serviceName, + start, + end, + transactionType, + transactionName, + transactionId, + traceId, + uiFilters + ]); return { data, status, error }; } diff --git a/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts b/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts index 5078597d6935e..d3b636e3a4add 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts @@ -5,10 +5,10 @@ */ import { useMemo } from 'react'; +import { getWaterfall } from '../components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; import { IUrlParams } from '../context/UrlParamsContext/types'; import { useFetcher } from './useFetcher'; import { callApmApi } from '../services/rest/callApmApi'; -import { getWaterfall } from '../components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; const INITIAL_DATA = { root: undefined, @@ -38,5 +38,5 @@ export function useWaterfall(urlParams: IUrlParams) { transactionId ]); - return { waterfall, status, error, exceedsMax: data.trace.exceedsMax }; + return { data: waterfall, status, error, exceedsMax: data.trace.exceedsMax }; } diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 39912ec2ca8b4..c9a1e583a3cb0 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -8,7 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Router, Route, Switch } from 'react-router-dom'; import styled from 'styled-components'; -import { LegacyCoreStart } from 'src/core/public'; +import { InternalCoreStart } from 'src/core/public'; import { KibanaCoreContextProvider } from '../../../observability/public'; import { history } from '../utils/history'; import { LocationProvider } from '../context/LocationContext'; @@ -54,7 +54,7 @@ const App = () => { }; export class Plugin { - public start(core: LegacyCoreStart) { + public start(core: InternalCoreStart) { const { i18n } = core; ReactDOM.render( diff --git a/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts b/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts index d7ebbd87c97e6..0251095c9e75e 100644 --- a/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts +++ b/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts @@ -6,16 +6,6 @@ import { CanvasWorkpad, CanvasElement, CanvasPage } from '../../types'; const BaseWorkpad: CanvasWorkpad = { - '@created': '2019-02-08T18:35:23.029Z', - '@timestamp': '2019-02-08T18:35:23.029Z', - assets: { - 'asset-ada763f1-295e-4188-8e08-b5bed9e006a1': { - id: 'asset-ada763f1-295e-4188-8e08-b5bed9e006a1', - '@created': '2018-01-17T19:13:09.185Z', - type: 'dataurl', - value: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=', - }, - }, name: 'base workpad', id: 'base-workpad', width: 0, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts index c36a05e405704..a461e4c305bed 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts @@ -5,7 +5,7 @@ */ import { Filter as ESFilterType } from '@kbn/es-query'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/public'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { buildEmbeddableFilters } from '../../../server/lib/build_embeddable_filters'; import { Filter } from '../../../types'; diff --git a/x-pack/legacy/plugins/canvas/i18n/constants.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/constants.ts similarity index 100% rename from x-pack/legacy/plugins/canvas/i18n/constants.ts rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/constants.ts diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/all.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/all.ts index 43bdc38340470..ab212419f4a9e 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/all.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/all.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { all } from '../../functions/common/all'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { BOOLEAN_TRUE } from '../../../i18n'; +import { BOOLEAN_TRUE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.allHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/any.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/any.ts index d071bc58e1d04..cdf24f7c5ea77 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/any.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/any.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { any } from '../../functions/common/any'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { BOOLEAN_TRUE } from '../../../i18n'; +import { BOOLEAN_TRUE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.anyHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/as.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/as.ts index f40107dc4b388..a8c9a8da1e985 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/as.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/as.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { asFn } from '../../functions/common/as'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE } from '../../../i18n'; +import { DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.asHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/axisConfig.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/axisConfig.ts index 30ecae13fdc64..5e24a499475b0 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/axisConfig.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/axisConfig.ts @@ -9,7 +9,7 @@ import { axisConfig } from '../../functions/common/axisConfig'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; import { Position } from '../../../types'; -import { ISO8601 } from '../../../i18n'; +import { ISO8601 } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.axisConfigHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/case.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/case.ts index 1a2824fb6af66..9eb4c84b1b301 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/case.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/case.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { caseFn } from '../../functions/common/case'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; const IF_ARG = '`if`'; const WHEN_ARG = '`when`'; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/clear.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/clear.ts index 6448d0bd4427b..75816f34f9e19 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/clear.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/clear.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { clear } from '../../functions/common/clear'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT, TYPE_NULL } from '../../../i18n'; +import { CONTEXT, TYPE_NULL } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.clearHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/columns.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/columns.ts index 807acb5b187d9..290c932789822 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/columns.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/columns.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { columns } from '../../functions/common/columns'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE } from '../../../i18n'; +import { DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.columnsHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/compare.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/compare.ts index d974526804955..1df7a3b27271d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/compare.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/compare.ts @@ -16,7 +16,7 @@ import { BOOLEAN_TRUE, BOOLEAN_FALSE, TYPE_NULL, -} from '../../../i18n'; +} from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.compareHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/containerStyle.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/containerStyle.ts index 39eb38a66279c..2feb6c703b072 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/containerStyle.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/containerStyle.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { containerStyle } from '../../functions/common/containerStyle'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CSS } from '../../../i18n'; +import { CSS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.containerStyleHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/context.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/context.ts index 6dc01030c5755..431228d760ad3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/context.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/context.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { context } from '../../functions/common/context'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.contextHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/csv.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/csv.ts index 0dbfbc8324842..b1085bb10b416 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/csv.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/csv.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { csv } from '../../functions/common/csv'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE, CSV } from '../../../i18n'; +import { DATATABLE, CSV } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.csvHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/date.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/date.ts index 3df56c51258f1..962c6b95b2b80 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/date.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/date.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { date } from '../../functions/common/date'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { ISO8601, MOMENTJS, JS } from '../../../i18n'; +import { ISO8601, MOMENTJS, JS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.dateHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/do.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/do.ts index fc424192ca01b..55ea0f8409bd8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/do.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/do.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { doFn } from '../../functions/common/do'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.doHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/eq.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/eq.ts index a67d223dc9bec..7fce30518cfd7 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/eq.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/eq.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { eq } from '../../functions/common/eq'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.eqHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/escount.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/escount.ts index 68f80f6b1e55f..754465546f9d8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/escount.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/escount.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { escount } from '../../functions/server/escount'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { ELASTICSEARCH, LUCENE } from '../../../i18n'; +import { ELASTICSEARCH, LUCENE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.escountHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/esdocs.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/esdocs.ts index a61ad06d9426f..239184c3cb129 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/esdocs.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/esdocs.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { esdocs } from '../../functions/server/esdocs'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { ELASTICSEARCH, LUCENE } from '../../../i18n'; +import { ELASTICSEARCH, LUCENE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.esdocsHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/essql.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/essql.ts index d12c9f3632313..8e4be350c61ab 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/essql.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/essql.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { essql } from '../../functions/server/essql'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { ELASTICSEARCH, SQL, ISO8601, UTC } from '../../../i18n'; +import { ELASTICSEARCH, SQL, ISO8601, UTC } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.essqlHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/filterrows.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/filterrows.ts index dc5e6a48c8f97..808d495f16444 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/filterrows.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/filterrows.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { filterrows } from '../../functions/common/filterrows'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE, TYPE_BOOLEAN, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../../../i18n'; +import { DATATABLE, TYPE_BOOLEAN, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.filterrowsHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatdate.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatdate.ts index 3aa0fb1d421e7..a141d2bffa8b2 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatdate.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatdate.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { formatdate } from '../../functions/common/formatdate'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { ISO8601, MOMENTJS } from '../../../i18n'; +import { ISO8601, MOMENTJS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.formatdateHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatnumber.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatnumber.ts index 9944d6cff89dc..f1734ec67622b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatnumber.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/formatnumber.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { formatnumber } from '../../functions/common/formatnumber'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { NUMERALJS } from '../../../i18n'; +import { NUMERALJS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.formatnumberHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/getCell.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/getCell.ts index a2e50160218c8..ae219b294e5ff 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/getCell.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/getCell.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { getCell } from '../../functions/common/getCell'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE } from '../../../i18n'; +import { DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.getCellHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gt.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gt.ts index c03672955b975..aec5012f2d077 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gt.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gt.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { gt } from '../../functions/common/gt'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.gtHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gte.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gte.ts index 41098683981d3..f216bd94fa51b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gte.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/gte.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { gte } from '../../functions/common/gte'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.gteHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/head.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/head.ts index 8fe1248f9fc8c..3a6ca22c4d63b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/head.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/head.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { head } from '../../functions/common/head'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE } from '../../../i18n'; +import { DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.headHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/if.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/if.ts index e58f11ad1bccd..5cbd9b9366791 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/if.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/if.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { ifFn } from '../../functions/common/if'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { BOOLEAN_TRUE, BOOLEAN_FALSE, CONTEXT } from '../../../i18n'; +import { BOOLEAN_TRUE, BOOLEAN_FALSE, CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.ifHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/image.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/image.ts index e34fba940ae08..719e9f8f33414 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/image.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/image.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { image, ImageMode } from '../../functions/common/image'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { URL, BASE64 } from '../../../i18n'; +import { URL, BASE64 } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.imageHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lt.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lt.ts index e05b8a260d4f9..aa6c0110f5f0f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lt.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lt.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { lt } from '../../functions/common/lt'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.ltHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lte.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lte.ts index 659e91aa99c16..bf6c0bf56e828 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lte.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/lte.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { lte } from '../../functions/common/lte'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.lteHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/mapColumn.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/mapColumn.ts index c3330132bb4b4..9aa41a0b634c0 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/mapColumn.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/mapColumn.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { mapColumn } from '../../functions/common/mapColumn'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CANVAS, DATATABLE } from '../../../i18n'; +import { CANVAS, DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.mapColumnHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/markdown.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/markdown.ts index 043c2b1dc135e..8b5805d0246b1 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/markdown.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/markdown.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { markdown } from '../../functions/browser/markdown'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { MARKDOWN, CSS } from '../../../i18n'; +import { MARKDOWN, CSS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.markdownHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/math.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/math.ts index 1da6ec0c73dce..a10e0b6ded5aa 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/math.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/math.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { math } from '../../functions/common/math'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE, CONTEXT, TINYMATH, TINYMATH_URL } from '../../../i18n'; +import { DATATABLE, CONTEXT, TINYMATH, TINYMATH_URL } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.mathHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/metric.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/metric.ts index b4f1e2448d31a..77bf71800d38a 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/metric.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/metric.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { metric } from '../../functions/common/metric'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { FONT_FAMILY, FONT_WEIGHT, CSS, NUMERALJS } from '../../../i18n'; +import { FONT_FAMILY, FONT_WEIGHT, CSS, NUMERALJS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.metricHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/neq.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/neq.ts index de5ab3a2c301d..88c2792393b47 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/neq.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/neq.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { neq } from '../../functions/common/neq'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.neqHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pie.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pie.ts index f8b0250a9687c..3c90ac3ba9d6f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pie.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pie.ts @@ -9,7 +9,7 @@ import { pie } from '../../functions/common/pie'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; import { Position } from '../../../types'; -import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_FALSE } from '../../../i18n'; +import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_FALSE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.pieHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/plot.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/plot.ts index 45a764ce32ef9..ad9cbd7cfec8a 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/plot.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/plot.ts @@ -9,7 +9,7 @@ import { plot } from '../../functions/common/plot'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; import { Position } from '../../../types'; -import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_FALSE } from '../../../i18n'; +import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_FALSE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.plotHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/ply.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/ply.ts index f0a208f256fa8..1a4b4ab53a123 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/ply.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/ply.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { ply } from '../../functions/common/ply'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE } from '../../../i18n'; +import { DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.plyHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pointseries.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pointseries.ts index 044c4c59eab01..ceb0f0e8ac5fd 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pointseries.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/pointseries.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { pointseries } from '../../functions/server/pointseries'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE, TINYMATH, TINYMATH_URL } from '../../../i18n'; +import { DATATABLE, TINYMATH, TINYMATH_URL } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.pointseriesHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/progress.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/progress.ts index e300314629594..cde3ae71a39ca 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/progress.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/progress.ts @@ -10,7 +10,7 @@ import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; import { Shape } from '../../functions/common/progress'; -import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../../../i18n'; +import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.progressHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/render.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/render.ts index 1fea040292626..54185771417b4 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/render.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/render.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { render } from '../../functions/common/render'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT, CSS } from '../../../i18n'; +import { CONTEXT, CSS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.renderHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/repeatImage.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/repeatImage.ts index 3ba2f369225b2..92df1667e2bf5 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/repeatImage.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/repeatImage.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { repeatImage } from '../../functions/common/repeatImage'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT, BASE64, URL } from '../../../i18n'; +import { CONTEXT, BASE64, URL } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.repeatImageHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/replace.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/replace.ts index 680379c233078..f2d27280cdfd6 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/replace.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/replace.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { replace } from '../../functions/common/replace'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { JS } from '../../../i18n'; +import { JS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.replaceImageHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/revealImage.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/revealImage.ts index 85464abd8c349..0ef8bed9d9c78 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/revealImage.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/revealImage.ts @@ -9,7 +9,7 @@ import { revealImage } from '../../functions/common/revealImage'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; import { Position } from '../../../types'; -import { BASE64, URL } from '../../../i18n'; +import { BASE64, URL } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.revealImageHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/rounddate.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/rounddate.ts index 6fe1901ed9c8d..26901b0f7a776 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/rounddate.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/rounddate.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { rounddate } from '../../functions/common/rounddate'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { MOMENTJS } from '../../../i18n'; +import { MOMENTJS } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.rounddateHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/shape.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/shape.ts index 0e2081ffd6aaa..09edb9f2a3dea 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/shape.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/shape.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { shape } from '../../functions/common/shape'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { SVG } from '../../../i18n'; +import { SVG } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.shapeHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/sort.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/sort.ts index c377d4900279a..a7aed9d955aaa 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/sort.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/sort.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { sort } from '../../functions/common/sort'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE } from '../../../i18n'; +import { DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.sortHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/switch.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/switch.ts index 45a7fb3f372da..8d5ec9a2b354e 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/switch.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/switch.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { switchFn } from '../../functions/common/switch'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CONTEXT } from '../../../i18n'; +import { CONTEXT } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.switchHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/table.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/table.ts index 7472f2d0aa4a8..3a6ff57a9896d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/table.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/table.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { table } from '../../functions/common/table'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_FALSE } from '../../../i18n'; +import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_FALSE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.tableHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/tail.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/tail.ts index 4e84b13dbf086..b97d90923f59c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/tail.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/tail.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { tail } from '../../functions/common/tail'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { DATATABLE } from '../../../i18n'; +import { DATATABLE } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.tailHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/timefilter.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/timefilter.ts index c202873457022..18c655ac683b3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/timefilter.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/timefilter.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { timefilter } from '../../functions/common/timefilter'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { ISO8601, ELASTICSEARCH, DATEMATH } from '../../../i18n'; +import { ISO8601, ELASTICSEARCH, DATEMATH } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.timefilterHelpText', { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/urlparam.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/urlparam.ts index 918527320868a..f623409d46e73 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/urlparam.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/strings/functions/urlparam.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { urlparam } from '../../functions/browser/urlparam'; import { FunctionHelp } from '.'; import { FunctionFactory } from '../../../types'; -import { TYPE_STRING, URL } from '../../../i18n'; +import { TYPE_STRING, URL } from '../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.urlparamHelpText', { diff --git a/x-pack/legacy/plugins/canvas/i18n/README.md b/x-pack/legacy/plugins/canvas/i18n/README.md deleted file mode 100644 index 45459b4191fae..0000000000000 --- a/x-pack/legacy/plugins/canvas/i18n/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Canvas and Internationalization (i18n) - -Creating i18n strings in Kibana requires use of the [`@kbn/i18n`](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/GUIDELINE.md) library. The following outlines the strategy for localizing strings in Canvas - -## Why i18n Dictionaries - -In Canvas, we prefer to use "dictionaries" of i18n strings over including translation inline. There are a number of reasons for this. - -### API Signature is Lengthy - -A call to localize a string can look something like this: - -```ts -i18n.translate('xpack.canvas.functions.alterColumn.args.columnHelpText', { - defaultMessage: 'The name of the column to alter.', -}), -``` - -But it can also look something like this: - -```ts -i18n.translate('xpack.canvas.functions.alterColumnHelpText', { - defaultMessage: - 'Converts between core types, including {list}, and {end}, and rename columns. ' + - 'See also {mapColumnFn} and {staticColumnFn}.', - values: { - list: Object.values(DATATABLE_COLUMN_TYPES) - .slice(0, -1) - .map(type => `\`${type}\``) - .join(', '), - end: Object.values(DATATABLE_COLUMN_TYPES).slice(-1)[0], - mapColumnFn: '`mapColumn`', - staticColumnFn: '`staticColumn`', - }, -}); -``` - -In either case, including all of this code inline, where the string is ultimately utilized, makes the code look very uneven, or even complicated. By externalizing the construction of localized strings, we can reduce both of these examples: - -```ts -import { FunctionStrings } from './some/i18n/dictionary'; -const { AlterColumn: strings } = FunctionStrings; - -const help = strings.getColumnHelpText(); -const moreHelp = strings.getAlterColumnHelpText(); -``` - -### Reducing Duplication, Auditing - -By externalizing our strings into these functional dictionaries, we also make identifying duplicate strings easier... thus removing workload from translation teams. We can also deprecate functions. And Since they're written in Typescript, finding usage is easier, so we can easily remove them if a string is no longer used. - -It will also make writing more advanced auditing tools easier. - -## Creating i18n Dictionaries - -There are some Best Practices™️ to follow when localizing Canvas strings: - -- Create dictionaries in `/canvas/i18n`. - - Organize first by the top-level subject or directory, (e.g. `functions`, `renderers`, `components`, etc). -- Don't create too many files. Prefer to eventually split up a dictionary rather than start with many small ones. - - Let's avoid ten files with two strings apiece, for example. -- Create functions that produce a `string`, rather than properties of `string`s. - - Prefer `getSomeString({...values}) => i18n.translate(...);`. - - Avoid `someString: i18n.translate(...);`. - - Standardizes the practice, also allows for passing dynamic values to the localized string in the future. - - Exception to this is the dictionary for Canvas `Function`s, which use a more complex dictionary, influenced by `data/interpreter`. - -## Constants - -In some cases, there are proper nouns or other words that should not be translated, (e.g. 'Canvas', 'JavaScript', 'momentJS', etc). We've created `/canvas/i18n/constants.ts` to collect these words. Use and add to these constants as necessary. diff --git a/x-pack/legacy/plugins/canvas/i18n/angular.ts b/x-pack/legacy/plugins/canvas/i18n/angular.ts deleted file mode 100644 index 74e5ceb7d6bb7..0000000000000 --- a/x-pack/legacy/plugins/canvas/i18n/angular.ts +++ /dev/null @@ -1,24 +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 { i18n } from '@kbn/i18n'; -import { CANVAS as canvas } from './constants'; - -export const AngularStrings = { - CanvasRootController: { - getReadOnlyBadgeText: () => - i18n.translate('xpack.canvas.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - getReadOnlyBadgeTooltip: () => - i18n.translate('xpack.canvas.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save {canvas} workpads', - values: { - canvas, - }, - }), - }, -}; diff --git a/x-pack/legacy/plugins/canvas/i18n/components.ts b/x-pack/legacy/plugins/canvas/i18n/components.ts deleted file mode 100644 index a076c878d6cc5..0000000000000 --- a/x-pack/legacy/plugins/canvas/i18n/components.ts +++ /dev/null @@ -1,16 +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 { i18n } from '@kbn/i18n'; - -export const ComponentStrings = { - AddEmbeddableFlyout: { - getNoItemsText: () => - i18n.translate('xpack.canvas.embedObject.noMatchingObjectsMessage', { - defaultMessage: 'No matching objects found.', - }), - }, -}; diff --git a/x-pack/legacy/plugins/canvas/i18n/index.ts b/x-pack/legacy/plugins/canvas/i18n/index.ts deleted file mode 100644 index b67ae13a7c160..0000000000000 --- a/x-pack/legacy/plugins/canvas/i18n/index.ts +++ /dev/null @@ -1,16 +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 { i18n } from '@kbn/i18n'; - -export * from './angular'; -export * from './components'; -export * from './constants'; - -export const getAppDescription = () => - i18n.translate('xpack.canvas.appDescription', { - defaultMessage: 'Showcase your data in a pixel-perfect way.', - }); diff --git a/x-pack/legacy/plugins/canvas/public/angular/controllers/canvas.tsx b/x-pack/legacy/plugins/canvas/public/angular/controllers/canvas.js similarity index 65% rename from x-pack/legacy/plugins/canvas/public/angular/controllers/canvas.tsx rename to x-pack/legacy/plugins/canvas/public/angular/controllers/canvas.js index 4738d35836358..ad51d7b57d1ee 100644 --- a/x-pack/legacy/plugins/canvas/public/angular/controllers/canvas.tsx +++ b/x-pack/legacy/plugins/canvas/public/angular/controllers/canvas.js @@ -7,22 +7,11 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { Provider } from 'react-redux'; -import { Store } from 'redux'; +import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; -import { UICapabilities } from 'ui/capabilities'; - -// @ts-ignore Untyped local import { App } from '../../components/app'; -import { AngularStrings } from '../../../i18n'; - -const { CanvasRootController: strings } = AngularStrings; -export function CanvasRootController( - canvasStore: Store, - $scope: any, // Untyped in Kibana - $element: any, // Untyped in Kibana - uiCapabilities: UICapabilities -) { +export function CanvasRootController(canvasStore, $scope, $element, uiCapabilities) { const domNode = $element[0]; // set the read-only badge when appropriate @@ -30,8 +19,12 @@ export function CanvasRootController( uiCapabilities.canvas.save ? undefined : { - text: strings.getReadOnlyBadgeText(), - tooltip: strings.getReadOnlyBadgeTooltip(), + text: i18n.translate('xpack.canvas.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('xpack.canvas.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save Canvas workpads', + }), iconType: 'glasses', } ); diff --git a/x-pack/legacy/plugins/canvas/public/angular/controllers/index.ts b/x-pack/legacy/plugins/canvas/public/angular/controllers/index.js similarity index 100% rename from x-pack/legacy/plugins/canvas/public/angular/controllers/index.ts rename to x-pack/legacy/plugins/canvas/public/angular/controllers/index.js diff --git a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx index 52a91a3a2fae6..373f509bf7d9a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/embeddable_flyout/flyout.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { SavedObjectFinder, @@ -13,9 +14,6 @@ import { } from 'ui/saved_objects/components/saved_object_finder'; import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui'; import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; -import { ComponentStrings } from '../../../i18n'; - -const { AddEmbeddableFlyout: strings } = ComponentStrings; export interface Props { onClose: () => void; @@ -68,7 +66,9 @@ export class AddEmbeddableFlyout extends React.Component { onChoose={this.onAddPanel} savedObjectMetaData={availableSavedObjects} showFilter={true} - noItemsMessage={strings.getNoItemsText()} + noItemsMessage={i18n.translate('xpack.canvas.embedObject.noMatchingObjectsMessage', { + defaultMessage: 'No matching objects found.', + })} /> diff --git a/x-pack/legacy/plugins/graph/public/types/config.ts b/x-pack/legacy/plugins/canvas/public/components/remove_icon/index.js similarity index 64% rename from x-pack/legacy/plugins/graph/public/types/config.ts rename to x-pack/legacy/plugins/canvas/public/components/remove_icon/index.js index eaff11d9d4548..0c4f97372670d 100644 --- a/x-pack/legacy/plugins/graph/public/types/config.ts +++ b/x-pack/legacy/plugins/canvas/public/components/remove_icon/index.js @@ -4,4 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export type GraphSavePolicy = 'configAndDataWithConsent' | 'configAndData' | 'config' | 'none'; +import { pure } from 'recompose'; +import { RemoveIcon as Component } from './remove_icon'; + +export const RemoveIcon = pure(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/remove_icon/remove_icon.js b/x-pack/legacy/plugins/canvas/public/components/remove_icon/remove_icon.js new file mode 100644 index 0000000000000..726936b8b4e9b --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/remove_icon/remove_icon.js @@ -0,0 +1,21 @@ +/* + * 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 PropTypes from 'prop-types'; +import { EuiIcon } from '@elastic/eui'; + +export const RemoveIcon = ({ onClick, className }) => ( +
+ +
+); + +RemoveIcon.propTypes = { + onClick: PropTypes.func, + style: PropTypes.object, + className: PropTypes.string, +}; diff --git a/x-pack/legacy/plugins/canvas/public/components/remove_icon/remove_icon.scss b/x-pack/legacy/plugins/canvas/public/components/remove_icon/remove_icon.scss new file mode 100644 index 0000000000000..efd525f3e8417 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/remove_icon/remove_icon.scss @@ -0,0 +1,29 @@ +.canvasRemove { + $clearSize: $euiSize; + @include size($clearSize); // sass-lint:disable-line mixins-before-declarations + position: absolute; + pointer-events: all; + background-color: $euiColorLightShade; + border-radius: $clearSize; + line-height: $clearSize; + top: -$euiSizeL; + right: -$euiSizeL; + + .canvasRemove__icon { + @include size($euiSizeS); + fill: $euiColorEmptyShade; + stroke: $euiColorEmptyShade; + stroke-width: 3px; // increase thickness of icon at such a small size + // better vertical position fix that works with IE + position: relative; + top: -1px; + left: $euiSizeXS; + + } + + &:hover { + background-color: $euiColorDanger; + cursor: pointer; + } + +} diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/integration_utils.js b/x-pack/legacy/plugins/canvas/public/components/workpad_page/integration_utils.js index 51a897d3c6d9a..5866faa08bdfe 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/integration_utils.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_page/integration_utils.js @@ -8,12 +8,61 @@ import { shallowEqual } from 'recompose'; import { getNodes, getSelectedPage } from '../../state/selectors/workpad'; import { addElement, removeElements, setMultiplePositions } from '../../state/actions/elements'; import { selectToplevelNodes } from '../../state/actions/transient'; -import { matrixToAngle } from '../../lib/aeroelastic/matrix'; +import { matrixToAngle, multiply, rotateZ, translate } from '../../lib/aeroelastic/matrix'; import { arrayToMap, flatten, identity } from '../../lib/aeroelastic/functional'; import { getLocalTransformMatrix } from '../../lib/aeroelastic/layout_functions'; -import { isGroupId, elementToShape } from './positioning_utils'; -export * from './positioning_utils'; +export const isGroupId = id => id.startsWith('group'); + +const headerData = id => + isGroupId(id) + ? { id, type: 'group', subtype: 'persistentGroup' } + : { id, type: 'rectangleElement', subtype: '' }; + +const transformData = ({ top, left, width, height, angle }, z) => + multiply( + translate(left + width / 2, top + height / 2, z), // painter's algo: latest item (highest z) goes to top + rotateZ((-angle / 180) * Math.PI) // minus angle as transform:matrix3d uses a left-handed coordinate system + ); + +const simplePosition = ({ id, position, filter }, z) => ({ + ...headerData(id), + width: position.width, + height: position.height, + transformMatrix: transformData(position, z), + filter, +}); + +export const simplePositioning = ({ elements }) => ({ elements: elements.map(simplePosition) }); + +/** + * elementToShape + * + * converts a `kibana-canvas` element to an `aeroelastic` shape. + * + * Shape: the layout algorithms need to deal with objects through their geometric properties, excluding other aspects, + * such as what's inside the element, eg. image or scatter plot. This representation is, at its core, a transform matrix + * that establishes a new local coordinate system https://drafts.csswg.org/css-transforms/#local-coordinate-system plus a + * size descriptor. There are two versions of the transform matrix: + * - `transformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#current-transformation-matrix + * - `localTransformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#transformation-matrix + * + * Element: it also needs to represent the geometry, primarily because of the need to persist it in `redux` and on the + * server, and to accept such data from the server. The redux and server representations will need to change as more general + * projections such as 3D are added. The element also needs to maintain its content, such as an image or a plot. + * + * While all elements on the current page also exist as shapes, there are shapes that are not elements: annotations. + * For example, `rotation_handle`, `border_resize_handle` and `border_connection` are modeled as shapes by the layout + * library, simply for generality. + */ + +export const elementToShape = ({ id, position }, z) => ({ + ...headerData(id), + parent: (position && position.parent) || null, + transformMatrix: transformData(position, z), + a: position.width / 2, // we currently specify half-width, half-height as it leads to + b: position.height / 2, // more regular math (like ellipsis radii rather than diameters) +}); const shapeToElement = shape => ({ left: shape.transformMatrix[12] - shape.a, diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/positioning_utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_page/positioning_utils.ts deleted file mode 100644 index f4d399065df83..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/positioning_utils.ts +++ /dev/null @@ -1,68 +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 { PositionedElement, ElementPosition } from '../../../types'; -import { multiply, rotateZ, translate } from '../../lib/aeroelastic/matrix'; - -export const isGroupId = (id: string) => id.startsWith('group'); - -const headerData = (id: string) => - isGroupId(id) - ? { id, type: 'group', subtype: 'persistentGroup' } - : { id, type: 'rectangleElement', subtype: '' }; - -const transformData = ({ top, left, width, height, angle }: ElementPosition, z: number) => - multiply( - translate(left + width / 2, top + height / 2, z), // painter's algo: latest item (highest z) goes to top - rotateZ((-angle / 180) * Math.PI) // minus angle as transform:matrix3d uses a left-handed coordinate system - ); - -/** - * elementToShape - * - * converts a `kibana-canvas` element to an `aeroelastic` shape. - * - * Shape: the layout algorithms need to deal with objects through their geometric properties, excluding other aspects, - * such as what's inside the element, eg. image or scatter plot. This representation is, at its core, a transform matrix - * that establishes a new local coordinate system https://drafts.csswg.org/css-transforms/#local-coordinate-system plus a - * size descriptor. There are two versions of the transform matrix: - * - `transformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#current-transformation-matrix - * - `localTransformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#transformation-matrix - * - * Element: it also needs to represent the geometry, primarily because of the need to persist it in `redux` and on the - * server, and to accept such data from the server. The redux and server representations will need to change as more general - * projections such as 3D are added. The element also needs to maintain its content, such as an image or a plot. - * - * While all elements on the current page also exist as shapes, there are shapes that are not elements: annotations. - * For example, `rotation_handle`, `border_resize_handle` and `border_connection` are modeled as shapes by the layout - * library, simply for generality. - */ - -export const elementToShape = ( - { id, position }: { id: string; position: ElementPosition }, - z: number -) => ({ - ...headerData(id), - parent: (position && position.parent) || null, - transformMatrix: transformData(position, z), - a: position.width / 2, // we currently specify half-width, half-height as it leads to - b: position.height / 2, // more regular math (like ellipsis radii rather than diameters) -}); - -const simplePosition = ( - { id, position, filter }: { id: string; position: ElementPosition; filter: string }, - z: number -) => ({ - ...headerData(id), - width: position.width, - height: position.height, - transformMatrix: transformData(position, z), - filter, -}); - -export const simplePositioning = ({ elements }: { elements: PositionedElement[] }) => ({ - elements: elements.map(simplePosition), -}); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/index.js index 6bd7af7cc89ef..f539436e97e89 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/index.js @@ -5,7 +5,7 @@ */ import { withProps } from 'recompose'; -import { simplePositioning } from '../positioning_utils'; +import { simplePositioning } from '../integration_utils'; import { StaticWorkpadPage } from './static_workpad_page'; export const StaticPage = () => withProps(simplePositioning)(StaticWorkpadPage); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js index 9e8962755e00b..94e16fd97bb8b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js @@ -7,7 +7,7 @@ import React, { PureComponent } from 'react'; import { ElementWrapper } from '../../element_wrapper'; import { staticWorkpadPagePropTypes } from '../prop_types'; -import { isGroupId } from '../positioning_utils'; +import { isGroupId } from '../integration_utils'; export class StaticWorkpadPage extends PureComponent { static propTypes = staticWorkpadPagePropTypes; diff --git a/x-pack/legacy/plugins/canvas/public/register_feature.js b/x-pack/legacy/plugins/canvas/public/register_feature.js index 8d78498de34b2..2c9837ffeb285 100644 --- a/x-pack/legacy/plugins/canvas/public/register_feature.js +++ b/x-pack/legacy/plugins/canvas/public/register_feature.js @@ -4,18 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory, } from 'ui/registry/feature_catalogue'; -import { getAppDescription } from '../i18n'; - FeatureCatalogueRegistryProvider.register(() => { return { id: 'canvas', title: 'Canvas', - description: getAppDescription(), + description: i18n.translate('xpack.canvas.appDescription', { + defaultMessage: 'Showcase your data in a pixel-perfect way.', + }), icon: 'canvasApp', path: '/app/canvas', showOnHomePage: true, diff --git a/x-pack/legacy/plugins/canvas/public/state/reducers/pages.js b/x-pack/legacy/plugins/canvas/public/state/reducers/pages.js index 50a28475ef5bc..224d0a3c03795 100644 --- a/x-pack/legacy/plugins/canvas/public/state/reducers/pages.js +++ b/x-pack/legacy/plugins/canvas/public/state/reducers/pages.js @@ -12,7 +12,7 @@ import { routerProvider } from '../../lib/router_provider'; import { getDefaultPage } from '../defaults'; import * as actions from '../actions/pages'; import { getSelectedPageIndex } from '../selectors/workpad'; -import { isGroupId } from '../../components/workpad_page/positioning_utils'; +import { isGroupId } from '../../components/workpad_page/integration_utils'; const { set, del, insert } = immutable; diff --git a/x-pack/legacy/plugins/canvas/public/style/index.scss b/x-pack/legacy/plugins/canvas/public/style/index.scss index 7c3f350aa1f11..0f59142b10a55 100644 --- a/x-pack/legacy/plugins/canvas/public/style/index.scss +++ b/x-pack/legacy/plugins/canvas/public/style/index.scss @@ -43,6 +43,7 @@ @import '../components/palette_picker/palette_picker'; @import '../components/palette_swatch/palette_swatch'; @import '../components/positionable/positionable'; +@import '../components/remove_icon/remove_icon'; @import '../components/rotation_handle/rotation_handle'; @import '../components/shape_preview/shape_preview'; @import '../components/shape_picker/shape_picker'; diff --git a/x-pack/legacy/plugins/canvas/scripts/jest.js b/x-pack/legacy/plugins/canvas/scripts/jest.js index 12a2b9921455b..0aea9fdda9c77 100644 --- a/x-pack/legacy/plugins/canvas/scripts/jest.js +++ b/x-pack/legacy/plugins/canvas/scripts/jest.js @@ -12,43 +12,19 @@ const { runXPackScript } = require('./_helpers'); // we're making this script allow run( ({ log, flags }) => { - const { all, storybook, update, coverage } = flags; + const { all, storybook, update } = flags; let { path } = flags; let options = []; - process.argv.splice(2, process.argv.length - 2); - - if (path) { - log.info(`Limiting tests to ${path}...`); - path = 'legacy/plugins/canvas/' + path; - } else { - path = 'legacy/plugins/canvas'; - } - if (coverage) { - log.info(`Collecting test coverage and writing to canvas/coverage...`); - options = [ - '--coverage', - '--collectCoverageFrom', // Ignore TS definition files - `!${path}/**/*.d.ts`, - '--collectCoverageFrom', // Ignore build directories - `!${path}/**/build/**`, - '--collectCoverageFrom', // Ignore coverage on test files - `!${path}/**/__tests__/**/*`, - '--collectCoverageFrom', // Include JS files - `${path}/**/*.js`, - '--collectCoverageFrom', // Include TS/X files - `${path}/**/*.ts*`, - '--coverageDirectory', // Output to canvas/coverage - 'legacy/plugins/canvas/coverage', - ]; - } else { + if (!path) { // Mitigation for https://github.com/facebook/jest/issues/7267 if (all || storybook || update) { - options = options.concat(['--no-cache', '--no-watchman']); + options = ['--no-cache', '--no-watchman']; } if (all) { log.info('Running all available tests. This will take a while...'); + path = 'legacy/plugins/canvas'; } else if (storybook || update) { path = 'legacy/plugins/canvas/.storybook'; @@ -60,9 +36,13 @@ run( } } else { log.info('Running tests. This does not include Storybook Snapshots...'); + path = 'legacy/plugins/canvas'; } + } else { + log.info(`Running tests found at ${path}...`); } + process.argv.splice(2, process.argv.length - 2); runXPackScript('jest', [path].concat(options)); }, { @@ -70,14 +50,13 @@ run( Jest test runner for Canvas. By default, will not include Storybook Snapshots. `, flags: { - boolean: ['all', 'storybook', 'update', 'coverage'], + boolean: ['all', 'storybook', 'update'], string: ['path'], help: ` --all Runs all tests and snapshots. Slower. --storybook Runs Storybook Snapshot tests only. --update Updates Storybook Snapshot tests. --path Runs any tests at a given path. - --coverage Collect coverage statistics. `, }, } diff --git a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts index 8de813255a230..9e49d07969dde 100644 --- a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts +++ b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts @@ -5,7 +5,7 @@ */ import { buildQueryFilter, Filter as ESFilterType } from '@kbn/es-query'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { Filter } from '../../types'; // @ts-ignore Untyped Local import { buildBoolArray } from './build_bool_array'; diff --git a/x-pack/legacy/plugins/canvas/server/lib/normalize_type.js b/x-pack/legacy/plugins/canvas/server/lib/normalize_type.js index 07f1cac0f7819..b1dd880334308 100644 --- a/x-pack/legacy/plugins/canvas/server/lib/normalize_type.js +++ b/x-pack/legacy/plugins/canvas/server/lib/normalize_type.js @@ -6,7 +6,7 @@ export function normalizeType(type) { const normalTypes = { - string: ['string', 'text', 'keyword', '_type', '_id', '_index', 'geo_point'], + string: ['string', 'text', 'keyword', '_type', '_id', '_index'], number: [ 'float', 'half_float', diff --git a/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.ts b/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.js similarity index 86% rename from x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.ts rename to x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.js index 22dfc65bf924f..e189db6146ba8 100644 --- a/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.ts +++ b/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.js @@ -4,18 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CANVAS as label } from '../../i18n'; -// @ts-ignore Untyped local +import { i18n } from '@kbn/i18n'; import { ecommerceSavedObjects, flightsSavedObjects, webLogsSavedObjects } from './index'; -// @ts-ignore: Untyped in Kibana export function loadSampleData(server) { const now = new Date(); const nowTimestamp = now.toISOString(); - - // @ts-ignore: Untyped local function updateCanvasWorkpadTimestamps(savedObjects) { - // @ts-ignore: Untyped local return savedObjects.map(savedObject => { if (savedObject.type === 'canvas-workpad') { savedObject.attributes['@timestamp'] = nowTimestamp; @@ -26,14 +21,17 @@ export function loadSampleData(server) { }); } + const sampleDataLinkLabel = i18n.translate('xpack.canvas.sampleDataLinkLabel', { + defaultMessage: 'Canvas', + }); server.addSavedObjectsToSampleDataset( 'ecommerce', updateCanvasWorkpadTimestamps(ecommerceSavedObjects) ); server.addAppLinksToSampleDataset('ecommerce', { path: '/app/canvas#/workpad/workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e', + label: sampleDataLinkLabel, icon: 'canvasApp', - label, }); server.addSavedObjectsToSampleDataset( @@ -42,14 +40,14 @@ export function loadSampleData(server) { ); server.addAppLinksToSampleDataset('flights', { path: '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0', + label: sampleDataLinkLabel, icon: 'canvasApp', - label, }); server.addSavedObjectsToSampleDataset('logs', updateCanvasWorkpadTimestamps(webLogsSavedObjects)); server.addAppLinksToSampleDataset('logs', { path: '/app/canvas#/workpad/workpad-5563cc40-5760-4afe-bf33-9da72fac53b7', + label: sampleDataLinkLabel, icon: 'canvasApp', - label, }); } diff --git a/x-pack/legacy/plugins/canvas/types/canvas.ts b/x-pack/legacy/plugins/canvas/types/canvas.ts index 0843e5e99dc4e..97f8917d50725 100644 --- a/x-pack/legacy/plugins/canvas/types/canvas.ts +++ b/x-pack/legacy/plugins/canvas/types/canvas.ts @@ -6,13 +6,6 @@ import { ElementPosition } from './elements'; -export interface CanvasAsset { - '@created': string; - id: string; - type: 'dataurl'; - value: string; -} - export interface CanvasElement { id: string; position: ElementPosition; @@ -32,16 +25,13 @@ export interface CanvasPage { } export interface CanvasWorkpad { - '@created': string; - '@timestamp': string; - assets: { [id: string]: CanvasAsset }; - colors: string[]; - css: string; - height: number; - id: string; - isWriteable: boolean; name: string; + id: string; + width: number; + height: number; + css: string; page: number; pages: CanvasPage[]; - width: number; + colors: string[]; + isWriteable: boolean; } diff --git a/x-pack/legacy/plugins/canvas/types/elements.ts b/x-pack/legacy/plugins/canvas/types/elements.ts index ce9db8202072f..48047c327a6e9 100644 --- a/x-pack/legacy/plugins/canvas/types/elements.ts +++ b/x-pack/legacy/plugins/canvas/types/elements.ts @@ -95,8 +95,4 @@ export interface PositionedElement { * AST of the Canvas expression for the element */ ast: ExpressionAST; - /** - * Applied filter - */ - filter: string; } diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_adapter.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_adapter.ts index 6d70c8386c31d..d1b0e53ec5a12 100644 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_adapter.ts +++ b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_adapter.ts @@ -40,8 +40,8 @@ import { ClusterNodeEndpoint } from './cluster_node_endpoint'; * - serve request locally if the requested resource is on the local node, otherwise reject it */ export class ClusterNodeAdapter implements ServiceHandlerAdapter { - readonly clusterService: ClusterService; - readonly clusterMembershipService: ClusterMembershipService; + private readonly clusterService: ClusterService; + private readonly clusterMembershipService: ClusterMembershipService; private readonly schedulerService: ResourceSchedulerService; private readonly handlers: Map = new Map(); // used to forward requests diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_resource_locator.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_resource_locator.ts index 27f5c57214112..75dc7898e7207 100644 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_resource_locator.ts +++ b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_resource_locator.ts @@ -18,7 +18,6 @@ export class ClusterResourceLocator implements ResourceLocator { constructor( private readonly clusterService: ClusterService, private readonly clusterMembershipService: ClusterMembershipService, - // @ts-ignore private readonly schedulerService: ResourceSchedulerService ) {} @@ -55,12 +54,12 @@ export class ClusterResourceLocator implements ResourceLocator { ); } - /** - * Return undefined to let NodeRepositoriesService enqueue the clone job in cluster mode. - */ async allocate(req: Request, resource: string): Promise { // make the cluster service synchronize the meta data and allocate new resources to nodes await this.clusterService.pollClusterState(); - return undefined; + // allocate the repository to nodes + await this.schedulerService.allocateUnassigned(); + // the resource should be assigned to a node for now, if possible + return this.locate(req, resource); } } diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.ts index a5ae0a27e128b..578e503021472 100644 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.ts +++ b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import util from 'util'; import { ClusterMetadata } from './cluster_meta'; import { EsClient } from '../../lib/esqueue'; import { RepositoryObjectClient } from '../../search'; @@ -80,11 +79,7 @@ export class ClusterService { private async callClusterStateListeners(event: ClusterStateEvent) { for (const applier of this.clusterStateListeners) { - try { - await applier.onClusterStateChanged(event); - } catch (e) { - this.logger.error(`Failed to apply cluster state ${util.inspect(event)}`); - } + await applier.onClusterStateChanged(event); } } diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.test.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.test.ts deleted file mode 100644 index 17893e0f6b1bc..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.test.ts +++ /dev/null @@ -1,122 +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 sinon from 'sinon'; -import { Logger } from '../../log'; -import { ConsoleLoggerFactory } from '../../utils/console_logger_factory'; -import { NodeRepositoriesService } from './node_repositories_service'; -import { ClusterService } from './cluster_service'; -import { ClusterMembershipService } from './cluster_membership_service'; -import { CodeNode, CodeNodes } from './code_nodes'; -import { emptyAsyncFunc } from '../../test_utils'; -import { CloneWorker } from '../../queue'; -import { ClusterStateEvent } from './cluster_state_event'; -import { ClusterState } from './cluster_state'; -import { ClusterMetadata } from './cluster_meta'; -import { Repository } from '../../../model'; -import { ResourceAssignment, RoutingTable } from './routing_table'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -afterEach(() => { - sinon.restore(); -}); - -const cloneWorker = ({ - enqueueJob: emptyAsyncFunc, -} as any) as CloneWorker; - -const clusterService = {} as ClusterService; - -const testNodes = [ - { id: 'node1', address: 'http://node1' } as CodeNode, - { id: 'node2', address: 'http://node2' } as CodeNode, -]; - -const testRepos = [ - { uri: 'test1', url: 'http://test1' } as Repository, - { uri: 'test2', url: 'http://test2' } as Repository, -]; - -test('Enqueue clone job after new repository is added to the local node', async () => { - const enqueueJobSpy = sinon.spy(cloneWorker, 'enqueueJob'); - - const clusterMembershipService = { - localNode: testNodes[0], - } as ClusterMembershipService; - - const nodeService = new NodeRepositoriesService( - log, - clusterService, - clusterMembershipService, - cloneWorker - ); - - // event with no new repositories - let event = new ClusterStateEvent(ClusterState.empty(), ClusterState.empty()); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.called).toBeFalsy(); - expect(nodeService.localRepos.size).toBe(0); - - // event with a new repository - event = new ClusterStateEvent( - new ClusterState( - new ClusterMetadata([testRepos[0]]), - new RoutingTable([ - { nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, - ]), - new CodeNodes([testNodes[0]]) - ), - event.current - ); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.calledOnce).toBeTruthy(); - expect(nodeService.localRepos.size).toBe(1); - - // event with removed repository - event = new ClusterStateEvent(ClusterState.empty(), event.current); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.calledOnce).toBeTruthy(); - expect(nodeService.localRepos.size).toBe(0); - - // event with two added repositories - event = new ClusterStateEvent( - new ClusterState( - new ClusterMetadata([testRepos[0], testRepos[1]]), - new RoutingTable([ - { nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, - { nodeId: testNodes[0].id, resource: testRepos[1].uri } as ResourceAssignment, - ]), - new CodeNodes([testNodes[0]]) - ), - event.current - ); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.callCount).toBe(3); - expect(nodeService.localRepos.size).toBe(2); - - // event with removed repository - event = new ClusterStateEvent(ClusterState.empty(), event.current); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.callCount).toBe(3); - expect(nodeService.localRepos.size).toBe(0); - - // event with two added repositories, one for the other node - event = new ClusterStateEvent( - new ClusterState( - new ClusterMetadata([testRepos[0], testRepos[1]]), - new RoutingTable([ - { nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, - { nodeId: testNodes[1].id, resource: testRepos[1].uri } as ResourceAssignment, - ]), - new CodeNodes([testNodes[0]]) - ), - event.current - ); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.callCount).toBe(4); - expect(nodeService.localRepos.size).toBe(1); -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.ts deleted file mode 100644 index 42d68b6f692f9..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.ts +++ /dev/null @@ -1,71 +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 util from 'util'; -import { ClusterService, ClusterStateListener } from './cluster_service'; -import { ClusterStateEvent } from './cluster_state_event'; -import { ClusterMembershipService } from './cluster_membership_service'; -import { CloneWorker } from '../../queue'; -import { Repository, RepositoryUri } from '../../../model'; -import { Logger } from '../../log'; -import { RepoState } from '../../../public/actions'; - -export class NodeRepositoriesService implements ClusterStateListener { - // visible for test - readonly localRepos = new Map(); - private readonly localNodeId = this.clusterMembershipService.localNode.id; - - constructor( - private readonly log: Logger, - private readonly clusterService: ClusterService, - private readonly clusterMembershipService: ClusterMembershipService, - private readonly cloneWorker: CloneWorker - ) {} - - public async start() { - /** - * we can add locally exists repositories to localRepos when the service is started to avoid unnecessarily add clone - * tasks for them, but for now it's OK because clone job is idempotent. - */ - this.clusterService.addClusterStateListener(this); - } - - public async stop() {} - - async onClusterStateChanged(event: ClusterStateEvent): Promise { - // compare repositories in the cluster state with repositories in the local node, and remove - const repos = event.current.getNodeRepositories(this.clusterMembershipService.localNode.id); - const localNewRepos = repos.filter(repo => !this.localRepos.has(repo.uri)); - const localRemovedRepos = Array.from(this.localRepos.values()).filter( - repo => - event.current.routingTable.getNodeIdByRepositoryURI(repo.metadata.uri) !== this.localNodeId - ); - for (const localNewRepo of localNewRepos) { - this.log.info( - `Repository added to node [${this.localNodeId}]: ${util.inspect(localNewRepo)}` - ); - await this.cloneWorker.enqueueJob({ url: localNewRepo.url }, {}); - this.localRepos.set(localNewRepo.uri, { - metadata: localNewRepo, - currentState: RepoState.CLONING, - }); - } - // TODO remove the stale local repo after the Kibana HA is ready - for (const localRemovedRepo of localRemovedRepos) { - this.log.info( - `Repository removed from node [${this.localNodeId}]: ${util.inspect( - localRemovedRepo.metadata - )}` - ); - this.localRepos.delete(localRemovedRepo.metadata.uri); - } - } -} - -interface LocalRepository { - metadata: Repository; - currentState: RepoState; -} diff --git a/x-pack/legacy/plugins/code/server/init_workers.ts b/x-pack/legacy/plugins/code/server/init_workers.ts index c4385cd711c5c..b22fce8157e37 100644 --- a/x-pack/legacy/plugins/code/server/init_workers.ts +++ b/x-pack/legacy/plugins/code/server/init_workers.ts @@ -92,16 +92,13 @@ export function initWorkers( ); // Initialize schedulers. + const cloneScheduler = new CloneScheduler(cloneWorker, serverOptions, esClient, log); const updateScheduler = new UpdateScheduler(updateWorker, serverOptions, esClient, log); const indexScheduler = new IndexScheduler(indexWorker, serverOptions, esClient, log); updateScheduler.start(); indexScheduler.start(); // Check if the repository is local on the file system. // This should be executed once at the startup time of Kibana. - // Ignored in cluster mode, leave it to the node level control loop - if (!serverOptions.clusterEnabled) { - const cloneScheduler = new CloneScheduler(cloneWorker, serverOptions, esClient, log); - cloneScheduler.schedule(); - } - return { indexScheduler, updateScheduler, cloneWorker, deleteWorker, indexWorker, updateWorker }; + cloneScheduler.schedule(); + return { indexScheduler, updateScheduler }; } diff --git a/x-pack/legacy/plugins/code/server/lsp/go_launcher.ts b/x-pack/legacy/plugins/code/server/lsp/go_launcher.ts index c8b707f9b5fb9..016afdc18eb49 100644 --- a/x-pack/legacy/plugins/code/server/lsp/go_launcher.ts +++ b/x-pack/legacy/plugins/code/server/lsp/go_launcher.ts @@ -77,12 +77,9 @@ export class GoServerLauncher extends AbstractLauncher { } async spawnProcess(installationPath: string, port: number, log: Logger) { - const launchersFound = glob.sync( - process.platform === 'win32' ? 'go-langserver.exe' : 'go-langserver', - { - cwd: installationPath, - } - ); + const launchersFound = glob.sync('go-langserver', { + cwd: installationPath, + }); if (!launchersFound.length) { throw new Error('Cannot find executable go language server'); } @@ -95,13 +92,12 @@ export class GoServerLauncher extends AbstractLauncher { // Construct $GOROOT from the bundled go toolchain. const goRoot = goToolchain; const goHome = path.resolve(goToolchain, 'bin'); - envPath = process.platform === 'win32' ? envPath + ';' + goHome : envPath + ':' + goHome; + envPath = envPath + ':' + goHome; // Construct $GOPATH under 'kibana/data/code'. const goPath = this.options.goPath; if (!fs.existsSync(goPath)) { fs.mkdirSync(goPath); } - const goCache = path.resolve(goPath, '.cache'); const params: string[] = ['-port=' + port.toString()]; const golsp = path.resolve(installationPath, launchersFound[0]); const p = spawn(golsp, params, { @@ -113,7 +109,6 @@ export class GoServerLauncher extends AbstractLauncher { CLIENT_PORT: port.toString(), GOROOT: goRoot, GOPATH: goPath, - GOCACHE: goCache, PATH: envPath, CGO_ENABLED: '0', }, diff --git a/x-pack/legacy/plugins/code/server/plugin.ts b/x-pack/legacy/plugins/code/server/plugin.ts index 87680837cb864..89b46c23b4320 100644 --- a/x-pack/legacy/plugins/code/server/plugin.ts +++ b/x-pack/legacy/plugins/code/server/plugin.ts @@ -54,7 +54,6 @@ import { initLocalService } from './init_local'; import { initQueue } from './init_queue'; import { initWorkers } from './init_workers'; import { ClusterNodeAdapter } from './distributed/cluster/cluster_node_adapter'; -import { NodeRepositoriesService } from './distributed/cluster/node_repositories_service'; export class CodePlugin { private isCodeNode = false; @@ -67,7 +66,6 @@ export class CodePlugin { private updateScheduler: UpdateScheduler | null = null; private lspService: LspService | null = null; private codeServices: CodeServices | null = null; - private nodeService: NodeRepositoriesService | null = null; constructor(initializerContext: PluginInitializerContext) { this.log = {} as Logger; @@ -155,15 +153,10 @@ export class CodePlugin { server, this.log ); - const clusterNodeAdapter = new ClusterNodeAdapter( - codeServerRouter, - this.log, - this.serverOptions, - esClient + const codeServices = new CodeServices( + new ClusterNodeAdapter(codeServerRouter, this.log, this.serverOptions, esClient) ); - const codeServices = new CodeServices(clusterNodeAdapter); - this.queue = initQueue(server, this.log, esClient); const { gitOps, lspService } = initLocalService( @@ -176,7 +169,7 @@ export class CodePlugin { ); this.lspService = lspService; this.gitOps = gitOps; - const { indexScheduler, updateScheduler, cloneWorker } = initWorkers( + const { indexScheduler, updateScheduler } = initWorkers( server, this.log, esClient, @@ -189,14 +182,6 @@ export class CodePlugin { this.indexScheduler = indexScheduler; this.updateScheduler = updateScheduler; - this.nodeService = new NodeRepositoriesService( - this.log, - clusterNodeAdapter.clusterService, - clusterNodeAdapter.clusterMembershipService, - cloneWorker - ); - await this.nodeService.start(); - // Execute index version checking and try to migrate index data if necessary. await tryMigrateIndices(esClient, this.log); @@ -255,9 +240,6 @@ export class CodePlugin { if (this.codeServices) { await this.codeServices.stop(); } - if (this.nodeService) { - await this.nodeService.stop(); - } } private async initNonCodeNode(url: string, core: CoreSetup) { diff --git a/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json b/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json new file mode 100644 index 0000000000000..f2fe456a0c6e2 --- /dev/null +++ b/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json @@ -0,0 +1,23 @@ +{ + "sql.query": { + "data_autocomplete_rules": { + "query": { + "__template": { + "__raw": true, + "value": "\"\"\"\nSELECT * FROM \"TABLE\"\n\"\"\"" + } + } + }, + "url_params": { + "format": [ + "csv", + "json", + "tsv", + "txt", + "yaml", + "cbor", + "smile" + ] + } + } +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js index f0ae17c6bb8d4..47b12ae5552e4 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js @@ -7,6 +7,7 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { Route, Switch, Redirect } from 'react-router-dom'; +import chrome from 'ui/chrome'; import { fatalError } from 'ui/notify'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -22,8 +23,9 @@ import { } from '@elastic/eui'; import { BASE_PATH } from '../../common/constants'; -import { SectionError } from './components'; +import { SectionUnauthorized, SectionError } from './components'; import routing from './services/routing'; +import { isAvailable, isActive, getReason } from './services/license'; import { loadPermissions } from './services/api'; import { @@ -111,6 +113,31 @@ export class App extends Component { missingClusterPrivileges, } = this.state; + if (!isAvailable() || !isActive()) { + return ( + + )} + > + {getReason()} + {' '} + + + + + ); + } + if (isFetchingPermissions) { return ( diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/license.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/license.js new file mode 100644 index 0000000000000..c61a363472149 --- /dev/null +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/license.js @@ -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. + */ + +export let isAvailable; +export let isActive; +export let getReason; + +export function setLicense(isAvailableCallback, isActiveCallback, getReasonCallback) { + isAvailable = isAvailableCallback; + isActive = isActiveCallback; + getReason = getReasonCallback; +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/register_routes.js b/x-pack/legacy/plugins/cross_cluster_replication/public/register_routes.js index 15017f7922a89..54c26308c8e4b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/register_routes.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/register_routes.js @@ -15,13 +15,9 @@ import template from './main.html'; import { BASE_PATH } from '../common/constants'; import { renderReact } from './app'; import { setHttpClient } from './app/services/api'; +import { setLicense } from './app/services/license'; -const isAvailable = xpackInfo.get('features.crossClusterReplication.isAvailable'); -const isActive = xpackInfo.get('features.crossClusterReplication.isActive'); -const isLicenseOK = isAvailable && isActive; -const isCcrUiEnabled = chrome.getInjected('ccrUiEnabled'); - -if (isLicenseOK && isCcrUiEnabled) { +if (chrome.getInjected('ccrUiEnabled')) { const esSection = management.getSection('elasticsearch'); esSection.register('ccr', { @@ -39,9 +35,21 @@ if (isLicenseOK && isCcrUiEnabled) { routes.when(`${BASE_PATH}/:section?/:subsection?/:view?/:id?`, { template, + resolve: { + license() { + return { + isAvailable: () => xpackInfo.get('features.crossClusterReplication.isAvailable'), + isActive: () => xpackInfo.get('features.crossClusterReplication.isActive'), + getReason: () => xpackInfo.get('features.crossClusterReplication.message'), + }; + } + }, controllerAs: 'ccr', controller: class CrossClusterReplicationController { constructor($scope, $route, $http, $q) { + const { license: { isAvailable, isActive, getReason } } = $route.current.locals; + setLicense(isAvailable, isActive, getReason); + // React-router's does not play well with the angular router. It will cause this controller // to re-execute without the $destroy handler being called. This means that the app will be mounted twice // creating a memory leak when leaving (only 1 app will be unmounted). diff --git a/x-pack/legacy/plugins/graph/public/angular/directives/graph_save.js b/x-pack/legacy/plugins/graph/public/angular/directives/graph_save.js new file mode 100644 index 0000000000000..e8a2889feda24 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/angular/directives/graph_save.js @@ -0,0 +1,17 @@ +/* + * 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 { uiModules } from 'ui/modules'; +import template from '../templates/save_workspace.html'; +const app = uiModules.get('app/graph'); + +app.directive('graphSave', function () { + return { + replace: true, + restrict: 'E', + template, + }; +}); 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 5aaa944d45b27..4dfc1846bb259 100644 --- a/x-pack/legacy/plugins/graph/public/angular/templates/index.html +++ b/x-pack/legacy/plugins/graph/public/angular/templates/index.html @@ -3,7 +3,8 @@ -
+
+
diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/save_workspace.html b/x-pack/legacy/plugins/graph/public/angular/templates/save_workspace.html new file mode 100644 index 0000000000000..76c492397b42c --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/angular/templates/save_workspace.html @@ -0,0 +1,62 @@ +
+
+
+ + +
+ +
+ + +
+
+ + {{ ::'xpack.graph.topNavMenu.save.saveConfigurationOnlyWarning' | i18n: { + defaultMessage: 'The data in this workspace will be cleared and only the configuration will be saved', + } }} +
+
+ +
+ + + +
+ + + {{ ::'xpack.graph.topNavMenu.save.saveConfigurationOnlyText' | i18n: { + defaultMessage: 'The data in this workspace will be cleared and only the configuration will be saved', + } }} + +
+ +
diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js index 4dc7faaff4771..910bdd13c03c4 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/legacy/plugins/graph/public/app.js @@ -56,11 +56,12 @@ import { import { getOutlinkEncoders, } from './angular/services/outlink_encoders'; -import { getEditUrl, getNewPath, getEditPath, setBreadcrumbs, getHomePath } from './services/url'; -import { save } from './services/save'; +import { getEditUrl, getNewPath, getEditPath, setBreadcrumbs } from './services/url'; +import saveTemplate from './angular/templates/save_workspace.html'; import settingsTemplate from './angular/templates/settings.html'; +import './angular/directives/graph_save'; import './angular/directives/graph_settings'; const app = uiModules.get('app/graph'); @@ -206,21 +207,6 @@ app.controller('graphuiPlugin', function ( }); } - function updateBreadcrumbs() { - setBreadcrumbs({ - chrome, - savedWorkspace: $route.current.locals.savedWorkspace, - navigateTo: () => { - // 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()); - }); - } - }); - } - $scope.title = 'Graph'; $scope.spymode = 'request'; @@ -669,6 +655,7 @@ app.controller('graphuiPlugin', function ( $scope.resetWorkspace = function () { $scope.clearWorkspace(); + $scope.userHasConfirmedSaveWorkspaceData = false; $scope.selectedIndex = null; $scope.proposedIndex = null; $scope.detail = null; @@ -875,7 +862,7 @@ app.controller('graphuiPlugin', function ( defaultMessage: 'Save', }), description: i18n.translate('xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel', { - defaultMessage: 'Save workspace', + defaultMessage: 'Save Workspace', }), tooltip: () => { if ($scope.allSavingDisabled) { @@ -892,11 +879,10 @@ app.controller('graphuiPlugin', function ( return $scope.allSavingDisabled || $scope.selectedFields.length === 0; }, run: () => { - save({ - savePolicy: $scope.graphSavePolicy, - hasData: $scope.workspace && ($scope.workspace.nodes.length > 0 || $scope.workspace.blacklistedNodes.length > 0), - workspace: $scope.savedWorkspace, - saveWorkspace: $scope.saveWorkspace + $scope.$evalAsync(() => { + const curState = $scope.menus.showSave; + $scope.closeMenus(); + $scope.menus.showSave = !curState; }); }, testId: 'graphSaveButton', @@ -920,9 +906,21 @@ app.controller('graphuiPlugin', function ( }, }); - updateBreadcrumbs(); + setBreadcrumbs({ + chrome, + savedWorkspace: $route.current.locals.savedWorkspace, + navigateTo: () => { + // 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('/home/'); + }); + } + }); $scope.menus = { + showSave: false, showSettings: false, }; @@ -1084,7 +1082,7 @@ app.controller('graphuiPlugin', function ( }); } - $scope.saveWorkspace = function (saveOptions, userHasConfirmedSaveWorkspaceData) { + $scope.saveWorkspace = function () { if ($scope.allSavingDisabled) { // It should not be possible to navigate to this function if allSavingDisabled is set // but adding check here as a safeguard. @@ -1095,7 +1093,7 @@ app.controller('graphuiPlugin', function ( } initWorkspaceIfRequired(); const canSaveData = $scope.graphSavePolicy === 'configAndData' || - ($scope.graphSavePolicy === 'configAndDataWithConsent' && userHasConfirmedSaveWorkspaceData); + ($scope.graphSavePolicy === 'configAndDataWithConsent' && $scope.userHasConfirmedSaveWorkspaceData); let blacklist = []; @@ -1171,8 +1169,12 @@ app.controller('graphuiPlugin', function ( }); $scope.savedWorkspace.numVertices = vertices.length; $scope.savedWorkspace.numLinks = links.length; + $scope.savedWorkspace.description = $scope.description; + - return $scope.savedWorkspace.save(saveOptions).then(function (id) { + $scope.savedWorkspace.save().then(function (id) { + $scope.closeMenus(); + $scope.userHasConfirmedSaveWorkspaceData = false; //reset flag if (id) { const title = i18n.translate('xpack.graph.saveWorkspace.successNotificationTitle', { defaultMessage: 'Saved "{workspaceTitle}"', @@ -1190,11 +1192,9 @@ app.controller('graphuiPlugin', function ( text, 'data-test-subj': 'saveGraphSuccess', }); - if ($scope.savedWorkspace.id !== $route.current.params.id) { - kbnUrl.change(getEditPath($scope.savedWorkspace)); - } + if ($scope.savedWorkspace.id === $route.current.params.id) return; + kbnUrl.change(getEditPath($scope.savedWorkspace)); } - return { id }; }, fatalError); }; diff --git a/x-pack/legacy/plugins/graph/public/components/graph_save_modal.tsx b/x-pack/legacy/plugins/graph/public/components/graph_save_modal.tsx deleted file mode 100644 index 654cf473fb03e..0000000000000 --- a/x-pack/legacy/plugins/graph/public/components/graph_save_modal.tsx +++ /dev/null @@ -1,108 +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 React, { useState } from 'react'; -import { EuiFormRow, EuiTextArea, EuiCallOut, EuiSpacer, EuiSwitch } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { - SavedObjectSaveModal, - OnSaveProps, -} from '../../../../../../src/plugins/kibana_react/public'; - -import { GraphSavePolicy } from '../types/config'; - -export interface OnSaveGraphProps extends OnSaveProps { - newDescription: string; - dataConsent: boolean; -} - -export function GraphSaveModal({ - onSave, - onClose, - title, - description, - showCopyOnSave, - savePolicy, - hasData, -}: { - onSave: (props: OnSaveGraphProps) => void; - onClose: () => void; - title: string; - description: string; - showCopyOnSave: boolean; - savePolicy: GraphSavePolicy; - hasData: boolean; -}) { - const [newDescription, setDescription] = useState(description); - const [dataConsent, setDataConsent] = useState(false); - return ( - { - onSave({ ...props, newDescription, dataConsent }); - }} - onClose={onClose} - title={title} - showCopyOnSave={showCopyOnSave} - objectType={i18n.translate('xpack.graph.topNavMenu.save.objectType', { - defaultMessage: 'workspace', - })} - options={ - <> - - { - setDescription(e.target.value); - }} - fullWidth - rows={5} - /> - - {savePolicy === 'configAndDataWithConsent' && hasData && ( - - { - setDataConsent(e.target.checked); - }} - /> - - )} - {savePolicy === 'config' && hasData && ( - <> - -

- {i18n.translate('xpack.graph.topNavMenu.save.saveConfigurationOnlyText', { - defaultMessage: - 'The data in this workspace will be cleared and only the configuration will be saved.', - })} -

-
- - - )} - - } - /> - ); -} diff --git a/x-pack/legacy/plugins/graph/public/services/save.tsx b/x-pack/legacy/plugins/graph/public/services/save.tsx deleted file mode 100644 index 4903fb4913a3f..0000000000000 --- a/x-pack/legacy/plugins/graph/public/services/save.tsx +++ /dev/null @@ -1,69 +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 React from 'react'; -import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -import { SavedGraphWorkspace } from '../types/persistence'; -import { GraphSaveModal, OnSaveGraphProps } from '../components/graph_save_modal'; -import { GraphSavePolicy } from '../types/config'; - -export function save({ - savePolicy, - hasData, - workspace, - saveWorkspace, -}: { - savePolicy: GraphSavePolicy; - hasData: boolean; - workspace: SavedGraphWorkspace; - saveWorkspace: ( - saveOptions: { - confirmOverwrite: boolean; - isTitleDuplicateConfirmed: boolean; - onTitleDuplicate: () => void; - }, - dataConsent: boolean - ) => Promise<{ id?: string } | { error: string }>; -}) { - const currentTitle = workspace.title; - const currentDescription = workspace.description; - const onSave = ({ - newTitle, - newDescription, - newCopyOnSave, - isTitleDuplicateConfirmed, - onTitleDuplicate, - dataConsent, - }: OnSaveGraphProps) => { - workspace.title = newTitle; - workspace.description = newDescription; - workspace.copyOnSave = newCopyOnSave; - const saveOptions = { - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, - }; - return saveWorkspace(saveOptions, dataConsent).then(response => { - // If the save wasn't successful, put the original values back. - if (!('id' in response) || !Boolean(response.id)) { - workspace.title = currentTitle; - workspace.description = currentDescription; - } - return response; - }); - }; - showSaveModal( - {}} - title={workspace.title} - description={workspace.description} - showCopyOnSave={Boolean(workspace.id)} - /> - ); -} diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx index 92c6ddd193609..9a946150927f2 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx @@ -35,7 +35,7 @@ export const LogEntryActionsMenu: React.FunctionComponent<{ > , this.props.onViewChange(view); - // TODO: Change this to a real implementation using the tickFormatter from the prototype as an example. + // TODO: Change this to a real implimentation using the tickFormatter from the prototype as an example. private formatter = (val: string | number) => { const { metric } = this.props.options; const metricFormatter = get( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx index 2171103c349e0..8ae644a497e19 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx @@ -4,25 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; import { useEffect } from 'react'; import * as rt from 'io-ts'; import { useUrlState } from '../../../utils/use_url_state'; +import { timeRangeRT } from '../../../../common/http_api/shared/time_range'; -const autoRefreshRT = rt.union([ - rt.type({ - interval: rt.number, - isPaused: rt.boolean, - }), - rt.undefined, -]); - -export const stringTimeRangeRT = rt.type({ - startTime: rt.string, - endTime: rt.string, -}); -export type StringTimeRange = rt.TypeOf; - -const urlTimeRangeRT = rt.union([stringTimeRangeRT, rt.undefined]); +const autoRefreshRT = rt.union([rt.boolean, rt.undefined]); +const urlTimeRangeRT = rt.union([timeRangeRT, rt.undefined]); const TIME_RANGE_URL_STATE_KEY = 'timeRange'; const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh'; @@ -30,8 +19,11 @@ const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh'; export const useLogAnalysisResultsUrlState = () => { const [timeRange, setTimeRange] = useUrlState({ defaultState: { - startTime: 'now-2w', - endTime: 'now', + startTime: moment + .utc() + .subtract(2, 'weeks') + .valueOf(), + endTime: moment.utc().valueOf(), }, decodeUrlState: (value: unknown) => urlTimeRangeRT.decode(value).getOrElse(undefined), encodeUrlState: urlTimeRangeRT.encode, @@ -42,24 +34,21 @@ export const useLogAnalysisResultsUrlState = () => { setTimeRange(timeRange); }, []); - const [autoRefresh, setAutoRefresh] = useUrlState({ - defaultState: { - isPaused: false, - interval: 30000, - }, + const [autoRefreshEnabled, setAutoRefresh] = useUrlState({ + defaultState: false, decodeUrlState: (value: unknown) => autoRefreshRT.decode(value).getOrElse(undefined), encodeUrlState: autoRefreshRT.encode, urlStateKey: AUTOREFRESH_URL_STATE_KEY, }); useEffect(() => { - setAutoRefresh(autoRefresh); + setAutoRefresh(autoRefreshEnabled); }, []); return { timeRange, setTimeRange, - autoRefresh, + autoRefreshEnabled, setAutoRefresh, }; }; diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx index ca92c4086485a..8e6e61bc44f08 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx @@ -57,7 +57,7 @@ export const RedirectToHostDetailViaIP = injectI18n( )(''); if (name) { - return ; + return ; } return ( diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx index ad51307641780..d9f62f48a8a86 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx @@ -27,7 +27,7 @@ export const RedirectToNodeDetail = ({ getToFromLocation(location) )(''); - return ; + return ; }; export const getNodeDetailUrl = ({ diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 0ec027958cc33..71c46230d5d7c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import datemath from '@elastic/datemath'; +import React, { useCallback, useMemo, useState } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiBadge, + EuiSuperDatePicker, EuiFlexGroup, EuiFlexItem, EuiPage, @@ -14,25 +17,31 @@ import { EuiPageContent, EuiPageContentBody, EuiPanel, - EuiSuperDatePicker, + EuiBadge, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import dateMath from '@elastic/datemath'; import moment from 'moment'; -import React, { useCallback, useMemo, useState } from 'react'; import euiStyled from '../../../../../../common/eui_styled_components'; -import { TimeRange } from '../../../../common/http_api/shared/time_range'; -import { bucketSpan } from '../../../../common/log_analysis'; -import { LoadingPage } from '../../../components/loading_page'; -import { - StringTimeRange, - useLogAnalysisResults, - useLogAnalysisResultsUrlState, -} from '../../../containers/logs/log_analysis'; import { useTrackPageview } from '../../../hooks/use_track_metric'; -import { FirstUseCallout } from './first_use'; +import { useInterval } from '../../../hooks/use_interval'; +import { useLogAnalysisResults } from '../../../containers/logs/log_analysis'; +import { useLogAnalysisResultsUrlState } from '../../../containers/logs/log_analysis'; +import { LoadingPage } from '../../../components/loading_page'; import { LogRateResults } from './sections/log_rate'; +import { FirstUseCallout } from './first_use'; + +const DATE_PICKER_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; + +const getLoadingState = () => { + return ( + + ); +}; export const AnalysisResultsContent = ({ sourceId, @@ -45,15 +54,26 @@ export const AnalysisResultsContent = ({ useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); const { - timeRange: selectedTimeRange, - setTimeRange: setSelectedTimeRange, - autoRefresh, + timeRange, + setTimeRange, + autoRefreshEnabled, setAutoRefresh, } = useLogAnalysisResultsUrlState(); - const [queryTimeRange, setQueryTimeRange] = useState( - stringToNumericTimeRange(selectedTimeRange) - ); + const [refreshInterval, setRefreshInterval] = useState(300000); + + const setTimeRangeToNow = useCallback(() => { + const range = timeRange.endTime - timeRange.startTime; + const nowInMs = moment() + .utc() + .valueOf(); + setTimeRange({ + startTime: nowInMs - range, + endTime: nowInMs, + }); + }, [timeRange.startTime, timeRange.endTime, setTimeRange]); + + useInterval(setTimeRangeToNow, autoRefreshEnabled ? refreshInterval : null); const bucketDuration = useMemo(() => { // This function takes the current time range in ms, @@ -63,52 +83,33 @@ export const AnalysisResultsContent = ({ // 900000 (15 minutes) to it, so that we don't end up with // jaggy bucket boundaries between the ML buckets and our // aggregation buckets. - const msRange = moment(queryTimeRange.endTime).diff(moment(queryTimeRange.startTime)); + const msRange = timeRange.endTime - timeRange.startTime; const bucketIntervalInMs = msRange / 200; + const bucketSpan = 900000; // TODO: Pull this from 'common' when setup hook PR is merged const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); const roundedResult = parseInt(Number(result).toFixed(0), 10); return roundedResult < bucketSpan ? bucketSpan : roundedResult; - }, [queryTimeRange.startTime, queryTimeRange.endTime]); - + }, [timeRange]); const { isLoading, logEntryRate } = useLogAnalysisResults({ sourceId, - startTime: queryTimeRange.startTime, - endTime: queryTimeRange.endTime, + startTime: timeRange.startTime, + endTime: timeRange.endTime, bucketDuration, }); const hasResults = useMemo(() => logEntryRate && logEntryRate.histogramBuckets.length > 0, [ logEntryRate, ]); - - const handleQueryTimeRangeChange = useCallback( - ({ start: startTime, end: endTime }: { start: string; end: string }) => { - setQueryTimeRange(stringToNumericTimeRange({ startTime, endTime })); - }, - [setQueryTimeRange] - ); - - const handleSelectedTimeRangeChange = useCallback( - (selectedTime: { start: string; end: string; isInvalid: boolean }) => { - if (selectedTime.isInvalid) { - return; - } - setSelectedTimeRange({ - startTime: selectedTime.start, - endTime: selectedTime.end, - }); - handleQueryTimeRangeChange(selectedTime); - }, - [setSelectedTimeRange, handleQueryTimeRangeChange] - ); - - const handleAutoRefreshChange = useCallback( - ({ isPaused, refreshInterval: interval }: { isPaused: boolean; refreshInterval: number }) => { - setAutoRefresh({ - isPaused, - interval, + const handleTimeRangeChange = useCallback( + ({ start, end }: { start: string; end: string }) => { + const parsedStart = dateMath.parse(start); + const parsedEnd = dateMath.parse(end); + setTimeRange({ + startTime: + !parsedStart || !parsedStart.isValid() ? timeRange.startTime : parsedStart.valueOf(), + endTime: !parsedEnd || !parsedEnd.isValid() ? timeRange.endTime : parsedEnd.valueOf(), }); }, - [setAutoRefresh] + [setTimeRange, timeRange] ); const anomaliesDetected = useMemo(() => { @@ -116,10 +117,18 @@ export const AnalysisResultsContent = ({ return null; } else { if (logEntryRate.histogramBuckets && logEntryRate.histogramBuckets.length) { - return logEntryRate.histogramBuckets.reduce( - (acc, bucket) => acc + bucket.anomalies.length, - 0 - ); + return logEntryRate.histogramBuckets.reduce((acc: any, bucket) => { + if (bucket.anomalies.length > 0) { + return ( + acc + + bucket.anomalies.reduce((anomalyAcc: any, anomaly) => { + return anomalyAcc + 1; + }, 0) + ); + } else { + return acc; + } + }, 0); } else { return null; } @@ -129,11 +138,7 @@ export const AnalysisResultsContent = ({ return ( <> {isLoading && !logEntryRate ? ( - + <>{getLoadingState()} ) : ( <> @@ -143,33 +148,41 @@ export const AnalysisResultsContent = ({ {anomaliesDetected !== null ? ( - - - {anomaliesDetected} - - ), - number: anomaliesDetected, - }} - /> - + <> + + 0 + ) : ( + {anomaliesDetected} + ), + }} + /> + + ) : null} { + if (isPaused) { + setAutoRefresh(false); + } else { + setRefreshInterval(interval); + setAutoRefresh(true); + } + }} /> @@ -183,7 +196,7 @@ export const AnalysisResultsContent = ({ @@ -195,20 +208,6 @@ export const AnalysisResultsContent = ({ ); }; -const stringToNumericTimeRange = (timeRange: StringTimeRange): TimeRange => ({ - startTime: moment( - datemath.parse(timeRange.startTime, { - momentInstance: moment, - }) - ).valueOf(), - endTime: moment( - datemath.parse(timeRange.endTime, { - momentInstance: moment, - roundUp: true, - }) - ).valueOf(), -}); - const ExpandingPage = euiStyled(EuiPage)` flex: 1 0 0%; `; diff --git a/x-pack/legacy/plugins/infra/public/routes.tsx b/x-pack/legacy/plugins/infra/public/routes.tsx index 6e5ec8ea560b4..dcdb40cb5ed7e 100644 --- a/x-pack/legacy/plugins/infra/public/routes.tsx +++ b/x-pack/legacy/plugins/infra/public/routes.tsx @@ -6,7 +6,7 @@ import { History } from 'history'; import React from 'react'; -import { Route, Router, Switch } from 'react-router-dom'; +import { Redirect, Route, Router, Switch } from 'react-router-dom'; import { UICapabilities } from 'ui/capabilities'; import { injectUICapabilities } from 'ui/capabilities/react'; @@ -15,7 +15,6 @@ import { InfrastructurePage } from './pages/infrastructure'; import { LinkToPage } from './pages/link_to'; import { LogsPage } from './pages/logs'; import { MetricDetail } from './pages/metrics'; -import { RedirectWithQueryParams } from './utils/redirect_with_query_params'; interface RouterProps { history: History; @@ -27,39 +26,26 @@ const PageRouterComponent: React.SFC = ({ history, uiCapabilities } {uiCapabilities.infrastructure.show && ( - + )} {uiCapabilities.infrastructure.show && ( - + )} {uiCapabilities.infrastructure.show && ( - + )} {uiCapabilities.infrastructure.show && ( - - )} - {uiCapabilities.infrastructure.show && ( - - )} - {uiCapabilities.infrastructure.show && ( - - )} - {uiCapabilities.logs.show && ( - + )} + {uiCapabilities.logs.show && } {uiCapabilities.logs.show && } {uiCapabilities.infrastructure.show && ( )} + {uiCapabilities.infrastructure.show && ( + + )} diff --git a/x-pack/legacy/plugins/infra/public/utils/redirect_with_query_params.tsx b/x-pack/legacy/plugins/infra/public/utils/redirect_with_query_params.tsx deleted file mode 100644 index 915b82860dd1b..0000000000000 --- a/x-pack/legacy/plugins/infra/public/utils/redirect_with_query_params.tsx +++ /dev/null @@ -1,44 +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 React from 'react'; -import { Route, Redirect, RouteProps, RedirectProps } from 'react-router-dom'; - -interface RedirectWithQueryParamsProps extends Omit { - from: string; - to: string | RouteProps['location']; -} - -// This workaround preserves query parameters in the redirect -// https://github.com/ReactTraining/react-router/issues/5818#issuecomment-379212014 -export const RedirectWithQueryParams: React.FunctionComponent = ({ - from, - to, - ...rest -}) => ( - - location ? ( - - ) : null - } - /> -); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_layer_panel.scss b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_layer_panel.scss index 1e9b21c434290..9c8af22319918 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_layer_panel.scss +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_layer_panel.scss @@ -10,13 +10,16 @@ } .mapLayerPanel__body { - @include euiScrollBar; + @include euiOverflowShadow; padding-bottom: 0; /* 1 */ flex-grow: 1; flex-basis: 1px; /* Fixes scrolling for Firefox */ - overflow-y: auto; + overflow-y: hidden; .mapLayerPanel__bodyOverflow { + @include euiScrollBar; + height: 100%; + overflow-y: auto; padding: $euiSize; > *:last-child { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/__snapshots__/view.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/__snapshots__/view.test.js.snap index d0ea7043932d9..d61f08152f0c8 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/__snapshots__/view.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/__snapshots__/view.test.js.snap @@ -9,6 +9,8 @@ exports[`AttributionControl is rendered 1`] = ` > + attribution with no link + , attribution with link - , - attribution with no link diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js index 0b1c6d6d4e764..4a8a4a5a440a1 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js @@ -56,8 +56,6 @@ export class AttributionControl extends React.Component { } } } - // Reflect top-to-bottom layer order as left-to-right in attribs - uniqueAttributions.reverse(); if (!_.isEqual(this.state.uniqueAttributions, uniqueAttributions)) { this.setState({ uniqueAttributions }); } diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.js b/x-pack/legacy/plugins/maps/public/index_pattern_util.js index ca43826f307eb..48d25ebb1639b 100644 --- a/x-pack/legacy/plugins/maps/public/index_pattern_util.js +++ b/x-pack/legacy/plugins/maps/public/index_pattern_util.js @@ -23,11 +23,3 @@ export function getTermsFields(fields) { return field.aggregatable && ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type); }); } - -// Returns filtered fields list containing only fields that exist in _source. -export function getSourceFields(fields) { - return fields.filter(field => { - // Multi fields are not stored in _source and only exist in index. - return field.subType !== 'multi'; - }); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js index 8d1d1439a967e..dac05128e8d29 100644 --- a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js +++ b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js @@ -49,21 +49,12 @@ export class InnerJoin { } joinPropertiesToFeature(feature, propertiesMap, rightMetricFields) { - // delete feature properties added by previous join for (let j = 0; j < rightMetricFields.length; j++) { - const { propertyKey: metricPropertyKey } = rightMetricFields[j]; - delete feature.properties[metricPropertyKey]; - - // delete all dynamic properties for metric field - const stylePropertyPrefix = VectorStyle.getComputedFieldNamePrefix(metricPropertyKey); - Object.keys(feature.properties).forEach(featurePropertyKey => { - if (featurePropertyKey.length >= stylePropertyPrefix.length && - featurePropertyKey.substring(0, stylePropertyPrefix.length) === stylePropertyPrefix) { - delete feature.properties[featurePropertyKey]; - } - }); + const { propertyKey } = rightMetricFields[j]; + delete feature.properties[propertyKey]; + const stylePropertyName = VectorStyle.getComputedFieldName(propertyKey); + delete feature.properties[stylePropertyName]; } - const joinKey = feature.properties[this._descriptor.leftField]; const coercedKey = typeof joinKey === 'undefined' || joinKey === null ? null : joinKey.toString(); if (propertiesMap && coercedKey !== null && propertiesMap.has(coercedKey)) { diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js index 99177af4b7a40..e6fb1957fec41 100644 --- a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js @@ -58,7 +58,7 @@ describe('joinPropertiesToFeature', () => { properties: { iso2: 'CN', [COUNT_PROPERTY_NAME]: 61, - [`__kbn__dynamic__${COUNT_PROPERTY_NAME}__fillColor`]: 1, + [`__kbn__scaled(${COUNT_PROPERTY_NAME})`]: 1, } }; const propertiesMap = new Map(); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js index db255074fa261..a3b09de704595 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js @@ -24,10 +24,10 @@ export class GeojsonFileSource extends AbstractVectorSource { static type = GEOJSON_FILE; static title = i18n.translate('xpack.maps.source.geojsonFileTitle', { - defaultMessage: 'Uploaded GeoJSON' + defaultMessage: 'Upload GeoJSON vector file' }); static description = i18n.translate('xpack.maps.source.geojsonFileDescription', { - defaultMessage: 'Upload and index GeoJSON data in Elasticsearch' + defaultMessage: 'Upload a GeoJSON file and index in Elasticsearch' }); static icon = 'importAction'; static isIndexingSource = true; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js index f62693dae4db6..e18fe0cea1274 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js @@ -19,10 +19,10 @@ export class EMSFileSource extends AbstractVectorSource { static type = EMS_FILE; static title = i18n.translate('xpack.maps.source.emsFileTitle', { - defaultMessage: 'EMS Boundaries' + defaultMessage: 'Vector shapes' }); static description = i18n.translate('xpack.maps.source.emsFileDescription', { - defaultMessage: 'Administrative boundaries from Elastic Maps Service' + defaultMessage: 'Vector shapes of administrative boundaries from Elastic Maps Service' }); static icon = 'emsApp'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js index ff57bdc8dbfb3..5daa5c1dfde2a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js @@ -20,10 +20,10 @@ export class EMSTMSSource extends AbstractTMSSource { static type = EMS_TMS; static title = i18n.translate('xpack.maps.source.emsTileTitle', { - defaultMessage: 'EMS Basemaps' + defaultMessage: 'Tiles' }); static description = i18n.translate('xpack.maps.source.emsTileDescription', { - defaultMessage: 'Tile map service from Elastic Maps Service' + defaultMessage: 'Map tiles from Elastic Maps Service' }); static icon = 'emsApp'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index d0f175136e9b9..0b8b9aceae05a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -16,7 +16,6 @@ import { AggConfigs } from 'ui/vis/agg_configs'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { convertToGeoJson } from './convert_to_geojson'; import { VectorStyle } from '../../styles/vector_style'; -import { vectorStyles } from '../../styles/vector_style_defaults'; import { RENDER_AS } from './render_as'; import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; @@ -262,7 +261,7 @@ export class ESGeoGridSource extends AbstractESSource { ...options }); descriptor.style = VectorStyle.createDescriptor({ - [vectorStyles.FILL_COLOR]: { + fillColor: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { field: { @@ -273,7 +272,7 @@ export class ESGeoGridSource extends AbstractESSource { color: 'Blues' } }, - [vectorStyles.ICON_SIZE]: { + iconSize: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { field: { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 4514c8fac309a..e1b930c8ab39b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -13,7 +13,6 @@ import { VectorLayer } from '../../vector_layer'; import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { VectorStyle } from '../../styles/vector_style'; -import { vectorStyles } from '../../styles/vector_style_defaults'; import { i18n } from '@kbn/i18n'; import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW } from '../../../../common/constants'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -43,10 +42,10 @@ export class ESPewPewSource extends AbstractESSource { static type = ES_PEW_PEW; static title = i18n.translate('xpack.maps.source.pewPewTitle', { - defaultMessage: 'Point to point' + defaultMessage: 'Source-destination connections' }); static description = i18n.translate('xpack.maps.source.pewPewDescription', { - defaultMessage: 'Aggregated data paths between the source and destination' + defaultMessage: 'Aggregated data paths between the origin and destinations.' }); static createDescriptor({ indexPatternId, sourceGeoField, destGeoField }) { @@ -142,7 +141,7 @@ export class ESPewPewSource extends AbstractESSource { createDefaultLayer(options) { const styleDescriptor = VectorStyle.createDescriptor({ - [vectorStyles.LINE_COLOR]: { + lineColor: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { field: { @@ -153,7 +152,7 @@ export class ESPewPewSource extends AbstractESSource { color: 'Blues' } }, - [vectorStyles.LINE_WIDTH]: { + lineWidth: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { field: { 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 feebb50eeca42..d0cf7420d84a7 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 @@ -18,7 +18,7 @@ import { ES_SEARCH, ES_GEO_FIELD_TYPE, ES_SIZE_LIMIT } from '../../../../common/ import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { ESTooltipProperty } from '../../tooltips/es_tooltip_property'; -import { getSourceFields } from '../../../index_pattern_util'; +import { getTermsFields } from '../../../index_pattern_util'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; @@ -29,7 +29,7 @@ export class ESSearchSource extends AbstractESSource { defaultMessage: 'Documents' }); static description = i18n.translate('xpack.maps.source.esSearchDescription', { - defaultMessage: 'Vector data from a Kibana index pattern' + defaultMessage: 'Geospatial data from a Kibana index pattern' }); static renderEditor({ onPreviewSource, inspectorAdapters }) { @@ -325,8 +325,7 @@ export class ESSearchSource extends AbstractESSource { async getLeftJoinFields() { const indexPattern = await this._getIndexPattern(); - // Left fields are retrieved from _source. - return getSourceFields(indexPattern.fields) + return getTermsFields(indexPattern.fields) .map(field => { return { name: field.name, label: field.name }; }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js index 37de07921854b..7b9ae98d31e8e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js @@ -15,7 +15,7 @@ import { TooltipSelector } from '../../../components/tooltip_selector'; import { indexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; -import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; +import { getTermsFields } from '../../../index_pattern_util'; import { ValidatedRange } from '../../../components/validated_range'; export class UpdateSourceEditor extends Component { @@ -74,7 +74,11 @@ export class UpdateSourceEditor extends Component { this.setState({ dateFields, - tooltipFields: getSourceFields(indexPattern.fields), + tooltipFields: indexPattern.fields.filter(field => { + // Do not show multi fields as tooltip field options + // since they do not have values in _source and exist for indexing only + return field.subType !== 'multi'; + }), termFields: getTermsFields(indexPattern.fields), }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js index 0f52937f2211f..8eccda12b44a0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js @@ -15,10 +15,10 @@ export class KibanaRegionmapSource extends AbstractVectorSource { static type = 'REGIONMAP_FILE'; static title = i18n.translate('xpack.maps.source.kbnRegionMapTitle', { - defaultMessage: 'Configured GeoJSON' + defaultMessage: 'Custom vector shapes' }); static description = i18n.translate('xpack.maps.source.kbnRegionMapDescription', { - defaultMessage: 'Vector data from hosted GeoJSON configured in kibana.yml' + defaultMessage: 'Vector shapes from static files configured in kibana.yml' }) ; static icon = 'logoKibana'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js index f32396108e40c..2c507738bf7e2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js @@ -16,10 +16,10 @@ export class KibanaTilemapSource extends AbstractTMSSource { static type = 'KIBANA_TILEMAP'; static title = i18n.translate('xpack.maps.source.kbnTMSTitle', { - defaultMessage: 'Configured Tile Map Service' + defaultMessage: 'Custom Tile Map Service' }); static description = i18n.translate('xpack.maps.source.kbnTMSDescription', { - defaultMessage: 'Tile map service configured in kibana.yml' + defaultMessage: 'Map tiles configured in kibana.yml' }); static icon = 'logoKibana'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js index 4a370e7236933..9a3fbf286c7a1 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js @@ -20,10 +20,10 @@ export class XYZTMSSource extends AbstractTMSSource { static type = 'EMS_XYZ'; static title = i18n.translate('xpack.maps.source.ems_xyzTitle', { - defaultMessage: 'Tile Map Service' + defaultMessage: 'Tile Map Service from URL' }); static description = i18n.translate('xpack.maps.source.ems_xyzDescription', { - defaultMessage: 'Tile map service configured in interface' + defaultMessage: 'Map tiles from a URL that includes the XYZ coordinates' }); static icon = 'grid'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/get_vector_style_label.js b/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/get_vector_style_label.js index a39fabf20bae4..0e3f860dbe136 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/get_vector_style_label.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/get_vector_style_label.js @@ -6,23 +6,21 @@ import { i18n } from '@kbn/i18n'; -import { vectorStyles } from '../../vector_style_defaults'; - export function getVectorStyleLabel(styleName) { switch (styleName) { - case vectorStyles.FILL_COLOR: + case 'fillColor': return i18n.translate('xpack.maps.styles.vector.fillColorLabel', { defaultMessage: 'Fill color' }); - case vectorStyles.LINE_COLOR: + case 'lineColor': return i18n.translate('xpack.maps.styles.vector.borderColorLabel', { defaultMessage: 'Border color' }); - case vectorStyles.LINE_WIDTH: + case 'lineWidth': return i18n.translate('xpack.maps.styles.vector.borderWidthLabel', { defaultMessage: 'Border width' }); - case vectorStyles.ICON_SIZE: + case 'iconSize': return i18n.translate('xpack.maps.styles.vector.symbolSizeLabel', { defaultMessage: 'Symbol size' }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/vector_style_editor.js index 4d25953f5610b..f5dc8ebfb13bc 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/components/vector/vector_style_editor.js @@ -14,8 +14,7 @@ import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; import { OrientationEditor } from './orientation/orientation_editor'; import { getDefaultDynamicProperties, - getDefaultStaticProperties, - vectorStyles + getDefaultStaticProperties } from '../../vector_style_defaults'; import { DEFAULT_FILL_COLORS, @@ -98,7 +97,7 @@ export class VectorStyleEditor extends Component { _renderFillColor() { return ( Minimal initialization. 1`] = ` - -
- -
-
-`; diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.test.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.test.tsx deleted file mode 100644 index d98077da230c8..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.test.tsx +++ /dev/null @@ -1,99 +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 { mount, shallow } from 'enzyme'; -import React from 'react'; - -import { uiTimefilterMock } from '../../../contexts/ui/__mocks__/mocks'; -import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; - -import { MlSuperDatePickerWithUpdate, TopNav } from './top_nav'; - -uiTimefilterMock.enableAutoRefreshSelector(); -uiTimefilterMock.enableTimeRangeSelector(); - -jest.mock('../../../contexts/ui/use_ui_context'); - -const noop = () => {}; - -describe('Navigation Menu: ', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - test('Minimal initialization.', () => { - const refreshListener = jest.fn(); - const refreshSubscription = mlTimefilterRefresh$.subscribe(refreshListener); - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - expect(refreshListener).toBeCalledTimes(0); - - refreshSubscription.unsubscribe(); - }); - - // The following tests are written against MlSuperDatePickerWithUpdate - // instead of TopNav. TopNav uses hooks and we cannot writing tests - // with async hook updates yet until React 16.9 is available. - - // MlSuperDatePickerWithUpdate fixes an issue with EuiSuperDatePicker - // which didn't make it into Kibana 7.4. We should be able to just - // use EuiSuperDatePicker again once the following PR is in EUI: - // https://github.com/elastic/eui/pull/2298 - - test('Listen for consecutive super date picker refreshs.', async () => { - const onRefresh = jest.fn(); - - const componentRefresh = mount( - - ); - - const instanceRefresh = componentRefresh.instance(); - - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - - expect(onRefresh).toBeCalledTimes(2); - }); - - test('Switching refresh interval to pause should stop onRefresh being called.', async () => { - const onRefresh = jest.fn(); - - const componentRefresh = mount( - - ); - - const instanceRefresh = componentRefresh.instance(); - - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - componentRefresh.setProps({ isPaused: true, refreshInterval: 0 }); - jest.advanceTimersByTime(10); - // @ts-ignore - await instanceRefresh.asyncInterval.__pendingFn; - - expect(onRefresh).toBeCalledTimes(1); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx index f32632cd321ae..6e38b37c33a24 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx @@ -4,38 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, FC, Fragment, useState, useEffect } from 'react'; +import React, { FC, Fragment, useState, useEffect } from 'react'; import { Subscription } from 'rxjs'; -import { EuiSuperDatePicker, EuiSuperDatePickerProps } from '@elastic/eui'; -import { TimeHistory } from 'ui/timefilter'; -import { TimeRange } from 'src/plugins/data/public'; +import { EuiSuperDatePicker } from '@elastic/eui'; +import { TimeHistory, TimeRange } from 'ui/timefilter'; import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; import { useUiContext } from '../../../contexts/ui/use_ui_context'; -interface ComponentWithConstructor extends Component { - new (): Component; -} - -const MlSuperDatePicker = (EuiSuperDatePicker as any) as ComponentWithConstructor< - EuiSuperDatePickerProps ->; - -// This part fixes a problem with EuiSuperDater picker where it would not reflect -// a prop change of isPaused on the internal interval. This fix will be released -// with EUI 13.7.0 but only 13.6.1 without the fix made it into Kibana 7.4 so -// it's copied here. -export class MlSuperDatePickerWithUpdate extends MlSuperDatePicker { - componentDidUpdate = () => { - // @ts-ignore - this.stopInterval(); - if (!this.props.isPaused) { - // @ts-ignore - this.startInterval(this.props.refreshInterval); - } - }; -} - interface Duration { start: string; end: string; @@ -120,14 +96,20 @@ export const TopNav: FC = () => { {(isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled) && (
- mlTimefilterRefresh$.next()} + onRefresh={() => { + // This check is a workaround to catch a bug in EuiSuperDatePicker which + // might not have disabled the refresh interval after a props change. + if (!refreshInterval.pause) { + mlTimefilterRefresh$.next(); + } + }} onRefreshChange={updateInterval} recentlyUsedRanges={recentlyUsedRanges} dateFormat={dateFormat} diff --git a/x-pack/legacy/plugins/ml/public/contexts/ui/__mocks__/mocks.ts b/x-pack/legacy/plugins/ml/public/contexts/ui/__mocks__/mocks.ts index e6e51f0bdecf4..90bc028a7cd37 100644 --- a/x-pack/legacy/plugins/ml/public/contexts/ui/__mocks__/mocks.ts +++ b/x-pack/legacy/plugins/ml/public/contexts/ui/__mocks__/mocks.ts @@ -11,7 +11,7 @@ export const uiChromeMock = { get: (key: string) => { switch (key) { case 'dateFormat': - return 'MMM D, YYYY @ HH:mm:ss.SSS'; + return {}; case 'theme:darkMode': return false; case 'timepicker:timeDefaults': @@ -26,45 +26,12 @@ export const uiChromeMock = { }, }; -interface RefreshInterval { - value: number; - pause: boolean; -} - -const time = { - from: 'Thu Aug 29 2019 02:04:19 GMT+0200', - to: 'Sun Sep 29 2019 01:45:36 GMT+0200', -}; - export const uiTimefilterMock = { - enableAutoRefreshSelector() { - this.isAutoRefreshSelectorEnabled = true; - }, - enableTimeRangeSelector() { - this.isTimeRangeSelectorEnabled = true; - }, - getEnabledUpdated$() { - return { subscribe: jest.fn() }; - }, - getRefreshInterval() { - return this.refreshInterval; - }, - getRefreshIntervalUpdate$() { - return { subscribe: jest.fn() }; - }, - getTime: () => time, - getTimeUpdate$() { - return { subscribe: jest.fn() }; - }, - isAutoRefreshSelectorEnabled: false, - isTimeRangeSelectorEnabled: false, - refreshInterval: { value: 0, pause: true }, + getRefreshInterval: () => '30s', + getTime: () => ({ from: 0, to: 0 }), on: (event: string, reload: () => void) => {}, - setRefreshInterval(refreshInterval: RefreshInterval) { - this.refreshInterval = refreshInterval; - }, }; export const uiTimeHistoryMock = { - get: () => [time], + get: () => [{ from: 0, to: 0 }], }; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx index af26e99b3af99..4815d658f6540 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx @@ -84,7 +84,7 @@ export const StepCreateForm: SFC = React.memo( await ml.dataFrame.createDataFrameTransform(transformId, transformConfig); toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.stepCreateForm.createTransformSuccessMessage', { - defaultMessage: 'Request to create data frame transform {transformId} acknowledged.', + defaultMessage: 'Data frame transform {transformId} created successfully.', values: { transformId }, }) ); @@ -114,7 +114,7 @@ export const StepCreateForm: SFC = React.memo( await ml.dataFrame.startDataFrameTransforms([{ id: transformId }]); toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.stepCreateForm.startTransformSuccessMessage', { - defaultMessage: 'Request to start data frame transform {transformId} acknowledged.', + defaultMessage: 'Data frame transform {transformId} started successfully.', values: { transformId }, }) ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx index 09b97219ecdfa..7a85693b3c581 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx @@ -229,41 +229,43 @@ export const PivotPreview: SFC = React.memo(({ aggs, groupBy, const columnKeys = getFlattenedFields(dataFramePreviewData[0]); columnKeys.sort(sortColumns(groupByArr)); - const columns = columnKeys.map(k => { - const column: ColumnType = { - field: k, - name: k, - sortable: true, - truncateText: true, - }; - if (typeof dataFramePreviewMappings.properties[k] !== 'undefined') { - const esFieldType = dataFramePreviewMappings.properties[k].type; - switch (esFieldType) { - case ES_FIELD_TYPES.BOOLEAN: - column.dataType = 'boolean'; - break; - case ES_FIELD_TYPES.DATE: - column.align = 'right'; - column.render = (d: any) => formatHumanReadableDateTimeSeconds(moment(d).unix() * 1000); - break; - case ES_FIELD_TYPES.BYTE: - case ES_FIELD_TYPES.DOUBLE: - case ES_FIELD_TYPES.FLOAT: - case ES_FIELD_TYPES.HALF_FLOAT: - case ES_FIELD_TYPES.INTEGER: - case ES_FIELD_TYPES.LONG: - case ES_FIELD_TYPES.SCALED_FLOAT: - case ES_FIELD_TYPES.SHORT: - column.dataType = 'number'; - break; - case ES_FIELD_TYPES.KEYWORD: - case ES_FIELD_TYPES.TEXT: - column.dataType = 'string'; - break; + const columns = columnKeys + .filter(k => typeof dataFramePreviewMappings.properties[k] !== 'undefined') + .map(k => { + const column: ColumnType = { + field: k, + name: k, + sortable: true, + truncateText: true, + }; + if (typeof dataFramePreviewMappings.properties[k] !== 'undefined') { + const esFieldType = dataFramePreviewMappings.properties[k].type; + switch (esFieldType) { + case ES_FIELD_TYPES.BOOLEAN: + column.dataType = 'boolean'; + break; + case ES_FIELD_TYPES.DATE: + column.align = 'right'; + column.render = (d: any) => formatHumanReadableDateTimeSeconds(moment(d).unix() * 1000); + break; + case ES_FIELD_TYPES.BYTE: + case ES_FIELD_TYPES.DOUBLE: + case ES_FIELD_TYPES.FLOAT: + case ES_FIELD_TYPES.HALF_FLOAT: + case ES_FIELD_TYPES.INTEGER: + case ES_FIELD_TYPES.LONG: + case ES_FIELD_TYPES.SCALED_FLOAT: + case ES_FIELD_TYPES.SHORT: + column.dataType = 'number'; + break; + case ES_FIELD_TYPES.KEYWORD: + case ES_FIELD_TYPES.TEXT: + column.dataType = 'string'; + break; + } } - } - return column; - }); + return column; + }); if (columns.length === 0) { return null; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx index dd8de60eb0fdb..0b1d741d8245d 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -6,12 +6,7 @@ import React, { FC, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import moment from 'moment-timezone'; -import { - SortDirection, - SORT_DIRECTION, - FieldDataColumnType, -} from '../../../../../../common/types/eui/in_memory_table'; +import { SortDirection, SORT_DIRECTION } from '../../../../../../common/types/eui/in_memory_table'; import { ml } from '../../../../../services/ml_api_service'; @@ -21,10 +16,15 @@ import { PreviewRequestBody, DataFrameTransformPivotConfig, } from '../../../../common'; -import { ES_FIELD_TYPES } from '../../../../../../common/constants/field_types'; -import { formatHumanReadableDateTimeSeconds } from '../../../../../util/date_utils'; import { TransformTable } from './transform_table'; +interface Column { + field: string; + name: string; + sortable: boolean; + truncateText: boolean; +} + interface Props { transformConfig: DataFrameTransformPivotConfig; } @@ -72,7 +72,7 @@ function getDataFromTransform( export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { const [dataFramePreviewData, setDataFramePreviewData] = useState([]); - const [columns, setColumns] = useState([]); + const [columns, setColumns] = useState([]); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [sortField, setSortField] = useState(''); @@ -99,44 +99,13 @@ export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { if (resp.preview.length > 0) { const columnKeys = getFlattenedFields(resp.preview[0]); columnKeys.sort(sortColumns(groupByArr)); - - const tableColumns: FieldDataColumnType[] = columnKeys.map(k => { - const column: FieldDataColumnType = { + const tableColumns = columnKeys.map(k => { + return { field: k, name: k, sortable: true, truncateText: true, }; - - if (typeof resp.mappings.properties[k] !== 'undefined') { - const esFieldType = resp.mappings.properties[k].type; - switch (esFieldType) { - case ES_FIELD_TYPES.BOOLEAN: - column.dataType = 'boolean'; - break; - case ES_FIELD_TYPES.DATE: - column.align = 'right'; - column.render = (d: any) => - formatHumanReadableDateTimeSeconds(moment(d).unix() * 1000); - break; - case ES_FIELD_TYPES.BYTE: - case ES_FIELD_TYPES.DOUBLE: - case ES_FIELD_TYPES.FLOAT: - case ES_FIELD_TYPES.HALF_FLOAT: - case ES_FIELD_TYPES.INTEGER: - case ES_FIELD_TYPES.LONG: - case ES_FIELD_TYPES.SCALED_FLOAT: - case ES_FIELD_TYPES.SHORT: - column.dataType = 'number'; - break; - case ES_FIELD_TYPES.KEYWORD: - case ES_FIELD_TYPES.TEXT: - column.dataType = 'string'; - break; - } - } - - return column; }); setDataFramePreviewData(resp.preview); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts index b661890330b16..796af91d04d40 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts @@ -31,7 +31,7 @@ export const deleteTransforms = async (dataFrames: DataFrameTransformListRow[]) if (results[transformId].success === true) { toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.transformList.deleteTransformSuccessMessage', { - defaultMessage: 'Request to delete data frame transform {transformId} acknowledged.', + defaultMessage: 'Data frame transform {transformId} deleted successfully.', values: { transformId }, }) ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts index 44bded99c6c31..7887607ee573e 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts @@ -33,7 +33,7 @@ export const startTransforms = async (dataFrames: DataFrameTransformListRow[]) = if (results[transformId].success === true) { toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.transformList.startTransformSuccessMessage', { - defaultMessage: 'Request to start data frame transform {transformId} acknowledged.', + defaultMessage: 'Data frame transform {transformId} started successfully.', values: { transformId }, }) ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts index 717d8841335bb..d7e5485d22656 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts @@ -33,7 +33,7 @@ export const stopTransforms = async (dataFrames: DataFrameTransformListRow[]) => if (results[transformId].success === true) { toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.transformList.stopTransformSuccessMessage', { - defaultMessage: 'Request to stop data frame transform {transformId} acknowledged.', + defaultMessage: 'Data frame transform {transformId} stopped successfully.', values: { transformId }, }) ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts index 95310b414d6f9..5faf20991056a 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/common/analytics.ts @@ -65,7 +65,7 @@ export interface DataFrameAnalyticsConfig { results_field: string; }; source: { - index: IndexName | IndexName[]; + index: IndexName; }; analysis: AnalysisConfig; analyzed_fields: { diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx deleted file mode 100644 index 027acf6fa2e79..0000000000000 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx +++ /dev/null @@ -1,45 +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 { mount } from 'enzyme'; -import React from 'react'; -import { mountHook } from '../../../../../../../../../test_utils/enzyme_helpers'; - -import { CreateAnalyticsButton } from './create_analytics_button'; - -import { KibanaContext } from '../../../../../contexts/kibana'; -import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__/kibana_context_value'; - -import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; - -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, - INDEX_PATTERN_ILLEGAL_CHARACTERS: [], -})); - -const getMountedHook = () => - mountHook( - () => useCreateAnalyticsForm(), - ({ children }) => ( - {children} - ) - ); - -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - -describe('Data Frame Analytics: ', () => { - test('Minimal initialization', () => { - const { getLastHookValue } = getMountedHook(); - const props = getLastHookValue(); - const wrapper = mount(); - - expect(wrapper.find('EuiButton').text()).toBe('Create analytics job'); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx index 0d4c606db261e..dd48fdd2fea5a 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx @@ -32,7 +32,7 @@ export const CreateAnalyticsButton: FC = props => { data-test-subj="mlDataFrameAnalyticsButtonCreate" > {i18n.translate('xpack.ml.dataframe.analyticsList.createDataFrameAnalyticsButton', { - defaultMessage: 'Create analytics job', + defaultMessage: 'Create outlier detection job', })} ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx deleted file mode 100644 index d9118fab267d2..0000000000000 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx +++ /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 { mount } from 'enzyme'; -import React from 'react'; -import { mountHook } from '../../../../../../../../../test_utils/enzyme_helpers'; - -import { CreateAnalyticsForm } from './create_analytics_form'; - -import { KibanaContext } from '../../../../../contexts/kibana'; -import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__/kibana_context_value'; - -import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; - -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, - INDEX_PATTERN_ILLEGAL_CHARACTERS: [], -})); - -const getMountedHook = () => - mountHook( - () => useCreateAnalyticsForm(), - ({ children }) => ( - {children} - ) - ); - -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - -describe('Data Frame Analytics: ', () => { - test('Minimal initialization', () => { - const { getLastHookValue } = getMountedHook(); - const props = getLastHookValue(); - const wrapper = mount(); - - const euiFormRows = wrapper.find('EuiFormRow'); - expect(euiFormRows.length).toBe(5); - - const row1 = euiFormRows.at(0); - expect(row1.find('label').text()).toBe('Job type'); - expect(row1.find('EuiText').text()).toBe('Outlier detection'); - expect(row1.find('EuiLink').text()).toBe('advanced editor'); - - const row2 = euiFormRows.at(1); - expect(row2.find('label').text()).toBe('Job ID'); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx index eb59bc9af164b..59a0ba426894a 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx @@ -7,6 +7,7 @@ import React, { Fragment, FC } from 'react'; import { + EuiButtonEmpty, EuiCallOut, EuiComboBox, EuiForm, @@ -19,7 +20,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { metadata } from 'ui/metadata'; import { INDEX_PATTERN_ILLEGAL_CHARACTERS } from 'ui/index_patterns'; @@ -75,36 +75,9 @@ export const CreateAnalyticsForm: FC = ({ actions, sta ))} {!isJobCreated && ( - - - - ), - }} - /> - } - > - - {i18n.translate('xpack.ml.dataframe.analytics.create.outlierDetectionText', { - defaultMessage: 'Outlier detection', - })} - - = ({ actions, sta > setFormState({ jobId: e.target.value })} aria-label={i18n.translate( @@ -148,7 +119,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta helpText={ !sourceIndexNameEmpty && !indexPatternsWithNumericFields.includes(sourceIndex) && - i18n.translate('xpack.ml.dataframe.analytics.create.sourceIndexHelpText', { + i18n.translate('xpack.ml.dataframe.stepDetailsForm.sourceIndexHelpText', { defaultMessage: 'This index pattern does not contain any numeric type fields. The analytics job may not be able to come up with any outliers.', }) @@ -272,6 +243,17 @@ export const CreateAnalyticsForm: FC = ({ actions, sta onChange={() => setFormState({ createIndexPattern: !createIndexPattern })} /> + + {i18n.translate('xpack.ml.dataframe.analytics.create.switchToAdvancedEditorButton', { + defaultMessage: 'Switch to advanced editor', + })} + + + {i18n.translate('xpack.ml.dataframe.analytics.create.switchToAdvancedEditorHelpText', { + defaultMessage: + 'Note you cannot switch back to this form once the advanced editor is enabled.', + })} + )} diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.test.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.test.tsx deleted file mode 100644 index 7d505a86d08a9..0000000000000 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.test.tsx +++ /dev/null @@ -1,45 +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 { mount } from 'enzyme'; -import React from 'react'; -import { mountHook } from '../../../../../../../../../test_utils/enzyme_helpers'; - -import { CreateAnalyticsModal } from './create_analytics_modal'; - -import { KibanaContext } from '../../../../../contexts/kibana'; -import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__/kibana_context_value'; - -import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; - -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, - INDEX_PATTERN_ILLEGAL_CHARACTERS: [], -})); - -const getMountedHook = () => - mountHook( - () => useCreateAnalyticsForm(), - ({ children }) => ( - {children} - ) - ); - -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - -describe('Data Frame Analytics: ', () => { - test('Minimal initialization', () => { - const { getLastHookValue } = getMountedHook(); - const props = getLastHookValue(); - const wrapper = mount(); - - expect(wrapper.find('EuiModalHeaderTitle').text()).toBe('Create analytics job'); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.tsx index 5f735fff73536..05a5bf3524392 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_modal/create_analytics_modal.tsx @@ -43,7 +43,7 @@ export const CreateAnalyticsModal: FC = ({ {i18n.translate('xpack.ml.dataframe.analytics.create.modalHeaderTitle', { - defaultMessage: 'Create analytics job', + defaultMessage: 'Create outlier detection job', })} diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts index 46cf833610073..b74611fe9c77b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts @@ -4,34 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { merge } from 'lodash'; - -import { DataFrameAnalyticsConfig } from '../../../../common'; - import { ACTION } from './actions'; -import { reducer, validateAdvancedEditor } from './reducer'; +import { reducer } from './reducer'; import { getInitialState } from './state'; jest.mock('ui/index_patterns', () => ({ validateIndexPattern: () => true, })); -type SourceIndex = DataFrameAnalyticsConfig['source']['index']; - -const getMockState = (index: SourceIndex) => - merge(getInitialState(), { - form: { - jobIdEmpty: false, - jobIdValid: true, - jobIdExists: false, - createIndexPattern: false, - }, - jobConfig: { - source: { index }, - dest: { index: 'the-destination-index' }, - }, - }); - describe('useCreateAnalyticsForm', () => { test('reducer(): provide a minimum required valid job config, then reset.', () => { const initialState = getInitialState(); @@ -85,30 +65,4 @@ describe('useCreateAnalyticsForm', () => { }); expect(resetMessageState.requestMessages).toHaveLength(0); }); - - test('validateAdvancedEditor(): check index pattern variations', () => { - // valid single index pattern - expect(validateAdvancedEditor(getMockState('the-source-index')).isValid).toBe(true); - // valid array with one ES index pattern - expect(validateAdvancedEditor(getMockState(['the-source-index'])).isValid).toBe(true); - // valid array with two ES index patterns - expect( - validateAdvancedEditor(getMockState(['the-source-index-1', 'the-source-index-2'])).isValid - ).toBe(true); - // invalid comma-separated index pattern, this is only allowed in the simple form - // but not the advanced editor. - expect( - validateAdvancedEditor(getMockState('the-source-index-1,the-source-index-2')).isValid - ).toBe(false); - expect( - validateAdvancedEditor( - getMockState(['the-source-index-1,the-source-index-2', 'the-source-index-3']) - ).isValid - ).toBe(false); - // invalid formats ("fake" TS casting to get valid TS and be able to run the tests) - expect(validateAdvancedEditor(getMockState({} as SourceIndex)).isValid).toBe(false); - expect( - validateAdvancedEditor(getMockState((undefined as unknown) as SourceIndex)).isValid - ).toBe(false); - }); }); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index a52d88365d7ac..1c4aefcd26ced 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -16,45 +16,15 @@ import { isAnalyticsIdValid } from '../../../../common'; import { Action, ACTION } from './actions'; import { getInitialState, getJobConfigFromFormState, State } from './state'; -const getSourceIndexString = (state: State) => { - const { jobConfig } = state; - - const sourceIndex = idx(jobConfig, _ => _.source.index); - - if (typeof sourceIndex === 'string') { - return sourceIndex; - } - - if (Array.isArray(sourceIndex)) { - return sourceIndex.join(','); - } - - return ''; -}; - -export const validateAdvancedEditor = (state: State): State => { +const validateAdvancedEditor = (state: State): State => { const { jobIdEmpty, jobIdValid, jobIdExists, createIndexPattern } = state.form; const { jobConfig } = state; state.advancedEditorMessages = []; - const sourceIndexName = getSourceIndexString(state); + const sourceIndexName = idx(jobConfig, _ => _.source.index) || ''; const sourceIndexNameEmpty = sourceIndexName === ''; - // general check against Kibana index pattern names, but since this is about the advanced editor - // with support for arrays in the job config, we also need to check that each individual name - // doesn't include a comma if index names are supplied as an array. - // `validateIndexPattern()` returns a map of messages, we're only interested here if it's valid or not. - // If there are no messages, it means the index pattern is valid. - let sourceIndexNameValid = Object.keys(validateIndexPattern(sourceIndexName)).length === 0; - const sourceIndex = idx(jobConfig, _ => _.source.index); - if (sourceIndexNameValid) { - if (typeof sourceIndex === 'string') { - sourceIndexNameValid = !sourceIndex.includes(','); - } - if (Array.isArray(sourceIndex)) { - sourceIndexNameValid = !sourceIndex.some(d => d.includes(',')); - } - } + const sourceIndexNameValid = validateIndexPattern(sourceIndexName); const destinationIndexName = idx(jobConfig, _ => _.dest.index) || ''; const destinationIndexNameEmpty = destinationIndexName === ''; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts index ec70c54892a0e..f0d3bdf186994 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts @@ -21,13 +21,5 @@ describe('useCreateAnalyticsForm', () => { expect(idx(jobConfig, _ => _.source.index)).toBe('the-source-index'); expect(idx(jobConfig, _ => _.analyzed_fields.excludes)).toStrictEqual([]); expect(typeof idx(jobConfig, _ => _.analyzed_fields.includes)).toBe('undefined'); - - // test the conversion of comma-separated Kibana index patterns to ES array based index patterns - state.form.sourceIndex = 'the-source-index-1,the-source-index-2'; - const jobConfigSourceIndexArray = getJobConfigFromFormState(state.form); - expect(idx(jobConfigSourceIndexArray, _ => _.source.index)).toStrictEqual([ - 'the-source-index-1', - 'the-source-index-2', - ]); }); }); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index a4a0087c9d869..1aacab853f522 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -92,12 +92,7 @@ export const getJobConfigFromFormState = ( ): DeepPartial => { return { source: { - // If a Kibana index patterns includes commas, we need to split - // the into an array of indices to be in the correct format for - // the data frame analytics API. - index: formState.sourceIndex.includes(',') - ? formState.sourceIndex.split(',').map(d => d.trim()) - : formState.sourceIndex, + index: formState.sourceIndex, }, dest: { index: formState.destinationIndex, diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index 418eb75290e1e..dc4681169850a 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -105,7 +105,7 @@ export const useCreateAnalyticsForm = () => { message: i18n.translate( 'xpack.ml.dataframe.stepCreateForm.createDataFrameAnalyticsSuccessMessage', { - defaultMessage: 'Request to create data frame analytics {jobId} acknowledged.', + defaultMessage: 'Analytics job {jobId} created.', values: { jobId }, } ), @@ -273,7 +273,7 @@ export const useCreateAnalyticsForm = () => { message: i18n.translate( 'xpack.ml.dataframe.analytics.create.startDataFrameAnalyticsSuccessMessage', { - defaultMessage: 'Request to start data frame analytics {jobId} acknowledged.', + defaultMessage: 'Analytics job {jobId} started.', values: { jobId }, } ), diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts index dde4f8efc899c..27e453354cafb 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts @@ -27,7 +27,7 @@ export const deleteAnalytics = async (d: DataFrameAnalyticsListRow) => { await ml.dataFrameAnalytics.deleteDataFrameAnalytics(d.config.id); toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage', { - defaultMessage: 'Request to delete data frame analytics {analyticsId} acknowledged.', + defaultMessage: 'Data frame analytics {analyticsId} delete request acknowledged.', values: { analyticsId: d.config.id }, }) ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/start_analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/start_analytics.ts index da09c4842b843..5ce05c5ddbfce 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/start_analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/start_analytics.ts @@ -17,7 +17,7 @@ export const startAnalytics = async (d: DataFrameAnalyticsListRow) => { await ml.dataFrameAnalytics.startDataFrameAnalytics(d.config.id); toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.analyticsList.startAnalyticsSuccessMessage', { - defaultMessage: 'Request to start data frame analytics {analyticsId} acknowledged.', + defaultMessage: 'Data frame analytics {analyticsId} start request acknowledged.', values: { analyticsId: d.config.id }, }) ); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts index 44286c8b1f7dd..5af3e7e6696a8 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts @@ -24,7 +24,7 @@ export const stopAnalytics = async (d: DataFrameAnalyticsListRow) => { ); toastNotifications.addSuccess( i18n.translate('xpack.ml.dataframe.analyticsList.stopAnalyticsSuccessMessage', { - defaultMessage: 'Request to stop data frame analytics {analyticsId} acknowledged.', + defaultMessage: 'Data frame analytics {analyticsId} stop request acknowledged.', values: { analyticsId: d.config.id }, }) ); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/import_view/importer/importer.js b/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/import_view/importer/importer.js index a17c563b78dc3..d055a2ee09d6f 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/import_view/importer/importer.js +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/import_view/importer/importer.js @@ -10,8 +10,7 @@ import { chunk } from 'lodash'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; -const CHUNK_SIZE = 5000; -const MAX_CHUNK_CHAR_COUNT = 1000000; +const CHUNK_SIZE = 10000; const IMPORT_RETRIES = 5; export class Importer { @@ -22,7 +21,6 @@ export class Importer { this.data = []; this.docArray = []; - this.docSizeArray = []; } async initializeImport(index) { @@ -60,7 +58,7 @@ export class Importer { }; } - const chunks = createDocumentChunks(this.docArray); + const chunks = chunk(this.docArray, CHUNK_SIZE); const ingestPipeline = { id: pipelineId, @@ -88,18 +86,13 @@ export class Importer { }; while (resp.success === false && retries > 0) { - try { - resp = await ml.fileDatavisualizer.import(aggs); + resp = await ml.fileDatavisualizer.import(aggs); - if (retries < IMPORT_RETRIES) { - console.log(`Retrying import ${IMPORT_RETRIES - retries}`); - } - - retries--; - } catch (err) { - resp = { success: false, error: err }; - retries = 0; + if (retries < IMPORT_RETRIES) { + console.log(`Retrying import ${IMPORT_RETRIES - retries}`); } + + retries--; } if (resp.success) { @@ -159,32 +152,3 @@ function updatePipelineTimezone(ingestPipeline) { } } } - -function createDocumentChunks(docArray) { - const chunks = []; - // chop docArray into 5000 doc chunks - const tempChunks = chunk(docArray, CHUNK_SIZE); - - // loop over tempChunks and check that the total character length - // for each chunk is within the MAX_CHUNK_CHAR_COUNT. - // if the length is too long, split the chunk into smaller chunks - // based on how much larger it is than MAX_CHUNK_CHAR_COUNT - // note, each document is a different size, so dividing by charCountOfDocs - // only produces an average chunk size that should be smaller than the max length - for (let i = 0; i < tempChunks.length; i++) { - const docs = tempChunks[i]; - const numberOfDocs = docs.length; - - const charCountOfDocs = JSON.stringify(docs).length; - if (charCountOfDocs > MAX_CHUNK_CHAR_COUNT) { - // calculate new chunk size which should produce a chunk - // who's length is on average around MAX_CHUNK_CHAR_COUNT - const adjustedChunkSize = Math.floor((MAX_CHUNK_CHAR_COUNT / charCountOfDocs) * numberOfDocs); - const smallerChunks = chunk(docs, adjustedChunkSize); - chunks.push(...smallerChunks); - } else { - chunks.push(docs); - } - } - return chunks; -} diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js index 172c198509d28..8243f64f6114e 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js @@ -52,7 +52,6 @@ function ResultLinksUI({ jobs, intl }) { aria-label={openJobsInSingleMetricViewerText} className="results-button" isDisabled={(singleMetricEnabled === false || jobActionsDisabled === true)} - data-test-subj={`openJobsInSingleMetricViewer openJobsInSingleMetricViewer-${jobs[0].id}`} /> } @@ -66,7 +65,6 @@ function ResultLinksUI({ jobs, intl }) { aria-label={openJobsInAnomalyExplorerText} className="results-button" isDisabled={(jobActionsDisabled === true)} - data-test-subj={`openJobsInAnomalyExplorer openJobsInSingleAnomalyExplorer-${jobs[0].id}`} />
diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/extract_job_details.js index 40e0c4392d959..2d8fcb55d7f74 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/extract_job_details.js @@ -16,7 +16,6 @@ export function extractJobDetails(job) { } const general = { - id: 'general', title: i18n.translate('xpack.ml.jobsList.jobDetails.generalTitle', { defaultMessage: 'General' }), @@ -26,7 +25,6 @@ export function extractJobDetails(job) { const customUrl = { - id: 'customUrl', title: i18n.translate('xpack.ml.jobsList.jobDetails.customUrlsTitle', { defaultMessage: 'Custom URLs' }), @@ -38,7 +36,6 @@ export function extractJobDetails(job) { } const node = { - id: 'node', title: i18n.translate('xpack.ml.jobsList.jobDetails.nodeTitle', { defaultMessage: 'Node' }), @@ -50,7 +47,6 @@ export function extractJobDetails(job) { } const calendars = { - id: 'calendars', title: i18n.translate('xpack.ml.jobsList.jobDetails.calendarsTitle', { defaultMessage: 'Calendars' }), @@ -68,7 +64,6 @@ export function extractJobDetails(job) { } const detectors = { - id: 'detectors', title: i18n.translate('xpack.ml.jobsList.jobDetails.detectorsTitle', { defaultMessage: 'Detectors' }), @@ -86,7 +81,6 @@ export function extractJobDetails(job) { } const influencers = { - id: 'influencers', title: i18n.translate('xpack.ml.jobsList.jobDetails.influencersTitle', { defaultMessage: 'Influencers' }), @@ -95,7 +89,6 @@ export function extractJobDetails(job) { }; const analysisConfig = { - id: 'analysisConfig', title: i18n.translate('xpack.ml.jobsList.jobDetails.analysisConfigTitle', { defaultMessage: 'Analysis config' }), @@ -104,7 +97,6 @@ export function extractJobDetails(job) { }; const analysisLimits = { - id: 'analysisLimits', title: i18n.translate('xpack.ml.jobsList.jobDetails.analysisLimitsTitle', { defaultMessage: 'Analysis limits' }), @@ -113,7 +105,6 @@ export function extractJobDetails(job) { }; const dataDescription = { - id: 'dataDescription', title: i18n.translate('xpack.ml.jobsList.jobDetails.dataDescriptionTitle', { defaultMessage: 'Data description' }), @@ -122,7 +113,6 @@ export function extractJobDetails(job) { }; const datafeed = { - id: 'datafeed', title: i18n.translate('xpack.ml.jobsList.jobDetails.datafeedTitle', { defaultMessage: 'Datafeed' }), @@ -142,7 +132,6 @@ export function extractJobDetails(job) { } const counts = { - id: 'counts', title: i18n.translate('xpack.ml.jobsList.jobDetails.countsTitle', { defaultMessage: 'Counts' }), @@ -151,7 +140,6 @@ export function extractJobDetails(job) { }; const modelSizeStats = { - id: 'modelSizeStats', title: i18n.translate('xpack.ml.jobsList.jobDetails.modelSizeStatsTitle', { defaultMessage: 'Model size stats' }), @@ -160,7 +148,6 @@ export function extractJobDetails(job) { }; const datafeedTimingStats = { - id: 'datafeedTimingStats', title: i18n.translate('xpack.ml.jobsList.jobDetails.datafeedTimingStatsTitle', { defaultMessage: 'Timing stats' }), diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/job_details.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/job_details.js index 97e7c1b72e565..fc7d99c01a8cc 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_details/job_details.js @@ -51,9 +51,7 @@ class JobDetailsUI extends Component { const { job } = this.state; if (job === undefined) { return ( -
- -
+
); } else { @@ -77,43 +75,35 @@ class JobDetailsUI extends Component { const tabs = [{ id: 'job-settings', - 'data-test-subj': 'tab-job-settings', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.jobSettingsLabel', defaultMessage: 'Job settings' }), - content: , + content: , time: job.open_time }, { id: 'job-config', - 'data-test-subj': 'tab-job-config', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.jobConfigLabel', defaultMessage: 'Job config' }), - content: , + content: , }, { id: 'counts', - 'data-test-subj': 'tab-counts', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.countsLabel', defaultMessage: 'Counts' }), - content: , + content: , }, { id: 'json', - 'data-test-subj': 'tab-json', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.jsonLabel', defaultMessage: 'JSON' }), - content: , + content: , }, { id: 'job-messages', - 'data-test-subj': 'tab-job-messages', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.jobMessagesLabel', defaultMessage: 'Job messages' @@ -126,17 +116,15 @@ class JobDetailsUI extends Component { // Datafeed should be at index 2 in tabs array for full details tabs.splice(2, 0, { id: 'datafeed', - 'data-test-subj': 'tab-datafeed', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.datafeedLabel', defaultMessage: 'Datafeed' }), - content: , + content: , }); tabs.push({ id: 'datafeed-preview', - 'data-test-subj': 'tab-datafeed-preview', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.datafeedPreviewLabel', defaultMessage: 'Datafeed preview' @@ -144,7 +132,6 @@ class JobDetailsUI extends Component { content: , }, { id: 'forecasts', - 'data-test-subj': 'tab-forecasts', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.forecastsLabel', defaultMessage: 'Forecasts' @@ -156,7 +143,6 @@ class JobDetailsUI extends Component { if (mlAnnotationsEnabled && showFullDetails) { tabs.push({ id: 'annotations', - 'data-test-subj': 'tab-annotations', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobDetails.tabs.annotationsLabel', defaultMessage: 'Annotations' @@ -171,7 +157,7 @@ class JobDetailsUI extends Component { } return ( -
+

{section.title}

-
+
{ section.items.map((item, i) => ()) } @@ -78,7 +78,7 @@ export class JobDetailsPane extends Component { return ( -
+
{ sections @@ -100,6 +100,5 @@ export class JobDetailsPane extends Component { } JobDetailsPane.propTypes = { sections: PropTypes.array.isRequired, - 'data-test-subj': PropTypes.string, }; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_group/job_group.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_group/job_group.js index 9ba7aba9dc876..345807f0a016e 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_group/job_group.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_group/job_group.js @@ -16,7 +16,6 @@ export function JobGroup({ name }) { return (
), width: '3%' }, { field: 'id', - 'data-test-subj': 'id', name: intl.formatMessage({ id: 'xpack.ml.jobsList.idLabel', defaultMessage: 'ID' @@ -195,7 +193,6 @@ class JobsListUI extends Component { }), sortable: true, field: 'description', - 'data-test-subj': 'description', render: (description, item) => ( ), @@ -203,7 +200,6 @@ class JobsListUI extends Component { width: '20%' }, { field: 'processed_record_count', - 'data-test-subj': 'recordCount', name: intl.formatMessage({ id: 'xpack.ml.jobsList.processedRecordsLabel', defaultMessage: 'Processed records' @@ -215,7 +211,6 @@ class JobsListUI extends Component { width: '10%' }, { field: 'memory_status', - 'data-test-subj': 'memoryStatus', name: intl.formatMessage({ id: 'xpack.ml.jobsList.memoryStatusLabel', defaultMessage: 'Memory status' @@ -225,7 +220,6 @@ class JobsListUI extends Component { width: '5%' }, { field: 'jobState', - 'data-test-subj': 'jobState', name: intl.formatMessage({ id: 'xpack.ml.jobsList.jobStateLabel', defaultMessage: 'Job state' @@ -235,7 +229,6 @@ class JobsListUI extends Component { width: '8%' }, { field: 'datafeedState', - 'data-test-subj': 'datafeedState', name: intl.formatMessage({ id: 'xpack.ml.jobsList.datafeedStateLabel', defaultMessage: 'Datafeed state' @@ -278,7 +271,6 @@ class JobsListUI extends Component { }), truncateText: false, field: 'latestTimestampSortValue', - 'data-test-subj': 'latestTimestamp', sortable: true, render: (time, item) => ( @@ -331,7 +323,7 @@ class JobsListUI extends Component { return ( ({ - 'data-test-subj': `row row-${item.id}` - })} /> ); } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html index 1dc3aea215d93..2f577bbba5435 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type.html @@ -231,10 +231,11 @@
-
+
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/index.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/index.ts index 850f3259c582a..eca52c064ce67 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/index.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/index.ts @@ -9,7 +9,6 @@ export { SingleMetricJobCreator } from './single_metric_job_creator'; export { MultiMetricJobCreator } from './multi_metric_job_creator'; export { PopulationJobCreator } from './population_job_creator'; export { - JobCreatorType, isSingleMetricJobCreator, isMultiMetricJobCreator, isPopulationJobCreator, diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/type_guards.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/type_guards.ts index 194d30bb69026..9bba4981ea078 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/type_guards.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/type_guards.ts @@ -9,22 +9,20 @@ import { MultiMetricJobCreator } from './multi_metric_job_creator'; import { PopulationJobCreator } from './population_job_creator'; import { JOB_TYPE } from './util/constants'; -export type JobCreatorType = SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator; - export function isSingleMetricJobCreator( - jobCreator: JobCreatorType + jobCreator: SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator ): jobCreator is SingleMetricJobCreator { return jobCreator.type === JOB_TYPE.SINGLE_METRIC; } export function isMultiMetricJobCreator( - jobCreator: JobCreatorType + jobCreator: SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator ): jobCreator is MultiMetricJobCreator { return jobCreator.type === JOB_TYPE.MULTI_METRIC; } export function isPopulationJobCreator( - jobCreator: JobCreatorType + jobCreator: SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator ): jobCreator is PopulationJobCreator { return jobCreator.type === JOB_TYPE.POPULATION; } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/constants.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/constants.ts index 030b8c56c9334..faa04dc17c845 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/constants.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/constants.ts @@ -8,7 +8,6 @@ export enum JOB_TYPE { SINGLE_METRIC = 'single_metric', MULTI_METRIC = 'multi_metric', POPULATION = 'population', - ADVANCED = 'advanced', } export enum CREATED_BY_LABEL { diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts index acfc9fceb4e29..02b8b273614c1 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts @@ -13,8 +13,8 @@ import { } from '../../../../../../common/constants/aggregation_types'; import { EVENT_RATE_FIELD_ID } from '../../../../../../common/types/fields'; import { mlJobService } from '../../../../../services/job_service'; -import { JobCreatorType, isMultiMetricJobCreator, isPopulationJobCreator } from '../'; -import { CREATED_BY_LABEL, JOB_TYPE } from './constants'; +import { JobCreator } from '../job_creator'; +import { CREATED_BY_LABEL } from './constants'; // populate the detectors with Field and Agg objects loaded from the job capabilities service export function getRichDetectors(job: Job, datafeed: Datafeed) { @@ -125,53 +125,34 @@ export function isSparseDataJob(job: Job, datafeed: Datafeed): boolean { return false; } -function stashCombinedJob( - jobCreator: JobCreatorType, - skipTimeRangeStep: boolean = false, - advanced: boolean = false -) { - const combinedJob = { +function stashCombinedJob(jobCreator: JobCreator, skipTimeRangeStep: boolean = false) { + mlJobService.tempJobCloningObjects.job = { ...jobCreator.jobConfig, datafeed_config: jobCreator.datafeedConfig, }; - if (advanced === true) { - mlJobService.currentJob = combinedJob; - } else { - mlJobService.tempJobCloningObjects.job = combinedJob; - } if (skipTimeRangeStep === true) { mlJobService.tempJobCloningObjects.skipTimeRangeStep = true; } } -export function convertToMultiMetricJob(jobCreator: JobCreatorType) { +export function convertToMultiMetricJob(jobCreator: JobCreator) { jobCreator.createdBy = CREATED_BY_LABEL.MULTI_METRIC; - stashCombinedJob(jobCreator, true, false); + stashCombinedJob(jobCreator, true); - window.location.href = window.location.href.replace( - JOB_TYPE.SINGLE_METRIC, - JOB_TYPE.MULTI_METRIC - ); + window.location.href = window.location.href.replace('single_metric', 'multi_metric'); } -export function convertToAdvancedJob(jobCreator: JobCreatorType) { +export function convertToAdvancedJob(jobCreator: JobCreator) { jobCreator.createdBy = null; - stashCombinedJob(jobCreator, false, true); - - let jobType = JOB_TYPE.SINGLE_METRIC; - if (isMultiMetricJobCreator(jobCreator)) { - jobType = JOB_TYPE.MULTI_METRIC; - } else if (isPopulationJobCreator(jobCreator)) { - jobType = JOB_TYPE.POPULATION; - } + stashCombinedJob(jobCreator); - window.location.href = window.location.href.replace(jobType, JOB_TYPE.ADVANCED); + window.location.href = window.location.href.replace('multi_metric', 'advanced'); } -export function resetJob(jobCreator: JobCreatorType) { +export function resetJob(jobCreator: JobCreator) { jobCreator.jobId = ''; - stashCombinedJob(jobCreator, true, false); + stashCombinedJob(jobCreator, true); window.location.href = '#/jobs/new_job'; } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts index b801494199290..a11b1aadfe6ae 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_runner/job_runner.ts @@ -12,9 +12,6 @@ import { DatafeedId, JobId } from '../job_creator/configs'; import { DATAFEED_STATE } from '../../../../../common/constants/states'; const REFRESH_INTERVAL_MS = 100; -const TARGET_PROGRESS_DELTA = 2; -const REFRESH_RATE_ADJUSTMENT_DELAY_MS = 2000; - type Progress = number; export type ProgressSubscriber = (progress: number) => void; @@ -33,9 +30,6 @@ export class JobRunner { }; private _subscribers: ProgressSubscriber[]; - private _datafeedStartTime: number = 0; - private _performRefreshRateAdjustment: boolean = false; - constructor(jobCreator: JobCreator) { this._jobId = jobCreator.jobId; this._datafeedId = jobCreator.datafeedId; @@ -77,7 +71,6 @@ export class JobRunner { pollProgress: boolean ): Promise { try { - this._datafeedStartTime = Date.now(); // link the _subscribers list from the JobCreator // to the progress BehaviorSubject. const subscriptions = @@ -97,7 +90,6 @@ export class JobRunner { const check = async () => { const { isRunning, progress } = await this.getProgress(); - this._adjustRefreshInterval(progress); this._percentageComplete = progress; this._progress$.next(this._percentageComplete); @@ -126,33 +118,6 @@ export class JobRunner { } } - private _adjustRefreshInterval(progress: number) { - if (this._performRefreshRateAdjustment === false) { - // for the first couple of seconds of the job running, don't - // adjust the refresh interval - const timeDeltaMs = Date.now() - this._datafeedStartTime; - if (timeDeltaMs > REFRESH_RATE_ADJUSTMENT_DELAY_MS) { - this._performRefreshRateAdjustment = true; - } else { - return; - } - } - - const progressDelta = progress - this._percentageComplete; - if (progressDelta !== 0) { - // adjust the refresh interval so that it produces a change in percentage - // that is close to the target - this._refreshInterval = Math.floor( - this._refreshInterval * (TARGET_PROGRESS_DELTA / progressDelta) - ); - - // don't let the interval fall below the initial default. - if (this._refreshInterval < REFRESH_INTERVAL_MS) { - this._refreshInterval = REFRESH_INTERVAL_MS; - } - } - } - public async startDatafeed() { return await this._startDatafeed(this._start, this._end, true); } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_validator/job_validator.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_validator/job_validator.ts index 669dc4d231aeb..2d5680f1a61a0 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_validator/job_validator.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_validator/job_validator.ts @@ -6,7 +6,7 @@ import { basicJobValidation } from '../../../../../common/util/job_utils'; import { newJobLimits } from '../../../new_job/utils/new_job_defaults'; -import { JobCreatorType } from '../job_creator'; +import { JobCreator } from '../job_creator'; import { populateValidationMessages, checkForExistingJobAndGroupIds } from './util'; import { ExistingJobsAndGroups } from '../../../../services/job_service'; @@ -34,7 +34,7 @@ export interface BasicValidations { } export class JobValidator { - private _jobCreator: JobCreatorType; + private _jobCreator: JobCreator; private _validationSummary: ValidationSummary; private _lastJobConfig: string; private _validateTimeout: NodeJS.Timeout; @@ -47,7 +47,7 @@ export class JobValidator { duplicateDetectors: { valid: true }, }; - constructor(jobCreator: JobCreatorType, existingJobsAndGroups: ExistingJobsAndGroups) { + constructor(jobCreator: JobCreator, existingJobsAndGroups: ExistingJobsAndGroups) { this._jobCreator = jobCreator; this._lastJobConfig = this._jobCreator.formattedJobJson; this._validationSummary = { diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/results_loader/results_loader.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/results_loader/results_loader.ts index 0daf23ca7f3a8..76bc030b4cad5 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/results_loader/results_loader.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/results_loader/results_loader.ts @@ -6,7 +6,13 @@ import { BehaviorSubject } from 'rxjs'; import { parseInterval } from 'ui/utils/parse_interval'; -import { JobCreatorType, isMultiMetricJobCreator, isPopulationJobCreator } from '../job_creator'; +import { + SingleMetricJobCreator, + MultiMetricJobCreator, + isMultiMetricJobCreator, + PopulationJobCreator, + isPopulationJobCreator, +} from '../job_creator'; import { mlResultsService, ModelPlotOutputResults } from '../../../../services/results_service'; import { MlTimeBuckets } from '../../../../util/ml_time_buckets'; import { getSeverityType } from '../../../../../common/util/anomaly_utils'; @@ -51,10 +57,12 @@ const LAST_UPDATE_DELAY_MS = 500; export type ResultsSubscriber = (results: Results) => void; +type AnyJobCreator = SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator; + export class ResultsLoader { private _results$: BehaviorSubject; private _resultsSearchRunning = false; - private _jobCreator: JobCreatorType; + private _jobCreator: AnyJobCreator; private _chartInterval: MlTimeBuckets; private _lastModelTimeStamp: number = 0; private _lastResultsTimeout: any = null; @@ -69,7 +77,7 @@ export class ResultsLoader { private _detectorSplitFieldFilters: SplitFieldWithValue | null = null; private _splitFieldFiltersLoaded: boolean = false; - constructor(jobCreator: JobCreatorType, chartInterval: MlTimeBuckets, chartLoader: ChartLoader) { + constructor(jobCreator: AnyJobCreator, chartInterval: MlTimeBuckets, chartLoader: ChartLoader) { this._jobCreator = jobCreator; this._chartInterval = chartInterval; this._results$ = new BehaviorSubject(this._results); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/common/settings.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/common/settings.ts index bcc84704e14cd..08d94a3a2b4f0 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/common/settings.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/charts/common/settings.ts @@ -8,7 +8,9 @@ import chrome from 'ui/chrome'; import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { - JobCreatorType, + SingleMetricJobCreator, + MultiMetricJobCreator, + PopulationJobCreator, isMultiMetricJobCreator, isPopulationJobCreator, } from '../../../../common/job_creator'; @@ -57,7 +59,10 @@ export const seriesStyle = { }, }; -export function getChartSettings(jobCreator: JobCreatorType, chartInterval: MlTimeBuckets) { +export function getChartSettings( + jobCreator: SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator, + chartInterval: MlTimeBuckets +) { const cs = { ...defaultChartSettings, intervalMs: chartInterval.getInterval().asMilliseconds(), diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_creator_context.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_creator_context.ts index 76f9b66f42305..8e242adc6fee7 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_creator_context.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_creator_context.ts @@ -7,7 +7,11 @@ import { createContext } from 'react'; import { Field, Aggregation } from '../../../../../common/types/fields'; import { MlTimeBuckets } from '../../../../util/ml_time_buckets'; -import { JobCreatorType, SingleMetricJobCreator } from '../../common/job_creator'; +import { + SingleMetricJobCreator, + MultiMetricJobCreator, + PopulationJobCreator, +} from '../../common/job_creator'; import { ChartLoader } from '../../common/chart_loader'; import { ResultsLoader } from '../../common/results_loader'; import { JobValidator } from '../../common/job_validator'; @@ -16,7 +20,7 @@ import { ExistingJobsAndGroups } from '../../../../services/job_service'; export interface JobCreatorContextValue { jobCreatorUpdated: number; jobCreatorUpdate: () => void; - jobCreator: JobCreatorType; + jobCreator: SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator; chartLoader: ChartLoader; resultsLoader: ResultsLoader; chartInterval: MlTimeBuckets; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/json_flyout.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/json_flyout.tsx index 1658d28b352b2..7d212078ac690 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/json_flyout.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/json_flyout.tsx @@ -17,11 +17,11 @@ import { EuiFlyoutBody, EuiSpacer, } from '@elastic/eui'; -import { JobCreatorType } from '../../../common/job_creator'; +import { JobCreator } from '../../../common/job_creator'; import { MLJobEditor } from '../../../../jobs_list/components/ml_job_editor'; interface Props { - jobCreator: JobCreatorType; + jobCreator: JobCreator; closeFlyout: () => void; } export const JsonFlyout: FC = ({ jobCreator, closeFlyout }) => { diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx index e09a8f0708b4a..5217acf72bc5d 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/summary_step/summary.tsx @@ -16,7 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { toastNotifications } from 'ui/notify'; -import { PreviousButton } from '../wizard_nav'; +import { WizardNav } from '../wizard_nav'; import { WIZARD_STEPS, StepProps } from '../step_types'; import { JobCreatorContext } from '../job_creator_context'; import { JobRunner } from '../../../common/job_runner'; @@ -97,30 +97,22 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => + {progress === 0 && setCurrentStep(WIZARD_STEPS.VALIDATION)} />} {progress < 100 && ( - - - setCurrentStep(WIZARD_STEPS.VALIDATION)} - previousActive={creatingJob === false && isValid === true} + + + - - - - - - - + + )} {creatingJob === false && ( diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/index.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/index.ts index 2a30013f06ec9..5d9db25730fce 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/index.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { WizardNav, PreviousButton, NextButton } from './wizard_nav'; +export { WizardNav } from './wizard_nav'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/wizard_nav.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/wizard_nav.tsx index acbb25264b7a2..9d8a9ead74fa5 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/wizard_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/wizard_nav/wizard_nav.tsx @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment } from 'react'; +import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; interface StepsNavProps { previousActive?: boolean; @@ -23,42 +23,36 @@ export const WizardNav: FC = ({ next, nextActive = true, }) => ( - - - - {previous && ( - - - - )} - {next && ( - - - - )} - - - -); - -export const PreviousButton: FC = ({ previous, previousActive = true }) => ( - - - -); - -export const NextButton: FC = ({ next, nextActive = true }) => ( - - - + + + {previous && ( + + + + + + )} + {next && ( + + + + + + )} + ); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx index a6ce188ddbad9..5a00171e6ebed 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx @@ -19,19 +19,22 @@ import { JobDetailsStep } from '../components/job_details_step'; import { ValidationStep } from '../components/validation_step'; import { SummaryStep } from '../components/summary_step'; import { MlTimeBuckets } from '../../../../util/ml_time_buckets'; -import { useKibanaContext } from '../../../../contexts/kibana'; import { JobCreatorContext, JobCreatorContextValue } from '../components/job_creator_context'; import { ExistingJobsAndGroups } from '../../../../services/job_service'; -import { JobCreatorType } from '../../common/job_creator'; +import { + SingleMetricJobCreator, + MultiMetricJobCreator, + PopulationJobCreator, +} from '../../common/job_creator'; import { ChartLoader } from '../../common/chart_loader'; import { ResultsLoader } from '../../common/results_loader'; import { JobValidator } from '../../common/job_validator'; import { newJobCapsService } from '../../../../services/new_job_capabilities_service'; interface Props { - jobCreator: JobCreatorType; + jobCreator: SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator; chartLoader: ChartLoader; resultsLoader: ResultsLoader; chartInterval: MlTimeBuckets; @@ -49,7 +52,6 @@ export const Wizard: FC = ({ existingJobsAndGroups, skipTimeRangeStep = false, }) => { - const kibanaContext = useKibanaContext(); const [jobCreatorUpdated, setJobCreatorUpdate] = useReducer<(s: number) => number>(s => s + 1, 0); const jobCreatorUpdate = () => setJobCreatorUpdate(jobCreatorUpdated); @@ -175,24 +177,6 @@ export const Wizard: FC = ({ }, ]; - function getSummaryStepTitle() { - if (kibanaContext.currentSavedSearch.id !== undefined) { - return i18n.translate('xpack.ml.newJob.wizard.stepComponentWrapper.summaryTitleSavedSearch', { - defaultMessage: 'New job from saved search {title}', - values: { title: kibanaContext.currentSavedSearch.title }, - }); - } else if (kibanaContext.currentIndexPattern.id !== undefined) { - return i18n.translate( - 'xpack.ml.newJob.wizard.stepComponentWrapper.summaryTitleIndexPattern', - { - defaultMessage: 'New job from index pattern {title}', - values: { title: kibanaContext.currentIndexPattern.title }, - } - ); - } - return ''; - } - return ( @@ -259,7 +243,12 @@ export const Wizard: FC = ({ )} {currentStep === WIZARD_STEPS.SUMMARY && ( - {getSummaryStepTitle()} + + <FormattedMessage + id="xpack.ml.newJob.wizard.stepComponentWrapper.summaryTitle" + defaultMessage="Summary" + /> + { diff --git a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js index ab29e0ba303a9..5441d6ccd74c0 100644 --- a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js @@ -22,6 +22,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiButton, EuiSelect, EuiSpacer, EuiText, @@ -169,14 +170,10 @@ export class TimeSeriesExplorer extends React.Component { detectorIndexChangeHandler = (e) => { const id = e.target.value; if (id !== undefined) { - this.setState({ detectorId: id }, () => { - this.updateControlsForDetector( - () => this.loadEntityValues( - () => this.saveSeriesPropertiesAndRefresh() - ) - ); - }); + this.setState({ detectorId: id }); } + this.updateControlsForDetector(); + this.loadEntityValues(); }; toggleShowAnnotationsHandler = () => { @@ -325,7 +322,7 @@ export class TimeSeriesExplorer extends React.Component { } return stateEntity; }) - }), () => this.saveSeriesPropertiesAndRefresh()); + })); }; loadAnomaliesTableData = (earliestMs, latestMs) => { @@ -381,7 +378,7 @@ export class TimeSeriesExplorer extends React.Component { }); } - loadEntityValues = (callback = () => {}) => { + loadEntityValues = () => { const { timefilter } = this.props; const { detectorId, entities, selectedJob } = this.state; @@ -414,7 +411,7 @@ export class TimeSeriesExplorer extends React.Component { } return entity; }) - }, callback); + }); } }); } @@ -467,10 +464,6 @@ export class TimeSeriesExplorer extends React.Component { } refresh = () => { - if (this.state.loading) { - return; - } - const { appStateHandler, timefilter } = this.props; const { detectorId: currentDetectorId, @@ -660,7 +653,7 @@ export class TimeSeriesExplorer extends React.Component { }); } - updateControlsForDetector = (callback = () => {}) => { + updateControlsForDetector = () => { const { appStateHandler } = this.props; const { detectorId, selectedJob } = this.state; // Update the entity dropdown control(s) according to the partitioning fields for the selected detector. @@ -691,7 +684,7 @@ export class TimeSeriesExplorer extends React.Component { entities.push({ fieldName: byFieldName, fieldValue: byFieldValue }); } - this.setState({ entities }, callback); + this.setState({ entities }); } loadForJobId(jobId, jobs) { @@ -746,16 +739,16 @@ export class TimeSeriesExplorer extends React.Component { this.setState( { detectorId, detectors, selectedJob }, () => { - this.updateControlsForDetector(() => { - // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. - mlFieldFormatService.populateFormats([jobId], getIndexPatterns()) - .catch((err) => { console.log('Error populating field formats:', err); }) + this.updateControlsForDetector(); + + // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. + mlFieldFormatService.populateFormats([jobId], getIndexPatterns()) + .catch((err) => { console.log('Error populating field formats:', err); }) // Load the data - if the FieldFormats failed to populate // the default formatting will be used for metric values. - .then(() => { - this.refresh(); - }); - }); + .then(() => { + this.refresh(); + }); } ); } @@ -1031,6 +1024,19 @@ export class TimeSeriesExplorer extends React.Component { /> ); })} + + + + {i18n.translate('xpack.ml.timeSeriesExplorer.refreshButtonAriaLabel', { + defaultMessage: 'Refresh' + })} + + + { + mergeMap(({ driver$, exit$, message$, consoleMessage$ }) => { + message$.subscribe((line: string) => { + logger.debug(line, ['browser']); + }); + consoleMessage$.subscribe((line: string) => { + logger.debug(line, ['browserConsole']); + }); const screenshot$ = driver$.pipe( mergeMap( (browser: HeadlessBrowser) => openUrl(browser, url, conditionalHeaders, logger), diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js index 3d2c7c3b0ae3c..c39bb4ac2dde1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js @@ -10,9 +10,9 @@ import sinon from 'sinon'; import nodeCrypto from '@elastic/node-crypto'; import { CancellationToken } from '../../../../common/cancellation_token'; -import { FieldFormat } from '../../../../../../../../src/legacy/ui/field_formats/field_format'; -import { FieldFormatsService } from '../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; -import { createStringFormat } from '../../../../../../../../src/legacy/core_plugins/kibana/common/field_formats/types/string'; +import { FieldFormat } from '../../../../../../../../src/legacy/ui/field_formats/field_format.js'; +import { FieldFormatsService } from '../../../../../../../../src/legacy/ui/field_formats/field_formats_service.js'; +import { createStringFormat } from '../../../../../../../../src/legacy/core_plugins/kibana/common/field_formats/types/string.js'; import { executeJobFactory } from '../execute_job'; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js index acba46c5dd31b..164f2f96655e7 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js @@ -6,10 +6,10 @@ import expect from '@kbn/expect'; -import { FieldFormat } from '../../../../../../../../../src/legacy/ui/field_formats/field_format'; -import { FieldFormatsService } from '../../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; -import { createBytesFormat } from '../../../../../../../../../src/legacy/core_plugins/kibana/common/field_formats/types/bytes'; -import { createNumberFormat } from '../../../../../../../../../src/legacy/core_plugins/kibana/common/field_formats/types/number'; +import { FieldFormat } from '../../../../../../../../../src/legacy/ui/field_formats/field_format.js'; +import { FieldFormatsService } from '../../../../../../../../../src/legacy/ui/field_formats/field_formats_service.js'; +import { createBytesFormat } from '../../../../../../../../../src/legacy/core_plugins/kibana/common/field_formats/types/bytes.js'; +import { createNumberFormat } from '../../../../../../../../../src/legacy/core_plugins/kibana/common/field_formats/types/number.js'; import { fieldFormatMapFactory } from '../field_format_map'; diff --git a/x-pack/legacy/plugins/reporting/index.js b/x-pack/legacy/plugins/reporting/index.js index 6920028e4573d..6acf77f1dc726 100644 --- a/x-pack/legacy/plugins/reporting/index.js +++ b/x-pack/legacy/plugins/reporting/index.js @@ -130,11 +130,6 @@ export const reporting = (kibana) => { }).default(), maxScreenshotDimension: Joi.number().integer().default(1950) }).default() - }).default(), - maxAttempts: Joi.number().integer().greater(0).when('$dist', { - is: true, - then: Joi.default(3), - otherwise: Joi.default(1), }).default() }).default(), csv: Joi.object({ diff --git a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index c1812cb40cf23..cc182b817339f 100644 --- a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -15,6 +15,7 @@ import { EuiIcon } from '@elastic/eui'; import { Action, + ActionContext, ViewMode, IncompatibleActionError, IEmbeddable, @@ -36,12 +37,7 @@ function isSavedSearchEmbeddable( ): embeddable is ISearchEmbeddable { return embeddable.type === SEARCH_EMBEDDABLE_TYPE; } - -interface ActionContext { - embeddable: ISearchEmbeddable; -} - -class GetCsvReportPanelAction extends Action { +class GetCsvReportPanelAction extends Action { private isDownloading: boolean; public readonly type = CSV_REPORTING_ACTION; @@ -86,7 +82,7 @@ class GetCsvReportPanelAction extends Action { return embeddable.getInput().viewMode !== ViewMode.EDIT && embeddable.type === 'search'; }; - public execute = async (context: ActionContext) => { + public execute = async (context: ActionContext) => { const { embeddable } = context; if (!isSavedSearchEmbeddable(embeddable)) { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 01454f2a9fae0..6df8b37dd1d30 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -6,16 +6,10 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import { - Browser, - Page, - LaunchOptions, - ConsoleMessage, - Request as PuppeteerRequest, -} from 'puppeteer'; +import { Browser, Page, LaunchOptions } from 'puppeteer'; import rimraf from 'rimraf'; import * as Rx from 'rxjs'; -import { ignoreElements, mergeMap, tap } from 'rxjs/operators'; +import { map, share, mergeMap, filter, partition, ignoreElements, tap } from 'rxjs/operators'; import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber'; import { puppeteerLaunch } from '../puppeteer'; @@ -31,6 +25,10 @@ interface IBrowserConfig { [key: string]: any; } +const compactWhitespace = (str: string) => { + return str.replace(/\s+/, ' '); +}; + export class HeadlessChromiumDriverFactory { private binaryPath: binaryPath; private logger: Logger; @@ -73,10 +71,9 @@ export class HeadlessChromiumDriverFactory { TZ: browserTimezone, }, } as LaunchOptions).catch((error: Error) => { - logger.error( - `The Reporting plugin encountered issues launching Chromium in a self-test. You may have trouble generating reports.` + logger.warning( + `The Reporting plugin encountered issues launching Chromium in a self-test. You may have trouble generating reports: [${error}]` ); - logger.error(error); logger.warning(`See Chromium's log output at "${getChromeLogLocation(this.binaryPath)}"`); return null; }); @@ -90,6 +87,8 @@ export class HeadlessChromiumDriverFactory { browserTimezone: string; }): Rx.Observable<{ driver$: Rx.Observable; + consoleMessage$: Rx.Observable; + message$: Rx.Observable; exit$: Rx.Observable; }> { return Rx.Observable.create(async (observer: InnerSubscriber) => { @@ -158,24 +157,104 @@ export class HeadlessChromiumDriverFactory { .subscribe(observer) ); - // taps the browser log streams and combine them to Kibana logs - this.getBrowserLogger(page).subscribe(); - this.getProcessLogger(browser).subscribe(); + // Register with a few useful puppeteer event handlers: + // https://pptr.dev/#?product=Puppeteer&version=v1.10.0&show=api-event-error + // https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page + + const stderr$ = Rx.fromEvent(page as NodeJS.EventEmitter, 'console').pipe( + filter((line: any) => line._type === 'error'), + map((line: any) => line._text), + share() + ); + + const [consoleMessage$, message$] = partition( + (msg: string) => !!msg.match(/\[\d+\/\d+.\d+:\w+:CONSOLE\(\d+\)\]/) + )(stderr$); + + const driver$ = Rx.of( + new HeadlessChromiumDriver(page, { inspect: this.browserConfig.inspect }) + ); - const driver$ = Rx.of(new HeadlessChromiumDriver(page, { inspect: this.browserConfig.inspect })); // prettier-ignore + const processError$ = Rx.fromEvent(page, 'error').pipe( + mergeMap(err => Rx.throwError(new Error(`Unable to spawn Chromium: [${err}]`))) + ); - const exit$ = this.getPageExit(browser, page); + const processPageError$ = Rx.fromEvent(page, 'pageerror').pipe( + mergeMap(err => Rx.throwError(new Error(`Uncaught exception within the page: [${err}]`))) + ); - observer.next({ driver$, exit$ }); + const processRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( + mergeMap((req: any) => { + const failure = req.failure && req.failure(); + if (failure) { + return Rx.throwError( + new Error(`Request to [${req.url()}] failed! [${failure.errorText}]`) + ); + } + return Rx.throwError(new Error(`Unknown failure! [${JSON.stringify(req)}]`)); + }) + ); + + const processExit$ = Rx.fromEvent(browser, 'disconnected').pipe( + mergeMap(code => + Rx.throwError(new Error(`Chromium exited with: [${JSON.stringify({ code })}]`)) + ) + ); + + const nssError$ = message$.pipe( + filter((line: string) => line.includes('error while loading shared libraries: libnss3.so')), + mergeMap(() => Rx.throwError(new Error(`You must install nss for Reporting to work`))) + ); + + const fontError$ = message$.pipe( + filter((line: string) => + line.includes('Check failed: InitDefaultFont(). Could not find the default font') + ), + mergeMap(() => + Rx.throwError(new Error('You must install freetype and ttf-font for Reporting to work')) + ) + ); + const noUsableSandbox$ = message$.pipe( + filter((line: string) => line.includes('No usable sandbox! Update your kernel')), + mergeMap(() => + Rx.throwError( + new Error( + compactWhitespace(` + Unable to use Chromium sandbox. This can be disabled at your own risk with + 'xpack.reporting.capture.browser.chromium.disableSandbox' + `) + ) + ) + ) + ); + + const exit$ = Rx.merge( + processError$, + processPageError$, + processRequestFailed$, + processExit$, + nssError$, + fontError$, + noUsableSandbox$ + ); + + observer.next({ + driver$, + consoleMessage$, + message$, + exit$, + }); + + const factoryLogger = this.logger.clone(['chromium-driver-factory']); // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium observer.add(() => { - this.logger.debug(`deleting chromium user data directory at [${userDataDir}]`); + factoryLogger.debug(`deleting chromium user data directory at [${userDataDir}]`); // the unsubscribe function isn't `async` so we're going to make our best effort at // deleting the userDataDir and if it fails log an error. rimraf(userDataDir, err => { if (err) { - return this.logger.error( + return factoryLogger.error( `error deleting user data directory at [${userDataDir}]: [${err}]` ); } @@ -183,71 +262,4 @@ export class HeadlessChromiumDriverFactory { }); }); } - - getBrowserLogger(page: Page): Rx.Observable { - return Rx.fromEvent(page as NodeJS.EventEmitter, 'console').pipe( - tap((line: ConsoleMessage) => { - if (line.type() === 'error') { - this.logger.error(line.text(), ['headless-browser-console']); - } else { - this.logger.debug(line.text(), [line.type(), 'headless-browser-console']); - } - }) - ); - } - - getProcessLogger(browser: Browser): Rx.Observable { - const childProcess = browser.process(); - // NOTE: The browser driver can not observe stdout and stderr of the child process - // Puppeteer doesn't give a handle to the original ChildProcess object - // See https://github.com/GoogleChrome/puppeteer/issues/1292#issuecomment-521470627 - - // just log closing of the process - const processClose$: Rx.Observable = Rx.fromEvent(childProcess, 'close').pipe( - tap(() => { - this.logger.debug('child process closed', ['headless-browser-process']); - }) - ); - - return processClose$; // ideally, this would also merge with observers for stdout and stderr - } - - getPageExit(browser: Browser, page: Page): Rx.Observable { - const pageError$: Rx.Observable = Rx.fromEvent(page, 'error').pipe( - mergeMap((err: Error) => Rx.throwError(err)) - ); - - const uncaughtExceptionPageError$: Rx.Observable = Rx.fromEvent(page, 'pageerror').pipe( - mergeMap((err: Error) => Rx.throwError(err)) - ); - - const pageRequestFailed$: Rx.Observable = Rx.fromEvent(page, 'requestfailed').pipe( - mergeMap((req: PuppeteerRequest) => { - const failure = req.failure && req.failure(); - if (failure) { - return Rx.throwError( - new Error(`Request to [${req.url()}] failed! [${failure.errorText}]`) - ); - } - return Rx.throwError(new Error(`Unknown failure!`)); - }) - ); - - const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe( - mergeMap(() => - Rx.throwError( - new Error( - `Puppeteer was disconnected from the Chromium instance! Chromium has closed or crashed.` - ) - ) - ) - ); - - return Rx.merge( - pageError$, - uncaughtExceptionPageError$, - pageRequestFailed$, - browserDisconnect$ - ); - } } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts index 8e3ab55a71f35..fee621d293c73 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts @@ -14,21 +14,21 @@ export const paths = { platforms: ['darwin', 'freebsd', 'openbsd'], archiveFilename: 'chromium-312d84c-darwin.zip', archiveChecksum: '020303e829745fd332ae9b39442ce570', - binaryChecksum: '5cdec11d45a0eddf782bed9b9f10319f', + rawChecksum: '101dfea297c5818a7a3f3317a99dde02', binaryRelativePath: 'headless_shell-darwin/headless_shell', }, { platforms: ['linux'], archiveFilename: 'chromium-312d84c-linux.zip', archiveChecksum: '15ba9166a42f93ee92e42217b737018d', - binaryChecksum: 'c7fe36ed3e86a6dd23323be0a4e8c0fd', + rawChecksum: '3455db62ea4bd2d6e891e9155313305a', binaryRelativePath: 'headless_shell-linux/headless_shell', }, { platforms: ['win32'], archiveFilename: 'chromium-312d84c-windows.zip', archiveChecksum: '3e36adfb755dacacc226ed5fd6b43105', - binaryChecksum: '9913e431fbfc7dfcd958db74ace4d58b', + rawChecksum: 'ec7aa6cfecb172129474b447311275ec', binaryRelativePath: 'headless_shell-windows\\headless_shell.exe', }, ], diff --git a/x-pack/legacy/plugins/reporting/server/browsers/install.ts b/x-pack/legacy/plugins/reporting/server/browsers/install.ts index 0f2ab28d2b75e..ed2fc3df459b8 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/install.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/install.ts @@ -38,9 +38,9 @@ export async function installBrowser( } const binaryPath = path.join(installsPath, pkg.binaryRelativePath); - const binaryChecksum = await md5(binaryPath).catch(() => ''); + const rawChecksum = await md5(binaryPath).catch(() => ''); - if (binaryChecksum !== pkg.binaryChecksum) { + if (rawChecksum !== pkg.rawChecksum) { const archive = path.join(browser.paths.archivesPath, pkg.archiveFilename); logger.debug(`Extracting [${archive}] to [${binaryPath}]`); await extract(archive, installsPath); diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index 98124e33a8fc5..9c202b4d6ce44 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -23,7 +23,6 @@ function enqueueJobFn(server: KbnServer) { const config = server.config(); const queueConfig = config.get('xpack.reporting.queue'); const browserType = config.get('xpack.reporting.capture.browser.type'); - const maxAttempts = config.get('xpack.reporting.capture.maxAttempts'); const exportTypesRegistry = server.plugins.reporting.exportTypesRegistry; return async function enqueueJob( @@ -43,7 +42,6 @@ function enqueueJobFn(server: KbnServer) { timeout: queueConfig.timeout, created_by: get(user, 'username', false), browser_type: browserType, - max_attempts: maxAttempts, }; return new Promise((resolve, reject) => { diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js index 567c8e7da56bc..233ecb07ca44c 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/job.js @@ -67,11 +67,6 @@ describe('Job Class', function () { const init = () => new Job(mockQueue, index, 'type1', [1, 2, 3]); expect(init).to.throwException(/plain.+object/i); }); - - it(`should throw error if invalid maxAttempts`, function () { - const init = () => new Job(mockQueue, index, 'type1', { id: '123' }, { max_attempts: -1 }); - expect(init).to.throwException(/invalid.+max_attempts/i); - }); }); describe('construction', function () { diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js index c470afe604af7..8eedf08018f18 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js @@ -32,10 +32,6 @@ export class Job extends events.EventEmitter { this.indexSettings = options.indexSettings || {}; this.browser_type = options.browser_type; - if (typeof this.maxAttempts !== 'number' || this.maxAttempts < 1) { - throw new Error(`Invalid max_attempts: ${this.maxAttempts}`); - } - this.debug = (msg, err) => { const logger = options.logger || function () {}; const message = `${this.id} - ${msg}`; diff --git a/x-pack/legacy/plugins/reporting/server/lib/level_logger.ts b/x-pack/legacy/plugins/reporting/server/lib/level_logger.ts index 810ccf253943c..868ae163b15fc 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/level_logger.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/level_logger.ts @@ -6,10 +6,6 @@ type ServerLog = (tags: string[], msg: string) => void; -const trimStr = (toTrim: string) => { - return typeof toTrim === 'string' ? toTrim.trim() : toTrim; -}; - export class LevelLogger { private _logger: any; private _tags: string[]; @@ -34,20 +30,20 @@ export class LevelLogger { this.warn = this.warning.bind(this); } - public error(err: string | Error, tags: string[] = []) { - this._logger([...this._tags, ...tags, 'error'], err); + public error(msg: string, tags: string[] = []) { + this._logger([...this._tags, ...tags, 'error'], msg); } public warning(msg: string, tags: string[] = []) { - this._logger([...this._tags, ...tags, 'warning'], trimStr(msg)); + this._logger([...this._tags, ...tags, 'warning'], msg); } public debug(msg: string, tags: string[] = []) { - this._logger([...this._tags, ...tags, 'debug'], trimStr(msg)); + this._logger([...this._tags, ...tags, 'debug'], msg); } public info(msg: string, tags: string[] = []) { - this._logger([...this._tags, ...tags, 'info'], trimStr(msg)); + this._logger([...this._tags, ...tags, 'info'], msg); } public get isVerbose() { diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js index ccd4e1c3a82c6..5e2bfce7ada13 100644 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js +++ b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js @@ -12,12 +12,6 @@ import { KibanaRequest } from '../../../../../../../../src/core/server'; import { createCSPRuleString } from '../../../../../../../../src/legacy/server/csp'; export function initAuthenticateApi({ authc: { login, logout }, config }, server) { - function prepareCustomResourceResponse(response, contentType) { - return response - .header('cache-control', 'private, no-cache, no-store') - .header('content-security-policy', createCSPRuleString(server.config().get('csp.rules'))) - .type(contentType); - } server.route({ method: 'POST', @@ -99,36 +93,22 @@ export function initAuthenticateApi({ authc: { login, logout }, config }, server path: '/api/security/v1/oidc/implicit', config: { auth: false }, async handler(request, h) { - return prepareCustomResourceResponse( - h.response(` - - Kibana OpenID Connect Login - - `), - 'text/html' - ); - } - }); - - /** - * The route that accompanies `/api/security/v1/oidc/implicit` and renders a JavaScript snippet - * that extracts fragment part from the URL and send it to the `/api/security/v1/oidc` route. - * We need this separate endpoint because of default CSP policy that forbids inline scripts. - */ - server.route({ - method: 'GET', - path: '/api/security/v1/oidc/implicit.js', - config: { auth: false }, - async handler(request, h) { - return prepareCustomResourceResponse( - h.response(` + const legacyConfig = server.config(); + const basePath = legacyConfig.get('server.basePath'); + + const cspRulesHeader = createCSPRuleString(legacyConfig.get('csp.rules')); + return h.response(` + + Kibana OpenID Connect Login + + `) + .header('cache-control', 'private, no-cache, no-store') + .header('content-security-policy', cspRulesHeader) + .type('text/html'); } }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts index 30949abfe315f..f1a762cfab79b 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts @@ -15,7 +15,3 @@ export const ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS = `${ALL_HOSTS_WIDGET} ${ALL_HOSTS /** Clicking this button displays the `Events` tab */ export const EVENTS_TAB_BUTTON = '[data-test-subj="navigation-events"]'; - -export const NAVIGATION_HOSTS_ALL_HOSTS = '[data-test-subj="navigation-link-allHosts"]'; - -export const NAVIGATION_HOSTS_ANOMALIES = '[data-test-subj="navigation-link-anomalies"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts index 2d89eabda20ff..77f8e4786b2ed 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts @@ -37,13 +37,11 @@ export const ABSOLUTE_DATE_RANGE = { '/app/siem#/network/?kqlQuery=(filterQuery:!n,queryLocation:network.page)&timerange=(global:(linkTo:!(),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(),timerange:(from:1564776209186,kind:absolute,to:1564779809186)))', urlKqlNetworkNetwork: `/app/siem#/network/?_g=()&kqlQuery=(filterQuery:(expression:'source.ip:%20"10.142.0.9"',kind:kuery),queryLocation:network.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, urlKqlNetworkHosts: `/app/siem#/network/?_g=()&kqlQuery=(filterQuery:(expression:'source.ip:%20"10.142.0.9"',kind:kuery),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, - urlKqlHostsNetwork: `/app/siem#/hosts/allHosts?_g=()&kqlQuery=(filterQuery:(expression:'source.ip:%20"10.142.0.9"',kind:kuery),queryLocation:network.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, - urlKqlHostsHosts: `/app/siem#/hosts/allHosts?_g=()&kqlQuery=(filterQuery:(expression:'source.ip:%20"10.142.0.9"',kind:kuery),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, - urlHost: - '/app/siem#/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))', + urlKqlHostsNetwork: `/app/siem#/hosts/?_g=()&kqlQuery=(filterQuery:(expression:'source.ip:%20"10.142.0.9"',kind:kuery),queryLocation:network.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, + urlKqlHostsHosts: `/app/siem#/hosts/?_g=()&kqlQuery=(filterQuery:(expression:'source.ip:%20"10.142.0.9"',kind:kuery),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))`, }; export const DATE_PICKER_START_DATE_POPOVER_BUTTON = - 'div[data-test-subj="globalDatePicker"] button[data-test-subj="superDatePickerstartDatePopoverButton"]'; + '[data-test-subj="globalDatePicker"] [data-test-subj="superDatePickerstartDatePopoverButton"]'; export const DATE_PICKER_END_DATE_POPOVER_BUTTON = '[data-test-subj="globalDatePicker"] [data-test-subj="superDatePickerendDatePopoverButton"]'; export const DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE = @@ -58,6 +56,3 @@ export const DATE_PICKER_APPLY_BUTTON_TIMELINE = export const DATE_PICKER_ABSOLUTE_INPUT = '[data-test-subj="superDatePickerAbsoluteDateInput"]'; export const KQL_INPUT = '[data-test-subj="kqlInput"]'; export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; - -export const HOST_DETAIL_SIEM_KIBANA = '[data-test-subj="all-hosts"] a.euiLink'; -export const BREADCRUMBS = '[data-test-subj="breadcrumbs"] a'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/urls/index.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/urls/index.ts index 1d07d2693cfa8..25cff28836a7c 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/urls/index.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/urls/index.ts @@ -5,7 +5,7 @@ */ /** The SIEM app's Hosts page */ -export const HOSTS_PAGE = '/app/siem#/hosts/allHosts'; +export const HOSTS_PAGE = '/app/siem#/hosts'; /** Kibana's login page */ export const LOGIN_PAGE = '/login'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts index 41beb9c762c83..5d5feca99457a 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts @@ -17,8 +17,6 @@ import { DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE, KQL_INPUT, TIMELINE_TITLE, - HOST_DETAIL_SIEM_KIBANA, - BREADCRUMBS, } from '../../lib/url_state'; import { DEFAULT_TIMEOUT, loginAndWaitForPage } from '../../lib/util/helpers'; import { @@ -27,10 +25,8 @@ import { hostExistsQuery, toggleTimelineVisibility, } from '../../lib/timeline/helpers'; -import { NAVIGATION_NETWORK, NAVIGATION_HOSTS } from '../../lib/navigation/selectors'; +import { NAVIGATION_NETWORK } from '../../lib/navigation/selectors'; import { HOSTS_PAGE } from '../../lib/urls'; -import { waitForAllHostsWidget } from '../../lib/hosts/helpers'; -import { NAVIGATION_HOSTS_ALL_HOSTS, NAVIGATION_HOSTS_ANOMALIES } from '../../lib/hosts/selectors'; describe('url state', () => { afterEach(() => { @@ -194,65 +190,6 @@ describe('url state', () => { ); }); - it('sets the url state when kql is set and check if href reflect this change', () => { - loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url); - cy.get(KQL_INPUT, { timeout: 5000 }).type('source.ip: "10.142.0.9" {enter}'); - cy.get(NAVIGATION_HOSTS) - .first() - .click({ force: true }); - cy.get(NAVIGATION_NETWORK).should( - 'have.attr', - 'href', - "#/link-to/network?kqlQuery=(filterQuery:(expression:'source.ip:%20%2210.142.0.9%22%20',kind:kuery),queryLocation:network.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))" - ); - }); - - it('sets KQL in host page and detail page and check if href match on breadcrumb, tabs and subTabs', () => { - loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlHost); - cy.get(KQL_INPUT, { timeout: 5000 }).type('host.name: "siem-kibana" {enter}'); - cy.get(NAVIGATION_HOSTS_ALL_HOSTS) - .first() - .click({ force: true }); - waitForAllHostsWidget(); - cy.get(HOST_DETAIL_SIEM_KIBANA, { timeout: 5000 }) - .first() - .invoke('text') - .should('eq', 'siem-kibana'); - cy.get(HOST_DETAIL_SIEM_KIBANA) - .first() - .click({ force: true }); - cy.get(KQL_INPUT, { timeout: 5000 }).type('agent.type: "auditbeat" {enter}'); - cy.get(NAVIGATION_HOSTS).should( - 'have.attr', - 'href', - "#/link-to/hosts?kqlQuery=(filterQuery:(expression:'host.name:%20%22siem-kibana%22%20',kind:kuery),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))" - ); - cy.get(NAVIGATION_NETWORK).should( - 'have.attr', - 'href', - '#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))' - ); - cy.get(NAVIGATION_HOSTS_ANOMALIES).should( - 'have.attr', - 'href', - "#/hosts/siem-kibana/anomalies?kqlQuery=(filterQuery:(expression:'agent.type:%20%22auditbeat%22%20',kind:kuery),queryLocation:hosts.details)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))" - ); - cy.get(BREADCRUMBS) - .eq(1) - .should( - 'have.attr', - 'href', - "#/link-to/hosts?kqlQuery=(filterQuery:(expression:'host.name:%20%22siem-kibana%22%20',kind:kuery),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))" - ); - cy.get(BREADCRUMBS) - .eq(2) - .should( - 'have.attr', - 'href', - "#/link-to/hosts/siem-kibana?kqlQuery=(filterQuery:(expression:'agent.type:%20%22auditbeat%22%20',kind:kuery),queryLocation:hosts.details)&timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))" - ); - }); - it('clears kql when navigating to a new page', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); cy.get(NAVIGATION_NETWORK).click({ force: true }); @@ -265,10 +202,17 @@ describe('url state', () => { executeKQL(hostExistsQuery); assertAtLeastOneEventMatchesSearch(); const bestTimelineName = 'The Best Timeline'; - cy.get(TIMELINE_TITLE, { timeout: 5000 }).type(bestTimelineName); - cy.url().should('include', 'timelineId='); - cy.visit( - `/app/siem#/timelines?timerange=(global:(linkTo:!(),timerange:(from:1565274377369,kind:absolute,to:1565360777369)),timeline:(linkTo:!(),timerange:(from:1565274377369,kind:absolute,to:1565360777369)))` - ).then(() => cy.get(TIMELINE_TITLE).should('have.attr', 'value', bestTimelineName)); + cy.get(TIMELINE_TITLE).type(bestTimelineName); + cy.hash().then(hash => { + const matched = hash.match(/(?<=timelineId=\').+?(?=\')/g); + const newTimelineId = matched && matched.length > 0 ? matched[0] : 'null'; + expect(matched).to.have.lengthOf(1); + cy.log('hash', hash); + cy.log('matched', matched); + cy.log('newTimelineId', newTimelineId); + cy.visit( + `/app/siem#/timelines?timelineId='${newTimelineId}'&timerange=(global:(linkTo:!(),timerange:(from:1565274377369,kind:absolute,to:1565360777369)),timeline:(linkTo:!(),timerange:(from:1565274377369,kind:absolute,to:1565360777369)))` + ).then(() => cy.get(TIMELINE_TITLE).should('have.attr', 'value', bestTimelineName)); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts index 8ecd59063093b..e6751eeb2de70 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts @@ -16,7 +16,7 @@ export const mockSourceLayer = { type: 'ES_SEARCH', geoField: 'source.geo.location', filterByMapBounds: false, - tooltipProperties: ['host.name', 'source.ip', 'source.domain', 'source.as.organization.name'], + tooltipProperties: ['host.name', 'host.ip'], useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, @@ -51,12 +51,7 @@ export const mockDestinationLayer = { type: 'ES_SEARCH', geoField: 'destination.geo.location', filterByMapBounds: true, - tooltipProperties: [ - 'host.name', - 'destination.ip', - 'destination.domain', - 'destination.as.organization.name', - ], + tooltipProperties: ['host.name', 'host.ip'], useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.test.tsx index 731cc034f9778..47efbcce16867 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.test.tsx @@ -6,11 +6,7 @@ import { get } from 'lodash/fp'; -import { - ApplySiemFilterAction, - getExpressionFromArray, - getFilterExpression, -} from './apply_siem_filter_action'; +import { ApplySiemFilterAction, getExpressionFromArray } from './apply_siem_filter_action'; // @ts-ignore Missing type defs as maps moves to Typescript import { MAP_SAVED_OBJECT_TYPE } from '../../../../../maps/common/constants'; import { Action } from '../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions'; @@ -29,6 +25,10 @@ const isEmbeddable = ( return get('type', embeddable) != null; }; +const isTriggerContext = (triggerContext: unknown): triggerContext is { filters: Filter[] } => { + return typeof triggerContext === 'object'; +}; + describe('ApplySiemFilterAction', () => { let applyFilterQueryFromKueryExpression: (expression: string) => void; @@ -53,7 +53,7 @@ describe('ApplySiemFilterAction', () => { }); describe('#isCompatible', () => { - test('when embeddable type is MAP_SAVED_OBJECT_TYPE and filters exist, returns true', async () => { + test('when embeddable type is MAP_SAVED_OBJECT_TYPE and triggerContext filters exist, returns true', async () => { const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression }); const embeddable = { type: MAP_SAVED_OBJECT_TYPE, @@ -61,7 +61,9 @@ describe('ApplySiemFilterAction', () => { if (isEmbeddable(embeddable)) { const result = await action.isCompatible({ embeddable, - filters: [], + triggerContext: { + filters: [], + }, }); expect(result).toBe(true); } else { @@ -69,7 +71,7 @@ describe('ApplySiemFilterAction', () => { } }); - test('when embeddable type is MAP_SAVED_OBJECT_TYPE and filters do not exist, returns false', async () => { + test('when embeddable type is MAP_SAVED_OBJECT_TYPE and triggerContext does not exist, returns false', async () => { const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression }); const embeddable = { type: MAP_SAVED_OBJECT_TYPE, @@ -77,14 +79,30 @@ describe('ApplySiemFilterAction', () => { if (isEmbeddable(embeddable)) { const result = await action.isCompatible({ embeddable, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); + }); expect(result).toBe(false); } else { throw new Error('Invalid embeddable in unit test'); } }); + test('when embeddable type is MAP_SAVED_OBJECT_TYPE and triggerContext filters do not exist, returns false', async () => { + const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression }); + const embeddable = { + type: MAP_SAVED_OBJECT_TYPE, + }; + const triggerContext = {}; + if (isEmbeddable(embeddable) && isTriggerContext(triggerContext)) { + const result = await action.isCompatible({ + embeddable, + triggerContext, + }); + expect(result).toBe(false); + } else { + throw new Error('Invalid embeddable/triggerContext in unit test'); + } + }); + test('when embeddable type is not MAP_SAVED_OBJECT_TYPE, returns false', async () => { const action = new ApplySiemFilterAction({ applyFilterQueryFromKueryExpression }); const embeddable = { @@ -93,7 +111,9 @@ describe('ApplySiemFilterAction', () => { if (isEmbeddable(embeddable)) { const result = await action.isCompatible({ embeddable, - filters: [], + triggerContext: { + filters: [], + }, }); expect(result).toBe(false); } else { @@ -112,8 +132,7 @@ describe('ApplySiemFilterAction', () => { const error = expectError(() => action.execute({ embeddable, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any) + }) ); expect(error).toBeInstanceOf(Error); } else { @@ -129,27 +148,24 @@ describe('ApplySiemFilterAction', () => { query: { query: '' }, }), }; - const filters: Filter[] = [ - { - query: { - match: { - 'host.name': { - query: 'zeek-newyork-sha-aa8df15', - type: 'phrase', + const triggerContext = { + filters: [ + { + query: { + match: { + 'host.name': { + query: 'zeek-newyork-sha-aa8df15', + type: 'phrase', + }, }, }, }, - meta: { - disabled: false, - negate: false, - alias: '', - }, - }, - ]; - if (isEmbeddable(embeddable)) { + ], + }; + if (isEmbeddable(embeddable) && isTriggerContext(triggerContext)) { await action.execute({ embeddable, - filters, + triggerContext, }); expect( @@ -157,34 +173,12 @@ describe('ApplySiemFilterAction', () => { .calls[0][0] ).toBe('host.name: "zeek-newyork-sha-aa8df15"'); } else { - throw new Error('Invalid embeddable in unit test'); + throw new Error('Invalid embeddable/triggerContext in unit test'); } }); }); }); -describe('#getFilterExpression', () => { - test('it returns an empty expression if no filterValue is provided', () => { - const layerList = getFilterExpression('host.id', undefined); - expect(layerList).toEqual('(NOT host.id:*)'); - }); - - test('it returns a valid expression when provided single filterValue', () => { - const layerList = getFilterExpression('host.id', 'aa8df15'); - expect(layerList).toEqual('host.id: "aa8df15"'); - }); - - test('it returns a valid expression when provided array filterValue', () => { - const layerList = getFilterExpression('host.id', ['xavier', 'angela', 'frank']); - expect(layerList).toEqual('(host.id: "xavier" OR host.id: "angela" OR host.id: "frank")'); - }); - - test('it returns a valid expression when provided array filterValue with a single value', () => { - const layerList = getFilterExpression('host.id', ['xavier']); - expect(layerList).toEqual('(host.id: "xavier")'); - }); -}); - describe('#getExpressionFromArray', () => { test('it returns an empty expression if no filterValues are provided', () => { const layerList = getExpressionFromArray('host.id', []); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.tsx index 961ae1207df27..f25117ea3a0dc 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/actions/apply_siem_filter_action.tsx @@ -9,17 +9,15 @@ import { getOr } from 'lodash/fp'; import { i18n } from '@kbn/i18n'; // @ts-ignore Missing type defs as maps moves to Typescript import { MAP_SAVED_OBJECT_TYPE } from '../../../../../maps/common/constants'; -import { Action } from '../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions'; +import { + Action, + ActionContext, +} from '../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/actions'; import { IEmbeddable } from '../../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/lib/embeddables'; export const APPLY_SIEM_FILTER_ACTION_ID = 'APPLY_SIEM_FILTER_ACTION_ID'; -interface ActionContext { - embeddable: IEmbeddable; - filters: Filter[]; -} - -export class ApplySiemFilterAction extends Action { +export class ApplySiemFilterAction extends Action { public readonly type = APPLY_SIEM_FILTER_ACTION_ID; private readonly applyFilterQueryFromKueryExpression: (expression: string) => void; @@ -38,23 +36,34 @@ export class ApplySiemFilterAction extends Action { }); } - public async isCompatible(context: ActionContext): Promise { - return context.embeddable.type === MAP_SAVED_OBJECT_TYPE && context.filters !== undefined; + public async isCompatible( + context: ActionContext + ): Promise { + return ( + context.embeddable.type === MAP_SAVED_OBJECT_TYPE && + context.triggerContext != null && + context.triggerContext.filters !== undefined + ); } - public async execute({ embeddable, filters }: ActionContext) { - if (!filters) { + public execute({ + embeddable, + triggerContext, + }: ActionContext) { + if (!triggerContext) { throw new Error('Applying a filter requires a filter as context'); } // Parse queryExpression from queryDSL and apply to SIEM global KQL Bar via redux - const filterObject = getOr(null, '[0].query.match', filters); + const filterObject = getOr(null, 'filters[0].query.match', triggerContext); if (filterObject != null) { const filterQuery = getOr('', 'query.query', embeddable.getInput()); const filterKey = Object.keys(filterObject)[0]; - const filterExpression = getFilterExpression(filterKey, filterObject[filterKey].query); + const filterExpression = Array.isArray(filterObject[filterKey].query) + ? getExpressionFromArray(filterKey, filterObject[filterKey].query) + : `${filterKey}: "${filterObject[filterKey].query}"`; this.applyFilterQueryFromKueryExpression( filterQuery.length > 0 ? `${filterQuery} and ${filterExpression}` : filterExpression @@ -63,20 +72,7 @@ export class ApplySiemFilterAction extends Action { } } -export const getFilterExpression = ( - filterKey: string, - filterValue: string | string[] | undefined -): string => { - if (Array.isArray(filterValue)) { - return getExpressionFromArray(filterKey, filterValue); - } else if (filterValue != null) { - return `${filterKey}: "${filterValue}"`; - } else { - return `(NOT ${filterKey}:*)`; - } -}; - -export const getExpressionFromArray = (filterKey: string, filterValues: string[]): string => +export const getExpressionFromArray = (filterKey: string, filterValues: string[]) => filterValues.length > 0 ? `(${filterValues.map(filterValue => `${filterKey}: "${filterValue}"`).join(' OR ')})` : ''; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts index 492a33b549d42..7499903ddec88 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.ts @@ -51,7 +51,7 @@ export const getSourceLayer = (indexPatternTitle: string, indexPatternId: string type: 'ES_SEARCH', geoField: 'source.geo.location', filterByMapBounds: false, - tooltipProperties: ['host.name', 'source.ip', 'source.domain', 'source.as.organization.name'], + tooltipProperties: ['host.name', 'host.ip'], useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, @@ -93,12 +93,7 @@ export const getDestinationLayer = (indexPatternTitle: string, indexPatternId: s type: 'ES_SEARCH', geoField: 'destination.geo.location', filterByMapBounds: true, - tooltipProperties: [ - 'host.name', - 'destination.ip', - 'destination.domain', - 'destination.as.organization.name', - ], + tooltipProperties: ['host.name', 'host.ip'], useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts index 2701250c609fa..62f99688f9f8b 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts @@ -5,7 +5,7 @@ */ import { Filter as ESFilterType } from '@kbn/es-query'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from 'ui/timefilter'; import { EmbeddableInput } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; export interface MapEmbeddableInput extends EmbeddableInput { diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index d0857e6ff8b48..9268badd81234 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -20,27 +20,21 @@ interface LinkToPageProps { export const LinkToPage = pure(({ match }) => ( - - + + - + - - - + + + )); diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_hosts.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_hosts.tsx index ee4ff75595c66..9b1be534af9f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_hosts.tsx @@ -11,7 +11,7 @@ import { RedirectWrapper } from './redirect_wrapper'; import { HostsTableType } from '../../store/hosts/model'; export type HostComponentProps = RouteComponentProps<{ - detailName: string; + hostName: string; tabName: HostsTableType; search: string; }>; @@ -31,13 +31,13 @@ export const RedirectToHostsPage = ({ export const RedirectToHostDetailsPage = ({ match: { - params: { detailName, tabName }, + params: { hostName, tabName }, }, location: { search }, }: HostComponentProps) => { const defaultSelectedTab = HostsTableType.authentications; const selectedTab = tabName ? tabName : defaultSelectedTab; - const to = `/hosts/${detailName}/${selectedTab}${search}`; + const to = `/hosts/${hostName}/${selectedTab}${search}`; return ; }; @@ -45,8 +45,8 @@ export const getHostsUrl = () => '#/link-to/hosts'; export const getTabsOnHostsUrl = (tabName: HostsTableType) => `#/link-to/hosts/${tabName}`; -export const getHostDetailsUrl = (detailName: string) => `#/link-to/hosts/${detailName}`; +export const getHostDetailsUrl = (hostName: string) => `#/link-to/hosts/${hostName}`; -export const getTabsOnHostDetailsUrl = (detailName: string, tabName: HostsTableType) => { - return `#/link-to/hosts/${detailName}/${tabName}`; +export const getTabsOnHostDetailsUrl = (hostName: string, tabName: HostsTableType) => { + return `#/link-to/hosts/${hostName}/${tabName}`; }; diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_network.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_network.tsx index 50b23486d42a2..dba3f825362ba 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_network.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/redirect_to_network.tsx @@ -10,17 +10,17 @@ import { RouteComponentProps } from 'react-router-dom'; import { RedirectWrapper } from './redirect_wrapper'; export type NetworkComponentProps = RouteComponentProps<{ - detailName: string; + ip: string; search: string; }>; export const RedirectToNetworkPage = ({ match: { - params: { detailName }, + params: { ip }, }, location: { search }, }: NetworkComponentProps) => ( - + ); export const getNetworkUrl = () => '#/link-to/network'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.test.tsx index a7025c68c429e..05e8a8779e4fd 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.test.tsx @@ -18,7 +18,6 @@ import { getJobsToDisplay, getJobsToInstall, searchFilter, - getStablePatternTitles, } from './helpers'; jest.mock('../ml/permissions/has_ml_admin_permissions', () => ({ @@ -153,18 +152,4 @@ describe('helpers', () => { ]); }); }); - - describe('getStablePatternTitles', () => { - test('it returns a stable reference two times in a row with standard strings', () => { - const one = getStablePatternTitles(['a', 'b', 'c']); - const two = getStablePatternTitles(['a', 'b', 'c']); - expect(one).toBe(two); - }); - - test('it returns a stable reference two times in a row with strings interchanged', () => { - const one = getStablePatternTitles(['c', 'b', 'a']); - const two = getStablePatternTitles(['a', 'b', 'c']); - expect(one).toBe(two); - }); - }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.tsx index d0fc3ab074ebf..c17763bc60917 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/helpers.tsx @@ -76,14 +76,6 @@ export const searchFilter = (jobs: Job[], filterQuery?: string): Job[] => export const getIndexPatternTitles = (indexPatterns: IndexPatternSavedObject[]): string[] => indexPatterns.reduce((acc: string[], v) => [...acc, v.attributes.title], []); -/** - * Given an array of titles this will always return the same string for usage within - * useEffect and other shallow compare areas. - * This won't return a stable reference for case sensitive strings intentionally for speed. - * @param patterns string[] string array that will return a stable reference regardless of ordering or case sensitivity. - */ -export const getStablePatternTitles = (patterns: string[]) => patterns.sort().join(); - /** * Returns a mapping of indexPatternTitle to indexPatternId * diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx index 5244b2e040ae6..c7500074d0925 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx @@ -26,7 +26,6 @@ import { getIndexPatternTitles, getJobsToDisplay, getJobsToInstall, - getStablePatternTitles, } from './helpers'; import { configTemplates } from './config_templates'; import { useStateToaster } from '../toasters'; @@ -190,7 +189,7 @@ export const MlPopover = React.memo(() => { }; setupJobs(); } - }, [jobSummaryData, getStablePatternTitles(configuredIndexPatternTitles)]); + }, [jobSummaryData, configuredIndexPatternTitles]); if (!capabilities.isPlatinumOrTrialLicense) { // If the user does not have platinum show upgrade UI diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.test.ts b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.test.ts index 39a4c8efc4001..088c65b1da08b 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.test.ts @@ -6,11 +6,13 @@ import chrome from 'ui/chrome'; import '../../../mock/match_media'; import { encodeIpv6 } from '../../../lib/helpers'; +import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/hosts/host_details'; +import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../pages/network/ip_details'; +import { TIMELINES_PAGE_NAME } from '../../link_to/redirect_to_timelines'; -import { getBreadcrumbsForRoute, setBreadcrumbs } from '.'; +import { getBreadcrumbsForRoute, rootBreadcrumbs, setBreadcrumbs } from '.'; import { HostsTableType } from '../../../store/hosts/model'; -import { RouteSpyState } from '../../../utils/route/types'; -import { TabNavigationProps } from '../tab_navigation/types'; +import { SiemPageName } from '../../../pages/home/home_navigations'; jest.mock('ui/chrome', () => ({ getBasePath: () => { @@ -24,184 +26,96 @@ jest.mock('ui/chrome', () => ({ }), })); -const getMockObject = ( - pageName: string, - pathName: string, - detailName: string | undefined -): RouteSpyState & TabNavigationProps => ({ - detailName, - hostDetails: { filterQuery: null, queryLocation: null }, - hosts: { filterQuery: null, queryLocation: null }, - navTabs: { - hosts: { - disabled: false, - href: '#/link-to/hosts', - id: 'hosts', - name: 'Hosts', - urlKey: 'host', - }, - network: { - disabled: false, - href: '#/link-to/network', - id: 'network', - name: 'Network', - urlKey: 'network', - }, - overview: { - disabled: false, - href: '#/link-to/overview', - id: 'overview', - name: 'Overview', - urlKey: 'overview', - }, - timelines: { - disabled: false, - href: '#/link-to/timelines', - id: 'timelines', - name: 'Timelines', - urlKey: 'timeline', - }, - }, - network: { filterQuery: null, queryLocation: null }, - pageName, - pathName, - search: '', - tabName: HostsTableType.authentications, - timelineId: '', - timerange: { - global: { - linkTo: ['timeline'], - timerange: { - from: 1558048243696, - fromStr: 'now-24h', - kind: 'relative', - to: 1558134643697, - toStr: 'now', - }, - }, - timeline: { - linkTo: ['global'], - timerange: { - from: 1558048243696, - fromStr: 'now-24h', - kind: 'relative', - to: 1558134643697, - toStr: 'now', - }, - }, - }, -}); - describe('Navigation Breadcrumbs', () => { const hostName = 'siem-kibana'; - + const hostDetailsParams = { + pageName: SiemPageName.hosts, + hostName, + tabName: HostsTableType.authentications, + }; + const hostBreadcrumbs = [ + ...rootBreadcrumbs.overview, + ...getHostDetailsBreadcrumbs(hostDetailsParams), + ]; const ipv4 = '192.0.2.255'; + const ipv4Breadcrumbs = [...rootBreadcrumbs.overview, ...getIPDetailsBreadcrumbs(ipv4)]; const ipv6 = '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff'; const ipv6Encoded = encodeIpv6(ipv6); - + const ipv6Breadcrumbs = [...rootBreadcrumbs.overview, ...getIPDetailsBreadcrumbs(ipv6Encoded)]; describe('getBreadcrumbsForRoute', () => { + test('should return Host breadcrumbs when supplied link-to host pathname', () => { + const pathname = '/link-to/hosts'; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(rootBreadcrumbs.hosts); + }); + test('should return Host breadcrumbs when supplied host pathname', () => { - const breadcrumbs = getBreadcrumbsForRoute(getMockObject('hosts', '/hosts', undefined)); - expect(breadcrumbs).toEqual([ - { - href: '#/link-to/overview', - text: 'SIEM', - }, - { - href: - '#/link-to/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - text: 'Hosts', - }, - { - href: '', - text: 'Authentications', - }, - ]); + const pathname = '/hosts'; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(rootBreadcrumbs.hosts); + }); + + test('should return Host breadcrumbs when supplied host pathname with trailing slash', () => { + const pathname = '/hosts/'; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(rootBreadcrumbs.hosts); }); test('should return Network breadcrumbs when supplied network pathname', () => { - const breadcrumbs = getBreadcrumbsForRoute(getMockObject('network', '/network', undefined)); - expect(breadcrumbs).toEqual([ - { text: 'SIEM', href: '#/link-to/overview' }, - { - text: 'Network', - href: - '#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - }, - ]); + const pathname = '/network'; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(rootBreadcrumbs.network); + }); + + test('should return Timelines breadcrumbs when supplied link-to timelines pathname', () => { + const pathname = `/link-to/${TIMELINES_PAGE_NAME}`; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(rootBreadcrumbs.timelines); }); test('should return Timelines breadcrumbs when supplied timelines pathname', () => { - const breadcrumbs = getBreadcrumbsForRoute( - getMockObject('timelines', '/timelines', undefined) - ); - expect(breadcrumbs).toEqual([ - { text: 'SIEM', href: '#/link-to/overview' }, - { text: 'Timelines', href: '' }, - ]); + const pathname = '/timelines'; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(rootBreadcrumbs.timelines); + }); + + test('should return Host Details breadcrumbs when supplied link-to pathname with hostName', () => { + const pathname = `/link-to/hosts/${hostName}`; + + const breadcrumbs = getBreadcrumbsForRoute(pathname, hostDetailsParams); + expect(breadcrumbs).toEqual(hostBreadcrumbs); }); test('should return Host Details breadcrumbs when supplied a pathname with hostName', () => { - const breadcrumbs = getBreadcrumbsForRoute(getMockObject('hosts', '/hosts', hostName)); - expect(breadcrumbs).toEqual([ - { text: 'SIEM', href: '#/link-to/overview' }, - { - text: 'Hosts', - href: - '#/link-to/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - }, - { - text: 'siem-kibana', - href: - '#/link-to/hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - }, - { text: 'Authentications', href: '' }, - ]); + const pathname = `/hosts/${hostName}`; + + const breadcrumbs = getBreadcrumbsForRoute(pathname, hostDetailsParams); + expect(breadcrumbs).toEqual(hostBreadcrumbs); + }); + + test('should return IP Details breadcrumbs when supplied link-to pathname with ipv4', () => { + const pathname = `link-to/network/ip/${ipv4}`; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(ipv4Breadcrumbs); }); test('should return IP Details breadcrumbs when supplied pathname with ipv4', () => { - const breadcrumbs = getBreadcrumbsForRoute(getMockObject('network', '/network', ipv4)); - expect(breadcrumbs).toEqual([ - { text: 'SIEM', href: '#/link-to/overview' }, - { - text: 'Network', - href: - '#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - }, - { text: '192.0.2.255', href: '' }, - ]); + const pathname = `/network/ip/${ipv4}`; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(ipv4Breadcrumbs); }); test('should return IP Details breadcrumbs when supplied pathname with ipv6', () => { - const breadcrumbs = getBreadcrumbsForRoute(getMockObject('network', '/network', ipv6Encoded)); - expect(breadcrumbs).toEqual([ - { text: 'SIEM', href: '#/link-to/overview' }, - { - text: 'Network', - href: - '#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - }, - { text: '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff', href: '' }, - ]); + const pathname = `/network/ip/${ipv6Encoded}`; + const breadcrumbs = getBreadcrumbsForRoute(pathname); + expect(breadcrumbs).toEqual(ipv6Breadcrumbs); }); }); describe('setBreadcrumbs()', () => { test('should call chrome breadcrumb service with correct breadcrumbs', () => { - setBreadcrumbs(getMockObject('hosts', '/hosts', hostName)); - expect(chrome.breadcrumbs.set).toBeCalledWith([ - { text: 'SIEM', href: '#/link-to/overview' }, - { - text: 'Hosts', - href: - '#/link-to/hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - }, - { - text: 'siem-kibana', - href: - '#/link-to/hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - }, - { text: 'Authentications', href: '' }, - ]); + const pathname = `/hosts/${hostName}`; + setBreadcrumbs(pathname, hostDetailsParams); + expect(chrome.breadcrumbs.set).toBeCalledWith(hostBreadcrumbs); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts index 3e6eb4e51685b..7037d3cc2e869 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts @@ -6,20 +6,23 @@ import chrome, { Breadcrumb } from 'ui/chrome'; -import { getOr } from 'lodash/fp'; import { APP_NAME } from '../../../../common/constants'; -import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/hosts/details/utils'; +import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/hosts/host_details'; import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../pages/network/ip_details'; +import { getNetworkUrl, getOverviewUrl, getTimelinesUrl } from '../../link_to'; +import * as i18n from '../translations'; +import { getHostsUrl } from '../../link_to/redirect_to_hosts'; +import { HostsTableType } from '../../../store/hosts/model'; import { SiemPageName } from '../../../pages/home/home_navigations'; -import { RouteSpyState } from '../../../utils/route/types'; -import { getOverviewUrl } from '../../link_to'; -import { TabNavigationProps } from '../tab_navigation/types'; -import { getSearch } from '../helpers'; -import { SearchNavTab } from '../types'; +export interface NavigationParams { + pageName?: SiemPageName; + hostName?: string; + tabName?: HostsTableType; +} -export const setBreadcrumbs = (object: RouteSpyState & TabNavigationProps) => { - const breadcrumbs = getBreadcrumbsForRoute(object); +export const setBreadcrumbs = (pathname: string, params?: NavigationParams) => { + const breadcrumbs = getBreadcrumbsForRoute(pathname, params); if (breadcrumbs) { chrome.breadcrumbs.set(breadcrumbs); } @@ -32,49 +35,48 @@ export const siemRootBreadcrumb: Breadcrumb[] = [ }, ]; +export const rootBreadcrumbs: { [name: string]: Breadcrumb[] } = { + overview: siemRootBreadcrumb, + hosts: [ + ...siemRootBreadcrumb, + { + text: i18n.HOSTS, + href: getHostsUrl(), + }, + ], + network: [ + ...siemRootBreadcrumb, + { + text: i18n.NETWORK, + href: getNetworkUrl(), + }, + ], + timelines: [ + ...siemRootBreadcrumb, + { + text: i18n.TIMELINES, + href: getTimelinesUrl(), + }, + ], +}; + export const getBreadcrumbsForRoute = ( - object: RouteSpyState & TabNavigationProps + pathname: string, + params?: NavigationParams ): Breadcrumb[] | null => { - if (object != null && object.navTabs && object.pageName === SiemPageName.hosts) { - const tempNav: SearchNavTab = { urlKey: 'host', isDetailPage: false }; - let urlStateKeys = [getOr(tempNav, object.pageName, object.navTabs)]; - if (object.tabName != null) { - urlStateKeys = [...urlStateKeys, getOr(tempNav, object.tabName, object.navTabs)]; + const removeSlash = pathname.replace(/\/$/, ''); + const trailingPath = removeSlash.match(/([^\/]+$)/); + + if (trailingPath !== null) { + if (params != null && params.pageName === SiemPageName.hosts) { + return [...siemRootBreadcrumb, ...getHostDetailsBreadcrumbs(params)]; + } + if (Object.keys(rootBreadcrumbs).includes(trailingPath[0])) { + return rootBreadcrumbs[trailingPath[0]]; + } + if (pathname.match(/network\/ip\/.*?/)) { + return [...siemRootBreadcrumb, ...getIPDetailsBreadcrumbs(trailingPath[0])]; } - return [ - ...siemRootBreadcrumb, - ...getHostDetailsBreadcrumbs( - object, - urlStateKeys.reduce((acc: string[], item: SearchNavTab) => { - acc = [...acc, getSearch(item, object)]; - return acc; - }, []) - ), - ]; - } - if (object != null && object.navTabs && object.pageName === SiemPageName.network) { - const tempNav: SearchNavTab = { urlKey: 'network', isDetailPage: false }; - const urlStateKeys = [getOr(tempNav, object.pageName, object.navTabs)]; - return [ - ...siemRootBreadcrumb, - ...getIPDetailsBreadcrumbs( - object.detailName, - urlStateKeys.reduce((acc: string[], item) => { - acc = [...acc, getSearch(item, object)]; - return acc; - }, []) - ), - ]; - } - if (object != null && object.navTabs && object.pageName && object.navTabs[object.pageName]) { - return [ - ...siemRootBreadcrumb, - { - text: object.navTabs[object.pageName].name, - href: '', - }, - ]; } - return null; }; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/helpers.ts b/x-pack/legacy/plugins/siem/public/components/navigation/helpers.ts deleted file mode 100644 index 466a200e662e3..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/navigation/helpers.ts +++ /dev/null @@ -1,50 +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 { Location } from 'history'; - -import { UrlInputsModel } from '../../store/inputs/model'; -import { CONSTANTS } from '../url_state/constants'; -import { KqlQuery, URL_STATE_KEYS, KeyUrlState } from '../url_state/types'; -import { - replaceQueryStringInLocation, - replaceStateKeyInQueryString, - getQueryStringFromLocation, -} from '../url_state/helpers'; - -import { TabNavigationProps } from './tab_navigation/types'; -import { SearchNavTab } from './types'; - -export const getSearch = (tab: SearchNavTab, urlState: TabNavigationProps): string => { - if (tab && tab.urlKey != null && URL_STATE_KEYS[tab.urlKey] != null) { - return URL_STATE_KEYS[tab.urlKey].reduce( - (myLocation: Location, urlKey: KeyUrlState) => { - let urlStateToReplace: UrlInputsModel | KqlQuery | string = urlState[CONSTANTS.timelineId]; - if (urlKey === CONSTANTS.kqlQuery && tab.urlKey === 'host') { - urlStateToReplace = tab.isDetailPage ? urlState.hostDetails : urlState.hosts; - } else if (urlKey === CONSTANTS.kqlQuery && tab.urlKey === 'network') { - urlStateToReplace = urlState.network; - } else if (urlKey === CONSTANTS.timerange) { - urlStateToReplace = urlState[CONSTANTS.timerange]; - } - myLocation = replaceQueryStringInLocation( - myLocation, - replaceStateKeyInQueryString(urlKey, urlStateToReplace)( - getQueryStringFromLocation(myLocation) - ) - ); - return myLocation; - }, - { - pathname: urlState.pathName, - hash: '', - search: '', - state: '', - } - ).search; - } - return ''; -}; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index 25ebb8ad89ecd..e15646d80ffb5 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -6,27 +6,51 @@ import { shallow } from 'enzyme'; import * as React from 'react'; +import { RouteComponentProps } from 'react-router'; import { CONSTANTS } from '../url_state/constants'; import { SiemNavigationComponent } from './'; import { setBreadcrumbs } from './breadcrumbs'; import { navTabs } from '../../pages/home/home_navigations'; -import { TabNavigationProps } from './tab_navigation/types'; -import { HostsTableType } from '../../store/hosts/model'; -import { RouteSpyState } from '../../utils/route/types'; +import { TabNavigationProps } from './type'; jest.mock('./breadcrumbs', () => ({ setBreadcrumbs: jest.fn(), })); +type Action = 'PUSH' | 'POP' | 'REPLACE'; +type Props = RouteComponentProps & TabNavigationProps; +const pop: Action = 'POP'; describe('SIEM Navigation', () => { - const mockProps: TabNavigationProps & RouteSpyState = { - pageName: 'hosts', - pathName: '/hosts', - detailName: undefined, + const location = { + pathname: '/hosts', search: '', - tabName: HostsTableType.authentications, + state: '', + hash: '', + }; + + const mockProps: Props = { + location, + match: { + isExact: true, + params: {}, + path: '', + url: '', + }, navTabs, + history: { + length: 2, + location, + action: pop, + push: jest.fn(), + replace: jest.fn(), + go: jest.fn(), + goBack: jest.fn(), + goForward: jest.fn(), + block: jest.fn(), + createHref: jest.fn(), + listen: jest.fn(), + }, [CONSTANTS.timerange]: { global: { [CONSTANTS.timerange]: { @@ -63,141 +87,13 @@ describe('SIEM Navigation', () => { }, [CONSTANTS.timelineId]: '', }; - const wrapper = shallow(); + const wrapper = shallow(); test('it calls setBreadcrumbs with correct path on mount', () => { - expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, { - detailName: undefined, - hostDetails: { filterQuery: null, queryLocation: null }, - hosts: { filterQuery: null, queryLocation: null }, - navTabs: { - hosts: { - disabled: false, - href: '#/link-to/hosts', - id: 'hosts', - name: 'Hosts', - urlKey: 'host', - }, - network: { - disabled: false, - href: '#/link-to/network', - id: 'network', - name: 'Network', - urlKey: 'network', - }, - overview: { - disabled: false, - href: '#/link-to/overview', - id: 'overview', - name: 'Overview', - urlKey: 'overview', - }, - timelines: { - disabled: false, - href: '#/link-to/timelines', - id: 'timelines', - name: 'Timelines', - urlKey: 'timeline', - }, - }, - network: { filterQuery: null, queryLocation: null }, - pageName: 'hosts', - pathName: '/hosts', - search: '', - tabName: 'authentications', - timelineId: '', - timerange: { - global: { - linkTo: ['timeline'], - timerange: { - from: 1558048243696, - fromStr: 'now-24h', - kind: 'relative', - to: 1558134643697, - toStr: 'now', - }, - }, - timeline: { - linkTo: ['global'], - timerange: { - from: 1558048243696, - fromStr: 'now-24h', - kind: 'relative', - to: 1558134643697, - toStr: 'now', - }, - }, - }, - }); + expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, '/hosts', {}); }); test('it calls setBreadcrumbs with correct path on update', () => { - wrapper.setProps({ - pageName: 'network', - pathName: '/network', - tabName: undefined, - }); + wrapper.setProps({ location: { pathname: '/network' } }); wrapper.update(); - expect(setBreadcrumbs).toHaveBeenNthCalledWith(2, { - detailName: undefined, - hostDetails: { filterQuery: null, queryLocation: null }, - hosts: { filterQuery: null, queryLocation: null }, - navTabs: { - hosts: { - disabled: false, - href: '#/link-to/hosts', - id: 'hosts', - name: 'Hosts', - urlKey: 'host', - }, - network: { - disabled: false, - href: '#/link-to/network', - id: 'network', - name: 'Network', - urlKey: 'network', - }, - overview: { - disabled: false, - href: '#/link-to/overview', - id: 'overview', - name: 'Overview', - urlKey: 'overview', - }, - timelines: { - disabled: false, - href: '#/link-to/timelines', - id: 'timelines', - name: 'Timelines', - urlKey: 'timeline', - }, - }, - network: { filterQuery: null, queryLocation: null }, - pageName: 'network', - pathName: '/network', - search: '', - tabName: undefined, - timelineId: '', - timerange: { - global: { - linkTo: ['timeline'], - timerange: { - from: 1558048243696, - fromStr: 'now-24h', - kind: 'relative', - to: 1558134643697, - toStr: 'now', - }, - }, - timeline: { - linkTo: ['global'], - timerange: { - from: 1558048243696, - fromStr: 'now-24h', - kind: 'relative', - to: 1558134643697, - toStr: 'now', - }, - }, - }, - }); + expect(setBreadcrumbs).toHaveBeenNthCalledWith(2, '/network', {}); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx index d53895606f9ee..cbfb53f13779f 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; import React from 'react'; import { compose } from 'redux'; import { connect } from 'react-redux'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; -import { RouteSpyState } from '../../utils/route/types'; -import { useRouteSpy } from '../../utils/route/use_route_spy'; -import { CONSTANTS } from '../url_state/constants'; +import { setBreadcrumbs } from './breadcrumbs'; +import { TabNavigation } from './tab_navigation'; +import { TabNavigationProps, SiemNavigationComponentProps } from './type'; import { inputsSelectors, hostsSelectors, @@ -21,23 +21,15 @@ import { hostsModel, networkModel, } from '../../store'; +import { CONSTANTS } from '../url_state/constants'; -import { setBreadcrumbs } from './breadcrumbs'; -import { TabNavigation } from './tab_navigation'; -import { TabNavigationProps } from './tab_navigation/types'; -import { SiemNavigationComponentProps } from './types'; - -export class SiemNavigationComponent extends React.Component { - public shouldComponentUpdate(nextProps: Readonly): boolean { +export class SiemNavigationComponent extends React.Component< + RouteComponentProps & TabNavigationProps +> { + public shouldComponentUpdate(nextProps: Readonly): boolean { if ( - this.props.pathName === nextProps.pathName && - this.props.search === nextProps.search && - isEqual(this.props.hosts, nextProps.hosts) && - isEqual(this.props.hostDetails, nextProps.hostDetails) && - isEqual(this.props.network, nextProps.network) && - isEqual(this.props.navTabs, nextProps.navTabs) && - isEqual(this.props.timerange, nextProps.timerange) && - isEqual(this.props.timelineId, nextProps.timelineId) + this.props.location.pathname === nextProps.location.pathname && + this.props.location.search === nextProps.location.search ) { return false; } @@ -46,104 +38,45 @@ export class SiemNavigationComponent extends React.Component): void { - if ( - this.props.pathName !== nextProps.pathName || - this.props.search !== nextProps.search || - !isEqual(this.props.hosts, nextProps.hosts) || - !isEqual(this.props.hostDetails, nextProps.hostDetails) || - !isEqual(this.props.network, nextProps.network) || - !isEqual(this.props.navTabs, nextProps.navTabs) || - !isEqual(this.props.timerange, nextProps.timerange) || - !isEqual(this.props.timelineId, nextProps.timelineId) - ) { - const { - detailName, - hosts, - hostDetails, - navTabs, - network, - pageName, - pathName, - search, - tabName, - timelineId, - timerange, - } = nextProps; - if (pathName) { - setBreadcrumbs({ - detailName, - hosts, - hostDetails, - navTabs, - network, - pageName, - pathName, - search, - tabName, - timerange, - timelineId, - }); - } + public componentWillReceiveProps(nextProps: Readonly): void { + if (this.props.location.pathname !== nextProps.location.pathname) { + setBreadcrumbs(nextProps.location.pathname, nextProps.match.params); } } public render() { const { display, - hostDetails, + location, hosts, + hostDetails, + match, navTabs, network, - pageName, - pathName, showBorder, - tabName, - timelineId, timerange, + timelineId, } = this.props; return ( ); } @@ -199,15 +132,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const SiemNavigationRedux = compose< - React.ComponentClass ->(connect(makeMapStateToProps))(SiemNavigationComponent); - -export const SiemNavigation = React.memo(props => { - const [routeProps] = useRouteSpy(); - const stateNavReduxProps: RouteSpyState & SiemNavigationComponentProps = { - ...routeProps, - ...props, - }; - return ; -}); +export const SiemNavigation = compose>( + withRouter, + connect(makeMapStateToProps) +)(SiemNavigationComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx index 1493ab8ac9ce8..7216d825f9c3d 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx @@ -8,26 +8,32 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { TabNavigation } from './'; -import { TabNavigationProps } from './types'; +import { TabNavigationProps } from '../type'; import { navTabs, SiemPageName } from '../../../pages/home/home_navigations'; import { HostsTableType } from '../../../store/hosts/model'; import { navTabsHostDetails } from '../../../pages/hosts/hosts_navigations'; import { CONSTANTS } from '../../url_state/constants'; -import { RouteSpyState } from '../../../utils/route/types'; describe('Tab Navigation', () => { const pageName = SiemPageName.hosts; const hostName = 'siem-window'; const tabName = HostsTableType.authentications; const pathName = `/${pageName}/${hostName}/${tabName}`; - - describe('Page Navigation', () => { - const mockProps: TabNavigationProps & RouteSpyState = { + const mockMatch = { + params: { pageName, - pathName, - detailName: undefined, - search: '', + hostName, tabName, + }, + }; + describe('Page Navigation', () => { + const mockProps: TabNavigationProps = { + location: { + pathname: pathName, + search: '', + state: '', + hash: '', + }, navTabs, [CONSTANTS.timerange]: { global: { @@ -71,6 +77,7 @@ describe('Tab Navigation', () => { test('it mounts with correct tab highlighted', () => { const wrapper = shallow(); const hostsTab = wrapper.find('[data-test-subj="navigation-hosts"]'); + expect(hostsTab.prop('isSelected')).toBeTruthy(); }); test('it changes active tab when nav changes by props', () => { @@ -78,9 +85,12 @@ describe('Tab Navigation', () => { const networkTab = () => wrapper.find('[data-test-subj="navigation-network"]'); expect(networkTab().prop('isSelected')).toBeFalsy(); wrapper.setProps({ - pageName: 'network', - pathName: '/network', - tabName: undefined, + location: { + pathname: '/network', + search: '', + state: '', + hash: '', + }, }); wrapper.update(); expect(networkTab().prop('isSelected')).toBeTruthy(); @@ -95,13 +105,15 @@ describe('Tab Navigation', () => { }); describe('Table Navigation', () => { - const mockProps: TabNavigationProps & RouteSpyState = { - pageName: 'hosts', - pathName: '/hosts', - detailName: undefined, - search: '', - tabName: HostsTableType.authentications, + const mockProps: TabNavigationProps = { + location: { + pathname: pathName, + search: '', + state: '', + hash: '', + }, navTabs: navTabsHostDetails(hostName), + match: mockMatch, [CONSTANTS.timerange]: { global: { [CONSTANTS.timerange]: { @@ -150,15 +162,18 @@ describe('Tab Navigation', () => { expect(tableNavigationTab.prop('isSelected')).toBeTruthy(); }); test('it changes active tab when nav changes by props', () => { + const newMatch = { + params: { + pageName: SiemPageName.hosts, + hostName, + tabName: HostsTableType.events, + }, + }; const wrapper = shallow(); const tableNavigationTab = () => wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`); expect(tableNavigationTab().prop('isSelected')).toBeFalsy(); - wrapper.setProps({ - pageName: SiemPageName.hosts, - pathName: `/${SiemPageName.hosts}`, - tabName: HostsTableType.events, - }); + wrapper.setProps({ location: `/${SiemPageName.hosts}`, match: newMatch }); wrapper.update(); expect(tableNavigationTab().prop('isSelected')).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx index 98357901e4273..5c831bf51e23d 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx @@ -5,15 +5,23 @@ */ import { EuiTab, EuiTabs, EuiLink } from '@elastic/eui'; import { get, getOr } from 'lodash/fp'; - +import { Location } from 'history'; import * as React from 'react'; import styled from 'styled-components'; import classnames from 'classnames'; import { trackUiAction as track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/track_usage'; import { HostsTableType } from '../../../store/hosts/model'; -import { getSearch } from '../helpers'; -import { TabNavigationProps } from './types'; +import { UrlInputsModel } from '../../../store/inputs/model'; +import { CONSTANTS } from '../../url_state/constants'; +import { KqlQuery, URL_STATE_KEYS, KeyUrlState } from '../../url_state/types'; +import { NavTab, NavMatchParams, TabNavigationProps } from '../type'; + +import { + replaceQueryStringInLocation, + replaceStateKeyInQueryString, + getQueryStringFromLocation, +} from '../../url_state/helpers'; const TabContainer = styled.div` .euiLink { @@ -34,11 +42,15 @@ interface TabNavigationState { export class TabNavigation extends React.PureComponent { constructor(props: TabNavigationProps) { super(props); - const selectedTabId = this.mapLocationToTab(props.pageName, props.tabName); + const pathname = props.location.pathname; + const match = props.match; + const selectedTabId = this.mapLocationToTab(pathname, match); this.state = { selectedTabId }; } public componentWillReceiveProps(nextProps: TabNavigationProps): void { - const selectedTabId = this.mapLocationToTab(nextProps.pageName, nextProps.tabName); + const pathname = nextProps.location.pathname; + const match = nextProps.match; + const selectedTabId = this.mapLocationToTab(pathname, match); if (this.state.selectedTabId !== selectedTabId) { this.setState(prevState => ({ @@ -56,13 +68,13 @@ export class TabNavigation extends React.PureComponent { + public mapLocationToTab = (pathname: string, match?: NavMatchParams): string => { const { navTabs } = this.props; - return getOr( - '', - 'id', - Object.values(navTabs).find(item => tabName === item.id || pageName === item.id) - ); + const tabName: HostsTableType | undefined = get('params.tabName', match); + const myNavTab = Object.keys(navTabs) + .map(tab => get(tab, navTabs)) + .filter((item: NavTab) => (tabName || pathname).includes(item.id))[0]; + return getOr('', 'id', myNavTab); }; private renderTabs = (): JSX.Element[] => { @@ -76,7 +88,7 @@ export class TabNavigation extends React.PureComponent { + return URL_STATE_KEYS[tab.urlKey].reduce( + (myLocation: Location, urlKey: KeyUrlState) => { + let urlStateToReplace: UrlInputsModel | KqlQuery | string = this.props[ + CONSTANTS.timelineId + ]; + if (urlKey === CONSTANTS.kqlQuery && tab.urlKey === 'host') { + urlStateToReplace = tab.isDetailPage ? this.props.hostDetails : this.props.hosts; + } else if (urlKey === CONSTANTS.kqlQuery && tab.urlKey === 'network') { + urlStateToReplace = this.props.network; + } else if (urlKey === CONSTANTS.timerange) { + urlStateToReplace = this.props[CONSTANTS.timerange]; + } + myLocation = replaceQueryStringInLocation( + myLocation, + replaceStateKeyInQueryString(urlKey, urlStateToReplace)( + getQueryStringFromLocation(myLocation) + ) + ); + return myLocation; + }, + { + ...this.props.location, + search: '', + } + ).search; + }; } diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/types.ts b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/types.ts deleted file mode 100644 index 38970e31332cd..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/types.ts +++ /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 { UrlInputsModel } from '../../../store/inputs/model'; -import { CONSTANTS } from '../../url_state/constants'; -import { KqlQuery } from '../../url_state/types'; -import { HostsTableType } from '../../../store/hosts/model'; - -import { SiemNavigationComponentProps } from '../types'; - -export interface TabNavigationProps extends SiemNavigationComponentProps { - pathName: string; - pageName: string; - tabName: HostsTableType | undefined; - hosts: KqlQuery; - hostDetails: KqlQuery; - network: KqlQuery; - [CONSTANTS.timerange]: UrlInputsModel; - [CONSTANTS.timelineId]: string; -} diff --git a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts b/x-pack/legacy/plugins/siem/public/components/navigation/translations.ts similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/home/translations.ts rename to x-pack/legacy/plugins/siem/public/components/navigation/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/type.ts b/x-pack/legacy/plugins/siem/public/components/navigation/type.ts new file mode 100644 index 0000000000000..ff96f28ed815e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/navigation/type.ts @@ -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 { Location } from 'history'; + +import { UrlInputsModel } from '../../store/inputs/model'; +import { CONSTANTS } from '../url_state/constants'; +import { KqlQuery, UrlStateType } from '../url_state/types'; +import { NavigationParams } from './breadcrumbs'; + +export interface NavTab { + id: string; + name: string; + href: string; + disabled: boolean; + urlKey: UrlStateType; + isDetailPage?: boolean; +} + +export interface NavMatchParams { + params: NavigationParams; +} + +export interface SiemNavigationComponentProps { + display?: 'default' | 'condensed'; + navTabs: Record; + showBorder?: boolean; +} + +export interface TabNavigationProps extends SiemNavigationComponentProps { + location: Location; + hosts: KqlQuery; + hostDetails: KqlQuery; + network: KqlQuery; + [CONSTANTS.timerange]: UrlInputsModel; + [CONSTANTS.timelineId]: string; + match?: NavMatchParams; +} diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/types.ts b/x-pack/legacy/plugins/siem/public/components/navigation/types.ts deleted file mode 100644 index 2918a19df52fd..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/navigation/types.ts +++ /dev/null @@ -1,24 +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 { UrlStateType } from '../url_state/constants'; - -export interface SiemNavigationComponentProps { - display?: 'default' | 'condensed'; - navTabs: Record; - showBorder?: boolean; -} - -export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean }; - -export interface NavTab { - id: string; - name: string; - href: string; - disabled: boolean; - urlKey: UrlStateType; - isDetailPage?: boolean; -} diff --git a/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx index c08b877076cbe..f2f37c78807a0 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/add_to_kql/index.test.tsx @@ -90,7 +90,7 @@ describe('AddToKql Component', () => { activePage: 0, limit: 10, }, - allHosts: { + hosts: { activePage: 0, limit: 10, direction: 'desc', diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx index 700340b8c9dd2..a4399a16dbf05 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx @@ -21,7 +21,6 @@ import { createStore, hostsModel, State } from '../../../../store'; import { HostsTable } from './index'; import { mockData } from './mock'; -import { HostsTableType } from '../../../../store/hosts/model'; describe('Hosts Table', () => { const loadPage = jest.fn(); @@ -101,7 +100,7 @@ describe('Hosts Table', () => { ); }); test('Initial value of the store', () => { - expect(store.getState().hosts.page.queries[HostsTableType.hosts]).toEqual({ + expect(store.getState().hosts.page.queries.hosts).toEqual({ activePage: 0, direction: 'desc', sortField: 'lastSeen', @@ -129,7 +128,7 @@ describe('Hosts Table', () => { wrapper.update(); - expect(store.getState().hosts.page.queries[HostsTableType.hosts]).toEqual({ + expect(store.getState().hosts.page.queries.hosts).toEqual({ activePage: 0, direction: 'asc', sortField: 'hostName', diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx index 598b9b038d386..abd3bf0a8a25b 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/columns.tsx @@ -50,7 +50,6 @@ export const getNetworkTopNFlowColumns = ( const ipAttr = `${flowTarget}.ip`; const ip: string | null = get(ipAttr, node); const geoAttr = `${flowTarget}.location.geo.country_iso_code[0]`; - const geoAttrName = `${flowTarget}.geo.country_iso_code`; const geo: string | null = get(geoAttr, node); const id = escapeDataProviderId(`${tableId}-table-${flowTarget}-ip-${ip}`); @@ -80,30 +79,10 @@ export const getNetworkTopNFlowColumns = ( /> {geo && ( - - snapshot.isDragging ? ( - - - - ) : ( - <> - {' '} - {geo} - - ) - } - /> + <> + {' '} + {geo} + )} ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx index 51ab07a4ad1d6..7026114795c0f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -20,15 +20,10 @@ import { getRowRenderer } from './get_row_renderer'; describe('get_column_renderer', () => { let nonSuricata: Ecs; let suricata: Ecs; - let zeek: Ecs; - let system: Ecs; - let auditd: Ecs; + beforeEach(() => { nonSuricata = cloneDeep(mockTimelineData[0].ecs); suricata = cloneDeep(mockTimelineData[2].ecs); - zeek = cloneDeep(mockTimelineData[13].ecs); - system = cloneDeep(mockTimelineData[28].ecs); - auditd = cloneDeep(mockTimelineData[19].ecs); }); test('renders correctly against snapshot', () => { @@ -74,76 +69,4 @@ describe('get_column_renderer', () => { 'some child 4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' ); }); - - test('should render a suricata row data if event.category is network_traffic', () => { - suricata.event = { ...suricata.event, ...{ category: ['network_traffic'] } }; - const rowRenderer = getRowRenderer(suricata, rowRenderers); - const row = rowRenderer.renderRow({ - browserFields: mockBrowserFields, - data: suricata, - children: {'some child '}, - }); - const wrapper = mount( - - {row} - - ); - expect(wrapper.text()).toContain( - 'some child 4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343' - ); - }); - - test('should render a zeek row data if event.category is network_traffic', () => { - zeek.event = { ...zeek.event, ...{ category: ['network_traffic'] } }; - const rowRenderer = getRowRenderer(zeek, rowRenderers); - const row = rowRenderer.renderRow({ - browserFields: mockBrowserFields, - data: zeek, - children: {'some child '}, - }); - const wrapper = mount( - - {row} - - ); - expect(wrapper.text()).toContain( - 'some child C8DRTq362Fios6hw16connectionREJSrConnection attempt rejectedtcpSource185.176.26.101:44059Destination207.154.238.205:11568' - ); - }); - - test('should render a system row data if event.category is network_traffic', () => { - system.event = { ...system.event, ...{ category: ['network_traffic'] } }; - const rowRenderer = getRowRenderer(system, rowRenderers); - const row = rowRenderer.renderRow({ - browserFields: mockBrowserFields, - data: system, - children: {'some child '}, - }); - const wrapper = mount( - - {row} - - ); - expect(wrapper.text()).toContain( - 'some child Braden@zeek-londonattempted a login via6278with resultfailureSource128.199.212.120' - ); - }); - - test('should render a auditd row data if event.category is network_traffic', () => { - auditd.event = { ...auditd.event, ...{ category: ['network_traffic'] } }; - const rowRenderer = getRowRenderer(auditd, rowRenderers); - const row = rowRenderer.renderRow({ - browserFields: mockBrowserFields, - data: auditd, - children: {'some child '}, - }); - const wrapper = mount( - - {row} - - ); - expect(wrapper.text()).toContain( - 'some child Sessionalice@zeek-sanfranin/executedgpgconf--list-dirs agent-socket' - ); - }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/index.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/index.ts index 8e95fc3ad238a..d465ed128ed49 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/index.ts @@ -16,19 +16,12 @@ import { unknownColumnRenderer } from './unknown_column_renderer'; import { zeekRowRenderer } from './zeek/zeek_row_renderer'; import { systemRowRenderers } from './system/generic_row_renderer'; -// The row renderers are order dependent and will return the first renderer -// which returns true from its isInstance call. The bottom renderers which -// are netflowRenderer and plainRowRenderer are the most accepting where -// netflowRowRenderer returns true on any netflow related data set including -// Suricata and Zeek which is why Suricata and Zeek are above it. The -// plainRowRenderer always returns true to everything which is why it always -// should be last. export const rowRenderers: RowRenderer[] = [ ...auditdRowRenderers, - ...systemRowRenderers, + netflowRowRenderer, suricataRowRenderer, + ...systemRowRenderers, zeekRowRenderer, - netflowRowRenderer, plainRowRenderer, // falls-back to the plain row renderer ]; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap index 47b07bbd09f5a..e3195baf20fd3 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap @@ -333,11 +333,28 @@ exports[`UrlStateContainer mounts and renders 1`] = ` "state": "", }, "push": [MockFunction], - "replace": [MockFunction], + "replace": [MockFunction] { + "calls": Array [ + Array [ + Object { + "hash": "", + "pathname": "/network", + "search": "?timerange=(global:(linkTo:!(timeline),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)),timeline:(linkTo:!(global),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)))", + "state": "", + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, } } > - - + - + - - + /> + + + diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts b/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts index e0ecfc1640bbe..66f79d53c77f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/constants.ts @@ -16,5 +16,3 @@ export enum CONSTANTS { timelineId = 'timelineId', unknown = 'unknown', } - -export type UrlStateType = 'host' | 'network' | 'overview' | 'timeline'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.test.ts b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.test.ts index 69e3d10fff8e9..7a0f1402d765a 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.test.ts @@ -3,69 +3,45 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { navTabs, SiemPageName } from '../../pages/home/home_navigations'; -import { isKqlForRoute, getTitle } from './helpers'; + +import { isKqlForRoute } from './helpers'; import { CONSTANTS } from './constants'; -describe('Helpers Url_State', () => { - describe('isKqlForRoute', () => { - test('host page and host page kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsPage); - expect(result).toBeTruthy(); - }); - test('host page and host details kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsDetails); - expect(result).toBeFalsy(); - }); - test('host details and host details kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsDetails); - expect(result).toBeTruthy(); - }); - test('host details and host page kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsPage); - expect(result).toBeFalsy(); - }); - test('network page and network page kuery', () => { - const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkPage); - expect(result).toBeTruthy(); - }); - test('network page and network details kuery', () => { - const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkDetails); - expect(result).toBeFalsy(); - }); - test('network details and network details kuery', () => { - const result = isKqlForRoute(SiemPageName.network, '10.100.7.198', CONSTANTS.networkDetails); - expect(result).toBeTruthy(); - }); - test('network details and network page kuery', () => { - const result = isKqlForRoute(SiemPageName.network, '123.234.34', CONSTANTS.networkPage); - expect(result).toBeFalsy(); - }); +describe('isKqlForRoute', () => { + test('host page and host page kuery', () => { + const result = isKqlForRoute('/hosts', CONSTANTS.hostsPage); + expect(result).toBeTruthy(); + }); + test('host page and host details kuery', () => { + const result = isKqlForRoute('/hosts', CONSTANTS.hostsDetails); + expect(result).toBeFalsy(); + }); + test('works when there is a trailing slash', () => { + const result = isKqlForRoute('/hosts/', CONSTANTS.hostsPage); + expect(result).toBeTruthy(); + }); + test('host details and host details kuery', () => { + const result = isKqlForRoute('/hosts/siem-kibana', CONSTANTS.hostsDetails); + expect(result).toBeTruthy(); + }); + test('host details and host page kuery', () => { + const result = isKqlForRoute('/hosts/siem-kibana', CONSTANTS.hostsPage); + expect(result).toBeFalsy(); + }); + test('network page and network page kuery', () => { + const result = isKqlForRoute('/network', CONSTANTS.networkPage); + expect(result).toBeTruthy(); + }); + test('network page and network details kuery', () => { + const result = isKqlForRoute('/network', CONSTANTS.networkDetails); + expect(result).toBeFalsy(); + }); + test('network details and network details kuery', () => { + const result = isKqlForRoute('/network/ip/10.100.7.198', CONSTANTS.networkDetails); + expect(result).toBeTruthy(); }); - describe('getTitle', () => { - test('host page name', () => { - const result = getTitle('hosts', undefined, navTabs); - expect(result).toEqual('Hosts'); - }); - test('network page name', () => { - const result = getTitle('network', undefined, navTabs); - expect(result).toEqual('Network'); - }); - test('overview page name', () => { - const result = getTitle('overview', undefined, navTabs); - expect(result).toEqual('Overview'); - }); - test('timelines page name', () => { - const result = getTitle('timelines', undefined, navTabs); - expect(result).toEqual('Timelines'); - }); - test('details page name', () => { - const result = getTitle('hosts', 'details', navTabs); - expect(result).toEqual('details'); - }); - test('Not existing', () => { - const result = getTitle('IamHereButNotReally', undefined, navTabs); - expect(result).toEqual(''); - }); + test('network details and network page kuery', () => { + const result = isKqlForRoute('/network/ip/123.234.34', CONSTANTS.networkPage); + expect(result).toBeFalsy(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts index 64a53a8402a57..cda63bd3a76bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts @@ -7,11 +7,8 @@ import { decode, encode, RisonValue } from 'rison-node'; import { Location } from 'history'; import { QueryString } from 'ui/utils/query_string'; - -import { SiemPageName } from '../../pages/home/home_navigations'; -import { NavTab } from '../navigation/types'; -import { CONSTANTS, UrlStateType } from './constants'; -import { LocationTypes } from './types'; +import { CONSTANTS } from './constants'; +import { LocationTypes, UrlStateType } from './types'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const decodeRisonUrlState = (value: string | undefined): RisonValue | any | undefined => { @@ -74,56 +71,50 @@ export const replaceQueryStringInLocation = (location: Location, queryString: st } }; -export const getUrlType = (pageName: string): UrlStateType => { - if (pageName === SiemPageName.hosts) { - return 'host'; - } else if (pageName === SiemPageName.network) { - return 'network'; - } else if (pageName === SiemPageName.overview) { - return 'overview'; - } else if (pageName === SiemPageName.timelines) { - return 'timeline'; +export const getUrlType = (pathname: string): UrlStateType => { + const removeSlash = pathname.replace(/\/$/, ''); + const trailingPath = removeSlash.match(/([^\/]+$)/); + if (trailingPath !== null) { + if (trailingPath[0] === 'hosts' || pathname.match(/^\/hosts\/.+$/) != null) { + return 'host'; + } else if (trailingPath[0] === 'network' || pathname.match(/^\/network\/.+$/) != null) { + return 'network'; + } else if (trailingPath[0] === 'overview') { + return 'overview'; + } else if (trailingPath[0] === 'timelines') { + return 'timeline'; + } } return 'overview'; }; -export const getTitle = ( - pageName: string, - detailName: string | undefined, - navTabs: Record -): string => { - if (detailName != null) return detailName; - return navTabs[pageName] != null ? navTabs[pageName].name : ''; -}; - -export const getCurrentLocation = ( - pageName: string, - detailName: string | undefined -): LocationTypes => { - if (pageName === SiemPageName.hosts) { - if (detailName != null) { +export const getCurrentLocation = (pathname: string): LocationTypes => { + const removeSlash = pathname.replace(/\/$/, ''); + const trailingPath = removeSlash.match(/([^\/]+$)/); + if (trailingPath !== null) { + if (trailingPath[0] === 'hosts') { + return CONSTANTS.hostsPage; + } else if (pathname.match(/^\/hosts\/.+$/) != null) { return CONSTANTS.hostsDetails; - } - return CONSTANTS.hostsPage; - } else if (pageName === SiemPageName.network) { - if (detailName != null) { + } else if (trailingPath[0] === 'network') { + return CONSTANTS.networkPage; + } else if (pathname.match(/^\/network\/.+$/) != null) { return CONSTANTS.networkDetails; + } else if (trailingPath[0] === 'overview') { + return CONSTANTS.overviewPage; + } else if (trailingPath[0] === 'timelines') { + return CONSTANTS.timelinePage; } - return CONSTANTS.networkPage; - } else if (pageName === SiemPageName.overview) { - return CONSTANTS.overviewPage; - } else if (pageName === SiemPageName.timelines) { - return CONSTANTS.timelinePage; } return CONSTANTS.unknown; + // throw new Error(`'Unknown pathName in else if statement': ${pathname}`); }; export const isKqlForRoute = ( - pageName: string, - detailName: string | undefined, + pathname: string, queryLocation: LocationTypes | null = null ): boolean => { - const currentLocation = getCurrentLocation(pageName, detailName); + const currentLocation = getCurrentLocation(pathname); if ( (currentLocation === CONSTANTS.hostsPage && queryLocation === CONSTANTS.hostsPage) || (currentLocation === CONSTANTS.networkPage && queryLocation === CONSTANTS.networkPage) || diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx index dd8e8909a0921..6957ed515b7c5 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx @@ -3,7 +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 { Location } from 'history'; import { mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; @@ -11,16 +11,20 @@ import { Router } from 'react-router-dom'; import { MockedProvider } from 'react-apollo/test-utils'; import { StaticIndexPattern } from 'ui/index_patterns'; -import { apolloClientObservable, HookWrapper, mockGlobalState, TestProviders } from '../../mock'; +import { + apolloClientObservable, + globalNode, + HookWrapper, + mockGlobalState, + TestProviders, +} from '../../mock'; import { createStore, State } from '../../store'; import { UseUrlState } from './'; import { defaultProps, getMockPropsObj, mockHistory, testCases } from './test_dependencies'; import { UrlStateContainerPropTypes } from './types'; -import { useUrlStateHooks } from './use_url_state'; +import { useUrlStateHooks, initializeLocation } from './use_url_state'; import { CONSTANTS } from './constants'; -import { RouteSpyState } from '../../utils/route/types'; -import { navTabs, SiemPageName } from '../../pages/home/home_navigations'; let mockProps: UrlStateContainerPropTypes; @@ -35,19 +39,6 @@ const indexPattern: StaticIndexPattern = { }, ], }; - -// const mockUseRouteSpy: jest.Mock = useRouteSpy as jest.Mock; -const mockRouteSpy: RouteSpyState = { - pageName: SiemPageName.network, - detailName: undefined, - tabName: undefined, - search: '', - pathName: '/network', -}; -jest.mock('../../utils/route/use_route_spy', () => ({ - useRouteSpy: () => [mockRouteSpy], -})); - describe('UrlStateContainer', () => { const state: State = mockGlobalState; @@ -64,7 +55,7 @@ describe('UrlStateContainer', () => { - + @@ -77,64 +68,53 @@ describe('UrlStateContainer', () => { describe('handleInitialize', () => { describe('URL state updates redux', () => { describe('relative timerange actions are called with correct data on component mount', () => { - test.each(testCases)( - '%o', - (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { - mockProps = getMockPropsObj({ - page, - examplePath, - namespaceLower, - pageName, - detailName, - }).relativeTimeSearch.undefinedQuery; - mount( useUrlStateHooks(args)} />); - - // @ts-ignore property mock does not exists - expect(defaultProps.setRelativeTimerange.mock.calls[1][0]).toEqual({ - from: 1558591200000, - fromStr: 'now-1d/d', - kind: 'relative', - to: 1558677599999, - toStr: 'now-1d/d', - id: 'global', - }); - // @ts-ignore property mock does not exists - expect(defaultProps.setRelativeTimerange.mock.calls[0][0]).toEqual({ - from: 1558732849370, - fromStr: 'now-15m', - kind: 'relative', - to: 1558733749370, - toStr: 'now', - id: 'timeline', - }); - } - ); + test.each(testCases)('%o', (page, namespaceLower, namespaceUpper, examplePath) => { + mockProps = getMockPropsObj({ page, examplePath, namespaceLower }).relativeTimeSearch + .undefinedQuery; + mount( useUrlStateHooks(args)} />); + + // @ts-ignore property mock does not exists + expect(defaultProps.setRelativeTimerange.mock.calls[1][0]).toEqual({ + from: 1558591200000, + fromStr: 'now-1d/d', + kind: 'relative', + to: 1558677599999, + toStr: 'now-1d/d', + id: 'global', + }); + // @ts-ignore property mock does not exists + expect(defaultProps.setRelativeTimerange.mock.calls[0][0]).toEqual({ + from: 1558732849370, + fromStr: 'now-15m', + kind: 'relative', + to: 1558733749370, + toStr: 'now', + id: 'timeline', + }); + }); }); describe('absolute timerange actions are called with correct data on component mount', () => { - test.each(testCases)( - '%o', - (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { - mockProps = getMockPropsObj({ page, examplePath, namespaceLower, pageName, detailName }) - .absoluteTimeSearch.undefinedQuery; - mount( useUrlStateHooks(args)} />); - - // @ts-ignore property mock does not exists - expect(defaultProps.setAbsoluteTimerange.mock.calls[1][0]).toEqual({ - from: 1556736012685, - kind: 'absolute', - to: 1556822416082, - id: 'global', - }); - // @ts-ignore property mock does not exists - expect(defaultProps.setAbsoluteTimerange.mock.calls[0][0]).toEqual({ - from: 1556736012685, - kind: 'absolute', - to: 1556822416082, - id: 'timeline', - }); - } - ); + test.each(testCases)('%o', (page, namespaceLower, namespaceUpper, examplePath) => { + mockProps = getMockPropsObj({ page, examplePath, namespaceLower }).absoluteTimeSearch + .undefinedQuery; + mount( useUrlStateHooks(args)} />); + + // @ts-ignore property mock does not exists + expect(defaultProps.setAbsoluteTimerange.mock.calls[1][0]).toEqual({ + from: 1556736012685, + kind: 'absolute', + to: 1556822416082, + id: 'global', + }); + // @ts-ignore property mock does not exists + expect(defaultProps.setAbsoluteTimerange.mock.calls[0][0]).toEqual({ + from: 1556736012685, + kind: 'absolute', + to: 1556822416082, + id: 'timeline', + }); + }); }); describe('kqlQuery action is called with correct data on component mount', () => { @@ -148,9 +128,9 @@ describe('UrlStateContainer', () => { }; test.each(testCases.slice(0, 4))( ' %o', - (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { - mockProps = getMockPropsObj({ page, examplePath, namespaceLower, pageName, detailName }) - .relativeTimeSearch.undefinedQuery; + (page, namespaceLower, namespaceUpper, examplePath, type) => { + mockProps = getMockPropsObj({ page, examplePath, namespaceLower }).relativeTimeSearch + .undefinedQuery; mount( useUrlStateHooks(args)} />); const functionName = namespaceUpper === 'Network' ? defaultProps.setNetworkKql : defaultProps.setHostsKql; @@ -164,54 +144,111 @@ describe('UrlStateContainer', () => { }); describe('kqlQuery action is not called called when the queryLocation does not match the router location', () => { - test.each(testCases)( - '%o', - (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { - mockProps = getMockPropsObj({ - page, - examplePath, - namespaceLower, - pageName, - detailName, - }).oppositeQueryLocationSearch.undefinedQuery; - mount( useUrlStateHooks(args)} />); - const functionName = - namespaceUpper === 'Network' ? defaultProps.setNetworkKql : defaultProps.setHostsKql; - // @ts-ignore property mock does not exists - expect(functionName.mock.calls.length).toEqual(0); - } - ); + test.each(testCases)('%o', (page, namespaceLower, namespaceUpper, examplePath) => { + mockProps = getMockPropsObj({ page, examplePath, namespaceLower }) + .oppositeQueryLocationSearch.undefinedQuery; + mount( useUrlStateHooks(args)} />); + const functionName = + namespaceUpper === 'Network' ? defaultProps.setNetworkKql : defaultProps.setHostsKql; + // @ts-ignore property mock does not exists + expect(functionName.mock.calls.length).toEqual(0); + }); }); }); describe('Redux updates URL state', () => { describe('kqlQuery url state is set from redux data on component mount', () => { - test.each(testCases)( - '%o', - (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { - mockProps = getMockPropsObj({ - page, - examplePath, - namespaceLower, - pageName, - detailName, - }).noSearch.definedQuery; - mount( useUrlStateHooks(args)} />); + test.each(testCases)('%o', (page, namespaceLower, namespaceUpper, examplePath) => { + mockProps = getMockPropsObj({ page, examplePath, namespaceLower }).noSearch.definedQuery; + mount( useUrlStateHooks(args)} />); + + // @ts-ignore property mock does not exists + expect( + mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0] + ).toEqual({ + hash: '', + pathname: examplePath, + search: [CONSTANTS.overviewPage, CONSTANTS.timelinePage].includes(page) + ? '?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))' + : `?_g=()&kqlQuery=(filterQuery:(expression:'host.name:%22siem-es%22',kind:kuery),queryLocation:${page})&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`, + state: '', + }); + }); + }); + }); + }); - // @ts-ignore property mock does not exists - expect( - mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0] - ).toEqual({ - hash: '', - pathname: examplePath, - search: [CONSTANTS.overviewPage, CONSTANTS.timelinePage].includes(page) - ? '?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))' - : `?_g=()&kqlQuery=(filterQuery:(expression:'host.name:%22siem-es%22',kind:kuery),queryLocation:${page})&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`, - state: '', - }); - } - ); + describe('initializeLocation', () => { + test('basic functionality with no pathname', () => { + Object.defineProperty(globalNode.window, 'location', { + value: { + href: 'http://localhost:5601/app/siem#/overview', + hash: '#/overview', + }, + writable: true, + }); + const location: Location = { + hash: '', + pathname: '/', + search: '', + state: null, + }; + expect(initializeLocation(location).search).toEqual(''); + }); + test('basic functionality with no search', () => { + Object.defineProperty(globalNode.window, 'location', { + value: { + href: 'http://localhost:5601/app/siem#/hosts?_g=()', + }, + writable: true, + }); + const location: Location = { + hash: '', + pathname: '/hosts', + search: '?_g=()', + state: null, + }; + expect(initializeLocation(location).search).toEqual('?_g=()'); + }); + + test('basic functionality with search', () => { + Object.defineProperty(globalNode.window, 'location', { + value: { + href: + "http://localhost:5601/app/siem#/hosts?_g=()&kqlQuery=(filterQuery:(expression:'%20host.name:%20%22beats-ci-immutable-ubuntu-1604-1560801145745062645%22%20and%20process.name:*',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)))", + }, + writable: true, + }); + const location: Location = { + hash: '', + pathname: '/hosts', + search: + "?_g=()&kqlQuery=(filterQuery:(expression:'%2Bhost.name:%2B%22beats-ci-immutable-ubuntu-1604-1560801145745062645%22%2Band%2Bprocess.name:*',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)))", + state: null, + }; + expect(initializeLocation(location).search).toEqual( + "?_g=()&kqlQuery=(filterQuery:(expression:'%20host.name:%20%22beats-ci-immutable-ubuntu-1604-1560801145745062645%22%20and%20process.name:*',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)))" + ); + }); + + test('If hash and pathname do not match href from the hash, do not do anything', () => { + Object.defineProperty(globalNode.window, 'location', { + value: { + href: + "http://localhost:5601/app/siem#/hosts?_g=()&kqlQuery=(filterQuery:(expression:'%20host.name:%20%22beats-ci-immutable-ubuntu-1604-1560801145745062645%22%20and%20process.name:*',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)))", + }, + writable: true, }); + const location: Location = { + hash: '', + pathname: '/network', + search: + "?_g=()&kqlQuery=(filterQuery:(expression:'%2Bhost.name:%2B%22beats-ci-immutable-ubuntu-1604-1560801145745062645%22%2Band%2Bprocess.name:*',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)))", + state: null, + }; + expect(initializeLocation(location).search).toEqual( + "?_g=()&kqlQuery=(filterQuery:(expression:'%2Bhost.name:%2B%22beats-ci-immutable-ubuntu-1604-1560801145745062645%22%2Band%2Bprocess.name:*',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1560714985274,fromStr:now-24h,kind:relative,to:1560801385274,toStr:now)))" + ); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx index e53cb55c9b792..700dc9f9ff440 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { compose, Dispatch } from 'redux'; import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; import { isEqual } from 'lodash/fp'; import { @@ -19,8 +20,6 @@ import { timelineSelectors, } from '../../store'; import { hostsActions, inputsActions, networkActions, timelineActions } from '../../store/actions'; -import { RouteSpyState } from '../../utils/route/types'; -import { useRouteSpy } from '../../utils/route/use_route_spy'; import { CONSTANTS } from './constants'; import { UrlStateContainerPropTypes, UrlStateProps, KqlQuery, LocationTypes } from './types'; @@ -29,12 +28,13 @@ import { dispatchUpdateTimeline } from '../open_timeline/helpers'; import { getCurrentLocation } from './helpers'; export const UrlStateContainer = React.memo( - (props: UrlStateContainerPropTypes) => { + props => { useUrlStateHooks(props); return null; }, (prevProps, nextProps) => - prevProps.pathName === nextProps.pathName && isEqual(prevProps.urlState, nextProps.urlState) + prevProps.location.pathname === nextProps.location.pathname && + isEqual(prevProps.urlState, nextProps.urlState) ); UrlStateContainer.displayName = 'UrlStateContainer'; @@ -44,12 +44,12 @@ const makeMapStateToProps = () => { const getHostsFilterQueryAsKuery = hostsSelectors.hostsFilterQueryAsKuery(); const getNetworkFilterQueryAsKuery = networkSelectors.networkFilterQueryAsKuery(); const getTimelines = timelineSelectors.getTimelines(); - const mapStateToProps = (state: State, { pageName, detailName }: UrlStateContainerPropTypes) => { + const mapStateToProps = (state: State, { location }: UrlStateContainerPropTypes) => { const inputState = getInputsSelector(state); const { linkTo: globalLinkTo, timerange: globalTimerange } = inputState.global; const { linkTo: timelineLinkTo, timerange: timelineTimerange } = inputState.timeline; - const page: LocationTypes | null = getCurrentLocation(pageName, detailName); + const page: LocationTypes | null = getCurrentLocation(location.pathname); const kqlQueryInitialState: KqlQuery = { filterQuery: null, queryLocation: page, @@ -121,15 +121,10 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch, }); -export const UrlStateRedux = compose>( +export const UseUrlState = compose>( + withRouter, connect( makeMapStateToProps, mapDispatchToProps ) )(UrlStateContainer); - -export const UseUrlState = React.memo(props => { - const [routeProps] = useRouteSpy(); - const urlStateReduxProps: RouteSpyState & UrlStateProps = { ...routeProps, ...props }; - return ; -}); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx index 6598bd491f73b..f194e4cc6f425 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx @@ -9,7 +9,6 @@ import { difference } from 'lodash/fp'; import * as React from 'react'; import { HookWrapper } from '../../mock/hook_wrapper'; -import { SiemPageName } from '../../pages/home/home_navigations'; import { CONSTANTS } from './constants'; import { getFilterQuery, getMockPropsObj, mockHistory, testCases } from './test_dependencies'; @@ -37,8 +36,6 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => page: CONSTANTS.networkPage, examplePath: '/network', namespaceLower: 'network', - pageName: SiemPageName.network, - detailName: undefined, }).noSearch.definedQuery; const wrapper = mount( useUrlStateHooks(args)} /> @@ -88,8 +85,6 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => page: CONSTANTS.networkPage, examplePath: '/network', namespaceLower: 'network', - pageName: SiemPageName.network, - detailName: undefined, }).noSearch.undefinedQuery; const wrapper = mount( useUrlStateHooks(args)} /> @@ -122,8 +117,6 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => page: CONSTANTS.networkPage, examplePath: '/network', namespaceLower: 'network', - pageName: SiemPageName.network, - detailName: undefined, }).noSearch.undefinedQuery; const wrapper = mount( useUrlStateHooks(args)} /> @@ -152,46 +145,39 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => describe('handleInitialize', () => { describe('Redux updates URL state', () => { describe('Timerange url state is set when not defined on component mount', () => { - test.each(testCases)( - '%o', - (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { - mockProps = getMockPropsObj({ page, examplePath, namespaceLower, pageName, detailName }) - .noSearch.undefinedQuery; - mount( useUrlStateHooks(args)} />); - - expect(mockHistory.replace.mock.calls[0][0]).toEqual({ - hash: '', - pathname: examplePath, - search: '?_g=()', - state: '', - }); - - expect( - mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0] - ).toEqual({ - hash: '', - pathname: examplePath, - search: - '?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', - state: '', - }); - } - ); + test.each(testCases)('%o', (page, namespaceLower, namespaceUpper, examplePath) => { + mockProps = getMockPropsObj({ page, examplePath, namespaceLower }).noSearch + .undefinedQuery; + mount( useUrlStateHooks(args)} />); + + expect(mockHistory.replace.mock.calls[0][0]).toEqual({ + hash: '', + pathname: examplePath, + search: '?_g=()', + state: '', + }); + + expect( + mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0] + ).toEqual({ + hash: '', + pathname: examplePath, + search: + '?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', + state: '', + }); + }); test('url state is set from redux data when location updates and initialization', () => { mockProps = getMockPropsObj({ page: CONSTANTS.hostsPage, examplePath: '/hosts', namespaceLower: 'hosts', - pageName: SiemPageName.hosts, - detailName: undefined, }).noSearch.undefinedQuery; const updatedProps = getMockPropsObj({ page: CONSTANTS.networkPage, examplePath: '/network', namespaceLower: 'network', - pageName: SiemPageName.network, - detailName: undefined, }).noSearch.definedQuery; const wrapper = mount( useUrlStateHooks(args)} /> diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/test_dependencies.ts b/x-pack/legacy/plugins/siem/public/components/url_state/test_dependencies.ts index 1915b2c524525..a520d54a6d493 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/test_dependencies.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/test_dependencies.ts @@ -10,8 +10,6 @@ import { UrlStateContainerPropTypes, LocationTypes, KqlQuery } from './types'; import { CONSTANTS } from './constants'; import { InputsModelId } from '../../store/inputs/constants'; import { DispatchUpdateTimeline } from '../open_timeline/types'; -import { navTabs, SiemPageName } from '../../pages/home/home_navigations'; -import { HostsTableType } from '../../store/hosts/model'; type Action = 'PUSH' | 'POP' | 'REPLACE'; const pop: Action = 'POP'; @@ -46,12 +44,13 @@ export const mockHistory = { }; export const defaultProps: UrlStateContainerPropTypes = { - pageName: SiemPageName.network, - detailName: undefined, - tabName: HostsTableType.authentications, - search: '', - pathName: '/network', - navTabs, + match: { + isExact: true, + params: '', + path: '', + url: '', + }, + isInitializing: true, indexPattern: { fields: [ { @@ -128,14 +127,13 @@ export const defaultProps: UrlStateContainerPropTypes = { ...mockHistory, location: defaultLocation, }, + location: defaultLocation, }; export const getMockProps = ( location = defaultLocation, kqlQueryKey = CONSTANTS.networkPage, - kqlQueryValue: KqlQuery | null, - pageName: string, - detailName: string | undefined + kqlQueryValue: KqlQuery | null ): UrlStateContainerPropTypes => ({ ...defaultProps, urlState: { @@ -146,27 +144,16 @@ export const getMockProps = ( ...mockHistory, location, }, - detailName, - pageName, - pathName: location.pathname, - search: location.search, + location, }); interface GetMockPropsObj { examplePath: string; namespaceLower: string; page: LocationTypes; - pageName: string; - detailName: string | undefined; } -export const getMockPropsObj = ({ - page, - examplePath, - namespaceLower, - pageName, - detailName, -}: GetMockPropsObj) => ({ +export const getMockPropsObj = ({ page, examplePath, namespaceLower }: GetMockPropsObj) => ({ noSearch: { undefinedQuery: getMockProps( { @@ -176,9 +163,7 @@ export const getMockPropsObj = ({ state: '', }, page, - null, - pageName, - detailName + null ), definedQuery: getMockProps( { @@ -188,9 +173,7 @@ export const getMockPropsObj = ({ state: '', }, page, - getFilterQuery(page), - pageName, - detailName + getFilterQuery(page) ), }, relativeTimeSearch: { @@ -202,9 +185,7 @@ export const getMockPropsObj = ({ state: '', }, page, - null, - pageName, - detailName + null ), definedQuery: getMockProps( { @@ -214,9 +195,7 @@ export const getMockPropsObj = ({ state: '', }, page, - getFilterQuery(page), - pageName, - detailName + getFilterQuery(page) ), }, absoluteTimeSearch: { @@ -229,9 +208,7 @@ export const getMockPropsObj = ({ state: '', }, page, - null, - pageName, - detailName + null ), definedQuery: getMockProps( { @@ -242,9 +219,7 @@ export const getMockPropsObj = ({ state: '', }, page, - getFilterQuery(page), - pageName, - detailName + getFilterQuery(page) ), }, oppositeQueryLocationSearch: { @@ -258,9 +233,7 @@ export const getMockPropsObj = ({ state: '', }, page, - null, - pageName, - detailName + null ), }, }); @@ -272,54 +245,40 @@ export const testCases = [ /* page */ CONSTANTS.networkPage, /* namespaceLower */ 'network', /* namespaceUpper */ 'Network', - /* pathName */ '/network', + /* examplePath */ '/network', /* type */ networkModel.NetworkType.page, - /* pageName */ SiemPageName.network, - /* detailName */ undefined, ], [ /* page */ CONSTANTS.hostsPage, /* namespaceLower */ 'hosts', /* namespaceUpper */ 'Hosts', - /* pathName */ '/hosts', + /* examplePath */ '/hosts', /* type */ hostsModel.HostsType.page, - /* pageName */ SiemPageName.hosts, - /* detailName */ undefined, ], [ /* page */ CONSTANTS.hostsDetails, /* namespaceLower */ 'hosts', /* namespaceUpper */ 'Hosts', - /* pathName */ '/hosts/siem-es', + /* examplePath */ '/hosts/siem-es', /* type */ hostsModel.HostsType.details, - /* pageName */ SiemPageName.hosts, - /* detailName */ 'host-test', ], [ /* page */ CONSTANTS.networkDetails, /* namespaceLower */ 'network', /* namespaceUpper */ 'Network', - /* pathName */ '/network/ip/100.90.80', + /* examplePath */ '/network/ip/100.90.80', /* type */ networkModel.NetworkType.details, - /* pageName */ SiemPageName.network, - /* detailName */ '100.90.80', ], [ /* page */ CONSTANTS.overviewPage, /* namespaceLower */ 'overview', /* namespaceUpper */ 'Overview', - /* pathName */ '/overview', - /* type */ null, - /* pageName */ SiemPageName.overview, - /* detailName */ undefined, + /* examplePath */ '/overview', ], [ /* page */ CONSTANTS.timelinePage, /* namespaceLower */ 'timeline', /* namespaceUpper */ 'Timeline', - /* pathName */ '/timeline', - /* type */ null, - /* pageName */ SiemPageName.timelines, - /* detailName */ undefined, + /* examplePath */ '/timeline', ], ]; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts index 8ff54e0f9d433..ea3ea40481179 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts @@ -4,18 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Location } from 'history'; +import { RouteComponentProps } from 'react-router'; import { ActionCreator } from 'typescript-fsa'; import { StaticIndexPattern } from 'ui/index_patterns'; -import { Dispatch } from 'redux'; +import { Dispatch } from 'redux'; import { hostsModel, KueryFilterQuery, networkModel, SerializedFilterQuery } from '../../store'; import { UrlInputsModel } from '../../store/inputs/model'; import { InputsModelId } from '../../store/inputs/constants'; -import { RouteSpyState } from '../../utils/route/types'; + +import { CONSTANTS } from './constants'; import { DispatchUpdateTimeline } from '../open_timeline/types'; -import { NavTab } from '../navigation/types'; -import { CONSTANTS, UrlStateType } from './constants'; +export type UrlStateType = 'host' | 'network' | 'overview' | 'timeline'; export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ CONSTANTS.kqlQuery, @@ -51,8 +53,8 @@ export interface UrlState { } export type KeyUrlState = keyof UrlState; -export interface UrlStateProps { - navTabs: Record; +export interface UrlStateProps { + isInitializing: boolean; indexPattern?: StaticIndexPattern; mapToUrlState?: (value: string) => UrlState; onChange?: (urlState: UrlState, previousUrlState: UrlState) => void; @@ -98,12 +100,12 @@ export interface UrlStateDispatchToPropsType { }>; } -export type UrlStateContainerPropTypes = RouteSpyState & +export type UrlStateContainerPropTypes = RouteComponentProps & UrlStateStateToPropsType & UrlStateDispatchToPropsType & UrlStateProps; export interface PreviousLocationUrlState { - pathName: string | undefined; + location: Location; urlState: UrlState; } diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx index f444b0105100c..441f828f03417 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx @@ -6,7 +6,7 @@ import { Location } from 'history'; import { get, isEqual, difference, isEmpty } from 'lodash/fp'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef } from 'react'; import { convertKueryToElasticSearchQuery } from '../../lib/keury'; import { InputsModelId, TimeRangeKinds } from '../../store/inputs/constants'; @@ -16,12 +16,8 @@ import { RelativeTimeRange, UrlInputsModel, } from '../../store/inputs/model'; -import { useApolloClient } from '../../utils/apollo_context'; -import { queryTimelineById } from '../open_timeline/helpers'; -import { HostsType } from '../../store/hosts/model'; -import { NetworkType } from '../../store/network/model'; -import { CONSTANTS, UrlStateType } from './constants'; +import { CONSTANTS } from './constants'; import { replaceQueryStringInLocation, getQueryStringFromLocation, @@ -31,7 +27,6 @@ import { isKqlForRoute, getCurrentLocation, getUrlType, - getTitle, } from './helpers'; import { normalizeTimeRange } from './normalize_time_range'; import { @@ -41,7 +36,12 @@ import { KeyUrlState, KqlQuery, ALL_URL_STATE_KEYS, + UrlStateType, } from './types'; +import { useApolloClient } from '../../utils/apollo_context'; +import { queryTimelineById } from '../open_timeline/helpers'; +import { HostsType } from '../../store/hosts/model'; +import { NetworkType } from '../../store/network/model'; function usePrevious(value: PreviousLocationUrlState) { const ref = useRef(value); @@ -54,52 +54,37 @@ function usePrevious(value: PreviousLocationUrlState) { export const useUrlStateHooks = ({ addGlobalLinkTo, addTimelineLinkTo, - detailName, dispatch, + location, indexPattern, + isInitializing, history, - navTabs, - pageName, - pathName, removeGlobalLinkTo, removeTimelineLinkTo, - search, setAbsoluteTimerange, setHostsKql, setNetworkKql, setRelativeTimerange, - tabName, updateTimeline, updateTimelineIsLoading, urlState, }: UrlStateContainerPropTypes) => { - const [isInitializing, setIsInitializing] = useState(true); const apolloClient = useApolloClient(); - const prevProps = usePrevious({ pathName, urlState }); + const prevProps = usePrevious({ location, urlState }); const replaceStateInLocation = ( urlStateToReplace: UrlInputsModel | KqlQuery | string, urlStateKey: string, - latestLocation: Location = { - hash: '', - pathname: pathName, - search, - state: '', - } + latestLocation: Location = location ) => { const newLocation = replaceQueryStringInLocation( - { - hash: '', - pathname: pathName, - search, - state: '', - }, + location, replaceStateKeyInQueryString(urlStateKey, urlStateToReplace)( getQueryStringFromLocation(latestLocation) ) ); - if (history && !isEqual(newLocation.search, latestLocation.search)) { + if (!isEqual(newLocation.search, latestLocation.search)) { history.replace(newLocation); } return newLocation; @@ -116,7 +101,7 @@ export const useUrlStateHooks = ({ const kqlQueryStateData: KqlQuery = decodeRisonUrlState(newUrlStateString); if ( urlKey === CONSTANTS.kqlQuery && - !isKqlForRoute(pageName, detailName, kqlQueryStateData.queryLocation) && + !isKqlForRoute(location.pathname, kqlQueryStateData.queryLocation) && urlState[urlKey].queryLocation === kqlQueryStateData.queryLocation ) { myLocation = replaceStateInLocation( @@ -216,7 +201,7 @@ export const useUrlStateHooks = ({ } if (urlKey === CONSTANTS.kqlQuery && indexPattern != null) { const kqlQueryStateData: KqlQuery = decodeRisonUrlState(newUrlStateString); - if (isKqlForRoute(pageName, detailName, kqlQueryStateData.queryLocation)) { + if (isKqlForRoute(location.pathname, kqlQueryStateData.queryLocation)) { const filterQuery = { kuery: kqlQueryStateData.filterQuery, serializedQuery: convertKueryToElasticSearchQuery( @@ -224,7 +209,7 @@ export const useUrlStateHooks = ({ indexPattern ), }; - const page = getCurrentLocation(pageName, detailName); + const page = getCurrentLocation(location.pathname); if ([CONSTANTS.hostsPage, CONSTANTS.hostsDetails].includes(page)) { dispatch( setHostsKql({ @@ -258,30 +243,38 @@ export const useUrlStateHooks = ({ }; useEffect(() => { - const type: UrlStateType = getUrlType(pageName); - const location: Location = { - hash: '', - pathname: pathName, - search, - state: '', - }; - - if (isInitializing && pageName != null && pageName !== '') { - handleInitialize(location, type); - setIsInitializing(false); + const type: UrlStateType = getUrlType(location.pathname); + if (isInitializing) { + handleInitialize(initializeLocation(location), type); } else if (!isEqual(urlState, prevProps.urlState)) { let newLocation: Location = location; URL_STATE_KEYS[type].forEach((urlKey: KeyUrlState) => { newLocation = replaceStateInLocation(urlState[urlKey], urlKey, newLocation); }); - } else if (pathName !== prevProps.pathName) { + } else if (location.pathname !== prevProps.location.pathname) { handleInitialize(location, type); } }); - useEffect(() => { - document.title = `${getTitle(pageName, detailName, navTabs)} - Kibana`; - }, [pageName]); - return null; }; + +/* + * Why are we doing that, it is because angular-ui router is encoding the `+` back to `2%B` after + * that react router is getting the data with the `+` and convert to `2%B` + * so we need to get back the value from the window location at initialization to avoid + * to bring back the `+` in the kql + */ +export const initializeLocation = (location: Location): Location => { + if (location.pathname === '/') { + location.pathname = window.location.hash.substring(1); + } + const substringIndex = + window.location.href.indexOf(`#${location.pathname}`) >= 0 + ? window.location.href.indexOf(`#${location.pathname}`) + location.pathname.length + 1 + : -1; + if (substringIndex >= 0 && location.pathname !== '/') { + location.search = window.location.href.substring(substringIndex); + } + return location; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx index aa90cb8025dd0..8280f7cb32c08 100644 --- a/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx @@ -22,7 +22,7 @@ interface GlobalQuery extends SetQuery { inputId: InputsModelId; } -export interface GlobalTimeArgs { +interface GlobalTimeArgs { from: number; to: number; setQuery: ({ id, inspect, loading, refetch }: SetQuery) => void; @@ -55,7 +55,7 @@ export const GlobalTimeComponent = React.memo( return () => { deleteAllQuery({ id: 'global' }); }; - }, []); + }); return ( <> diff --git a/x-pack/legacy/plugins/siem/public/mock/global_state.ts b/x-pack/legacy/plugins/siem/public/mock/global_state.ts index 83fa30c97145f..7a19c65ec9f34 100644 --- a/x-pack/legacy/plugins/siem/public/mock/global_state.ts +++ b/x-pack/legacy/plugins/siem/public/mock/global_state.ts @@ -38,7 +38,7 @@ export const mockGlobalState: State = { page: { queries: { authentications: { activePage: 0, limit: 10 }, - allHosts: { + hosts: { activePage: 0, limit: 10, direction: Direction.desc, @@ -54,7 +54,7 @@ export const mockGlobalState: State = { details: { queries: { authentications: { activePage: 0, limit: 10 }, - allHosts: { + hosts: { activePage: 0, limit: 10, direction: Direction.desc, diff --git a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx index 329547fe202e0..edbcad74a9cda 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx @@ -3,14 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import * as i18n from './translations'; +import * as i18n from '../../components/navigation/translations'; +import { NavTab } from '../../components/navigation/type'; import { getOverviewUrl, getNetworkUrl, getTimelinesUrl, getHostsUrl, } from '../../components/link_to'; -import { NavTab } from '../../components/navigation/types'; export enum SiemPageName { overview = 'overview', diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx index f5c22e6acbf64..6bfbdc48e63f5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx @@ -12,6 +12,7 @@ import { pure } from 'recompose'; import styled from 'styled-components'; import chrome from 'ui/chrome'; +import { i18n } from '@kbn/i18n'; import { AutoSizer } from '../../components/auto_sizer'; import { DragDropContextWrapper } from '../../components/drag_and_drop/drag_drop_context_wrapper'; import { Flyout, flyoutHeaderHeight } from '../../components/flyout'; @@ -24,6 +25,7 @@ import { NotFoundPage } from '../404'; import { HostsContainer } from '../hosts'; import { NetworkContainer } from '../network'; import { Overview } from '../overview'; +import { PageRoute } from '../../components/page_route/pageroute'; import { Timelines } from '../timelines'; import { WithSource } from '../../containers/source'; import { MlPopover } from '../../components/ml_popover/ml_popover'; @@ -31,7 +33,7 @@ import { MlHostConditionalContainer } from '../../components/ml/conditional_link import { MlNetworkConditionalContainer } from '../../components/ml/conditional_links/ml_network_conditional_container'; import { navTabs } from './home_navigations'; import { UseUrlState } from '../../components/url_state'; -import { SpyRoute } from '../../utils/route/spy_routes'; +import { useGlobalLoading } from '../../utils/use_global_loading'; const WrappedByAutoSizer = styled.div` height: 100%; @@ -75,100 +77,119 @@ const calculateFlyoutHeight = ({ windowHeight: number; }): number => Math.max(0, windowHeight - globalHeaderSize); -export const HomePage = pure(() => ( - - {({ measureRef, windowMeasurement: { height: windowHeight = 0 } }) => ( - - - - - {({ browserFields, indexPattern }) => ( - - - - - { + const isGlobalInitializing = useGlobalLoading(); + return ( + + {({ measureRef, windowMeasurement: { height: windowHeight = 0 } }) => ( + + + + + {({ browserFields, indexPattern }) => ( + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - } /> - ( - - )} + headerHeight={flyoutHeaderHeight} + timelineId="timeline-1" + usersViewing={usersViewing} + > + - ( - - )} - /> - } /> - - - - - - - - )} - - - - - )} - -)); + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( + + )} + /> + + + ( + + )} + /> + + + + + + + + )} + + + + )} + + ); +}); HomePage.displayName = 'HomePage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.tsx deleted file mode 100644 index ad2c38cb0af12..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.tsx +++ /dev/null @@ -1,71 +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 React from 'react'; -import { connect } from 'react-redux'; - -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; -import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions'; -import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime'; -import { Anomaly } from '../../../components/ml/types'; -import { getHostDetailsEventsKqlQueryExpression } from '../helpers'; - -import { HostDetailsBodyComponentProps } from './type'; -import { getFilterQuery, type, makeMapStateToProps } from './utils'; - -const HostDetailsBodyComponent = React.memo( - ({ - children, - filterQueryExpression, - from, - isInitializing, - detailName, - setAbsoluteRangeDatePicker, - setQuery, - to, - }) => { - return ( - - {({ indicesExist, indexPattern }) => - indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - <> - {children({ - endDate: to, - filterQuery: getFilterQuery(detailName, filterQueryExpression, indexPattern), - kqlQueryExpression: getHostDetailsEventsKqlQueryExpression({ - filterQueryExpression, - hostName: detailName, - }), - skip: isInitializing, - setQuery, - startDate: from, - type, - indexPattern, - narrowDateRange: (score: Anomaly, interval: string) => { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }, - })} - - ) : null - } - - ); - } -); - -HostDetailsBodyComponent.displayName = 'HostDetailsBodyComponent'; - -export const HostDetailsBody = connect( - makeMapStateToProps, - { - setAbsoluteRangeDatePicker: dispatchAbsoluteRangeDatePicker, - } -)(HostDetailsBodyComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx deleted file mode 100644 index ebcfeb30c6e0c..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx +++ /dev/null @@ -1,175 +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 { EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; -import React from 'react'; -import { compose } from 'redux'; -import { connect } from 'react-redux'; -import { StickyContainer } from 'react-sticky'; - -import { FiltersGlobal } from '../../../components/filters_global'; -import { HeaderPage } from '../../../components/header_page'; -import { LastEventTime } from '../../../components/last_event_time'; - -import { HostOverviewByNameQuery } from '../../../containers/hosts/overview'; -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; -import { LastEventIndexKey } from '../../../graphql/types'; - -import { HostsEmptyPage } from '../hosts_empty_page'; -import { HostsKql } from '../kql'; -import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions'; -import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime'; -import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details'; -import { hostToCriteria } from '../../../components/ml/criteria/host_to_criteria'; -import { navTabsHostDetails } from '../hosts_navigations'; -import { SiemNavigation } from '../../../components/navigation'; -import { HostsQueryProps } from '../hosts'; -import { SpyRoute } from '../../../utils/route/spy_routes'; -import { AnomalyTableProvider } from '../../../components/ml/anomaly/anomaly_table_provider'; -import { manageQuery } from '../../../components/page/manage_query'; -import { HostOverview } from '../../../components/page/hosts/host_overview'; -import { KpiHostsComponent } from '../../../components/page/hosts'; - -import { HostDetailsComponentProps } from './type'; -import { getFilterQuery, type, makeMapStateToProps } from './utils'; - -export { HostDetailsBody } from './body'; - -const HostOverviewManage = manageQuery(HostOverview); -const KpiHostDetailsManage = manageQuery(KpiHostsComponent); - -const HostDetailsComponent = React.memo( - ({ - isInitializing, - filterQueryExpression, - from, - detailName, - setQuery, - setAbsoluteRangeDatePicker, - to, - }) => { - return ( - <> - - {({ indicesExist, indexPattern }) => - indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - } - title={detailName} - /> - - {({ hostOverview, loading, id, inspect, refetch }) => ( - - {({ isLoadingAnomaliesData, anomaliesData }) => ( - { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} - /> - )} - - )} - - - - - - {({ kpiHostDetails, id, inspect, loading, refetch }) => ( - { - /** - * Using setTimeout here because of this issue: - * https://github.com/elastic/elastic-charts/issues/360 - * Need to remove the setTimeout here after this issue is fixed. - * */ - setTimeout(() => { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }, 500); - }} - /> - )} - - - - - - - ) : ( - <> - - - - - ) - } - - - - ); - } -); - -HostDetailsComponent.displayName = 'HostDetailsComponent'; - -export const HostDetails = compose>( - connect( - makeMapStateToProps, - { - setAbsoluteRangeDatePicker: dispatchAbsoluteRangeDatePicker, - } - ) -)(HostDetailsComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/type.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/details/type.ts deleted file mode 100644 index a057e791ab1d4..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/type.ts +++ /dev/null @@ -1,37 +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 { ActionCreator } from 'typescript-fsa'; - -import { InputsModelId } from '../../../store/inputs/constants'; -import { CommonChildren, AnonamaliesChildren, HostsQueryProps } from '../hosts'; -import { HostComponentProps } from '../../../components/link_to/redirect_to_hosts'; - -interface HostDetailsComponentReduxProps { - filterQueryExpression: string; -} - -interface HostDetailsComponentDispatchProps { - setAbsoluteRangeDatePicker: ActionCreator<{ - id: InputsModelId; - from: number; - to: number; - }>; - detailName: string; -} - -export interface HostDetailsBodyProps extends HostsQueryProps { - children: CommonChildren | AnonamaliesChildren; -} - -export type HostDetailsComponentProps = HostDetailsComponentReduxProps & - HostDetailsComponentDispatchProps & - HostComponentProps & - HostsQueryProps; - -export type HostDetailsBodyComponentProps = HostDetailsComponentReduxProps & - HostDetailsComponentDispatchProps & - HostDetailsBodyProps; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts deleted file mode 100644 index cd4239f00cac7..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts +++ /dev/null @@ -1,81 +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 { isEmpty } from 'lodash/fp'; -import { Breadcrumb } from 'ui/chrome'; -import { StaticIndexPattern } from 'ui/index_patterns'; - -import { ESTermQuery } from '../../../../common/typed_json'; - -import { hostsModel, hostsSelectors } from '../../../store/hosts'; -import { HostsTableType } from '../../../store/hosts/model'; -import { State } from '../../../store'; -import { getHostsUrl, getHostDetailsUrl } from '../../../components/link_to/redirect_to_hosts'; - -import * as i18n from '../translations'; -import { convertKueryToElasticSearchQuery, escapeQueryValue } from '../../../lib/keury'; -import { RouteSpyState } from '../../../utils/route/types'; - -export const type = hostsModel.HostsType.details; - -export const makeMapStateToProps = () => { - const getHostsFilterQuery = hostsSelectors.hostsFilterQueryExpression(); - return (state: State) => ({ - filterQueryExpression: getHostsFilterQuery(state, type) || '', - }); -}; - -const TabNameMappedToI18nKey = { - [HostsTableType.hosts]: i18n.NAVIGATION_ALL_HOSTS_TITLE, - [HostsTableType.authentications]: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, - [HostsTableType.uncommonProcesses]: i18n.NAVIGATION_UNCOMMON_PROCESSES_TITLE, - [HostsTableType.anomalies]: i18n.NAVIGATION_ANOMALIES_TITLE, - [HostsTableType.events]: i18n.NAVIGATION_EVENTS_TITLE, -}; - -export const getBreadcrumbs = (params: RouteSpyState, search: string[]): Breadcrumb[] => { - let breadcrumb = [ - { - text: i18n.PAGE_TITLE, - href: `${getHostsUrl()}${search && search[0] ? search[0] : ''}`, - }, - ]; - if (params.detailName != null) { - breadcrumb = [ - ...breadcrumb, - { - text: params.detailName, - href: `${getHostDetailsUrl(params.detailName)}${search && search[1] ? search[1] : ''}`, - }, - ]; - } - if (params.tabName != null) { - breadcrumb = [ - ...breadcrumb, - { - text: TabNameMappedToI18nKey[params.tabName], - href: '', - }, - ]; - } - return breadcrumb; -}; - -export const getFilterQuery = ( - hostName: string | null, - filterQueryExpression: string, - indexPattern: StaticIndexPattern -): ESTermQuery | string => - isEmpty(filterQueryExpression) - ? hostName - ? { term: { 'host.name': hostName } } - : '' - : convertKueryToElasticSearchQuery( - `${filterQueryExpression} ${ - hostName ? `and host.name: "${escapeQueryValue(hostName)}"` : '' - }`, - indexPattern - ); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/host_details.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/host_details.tsx new file mode 100644 index 0000000000000..972aef1327750 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/host_details.tsx @@ -0,0 +1,319 @@ +/* + * 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 { EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import { isEmpty, get } from 'lodash/fp'; +import React from 'react'; +import { connect } from 'react-redux'; +import { StickyContainer } from 'react-sticky'; +import { Breadcrumb } from 'ui/chrome'; +import { StaticIndexPattern } from 'ui/index_patterns'; + +import { ActionCreator } from 'typescript-fsa'; +import { ESTermQuery } from '../../../common/typed_json'; +import { FiltersGlobal } from '../../components/filters_global'; +import { HeaderPage } from '../../components/header_page'; +import { LastEventTime } from '../../components/last_event_time'; +import { + getHostsUrl, + HostComponentProps, + getHostDetailsUrl, +} from '../../components/link_to/redirect_to_hosts'; +import { KpiHostsComponent } from '../../components/page/hosts'; + +import { HostOverview } from '../../components/page/hosts/host_overview'; +import { manageQuery } from '../../components/page/manage_query'; +import { GlobalTime } from '../../containers/global_time'; +import { HostOverviewByNameQuery } from '../../containers/hosts/overview'; +import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; +import { LastEventIndexKey } from '../../graphql/types'; +import { convertKueryToElasticSearchQuery, escapeQueryValue } from '../../lib/keury'; +import { hostsModel, hostsSelectors, State } from '../../store'; + +import { HostsEmptyPage } from './hosts_empty_page'; +import { HostsKql } from './kql'; +import * as i18n from './translations'; +import { AnomalyTableProvider } from '../../components/ml/anomaly/anomaly_table_provider'; +import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../store/inputs/actions'; +import { InputsModelId } from '../../store/inputs/constants'; +import { scoreIntervalToDateTime } from '../../components/ml/score/score_interval_to_datetime'; +import { KpiHostDetailsQuery } from '../../containers/kpi_host_details'; +import { hostToCriteria } from '../../components/ml/criteria/host_to_criteria'; +import { navTabsHostDetails } from './hosts_navigations'; +import { SiemNavigation } from '../../components/navigation'; +import { Anomaly } from '../../components/ml/types'; +import { NavigationParams } from '../../components/navigation/breadcrumbs'; +import { HostsTableType } from '../../store/hosts/model'; +import { HostsQueryProps } from './hosts'; +import { getHostDetailsEventsKqlQueryExpression } from './helpers'; + +const type = hostsModel.HostsType.details; + +const HostOverviewManage = manageQuery(HostOverview); +const KpiHostDetailsManage = manageQuery(KpiHostsComponent); + +interface HostDetailsComponentReduxProps { + filterQueryExpression: string; + setAbsoluteRangeDatePicker: ActionCreator<{ + id: InputsModelId; + from: number; + to: number; + }>; +} + +type HostDetailsComponentProps = HostDetailsComponentReduxProps & + HostComponentProps & + HostsQueryProps; + +const HostDetailsComponent = React.memo( + ({ + match: { + params: { hostName, tabName }, + }, + filterQueryExpression, + setAbsoluteRangeDatePicker, + }) => { + return ( + + {({ indicesExist, indexPattern }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + } + title={hostName} + /> + + + {({ to, from, setQuery, isInitializing }) => ( + <> + + {({ hostOverview, loading, id, inspect, refetch }) => ( + + {({ isLoadingAnomaliesData, anomaliesData }) => ( + { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }} + /> + )} + + )} + + + + + + {({ kpiHostDetails, id, inspect, loading, refetch }) => ( + { + /** + * Using setTimeout here because of this issue: + * https://github.com/elastic/elastic-charts/issues/360 + * Need to remove the setTimeout here after this issue is fixed. + * */ + setTimeout(() => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, 500); + }} + /> + )} + + + + + + + )} + + + ) : ( + <> + + + + + ) + } + + ); + } +); + +HostDetailsComponent.displayName = 'HostDetailsComponent'; + +const HostDetailsBodyComponent = React.memo( + ({ + match: { + params: { hostName, tabName }, + }, + filterQueryExpression, + setAbsoluteRangeDatePicker, + children, + }) => { + return ( + + {({ indicesExist, indexPattern }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + {({ to, from, setQuery, isInitializing }) => ( + <> + {children({ + endDate: to, + filterQuery: getFilterQuery(hostName, filterQueryExpression, indexPattern), + kqlQueryExpression: getHostDetailsEventsKqlQueryExpression({ + filterQueryExpression, + hostName, + }), + skip: isInitializing, + setQuery, + startDate: from, + type, + indexPattern, + narrowDateRange: (score: Anomaly, interval: string) => { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }, + })} + + )} + + ) : null + } + + ); + } +); + +HostDetailsBodyComponent.displayName = 'HostDetailsBodyComponent'; + +const makeMapStateToProps = () => { + const getHostsFilterQuery = hostsSelectors.hostsFilterQueryExpression(); + return (state: State) => ({ + filterQueryExpression: getHostsFilterQuery(state, type) || '', + }); +}; + +export const HostDetails = connect( + makeMapStateToProps, + { + setAbsoluteRangeDatePicker: dispatchAbsoluteRangeDatePicker, + } +)(HostDetailsComponent); + +export const HostDetailsBody = connect( + makeMapStateToProps, + { + setAbsoluteRangeDatePicker: dispatchAbsoluteRangeDatePicker, + } +)(HostDetailsBodyComponent); + +const TabNameMappedToI18nKey = { + [HostsTableType.hosts]: i18n.NAVIGATION_ALL_HOSTS_TITLE, + [HostsTableType.authentications]: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, + [HostsTableType.uncommonProcesses]: i18n.NAVIGATION_UNCOMMON_PROCESSES_TITLE, + [HostsTableType.anomalies]: i18n.NAVIGATION_ANOMALIES_TITLE, + [HostsTableType.events]: i18n.NAVIGATION_EVENTS_TITLE, +}; + +export const getBreadcrumbs = (params: NavigationParams): Breadcrumb[] => { + const hostName = get('hostName', params); + const tabName = get('tabName', params); + let breadcrumb = [ + { + text: i18n.PAGE_TITLE, + href: getHostsUrl(), + }, + ]; + if (hostName) { + breadcrumb = [ + ...breadcrumb, + { + text: hostName, + href: getHostDetailsUrl(hostName), + }, + ]; + } + if (tabName) { + breadcrumb = [ + ...breadcrumb, + { + text: TabNameMappedToI18nKey[tabName], + href: '', + }, + ]; + } + return breadcrumb; +}; + +const getFilterQuery = ( + hostName: string | null, + filterQueryExpression: string, + indexPattern: StaticIndexPattern +): ESTermQuery | string => + isEmpty(filterQueryExpression) + ? hostName + ? { term: { 'host.name': hostName } } + : '' + : convertKueryToElasticSearchQuery( + `${filterQueryExpression} ${ + hostName ? `and host.name: "${escapeQueryValue(hostName)}"` : '' + }`, + indexPattern + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx index 98698d50da096..fbb522772d4a4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx @@ -7,11 +7,10 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { Router } from 'react-router-dom'; -import { ActionCreator } from 'typescript-fsa'; import '../../mock/match_media'; import '../../mock/ui_settings'; -import { Hosts, HostsComponentProps } from './hosts'; +import { Hosts, AnonamaliesChildren, HostsComponentProps } from './hosts'; import { mocksSource } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; @@ -20,8 +19,6 @@ import { cloneDeep } from 'lodash/fp'; import { SiemNavigation } from '../../components/navigation'; import { wait } from '../../lib/helpers'; -import { InputsModelId } from '../../store/inputs/constants'; - jest.mock('../../lib/settings/use_kibana_ui_setting'); jest.mock('ui/documentation_links', () => ({ @@ -65,26 +62,22 @@ const mockHistory = { listen: jest.fn(), }; +const mockMatch = { + isExact: false, + url: '/', + path: '/', +}; +const mockChildren: AnonamaliesChildren = () =>
; + // Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 /* eslint-disable no-console */ const originalError = console.error; -const to = new Date('2018-03-23T18:49:23.132Z').valueOf(); -const from = new Date('2018-03-24T03:33:52.253Z').valueOf(); - describe('Hosts - rendering', () => { - const hostProps: HostsComponentProps = { - from, - to, - setQuery: jest.fn(), - isInitializing: false, - setAbsoluteRangeDatePicker: (jest.fn() as unknown) as ActionCreator<{ - from: number; - id: InputsModelId; - to: number; - }>, - filterQuery: '', - }; + const hostProps = { + match: mockMatch, + children: mockChildren, + } as HostsComponentProps; beforeAll(() => { console.error = jest.fn(); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx index 6ada127c9f650..63a822b90d0ee 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -6,16 +6,18 @@ import { EuiSpacer } from '@elastic/eui'; import React from 'react'; -import { compose } from 'redux'; import { connect } from 'react-redux'; import { StickyContainer } from 'react-sticky'; +import { pure } from 'recompose'; import { ActionCreator } from 'typescript-fsa'; +import { RouteComponentProps } from 'react-router-dom'; +import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; import { LastEventTime } from '../../components/last_event_time'; import { KpiHostsComponent } from '../../components/page/hosts'; import { manageQuery } from '../../components/page/manage_query'; -import { GlobalTimeArgs } from '../../containers/global_time'; +import { GlobalTime } from '../../containers/global_time'; import { KpiHostsQuery } from '../../containers/kpi_hosts'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { LastEventIndexKey } from '../../graphql/types'; @@ -26,8 +28,6 @@ import { HostsKql } from './kql'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { InputsModelId } from '../../store/inputs/constants'; import { SiemNavigation } from '../../components/navigation'; -import { SpyRoute } from '../../utils/route/spy_routes'; -import { FiltersGlobal } from '../../components/filters_global'; import * as i18n from './translations'; import { @@ -40,9 +40,7 @@ const KpiHostsComponentManage = manageQuery(KpiHostsComponent); interface HostsComponentReduxProps { filterQuery: string; -} - -interface HostsComponentDispatchProps { + kqlQueryExpression: string; setAbsoluteRangeDatePicker: ActionCreator<{ id: InputsModelId; from: number; @@ -50,31 +48,32 @@ interface HostsComponentDispatchProps { }>; } -export type CommonChildren = (args: HostsComponentsQueryProps) => JSX.Element; +type CommonChildren = (args: HostsComponentsQueryProps) => JSX.Element; export type AnonamaliesChildren = (args: AnomaliesQueryTabBodyProps) => JSX.Element; -export type HostsQueryProps = GlobalTimeArgs; +export interface HostsQueryProps { + children: CommonChildren | AnonamaliesChildren; +} + +export type HostsComponentProps = RouteComponentProps & HostsComponentReduxProps & HostsQueryProps; -export type HostsComponentProps = HostsComponentReduxProps & - HostsComponentDispatchProps & - HostsQueryProps; +const HostsComponent = pure(({ filterQuery, setAbsoluteRangeDatePicker }) => { + return ( + + {({ indicesExist, indexPattern }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + -const HostsComponent = React.memo( - ({ isInitializing, filterQuery, from, setAbsoluteRangeDatePicker, setQuery, to }) => { - return ( - <> - - {({ indicesExist, indexPattern }) => - indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - + } + title={i18n.PAGE_TITLE} + /> - } - title={i18n.PAGE_TITLE} - /> + + {({ to, from, setQuery, isInitializing }) => ( <> ( - - ) : ( - <> - - - - ) - } - - - - ); - } -); + )} + + + ) : ( + <> + + + + ) + } + + ); +}); HostsComponent.displayName = 'HostsComponent'; const makeMapStateToProps = () => { const getHostsFilterQueryAsJson = hostsSelectors.hostsFilterQueryAsJson(); - const mapStateToProps = (state: State): HostsComponentReduxProps => ({ + const mapStateToProps = (state: State) => ({ filterQuery: getHostsFilterQueryAsJson(state, hostsModel.HostsType.page) || '', }); return mapStateToProps; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const Hosts = compose>( - connect( - makeMapStateToProps, - { - setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, - } - ) +export const Hosts = connect( + makeMapStateToProps, + { + setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, + } )(HostsComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_body.tsx index 898bdec5b281c..4b8fa3b734587 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_body.tsx @@ -14,17 +14,12 @@ import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../cont import { hostsModel, hostsSelectors, State } from '../../store'; -import { HostsComponentProps, CommonChildren, AnonamaliesChildren } from './hosts'; +import { HostsComponentProps } from './hosts'; import { scoreIntervalToDateTime } from '../../components/ml/score/score_interval_to_datetime'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { Anomaly } from '../../components/ml/types'; -interface HostsBodyComponentProps extends HostsComponentProps { - kqlQueryExpression: string; - children: CommonChildren | AnonamaliesChildren; -} - -const HostsBodyComponent = pure( +const HostsBodyComponent = pure( ({ filterQuery, kqlQueryExpression, setAbsoluteRangeDatePicker, children }) => { return ( diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_navigations.tsx index 1f496e4d85b6e..52416bf6997a8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_navigations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_navigations.tsx @@ -9,6 +9,7 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import * as i18n from './translations'; +import { NavTab } from '../../components/navigation/type'; import { HostsTable, UncommonProcessTable } from '../../components/page/hosts'; import { HostsQuery } from '../../containers/hosts'; @@ -23,7 +24,6 @@ import { AuthenticationsQuery } from '../../containers/authentications'; import { ESTermQuery } from '../../../common/typed_json'; import { HostsTableType } from '../../store/hosts/model'; import { StatefulEventsViewer } from '../../components/events_viewer'; -import { NavTab } from '../../components/navigation/types'; const getTabsOnHostsUrl = (tabName: HostsTableType) => `#/hosts/${tabName}`; const getTabsOnHostDetailsUrl = (hostName: string, tabName: HostsTableType) => { diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx index 36d9826c5d6a6..415848337d9e9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx @@ -5,9 +5,15 @@ */ import React from 'react'; -import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import { pure } from 'recompose'; +import { i18n } from '@kbn/i18n'; +import { PageRoute } from '../../components/page_route/pageroute'; -import { HostDetailsBody, HostDetails } from './details'; +import { HostComponentProps } from '../../components/link_to/redirect_to_hosts'; + +import { HostDetails, HostDetailsBody } from './host_details'; +import { Hosts } from './hosts'; import { HostsQueryTabBody, AuthenticationsQueryTabBody, @@ -17,8 +23,6 @@ import { } from './hosts_navigations'; import { HostsBody } from './hosts_body'; import { HostsTableType } from '../../store/hosts/model'; -import { GlobalTime } from '../../containers/global_time'; -import { Hosts } from './hosts'; const hostsPagePath = `/:pageName(hosts)`; @@ -31,199 +35,114 @@ const getHostsTabPath = (pagePath: string) => `${HostsTableType.events})`; const getHostDetailsTabPath = (pagePath: string) => - `${pagePath}/:detailName/:tabName(` + + `${pagePath}/:hostName/:tabName(` + `${HostsTableType.authentications}|` + `${HostsTableType.uncommonProcesses}|` + `${HostsTableType.anomalies}|` + `${HostsTableType.events})`; -type Props = Partial> & { url: string }; - -export const HostsContainer = React.memo(({ url }) => ( - - {({ to, from, setQuery, isInitializing }) => ( - - ( +export const HostsContainer = pure(({ match }) => ( + <> + + ( + <> + ( - <> - - - - )} + render={() => } + /> + + )} + /> + ( + <> + + } + /> + } + /> + } + /> + } + /> + } + /> + + )} + /> + ( + <> + + } + /> + } + /> + } + /> + } + /> + } /> - )} - /> - ( - <> - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - )} - /> - ( - <> - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - )} - /> - - - - )} - + + )} + /> + + + + )); HostsContainer.displayName = 'HostsContainer'; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/__snapshots__/ip_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/pages/network/__snapshots__/ip_details.test.tsx.snap index 71d799bbf7063..633b68e9359be 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/__snapshots__/ip_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/pages/network/__snapshots__/ip_details.test.tsx.snap @@ -2,7 +2,6 @@ exports[`Ip Details it matches the snapshot 1`] = ` > & { url: string }; - -export const NetworkContainer = React.memo(() => ( - - } /> - } - /> - - +export const NetworkContainer = pure(({ match }) => ( + <> + + ( + + )} + /> + ( + + )} + /> + + + )); NetworkContainer.displayName = 'NetworkContainer'; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.test.tsx index 5a31bc8e7ae5d..757829ffec82e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.test.tsx @@ -70,8 +70,7 @@ const getMockProps = (ip: string) => ({ state: '', hash: '', }, - detailName: ip, - match: { params: { detailName: ip, search: '' }, isExact: true, path: '', url: '' }, + match: { params: { ip, search: '' }, isExact: true, path: '', url: '' }, setAbsoluteRangeDatePicker: (jest.fn() as unknown) as ActionCreator<{ id: InputsModelId; from: number; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx index 6e723c1c83a78..dafa6f57b6b7a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details.tsx @@ -16,7 +16,7 @@ import { ActionCreator } from 'typescript-fsa'; import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; import { LastEventTime } from '../../components/last_event_time'; -import { getNetworkUrl } from '../../components/link_to/redirect_to_network'; +import { getNetworkUrl, NetworkComponentProps } from '../../components/link_to/redirect_to_network'; import { manageQuery } from '../../components/page/manage_query'; import { DomainsTable } from '../../components/page/network/domains_table'; import { FlowTargetSelectConnected } from '../../components/page/network/flow_target_select_connected'; @@ -42,7 +42,6 @@ import { InputsModelId } from '../../store/inputs/constants'; import { scoreIntervalToDateTime } from '../../components/ml/score/score_interval_to_datetime'; import { AnomaliesNetworkTable } from '../../components/ml/tables/anomalies_network_table'; import { networkToCriteria } from '../../components/ml/criteria/network_to_criteria'; -import { SpyRoute } from '../../utils/route/spy_routes'; const DomainsTableManage = manageQuery(DomainsTable); const TlsTableManage = manageQuery(TlsTable); @@ -59,216 +58,212 @@ interface IPDetailsComponentReduxProps { }>; } -export type IPDetailsComponentProps = IPDetailsComponentReduxProps & { detailName: string }; +export type IPDetailsComponentProps = IPDetailsComponentReduxProps & NetworkComponentProps; export const IPDetailsComponent = pure( - ({ detailName, filterQuery, flowTarget, setAbsoluteRangeDatePicker }) => ( - <> - - {({ indicesExist, indexPattern }) => { - const ip = decodeIpv6(detailName); - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - + ({ + match: { + params: { ip }, + }, + filterQuery, + flowTarget, + setAbsoluteRangeDatePicker, + }) => ( + + {({ indicesExist, indexPattern }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + - } - title={ip} - > - - + + } + title={decodeIpv6(ip)} + > + + - - {({ to, from, setQuery, isInitializing }) => ( - <> - - {({ id, inspect, ipOverviewData, loading, refetch }) => ( - - {({ isLoadingAnomaliesData, anomaliesData }) => ( - { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} - /> - )} - - )} - + + {({ to, from, setQuery, isInitializing }) => ( + <> + + {({ id, inspect, ipOverviewData, loading, refetch }) => ( + + {({ isLoadingAnomaliesData, anomaliesData }) => ( + { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }} + /> + )} + + )} + - + - - {({ - id, - inspect, - domains, - totalCount, - pageInfo, - loading, - loadPage, - refetch, - }) => ( - - )} - + + {({ + id, + inspect, + domains, + totalCount, + pageInfo, + loading, + loadPage, + refetch, + }) => ( + + )} + - + - - {({ - id, - inspect, - users, - totalCount, - pageInfo, - loading, - loadPage, - refetch, - }) => ( - - )} - + + {({ id, inspect, users, totalCount, pageInfo, loading, loadPage, refetch }) => ( + + )} + - + - - {({ id, inspect, tls, totalCount, pageInfo, loading, loadPage, refetch }) => ( - - )} - + + {({ id, inspect, tls, totalCount, pageInfo, loading, loadPage, refetch }) => ( + + )} + - + - { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} - /> - - )} - - - ) : ( - <> - + { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }} + /> + + )} + + + ) : ( + <> + - - - ); - }} - - - + + + ) + } + ) ); @@ -290,11 +285,11 @@ export const IPDetails = connect( } )(IPDetailsComponent); -export const getBreadcrumbs = (ip: string | undefined, search: string[]): Breadcrumb[] => { +export const getBreadcrumbs = (ip: string): Breadcrumb[] => { const breadcrumbs = [ { text: i18n.PAGE_TITLE, - href: `${getNetworkUrl()}${search && search[0] ? search[0] : ''}`, + href: getNetworkUrl(), }, ]; if (ip) { diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx index 061b1145cf5cd..28f395e5cf303 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -12,7 +12,6 @@ import { connect } from 'react-redux'; import { StickyContainer } from 'react-sticky'; import { ActionCreator } from 'typescript-fsa'; -import { RouteComponentProps } from 'react-router-dom'; import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; import { LastEventTime } from '../../components/last_event_time'; @@ -36,7 +35,6 @@ import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from import { InputsModelId } from '../../store/inputs/constants'; import { EmbeddedMap } from '../../components/embeddables/embedded_map'; import { NetworkFilter } from '../../containers/network'; -import { SpyRoute } from '../../utils/route/spy_routes'; const NetworkTopNFlowTableManage = manageQuery(NetworkTopNFlowTable); const NetworkDnsTableManage = manageQuery(NetworkDnsTable); @@ -51,7 +49,7 @@ interface NetworkComponentReduxProps { }>; } -type NetworkComponentProps = NetworkComponentReduxProps & Partial>; +type NetworkComponentProps = NetworkComponentReduxProps; const mediaMatch = window.matchMedia( 'screen and (min-width: ' + euiLightVars.euiBreakpoints.xl + ')' ); @@ -75,8 +73,8 @@ export const getFlexDirection = () => { }; const NetworkComponent = React.memo( - ({ filterQuery, queryExpression, setAbsoluteRangeDatePicker }) => ( - <> + ({ filterQuery, queryExpression, setAbsoluteRangeDatePicker }) => { + return ( {({ indicesExist, indexPattern }) => indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( @@ -285,9 +283,8 @@ const NetworkComponent = React.memo( ) } - - - ) + ); + } ); NetworkComponent.displayName = 'NetworkComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx index e0af54acde310..82f4d3e32b117 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/index.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo } from 'react'; +import React from 'react'; +import { pure } from 'recompose'; import { OverviewComponent } from './overview'; -export const Overview = memo(() => ); +export const Overview = pure(() => ); Overview.displayName = 'Overview'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx index 833030e0dc8a1..160379567133f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx @@ -5,16 +5,15 @@ */ import { mount } from 'enzyme'; -import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; -import { MemoryRouter } from 'react-router-dom'; import { Overview } from './index'; import '../../mock/ui_settings'; import { mocksSource } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { cloneDeep } from 'lodash/fp'; jest.mock('ui/documentation_links', () => ({ documentationLinks: { @@ -46,9 +45,7 @@ describe('Overview', () => { const wrapper = mount( - - - + ); @@ -63,9 +60,7 @@ describe('Overview', () => { const wrapper = mount( - - - + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx index d8965f4d49491..f13b48532dc14 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx @@ -19,7 +19,6 @@ import { GlobalTime } from '../../containers/global_time'; import { Summary } from './summary'; import { EmptyPage } from '../../components/empty_page'; import { WithSource, indicesExistOrDataTemporarilyUnavailable } from '../../containers/source'; -import { SpyRoute } from '../../utils/route/spy_routes'; import * as i18n from './translations'; @@ -66,7 +65,6 @@ export const OverviewComponent = pure(() => { ) } - ); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index adc5471cc37a7..942c5730e2222 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -10,7 +10,6 @@ import styled from 'styled-components'; import { HeaderPage } from '../../components/header_page'; import { StatefulOpenTimeline } from '../../components/open_timeline'; -import { SpyRoute } from '../../utils/route/spy_routes'; import * as i18n from './translations'; @@ -42,7 +41,6 @@ export class TimelinesPage extends React.PureComponent { title={i18n.ALL_TIMELINES_PANEL_TITLE} /> - ); } diff --git a/x-pack/legacy/plugins/siem/public/routes.tsx b/x-pack/legacy/plugins/siem/public/routes.tsx index 9a132eb8d4fac..37e56f4f067f7 100644 --- a/x-pack/legacy/plugins/siem/public/routes.tsx +++ b/x-pack/legacy/plugins/siem/public/routes.tsx @@ -10,19 +10,16 @@ import { Route, Router, Switch } from 'react-router-dom'; import { NotFoundPage } from './pages/404'; import { HomePage } from './pages/home'; -import { ManageRoutesSpy } from './utils/route/manage_spy_routes'; interface RouterProps { history: History; } export const PageRouter: FC = memo(({ history }) => ( - - - - - - - - + + + + + + )); diff --git a/x-pack/legacy/plugins/siem/public/store/hosts/model.ts b/x-pack/legacy/plugins/siem/public/store/hosts/model.ts index 69efa404d2eee..29469c129c23f 100644 --- a/x-pack/legacy/plugins/siem/public/store/hosts/model.ts +++ b/x-pack/legacy/plugins/siem/public/store/hosts/model.ts @@ -14,7 +14,7 @@ export enum HostsType { export enum HostsTableType { authentications = 'authentications', - hosts = 'allHosts', + hosts = 'hosts', events = 'events', uncommonProcesses = 'uncommonProcesses', anomalies = 'anomalies', diff --git a/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts b/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts index a597386942cb1..80d7876902203 100644 --- a/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/store/hosts/reducer.ts @@ -106,8 +106,8 @@ export const hostsReducer = reducerWithInitialState(initialHostsState) ...state[hostsType], queries: { ...state[hostsType].queries, - [HostsTableType.hosts]: { - ...state[hostsType].queries[HostsTableType.hosts], + hosts: { + ...state[hostsType].queries.hosts, direction: sort.direction, sortField: sort.field, }, diff --git a/x-pack/legacy/plugins/siem/public/store/hosts/selectors.ts b/x-pack/legacy/plugins/siem/public/store/hosts/selectors.ts index a4cf0715ef6da..dcd6cd8e67006 100644 --- a/x-pack/legacy/plugins/siem/public/store/hosts/selectors.ts +++ b/x-pack/legacy/plugins/siem/public/store/hosts/selectors.ts @@ -10,7 +10,7 @@ import { createSelector } from 'reselect'; import { isFromKueryExpressionValid } from '../../lib/keury'; import { State } from '../reducer'; -import { GenericHostsModel, HostsType, HostsTableType } from './model'; +import { GenericHostsModel, HostsType } from './model'; const selectHosts = (state: State, hostsType: HostsType): GenericHostsModel => get(hostsType, state.hosts); @@ -24,7 +24,7 @@ export const authenticationsSelector = () => export const hostsSelector = () => createSelector( selectHosts, - hosts => hosts.queries[HostsTableType.hosts] + hosts => hosts.queries.hosts ); export const eventsSelector = () => diff --git a/x-pack/legacy/plugins/siem/public/utils/default_date_settings.test.ts b/x-pack/legacy/plugins/siem/public/utils/default_date_settings.test.ts index 2507c2cf81c51..ce84da6ddf129 100644 --- a/x-pack/legacy/plugins/siem/public/utils/default_date_settings.test.ts +++ b/x-pack/legacy/plugins/siem/public/utils/default_date_settings.test.ts @@ -530,21 +530,6 @@ describe('default_date_settings', () => { }); describe('#parseDateWithDefault', () => { - beforeEach(() => { - // Disable momentJS deprecation warning and it looks like it is not typed either so - // we have to disable the type as well and cannot extend it easily. - ((moment as unknown) as { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = true; - }); - - afterEach(() => { - // Re-enable momentJS deprecation warning and it looks like it is not typed either so - // we have to disable the type as well and cannot extend it easily. - ((moment as unknown) as { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = false; - }); test('should return the first value if it is ok', () => { const value = parseDateWithDefault( '1930-05-31T13:03:54.234Z', diff --git a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts b/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts deleted file mode 100644 index 188ae9c6c1866..0000000000000 --- a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts +++ /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 { noop } from 'lodash/fp'; -import { createContext, Dispatch } from 'react'; - -import { RouteSpyState, RouteSpyAction } from './types'; - -export const initRouteSpy: RouteSpyState = { - pageName: '', - detailName: undefined, - tabName: undefined, - search: '', - pathName: '/', -}; - -export const RouterSpyStateContext = createContext<[RouteSpyState, Dispatch]>([ - initRouteSpy, - () => noop, -]); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/index.test.tsx b/x-pack/legacy/plugins/siem/public/utils/route/index.test.tsx deleted file mode 100644 index bcc256d50d960..0000000000000 --- a/x-pack/legacy/plugins/siem/public/utils/route/index.test.tsx +++ /dev/null @@ -1,202 +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 { mount } from 'enzyme'; -import React from 'react'; - -import { HostsTableType } from '../../store/hosts/model'; -import { RouteSpyState } from './types'; -import { ManageRoutesSpy } from './manage_spy_routes'; -import { SpyRouteComponent } from './spy_routes'; -import { useRouteSpy } from './use_route_spy'; - -type Action = 'PUSH' | 'POP' | 'REPLACE'; -const pop: Action = 'POP'; - -const defaultLocation = { - hash: '', - pathname: '/hosts', - search: '', - state: '', -}; - -export const mockHistory = { - action: pop, - block: jest.fn(), - createHref: jest.fn(), - go: jest.fn(), - goBack: jest.fn(), - goForward: jest.fn(), - length: 2, - listen: jest.fn(), - location: defaultLocation, - push: jest.fn(), - replace: jest.fn(), -}; - -const dispatchMock = jest.fn(); -const mockRoutes: RouteSpyState = { - pageName: '', - detailName: undefined, - tabName: undefined, - search: '', - pathName: '/', - history: mockHistory, -}; - -const mockUseRouteSpy: jest.Mock = useRouteSpy as jest.Mock; -jest.mock('./use_route_spy', () => ({ - useRouteSpy: jest.fn(), -})); - -describe('Spy Routes', () => { - describe('At Initialization of the app', () => { - beforeEach(() => { - dispatchMock.mockReset(); - dispatchMock.mockClear(); - }); - test('Make sure we update search state first', () => { - const pathname = '/'; - mockUseRouteSpy.mockImplementation(() => [mockRoutes, dispatchMock]); - mount( - - - - ); - - expect(dispatchMock.mock.calls[0]).toEqual([ - { - type: 'updateSearch', - search: '?importantQueryString="really"', - }, - ]); - }); - - test('Make sure we update search state first and then update the route but keeping the initial search', () => { - const pathname = '/hosts/allHosts'; - mockUseRouteSpy.mockImplementation(() => [mockRoutes, dispatchMock]); - mount( - - - - ); - - expect(dispatchMock.mock.calls[0]).toEqual([ - { - type: 'updateSearch', - search: '?importantQueryString="really"', - }, - ]); - - expect(dispatchMock.mock.calls[1]).toEqual([ - { - route: { - detailName: undefined, - history: mockHistory, - pageName: 'hosts', - pathName: pathname, - tabName: HostsTableType.hosts, - }, - type: 'updateRouteWithOutSearch', - }, - ]); - }); - }); - - describe('When app is running', () => { - beforeEach(() => { - dispatchMock.mockReset(); - dispatchMock.mockClear(); - }); - test('Update route should be updated when there is changed detected', () => { - const pathname = '/hosts/allHosts'; - const newPathname = `hosts/${HostsTableType.authentications}`; - mockUseRouteSpy.mockImplementation(() => [mockRoutes, dispatchMock]); - const wrapper = mount( - - ); - - dispatchMock.mockReset(); - dispatchMock.mockClear(); - - wrapper.setProps({ - location: { - hash: '', - pathname: newPathname, - search: '?updated="true"', - state: '', - }, - match: { - isExact: false, - path: newPathname, - url: newPathname, - params: { - pageName: 'hosts', - detailName: undefined, - tabName: HostsTableType.authentications, - search: '', - }, - }, - }); - wrapper.update(); - expect(dispatchMock.mock.calls[0]).toEqual([ - { - route: { - detailName: undefined, - history: mockHistory, - pageName: 'hosts', - pathName: newPathname, - tabName: HostsTableType.authentications, - search: '?updated="true"', - }, - type: 'updateRoute', - }, - ]); - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/manage_spy_routes.tsx b/x-pack/legacy/plugins/siem/public/utils/route/manage_spy_routes.tsx deleted file mode 100644 index 87b40c565c758..0000000000000 --- a/x-pack/legacy/plugins/siem/public/utils/route/manage_spy_routes.tsx +++ /dev/null @@ -1,31 +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 React, { memo, useReducer } from 'react'; - -import { ManageRoutesSpyProps, RouteSpyState, RouteSpyAction } from './types'; -import { RouterSpyStateContext, initRouteSpy } from './helpers'; - -export const ManageRoutesSpy = memo(({ children }: ManageRoutesSpyProps) => { - const reducerSpyRoute = (state: RouteSpyState, action: RouteSpyAction) => { - switch (action.type) { - case 'updateRoute': - return action.route; - case 'updateRouteWithOutSearch': - return { ...state, ...action.route }; - case 'updateSearch': - return { ...state, search: action.search }; - default: - return state; - } - }; - - return ( - - {children} - - ); -}); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx deleted file mode 100644 index 3a02d81272344..0000000000000 --- a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx +++ /dev/null @@ -1,68 +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 * as H from 'history'; -import { isEqual } from 'lodash/fp'; -import { memo, useEffect, useState } from 'react'; -import { withRouter } from 'react-router-dom'; - -import { SpyRouteProps } from './types'; -import { useRouteSpy } from './use_route_spy'; - -export const SpyRouteComponent = memo( - ({ - location: { pathname, search }, - history, - match: { - params: { pageName, detailName, tabName }, - }, - }) => { - const [isInitializing, setIsInitializing] = useState(true); - const [route, dispatch] = useRouteSpy(); - - useEffect(() => { - if (isInitializing && search !== '') { - dispatch({ - type: 'updateSearch', - search, - }); - setIsInitializing(false); - } - }, [search]); - useEffect(() => { - if (pageName && !isEqual(route.pathName, pathname)) { - if (isInitializing && detailName == null) { - dispatch({ - type: 'updateRouteWithOutSearch', - route: { - pageName, - detailName, - tabName, - pathName: pathname, - history, - }, - }); - setIsInitializing(false); - } else { - dispatch({ - type: 'updateRoute', - route: { - pageName, - detailName, - tabName, - search, - pathName: pathname, - history, - }, - }); - } - } - }, [pathname, search, pageName, detailName, tabName]); - return null; - } -); - -export const SpyRoute = withRouter(SpyRouteComponent); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/types.ts b/x-pack/legacy/plugins/siem/public/utils/route/types.ts deleted file mode 100644 index 62f6b67df245f..0000000000000 --- a/x-pack/legacy/plugins/siem/public/utils/route/types.ts +++ /dev/null @@ -1,45 +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 * as H from 'history'; -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; - -import { HostsTableType } from '../../store/hosts/model'; - -export interface RouteSpyState { - pageName: string; - detailName: string | undefined; - tabName: HostsTableType | undefined; - search: string; - pathName: string; - history?: H.History; -} - -export type RouteSpyAction = - | { - type: 'updateSearch'; - search: string; - } - | { - type: 'updateRouteWithOutSearch'; - route: Pick; - } - | { - type: 'updateRoute'; - route: RouteSpyState; - }; - -export interface ManageRoutesSpyProps { - children: React.ReactNode; -} - -export type SpyRouteProps = RouteComponentProps<{ - pageName: string | undefined; - detailName: string | undefined; - tabName: HostsTableType | undefined; - search: string; -}>; diff --git a/x-pack/legacy/plugins/siem/public/utils/route/use_route_spy.tsx b/x-pack/legacy/plugins/siem/public/utils/route/use_route_spy.tsx deleted file mode 100644 index ce988df1c9d2f..0000000000000 --- a/x-pack/legacy/plugins/siem/public/utils/route/use_route_spy.tsx +++ /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. - */ - -import { useContext } from 'react'; -import { RouterSpyStateContext } from './helpers'; - -export const useRouteSpy = () => useContext(RouterSpyStateContext); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap index d8fbe90000f9a..15d2aeef67e99 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap @@ -7,9 +7,7 @@ exports[`renders without crashing 1`] = ` fullWidth={true} hasEmptyLabelSpace={false} helpText={ -

+

{ }) ) => { return ( -

+

{ const tasks = [[1, 2, 3], [4, 5]]; let index = 0; const fetchAvailableTasks = async () => tasks[index++] || []; - const run = sinon.spy(async () => true); + const run = sinon.spy(() => true); const converter = _.identity; await fillPool(run, fetchAvailableTasks, converter); @@ -25,7 +25,7 @@ describe('fillPool', () => { const tasks = [[1, 2, 3], [4, 5]]; let index = 0; const fetchAvailableTasks = async () => tasks[index++] || []; - const run = sinon.spy(async () => false); + const run = sinon.spy(() => false); const converter = _.identity; await fillPool(run, fetchAvailableTasks, converter); @@ -37,7 +37,7 @@ describe('fillPool', () => { const tasks = [[1, 2, 3], [4, 5]]; let index = 0; const fetchAvailableTasks = async () => tasks[index++] || []; - const run = sinon.spy(async () => false); + const run = sinon.spy(() => false); const converter = (x: number) => x.toString(); await fillPool(run, fetchAvailableTasks, converter); @@ -47,7 +47,7 @@ describe('fillPool', () => { describe('error handling', () => { test('throws exception from fetchAvailableTasks', async () => { - const run = sinon.spy(async () => false); + const run = sinon.spy(() => false); const converter = (x: number) => x.toString(); try { @@ -80,7 +80,7 @@ describe('fillPool', () => { const tasks = [[1, 2, 3], [4, 5]]; let index = 0; const fetchAvailableTasks = async () => tasks[index++] || []; - const run = sinon.spy(async () => false); + const run = sinon.spy(() => false); const converter = (x: number) => { throw new Error(`can not convert ${x}`); }; 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 76087d9c1add6..b883831f2c6f9 100644 --- a/x-pack/legacy/plugins/task_manager/task_pool.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_pool.test.ts @@ -71,13 +71,11 @@ describe('TaskPool', () => { const firstRun = sinon.spy(async () => { await sleep(0); firstWork.resolve(); - return { state: {} }; }); const secondWork = resolvable(); const secondRun = sinon.spy(async () => { await sleep(0); secondWork.resolve(); - return { state: {} }; }); const result = await pool.run([ @@ -194,10 +192,7 @@ describe('TaskPool', () => { }); function mockRun() { - return sinon.spy(async () => { - await sleep(0); - return { state: {} }; - }); + return sinon.spy(async () => sleep(0)); } function mockTask() { 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 f33beca0948d9..ccc7322682f4c 100644 --- a/x-pack/legacy/plugins/task_manager/task_store.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_store.test.ts @@ -159,7 +159,7 @@ describe('TaskStore', () => { describe('fetch', () => { async function testFetch(opts?: FetchOpts, hits: any[] = []) { - const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits } })); + const callCluster = sinon.spy(async () => ({ hits: { hits } })); const store = new TaskStore({ index: 'tasky', serializer, @@ -328,7 +328,7 @@ describe('TaskStore', () => { describe('fetchAvailableTasks', () => { async function testFetchAvailableTasks({ opts = {}, hits = [] }: any = {}) { - const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits } })); + const callCluster = sinon.spy(async () => ({ hits: { hits } })); const store = new TaskStore({ callCluster, logger: mockLogger(), @@ -350,7 +350,7 @@ describe('TaskStore', () => { } test('it returns normally with no tasks when the index does not exist.', async () => { - const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits: [] } })); + const callCluster = sinon.spy(async () => ({ hits: { hits: [] } })); const store = new TaskStore({ index: 'tasky', serializer, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap index f07d0171b22ad..d41b4c806f5a9 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap @@ -62,7 +62,7 @@ exports[`IntegrationGroup will not display infra links when infra is unavailable - - - - For Uptime specific information - - - - - - - - - - + + + + + + + + + + + + `; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_framework_adapter.ts index dee8a957e54da..596389f34fb28 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -9,6 +9,7 @@ import { unmountComponentAtNode } from 'react-dom'; import chrome from 'ui/chrome'; import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import { PLUGIN, INTEGRATED_SOLUTIONS } from '../../../../common/constants'; +import { UMBreadcrumb } from '../../../breadcrumbs'; import { BootstrapUptimeApp, UMFrameworkAdapter } from '../../lib'; import { CreateGraphQLClient } from './framework_adapter_types'; import { renderUptimeKibanaGlobalHelp } from './kibana_global_help'; @@ -43,6 +44,12 @@ export class UMKibanaFrameworkAdapter implements UMFrameworkAdapter { $scope.$$postDigest(() => { const elem = document.getElementById('uptimeReactRoot'); + // configure breadcrumbs + let kibanaBreadcrumbs: UMBreadcrumb[] = []; + chrome.breadcrumbs.get$().subscribe((breadcrumbs: UMBreadcrumb[]) => { + kibanaBreadcrumbs = breadcrumbs; + }); + // set up route with current base path const basePath = chrome.getBasePath(); const routerBasename = basePath.endsWith('/') @@ -103,6 +110,7 @@ export class UMKibanaFrameworkAdapter implements UMFrameworkAdapter { isApmAvailable, isInfraAvailable, isLogsAvailable, + kibanaBreadcrumbs, logMonitorPageLoad: getTelemetryMonitorPageLogger(this.xsrfHeader, basePath), logOverviewPageLoad: getTelemetryOverviewPageLogger(this.xsrfHeader, basePath), renderGlobalHelpControls, diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx index 7896764d35963..59b3001d9e690 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx +++ b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx @@ -4,45 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLink, EuiSpacer, EuiHorizontalRule, EuiButton, EuiText } from '@elastic/eui'; +import { EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; export const renderUptimeKibanaGlobalHelp = (docsSiteUrl: string, docLinkVersion: string) => ( - - - - For Uptime specific information - - - - - - - - - + + + + + + + + + + + + ); diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/get_title.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/get_title.ts deleted file mode 100644 index 4aa8860de96fa..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/get_title.ts +++ /dev/null @@ -1,14 +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 { i18n } from '@kbn/i18n'; - -export const getTitle = (name?: string) => { - const appName = i18n.translate('xpack.uptime.title', { - defaultMessage: 'Uptime', - }); - return `${appName} ${name ? '| ' + name : ''} - Kibana`; -}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_apm_href.test.ts.snap b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_apm_href.test.ts.snap index 53d336b52bd24..c258c2ec44973 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_apm_href.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_apm_href.test.ts.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getApmHref creates href with base path when present 1`] = `"foo/app/apm#/services?kuery=url.domain:%20%22www.elastic.co%22&rangeFrom=now-15m&rangeTo=now"`; +exports[`getApmHref creates href with base path when present 1`] = `"/foo/app/apm#/services?kuery=url.domain:%20%22www.elastic.co%22&rangeFrom=now-15m&rangeTo=now"`; exports[`getApmHref does not add a base path or extra slash when base path is empty string 1`] = `"/app/apm#/services?kuery=url.domain:%20%22www.elastic.co%22&rangeFrom=now-15m&rangeTo=now"`; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_infra_href.test.ts.snap b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_infra_href.test.ts.snap index 5094aab1226a1..256d19f636bdd 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_infra_href.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_infra_href.test.ts.snap @@ -1,18 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getInfraHref getInfraContainerHref creates a link for valid parameters 1`] = `"foo/app/infra#/link-to/container-detail/test-container-id"`; +exports[`getInfraHref getInfraContainerHref creates a link for valid parameters 1`] = `"/foo/app/infra#/link-to/container-detail/test-container-id"`; exports[`getInfraHref getInfraContainerHref does not specify a base path when none is available 1`] = `"/app/infra#/link-to/container-detail/test-container-id"`; -exports[`getInfraHref getInfraContainerHref returns the first item when multiple container ids are supplied 1`] = `"bar/app/infra#/link-to/container-detail/test-container-id"`; +exports[`getInfraHref getInfraContainerHref returns the first item when multiple container ids are supplied 1`] = `"/bar/app/infra#/link-to/container-detail/test-container-id"`; -exports[`getInfraHref getInfraIpHref creates a link for valid parameters 1`] = `"bar/app/infra#/infrastructure/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`; +exports[`getInfraHref getInfraIpHref creates a link for valid parameters 1`] = `"/bar/app/infra#/infrastructure/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`; exports[`getInfraHref getInfraIpHref does not specify a base path when none is available 1`] = `"/app/infra#/infrastructure/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`; -exports[`getInfraHref getInfraIpHref returns a url for ors between multiple ips 1`] = `"foo/app/infra#/infrastructure/inventory?waffleFilter=(expression:'host.ip%20%3A%20152.151.23.192%20or%20host.ip%20%3A%20151.101.202.217',kind:kuery)"`; +exports[`getInfraHref getInfraIpHref returns a url for ors between multiple ips 1`] = `"/foo/app/infra#/infrastructure/inventory?waffleFilter=(expression:'host.ip%20%3A%20152.151.23.192%20or%20host.ip%20%3A%20151.101.202.217',kind:kuery)"`; -exports[`getInfraHref getInfraKubernetesHref creates a link for valid parameters 1`] = `"foo/app/infra#/link-to/pod-detail/test-pod-uid"`; +exports[`getInfraHref getInfraKubernetesHref creates a link for valid parameters 1`] = `"/foo/app/infra#/link-to/pod-detail/test-pod-uid"`; exports[`getInfraHref getInfraKubernetesHref does not specify a base path when none is available 1`] = `"/app/infra#/link-to/pod-detail/test-pod-uid"`; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_logging_href.test.ts.snap b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_logging_href.test.ts.snap index 67402d16d9a27..70eda1e5442cd 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_logging_href.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/__tests__/__snapshots__/get_logging_href.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getLoggingHref creates a container href with base path when present 1`] = `"bar/app/infra#/logs?logFilter=(expression:'container.id%20:%20test-container-id',kind:kuery)"`; +exports[`getLoggingHref creates a container href with base path when present 1`] = `"/bar/app/infra#/logs?logFilter=(expression:'container.id%20:%20test-container-id',kind:kuery)"`; exports[`getLoggingHref creates a container href without a base path if it's an empty string 1`] = `"/app/infra#/logs?logFilter=(expression:'container.id%20:%20test-container-id',kind:kuery)"`; -exports[`getLoggingHref creates a pod href with base path when present 1`] = `"bar/app/infra#/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`; +exports[`getLoggingHref creates a pod href with base path when present 1`] = `"/bar/app/infra#/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`; exports[`getLoggingHref creates a pod href without a base path when it's an empty string 1`] = `"/app/infra#/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`; -exports[`getLoggingHref creates an ip href with base path when present 1`] = `"bar/app/infra#/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`; +exports[`getLoggingHref creates an ip href with base path when present 1`] = `"/bar/app/infra#/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`; exports[`getLoggingHref creates an ip href without a base path when it's an empty string 1`] = `"/app/infra#/logs?logFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/add_base_path.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/add_base_path.ts index bbf5886cfde73..176674a6896a1 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/add_base_path.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/observability_integration/add_base_path.ts @@ -5,4 +5,4 @@ */ export const addBasePath = (basePath: string, url: string) => - `${basePath.length > 0 ? `${basePath}` : ''}${url}`; + `${basePath.length > 0 ? `/${basePath}` : ''}${url}`; diff --git a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx index e8ed1cc00fde0..bc294dd54d2ed 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx @@ -25,7 +25,6 @@ import { useUrlParams } from '../hooks'; import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { BaseLocationOptions } from '../components/functional/ping_list'; import { useTrackPageview } from '../../../infra/public'; -import { getTitle } from '../lib/helper/get_title'; interface MonitorPageProps { logMonitorPageLoad: () => void; @@ -66,7 +65,6 @@ export const MonitorPage = ({ }).then((result: any) => { const { name, url, id } = result.data.monitorPageTitle; const heading: string = name || url || id; - document.title = getTitle(name); setBreadcrumbs(getMonitorPageBreadcrumb(heading, stringifyUrlParams(params))); if (setHeadingText) { setHeadingText(heading); diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index 1d2534ad8a453..582eda2acf4ad 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -14,12 +14,12 @@ import { ApolloProvider } from 'react-apollo'; import { BrowserRouter as Router, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { capabilities } from 'ui/capabilities'; import { I18nContext } from 'ui/i18n'; +import { UMBreadcrumb } from './breadcrumbs'; import { UMGraphQLClient, UMUpdateBreadcrumbs, UMUpdateBadge } from './lib/lib'; import { MonitorPage, OverviewPage } from './pages'; import { UptimeRefreshContext, UptimeSettingsContext, UMSettingsContextValues } from './contexts'; import { UptimeDatePicker } from './components/functional/uptime_date_picker'; import { useUrlParams } from './hooks'; -import { getTitle } from './lib/helper/get_title'; export interface UptimeAppColors { danger: string; @@ -36,6 +36,7 @@ export interface UptimeAppProps { isApmAvailable: boolean; isInfraAvailable: boolean; isLogsAvailable: boolean; + kibanaBreadcrumbs: UMBreadcrumb[]; logMonitorPageLoad: () => void; logOverviewPageLoad: () => void; routerBasename: string; @@ -78,6 +79,7 @@ const Application = (props: UptimeAppProps) => { warning: euiLightVars.euiColorWarning, }; } + const [lastRefresh, setLastRefresh] = useState(Date.now()); const [headingText, setHeadingText] = useState(undefined); @@ -98,10 +100,6 @@ const Application = (props: UptimeAppProps) => { ); }, []); - useEffect(() => { - document.title = getTitle(); - }, []); - const refreshApp = () => { setLastRefresh(Date.now()); }; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts index 09010e9c76096..f274d974305f3 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts @@ -114,7 +114,7 @@ describe('ElasticsearchPingsAdapter class', () => { head: async (request: any, params: any) => null, }; const pingAdapter = new ElasticsearchPingsAdapter(pingDatabase); - const result = await pingAdapter.getPingHistogram(serverRequest, 'now-15m', 'now', null); + const result = await pingAdapter.getPingHistogram(serverRequest, '1234', '5678', null); expect(pingDatabase.search).toHaveBeenCalledTimes(1); expect(result).toEqual([]); }); @@ -131,7 +131,7 @@ describe('ElasticsearchPingsAdapter class', () => { head: async (request: any, params: any) => null, }; const pingAdapter = new ElasticsearchPingsAdapter(pingDatabase); - const result = await pingAdapter.getPingHistogram(serverRequest, 'now-15m', 'now', null); + const result = await pingAdapter.getPingHistogram(serverRequest, '1234', '5678', null); expect(pingDatabase.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); @@ -192,8 +192,8 @@ describe('ElasticsearchPingsAdapter class', () => { const pingAdapter = new ElasticsearchPingsAdapter(pingDatabase); const result = await pingAdapter.getPingHistogram( serverRequest, - 'now-15m', - 'now', + '1234', + '5678', JSON.stringify(searchFilter) ); @@ -248,8 +248,8 @@ describe('ElasticsearchPingsAdapter class', () => { const pingAdapter = new ElasticsearchPingsAdapter(pingDatabase); const result = await pingAdapter.getPingHistogram( serverRequest, - 'now-15m', - 'now', + '1234', + '5678', searchFilter ); @@ -270,8 +270,8 @@ describe('ElasticsearchPingsAdapter class', () => { const pingAdapter = new ElasticsearchPingsAdapter(pingDatabase); const result = await pingAdapter.getPingHistogram( serverRequest, - 'now-15m', - 'now', + '1234', + '5678', searchFilter ); @@ -294,8 +294,8 @@ describe('ElasticsearchPingsAdapter class', () => { const pingAdapter = new ElasticsearchPingsAdapter(pingDatabase); const result = await pingAdapter.getPingHistogram( serverRequest, - 'now-15m', - 'now', + '1234', + '5678', searchFilter ); diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts index e33ae02036224..a023358a7f0a6 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts @@ -92,8 +92,6 @@ export type TestSubjects = | 'watchActionAccordion' | 'watchActionAccordion.mockComboBox' | 'watchActionsPanel' - | 'watchThresholdButton' - | 'watchThresholdInput' | 'watchConditionTitle' | 'watchTimeFieldSelect' | 'watchVisualizationChart' diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx index f281408c9d439..4094c26d9cd32 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx @@ -190,45 +190,6 @@ describe.skip(' create route', () => { expect(exists('watchVisualizationChart')).toBe(true); expect(exists('watchActionsPanel')).toBe(true); }); - - describe('watch conditions', () => { - beforeEach(async () => { - const { form, find, component } = testBed; - - // Name, index and time fields are required before the watch condition expression renders - form.setInputValue('nameInput', 'my_test_watch'); - find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox - form.setInputValue('watchTimeFieldSelect', '@timestamp'); - - // @ts-ignore (remove when react 16.9.0 is released) - await act(async () => { - await nextTick(); - component.update(); - }); - }); - - test('should require a threshold value', async () => { - const { form, find, component } = testBed; - - find('watchThresholdButton').simulate('click'); - - // Provide invalid value - form.setInputValue('watchThresholdInput', ''); - - expect(form.getErrorsMessages()).toContain('A value is required.'); - - // Provide valid value - form.setInputValue('watchThresholdInput', '0'); - - // @ts-ignore (remove when react 16.9.0 is released) - await act(async () => { - await nextTick(); - component.update(); - }); - - expect(form.getErrorsMessages().length).toEqual(0); - }); - }); }); describe('actions', () => { diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js index af995d6594a38..5f46f1400c57a 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js @@ -206,7 +206,7 @@ export class ThresholdWatch extends BaseWatch { Array.from(Array(comparators[this.thresholdComparator].requiredValues)).forEach((value, i) => { const key = `threshold${i}`; errors[key] = []; - if (this.threshold[i] == null || this.threshold[i] === '') { + if (!this.threshold[i]) { errors[key].push(i18n.translate( 'xpack.watcher.thresholdWatchExpression.thresholdLevel.valueIsRequiredValidationMessage', { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index 1041213d355ba..4d092895b4149 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -698,7 +698,6 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { id="watchThresholdPopover" button={ { errors={errors} > { setupXPackMain(server); const { types: savedObjectTypes } = server.savedObjects; - const config = server.config(); - const isTimelionUiEnabled = config.get('timelion.enabled') && config.get('timelion.ui.enabled'); - registerOssFeatures(server.plugins.xpack_main.registerFeature, savedObjectTypes, isTimelionUiEnabled); + registerOssFeatures(server.plugins.xpack_main.registerFeature, savedObjectTypes, server.config().get('timelion.ui.enabled')); // register routes xpackInfoRoute(server); diff --git a/x-pack/package.json b/x-pack/package.json index a4e4437a9e3d0..06404068b1d65 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -47,7 +47,7 @@ "@types/base64-js": "^1.2.5", "@types/boom": "^7.2.0", "@types/cheerio": "^0.22.10", - "@types/chroma-js": "^1.4.2", + "@types/chroma-js": "^1.4.1", "@types/color": "^3.0.0", "@types/d3-array": "^1.2.1", "@types/d3-scale": "^2.0.0", @@ -60,7 +60,7 @@ "@types/glob": "^7.1.1", "@types/graphql": "^0.13.1", "@types/hapi__wreck": "^15.0.1", - "@types/history": "^4.7.3", + "@types/history": "^4.6.2", "@types/jest": "^24.0.9", "@types/joi": "^13.4.2", "@types/js-yaml": "^3.11.1", @@ -72,12 +72,12 @@ "@types/memoize-one": "^4.1.0", "@types/mime": "^2.0.1", "@types/mkdirp": "^0.5.2", - "@types/mocha": "^5.2.7", + "@types/mocha": "^5.2.6", "@types/nock": "^10.0.3", "@types/node": "^10.12.27", "@types/node-fetch": "^2.5.0", "@types/nodemailer": "^6.2.1", - "@types/object-hash": "^1.3.0", + "@types/object-hash": "^1.2.0", "@types/papaparse": "^4.5.11", "@types/pngjs": "^3.3.1", "@types/prop-types": "^15.5.3", @@ -94,7 +94,7 @@ "@types/reduce-reducers": "^0.3.0", "@types/redux-actions": "^2.2.1", "@types/rimraf": "^2.0.2", - "@types/sinon": "^7.0.13", + "@types/sinon": "^7.0.0", "@types/storybook__addon-actions": "^3.4.3", "@types/storybook__addon-info": "^4.1.2", "@types/storybook__addon-knobs": "^5.0.3", @@ -121,7 +121,7 @@ "commander": "3.0.0", "copy-webpack-plugin": "^5.0.4", "cypress": "^3.4.1", - "del": "^4.1.1", + "del": "^4.0.0", "dotenv": "2.0.0", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", @@ -166,7 +166,7 @@ "sass-loader": "^7.3.1", "sass-resources-loader": "^2.0.1", "simple-git": "1.116.0", - "sinon": "^7.4.2", + "sinon": "^7.2.2", "string-replace-loader": "^2.2.0", "supertest": "^3.1.0", "supertest-as-promised": "^4.0.2", diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts index c60605cf71715..6a88c5cddc441 100644 --- a/x-pack/plugins/security/server/authentication/index.ts +++ b/x-pack/plugins/security/server/authentication/index.ts @@ -129,7 +129,7 @@ export async function setupAuthentication({ }); } - authLogger.debug('Could not handle authentication attempt'); + authLogger.info('Could not handle authentication attempt'); return response.unauthorized({ headers: authenticationResult.authResponseHeaders, }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7885fbaad4660..b8938d2cae174 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -506,10 +506,24 @@ "common.ui.savedObjects.confirmModal.overwriteTitle": "{name} を上書きしますか?", "common.ui.savedObjects.confirmModal.saveDuplicateButtonLabel": "{name} を保存", "common.ui.savedObjects.confirmModal.saveDuplicateConfirmationMessage": "「{title}」というタイトルの {name} が既に存在します。保存を続けますか?", + "kibana-react.savedObjects.finder.filterButtonLabel": "タイプ", + "kibana-react.savedObjects.finder.searchPlaceholder": "検索…", + "kibana-react.savedObjects.finder.sortAsc": "昇順", + "kibana-react.savedObjects.finder.sortAuto": "ベストマッチ", + "kibana-react.savedObjects.finder.sortButtonLabel": "並べ替え", + "kibana-react.savedObjects.finder.sortDesc": "降順", "common.ui.savedObjects.howToSaveAsNewDescription": "Kibana の以前のバージョンでは、{savedObjectName} の名前を変更すると新しい名前でコピーが作成されました。今後この操作を行うには、「新規 {savedObjectName} として保存」を使用します。", "common.ui.savedObjects.overwriteRejectedDescription": "上書き確認が拒否されました", "common.ui.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存", "common.ui.savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました", + "kibana-react.savedObjects.saveModal.cancelButtonLabel": "キャンセル", + "kibana-react.savedObjects.saveModal.confirmSaveButtonLabel": "保存の確認", + "kibana-react.savedObjects.saveModal.duplicateTitleDescription": "重複タイトルで {objectType} を保存するには {confirmSaveLabel} をクリックしてください。", + "kibana-react.savedObjects.saveModal.duplicateTitleDescription.confirmSaveText": "保存の確認", + "kibana-react.savedObjects.saveModal.duplicateTitleLabel": "「{title}」というタイトルの {objectType} が既に存在します。", + "kibana-react.savedObjects.saveModal.saveAsNewLabel": "新規 {objectType} として保存", + "kibana-react.savedObjects.saveModal.saveTitle": "{objectType} を保存", + "kibana-react.savedObjects.saveModal.titleLabel": "タイトル", "common.ui.scriptingLanguages.errorFetchingToastDescription": "Elasticsearch から利用可能なスクリプト言語の取得中にエラーが発生しました", "common.ui.share.contextMenu.embedCodeLabel": "埋め込みコード", "common.ui.share.contextMenu.embedCodePanelTitle": "埋め込みコード", @@ -581,20 +595,6 @@ "common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "バウンドを取得できませんでした", "common.ui.welcomeErrorMessage": "Kibana が正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。", "common.ui.welcomeMessage": "Kibana を読み込み中", - "kibana-react.savedObjects.finder.filterButtonLabel": "タイプ", - "kibana-react.savedObjects.finder.searchPlaceholder": "検索…", - "kibana-react.savedObjects.finder.sortAsc": "昇順", - "kibana-react.savedObjects.finder.sortAuto": "ベストマッチ", - "kibana-react.savedObjects.finder.sortButtonLabel": "並べ替え", - "kibana-react.savedObjects.finder.sortDesc": "降順", - "kibana-react.savedObjects.saveModal.cancelButtonLabel": "キャンセル", - "kibana-react.savedObjects.saveModal.confirmSaveButtonLabel": "保存の確認", - "kibana-react.savedObjects.saveModal.duplicateTitleDescription": "重複タイトルで {objectType} を保存するには {confirmSaveLabel} をクリックしてください。", - "kibana-react.savedObjects.saveModal.duplicateTitleDescription.confirmSaveText": "保存の確認", - "kibana-react.savedObjects.saveModal.duplicateTitleLabel": "「{title}」というタイトルの {objectType} が既に存在します。", - "kibana-react.savedObjects.saveModal.saveAsNewLabel": "新規 {objectType} として保存", - "kibana-react.savedObjects.saveModal.saveTitle": "{objectType} を保存", - "kibana-react.savedObjects.saveModal.titleLabel": "タイトル", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonAreaLabel": "全画面モードを終了", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonLabel": "全画面を終了", "kibana-react.exitFullScreenButton.fullScreenModeDescription": "ESC キーで全画面モードを修了します。", @@ -3259,6 +3259,7 @@ "tsvb.getInterval.secondsLabel": "秒", "tsvb.getInterval.weeksLabel": "週間", "tsvb.getInterval.yearsLabel": "年", + "tsvb.horizontalLegend.toggleChartAriaLabel": "チャートの凡例を切り替える", "tsvb.iconSelect.asteriskLabel": "アスタリスク", "tsvb.iconSelect.bellLabel": "ベル", "tsvb.iconSelect.boltLabel": "ボルト", @@ -3563,6 +3564,7 @@ "tsvb.validateInterval.notifier.maxBucketsExceededErrorMessage": "バケットの最高数を超えました。{buckets} が {maxBuckets} を超えています。パネルオプションでより広い間隔を試してみてください。", "tsvb.vars.variableNameAriaLabel": "変数名", "tsvb.vars.variableNamePlaceholder": "変数名", + "tsvb.verticalLegend.toggleChartAriaLabel": "チャートの凡例を切り替える", "tsvb.visEditorVisualization.applyChangesLabel": "変更を適用", "tsvb.visEditorVisualization.autoApplyLabel": "自動適用", "tsvb.visEditorVisualization.changesHaveNotBeenAppliedMessage": "ビジュアライゼーションへの変更が適用されました。", @@ -4056,7 +4058,7 @@ "xpack.beatsManagement.tagTable.typeColumnName": "タイプ", "xpack.beatsManagement.walkthrough.initial.betaBadgeText": "ベータ", "xpack.canvas.badge.readOnly.text": "読み込み専用", - "xpack.canvas.badge.readOnly.tooltip": "{canvas} ワークパッドを保存できません", + "xpack.canvas.badge.readOnly.tooltip": "Canvas ワークパッドを保存できません", "xpack.canvas.elements.areaChartDisplayName": "面グラフ", "xpack.canvas.elements.areaChartHelpText": "塗りつぶされた折れ線グラフ", "xpack.canvas.elements.bubbleChartDisplayName": "バブルチャート", @@ -4217,6 +4219,7 @@ "xpack.canvas.functions.timefilter.invalidStringErrorMessage": "無効な日付/時刻文字列: 「{str}」", "xpack.canvas.functions.timefilterControl.args.columnHelpText": "フィルターを添付する列またはフィールドです", "xpack.canvas.functions.timefilterControl.args.compactHelpText": "時間フィルターを、ポップオーバーを実行するボタンとして表示します", + "xpack.canvas.sampleDataLinkLabel": "Canvas", "xpack.code.featureRegistry.codeFeatureName": "コード", "xpack.crossClusterReplication.addAutoFollowPatternButtonLabel": "自動フォローパターンを作成", "xpack.crossClusterReplication.addBreadcrumbTitle": "追加", @@ -4224,6 +4227,8 @@ "xpack.crossClusterReplication.app.checkPermissionsFatalErrorTitle": "クラスター横断レプリケーションアプリ", "xpack.crossClusterReplication.app.deniedPermissionDescription": "クラスター横断レプリケーションを使用するには、{clusterPrivilegesCount, plural, one {次のクラスター特権} other {次のクラスター特権}}が必要です: {clusterPrivileges}。", "xpack.crossClusterReplication.app.deniedPermissionTitle": "クラスター特権が足りません", + "xpack.crossClusterReplication.app.licenseErrorLinkText": "ライセンスの管理。", + "xpack.crossClusterReplication.app.licenseErrorTitle": "ライセンスエラー", "xpack.crossClusterReplication.app.permissionCheckErrorTitle": "パーミッションの確認中にエラーが発生", "xpack.crossClusterReplication.app.permissionCheckTitle": "パーミッションを確認中…", "xpack.crossClusterReplication.appTitle": "クラスター横断レプリケーション", @@ -4637,9 +4642,16 @@ "xpack.graph.topNavMenu.newWorkspaceAriaLabel": "新規ワークスペース", "xpack.graph.topNavMenu.newWorkspaceLabel": "新規", "xpack.graph.topNavMenu.newWorkspaceTooltip": "新規ワークスペースを作成します", + "xpack.graph.topNavMenu.save.confirmButtonAriaLabel": "ワークスペースを保存", + "xpack.graph.topNavMenu.save.confirmButtonLabel": "保存", "xpack.graph.topNavMenu.save.descriptionInputLabel": "説明", + "xpack.graph.topNavMenu.save.descriptionInputPlaceholder": "メモ…", + "xpack.graph.topNavMenu.save.nameInputLabel": "名前", + "xpack.graph.topNavMenu.save.nameInputPlaceholder": "グラフワークスペース名", "xpack.graph.topNavMenu.save.saveConfigurationOnlyText": "このワークスペースのデータは消去され、構成のみが保存されます", + "xpack.graph.topNavMenu.save.saveConfigurationOnlyWarning": "このワークスペースのデータは消去され、構成のみが保存されます", "xpack.graph.topNavMenu.save.saveGraphContentCheckboxLabel": "Graph コンテンツを保存", + "xpack.graph.topNavMenu.save.saveWorkspaceTitle": "ワークスペースの保存", "xpack.graph.topNavMenu.saveWorkspace.disabledTooltip": "現在の保存ポリシーでは、保存されたワークスペースへの変更が許可されていません", "xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel": "ワークスペースを保存", "xpack.graph.topNavMenu.saveWorkspace.enabledLabel": "保存", @@ -7171,6 +7183,7 @@ "xpack.ml.timeSeriesExplorer.loadingLabel": "読み込み中", "xpack.ml.timeSeriesExplorer.noResultsFoundLabel": "結果が見つかりませんでした", "xpack.ml.timeSeriesExplorer.noSingleMetricJobsFoundLabel": "シングルメトリックジョブが見つかりませんでした", + "xpack.ml.timeSeriesExplorer.refreshButtonAriaLabel": "更新", "xpack.ml.timeSeriesExplorer.requestedDetectorIndexNotValidWarningMessage": "リクエストされた検知器インデックス {detectorIndex} はジョブ {jobId} に有効ではありません", "xpack.ml.timeSeriesExplorer.runControls.durationLabel": "期間", "xpack.ml.timeSeriesExplorer.runControls.forecastMaximumLengthHelpText": "予想の長さで、最長 {maximumForecastDurationDays} 日です。秒には s、分には m、時間には h、日には d、週には w を使います。", @@ -10373,4 +10386,4 @@ "xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "ログテキストが必要です。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1285cb288f3f4..950fe053ca5e6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -506,10 +506,24 @@ "common.ui.savedObjects.confirmModal.overwriteTitle": "覆盖“{name}”?", "common.ui.savedObjects.confirmModal.saveDuplicateButtonLabel": "保存“{name}”", "common.ui.savedObjects.confirmModal.saveDuplicateConfirmationMessage": "具有标题 “{title}” 的 “{name}” 已存在。是否确定要保存?", + "kibana-react.savedObjects.finder.filterButtonLabel": "类型", + "kibana-react.savedObjects.finder.searchPlaceholder": "搜索……", + "kibana-react.savedObjects.finder.sortAsc": "升序", + "kibana-react.savedObjects.finder.sortAuto": "最佳匹配", + "kibana-react.savedObjects.finder.sortButtonLabel": "排序", + "kibana-react.savedObjects.finder.sortDesc": "降序", "common.ui.savedObjects.howToSaveAsNewDescription": "在 Kibana 的以前版本中,更改 {savedObjectName} 的名称将创建具有新名称的副本。使用“另存为新的 {savedObjectName}” 复选框可立即达到此目的。", "common.ui.savedObjects.overwriteRejectedDescription": "已拒绝覆盖确认", "common.ui.savedObjects.saveAsNewLabel": "另存为新的 {savedObjectName}", "common.ui.savedObjects.saveDuplicateRejectedDescription": "已拒绝使用重复标题保存确认", + "kibana-react.savedObjects.saveModal.cancelButtonLabel": "取消", + "kibana-react.savedObjects.saveModal.confirmSaveButtonLabel": "确认保存", + "kibana-react.savedObjects.saveModal.duplicateTitleDescription": "单击 “{confirmSaveLabel}” 以保存标题重复的{objectType}", + "kibana-react.savedObjects.saveModal.duplicateTitleDescription.confirmSaveText": "确认保存", + "kibana-react.savedObjects.saveModal.duplicateTitleLabel": "具有标题 “{title}” 的 {objectType} 已存在。", + "kibana-react.savedObjects.saveModal.saveAsNewLabel": "另存为新的{objectType}", + "kibana-react.savedObjects.saveModal.saveTitle": "保存{objectType}", + "kibana-react.savedObjects.saveModal.titleLabel": "标题", "common.ui.scriptingLanguages.errorFetchingToastDescription": "从 Elasticsearch 获取可用的脚本语言时出错", "common.ui.share.contextMenu.embedCodeLabel": "嵌入代码", "common.ui.share.contextMenu.embedCodePanelTitle": "嵌入代码", @@ -581,20 +595,6 @@ "common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "无法获取边界", "common.ui.welcomeErrorMessage": "Kibana 未正确加载。检查服务器输出以了解详情。", "common.ui.welcomeMessage": "正在加载 Kibana", - "kibana-react.savedObjects.finder.filterButtonLabel": "类型", - "kibana-react.savedObjects.finder.searchPlaceholder": "搜索……", - "kibana-react.savedObjects.finder.sortAsc": "升序", - "kibana-react.savedObjects.finder.sortAuto": "最佳匹配", - "kibana-react.savedObjects.finder.sortButtonLabel": "排序", - "kibana-react.savedObjects.finder.sortDesc": "降序", - "kibana-react.savedObjects.saveModal.cancelButtonLabel": "取消", - "kibana-react.savedObjects.saveModal.confirmSaveButtonLabel": "确认保存", - "kibana-react.savedObjects.saveModal.duplicateTitleDescription": "单击 “{confirmSaveLabel}” 以保存标题重复的{objectType}", - "kibana-react.savedObjects.saveModal.duplicateTitleDescription.confirmSaveText": "确认保存", - "kibana-react.savedObjects.saveModal.duplicateTitleLabel": "具有标题 “{title}” 的 {objectType} 已存在。", - "kibana-react.savedObjects.saveModal.saveAsNewLabel": "另存为新的{objectType}", - "kibana-react.savedObjects.saveModal.saveTitle": "保存{objectType}", - "kibana-react.savedObjects.saveModal.titleLabel": "标题", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonAreaLabel": "退出全屏模式", "kibana-react.exitFullScreenButton.exitFullScreenModeButtonLabel": "退出全屏", "kibana-react.exitFullScreenButton.fullScreenModeDescription": "在全屏模式下,按 ESC 键可退出。", @@ -3259,6 +3259,7 @@ "tsvb.getInterval.secondsLabel": "秒", "tsvb.getInterval.weeksLabel": "周", "tsvb.getInterval.yearsLabel": "年", + "tsvb.horizontalLegend.toggleChartAriaLabel": "切换图例", "tsvb.iconSelect.asteriskLabel": "星号", "tsvb.iconSelect.bellLabel": "钟铃", "tsvb.iconSelect.boltLabel": "闪电", @@ -3563,6 +3564,7 @@ "tsvb.validateInterval.notifier.maxBucketsExceededErrorMessage": "超过最大桶数:{buckets} 大于 {maxBuckets},请在面板选项中尝试更大的时间间隔。", "tsvb.vars.variableNameAriaLabel": "变量名称", "tsvb.vars.variableNamePlaceholder": "变量名称", + "tsvb.verticalLegend.toggleChartAriaLabel": "切换图例", "tsvb.visEditorVisualization.applyChangesLabel": "应用更改", "tsvb.visEditorVisualization.autoApplyLabel": "自动应用", "tsvb.visEditorVisualization.changesHaveNotBeenAppliedMessage": "未应用对此可视化的更改。", @@ -4056,7 +4058,7 @@ "xpack.beatsManagement.tagTable.typeColumnName": "类型", "xpack.beatsManagement.walkthrough.initial.betaBadgeText": "公测版", "xpack.canvas.badge.readOnly.text": "只读", - "xpack.canvas.badge.readOnly.tooltip": "无法保存 {canvas} Workpad", + "xpack.canvas.badge.readOnly.tooltip": "无法保存 Canvas Workpad", "xpack.canvas.elements.areaChartDisplayName": "面积图", "xpack.canvas.elements.areaChartHelpText": "已填充主体的折线图", "xpack.canvas.elements.bubbleChartDisplayName": "气泡图", @@ -4217,6 +4219,7 @@ "xpack.canvas.functions.timefilter.invalidStringErrorMessage": "无效的日期/时间字符串:“{str}”", "xpack.canvas.functions.timefilterControl.args.columnHelpText": "附加筛选的列字段", "xpack.canvas.functions.timefilterControl.args.compactHelpText": "将时间筛选显示为触发弹出框的按钮", + "xpack.canvas.sampleDataLinkLabel": "Canvas", "xpack.code.adminPage.langserverTab.installedText": "已安装", "xpack.code.adminPage.langserverTab.languageServersDescription": "{serverCount} {serverCount, plural, one {服务器} other {服务器}}", "xpack.code.adminPage.langserverTab.notInstalledText": "未安装", @@ -4367,6 +4370,8 @@ "xpack.crossClusterReplication.app.checkPermissionsFatalErrorTitle": "跨集群复制应用", "xpack.crossClusterReplication.app.deniedPermissionDescription": "要使用跨集群复制,您必须具有{clusterPrivilegesCount, plural, one {以下集群权限} other {以下集群权限}}:{clusterPrivileges}。", "xpack.crossClusterReplication.app.deniedPermissionTitle": "您缺少集群权限", + "xpack.crossClusterReplication.app.licenseErrorLinkText": "管理您的许可。", + "xpack.crossClusterReplication.app.licenseErrorTitle": "许可错误", "xpack.crossClusterReplication.app.permissionCheckErrorTitle": "检查权限时出错", "xpack.crossClusterReplication.app.permissionCheckTitle": "正在检查权限......", "xpack.crossClusterReplication.appTitle": "跨集群复制", @@ -4780,9 +4785,16 @@ "xpack.graph.topNavMenu.newWorkspaceAriaLabel": "新建工作空间", "xpack.graph.topNavMenu.newWorkspaceLabel": "新建", "xpack.graph.topNavMenu.newWorkspaceTooltip": "新建工作空间", + "xpack.graph.topNavMenu.save.confirmButtonAriaLabel": "保存工作空间", + "xpack.graph.topNavMenu.save.confirmButtonLabel": "保存", "xpack.graph.topNavMenu.save.descriptionInputLabel": "描述", + "xpack.graph.topNavMenu.save.descriptionInputPlaceholder": "任何备注......", + "xpack.graph.topNavMenu.save.nameInputLabel": "名称", + "xpack.graph.topNavMenu.save.nameInputPlaceholder": "Graph 工作空间名称", "xpack.graph.topNavMenu.save.saveConfigurationOnlyText": "将清除此工作空间的数据,仅保存配置", + "xpack.graph.topNavMenu.save.saveConfigurationOnlyWarning": "将清除此工作空间的数据,仅保存配置", "xpack.graph.topNavMenu.save.saveGraphContentCheckboxLabel": "保存 Graph 内容", + "xpack.graph.topNavMenu.save.saveWorkspaceTitle": "保存工作空间", "xpack.graph.topNavMenu.saveWorkspace.disabledTooltip": "当前保存策略不允许对已保存的工作空间做任何更改", "xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel": "保存工作空间", "xpack.graph.topNavMenu.saveWorkspace.enabledLabel": "保存", @@ -7313,6 +7325,7 @@ "xpack.ml.timeSeriesExplorer.loadingLabel": "正在加载", "xpack.ml.timeSeriesExplorer.noResultsFoundLabel": "找不到结果", "xpack.ml.timeSeriesExplorer.noSingleMetricJobsFoundLabel": "未找到单指标作业", + "xpack.ml.timeSeriesExplorer.refreshButtonAriaLabel": "刷新", "xpack.ml.timeSeriesExplorer.requestedDetectorIndexNotValidWarningMessage": "请求的检测工具索引 {detectorIndex} 对于作业 {jobId} 无效", "xpack.ml.timeSeriesExplorer.runControls.durationLabel": "持续时间", "xpack.ml.timeSeriesExplorer.runControls.forecastMaximumLengthHelpText": "预测时长,最多 {maximumForecastDurationDays} 天。使用 s 表示秒,m 表示分钟,h 表示小时,d 表示天,w 表示周。", @@ -10515,4 +10528,4 @@ "xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "“日志文本”必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 1bfc50e4c83db..4bd207e51a941 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -54,10 +54,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) serverArgs: [ ...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'), '--xpack.actions.enabled=true', - `--xpack.actions.whitelistedHosts=${JSON.stringify([ - 'localhost', - 'some.non.existent.com', - ])}`, '--xpack.alerting.enabled=true', ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index 89fc986fd0255..495691cb386c6 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -162,27 +162,6 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(result.status).to.eql('ok'); }); - it('should handle target webhooks that are not whitelisted', async () => { - const { body: result } = await supertest - .post('/api/action') - .set('kbn-xsrf', 'test') - .send({ - description: 'A generic Webhook action', - actionTypeId: '.webhook', - secrets: { - user: 'username', - password: 'mypassphrase', - }, - config: { - url: 'http://a.none.whitelisted.webhook/endpoint', - }, - }) - .expect(400); - - expect(result.error).to.eql('Bad Request'); - expect(result.message).to.match(/not in the Kibana whitelist/); - }); - it('should handle unreachable webhook targets', async () => { const webhookActionId = await createWebhookAction('http://some.non.existent.com/endpoint'); const { body: result } = await supertest @@ -198,6 +177,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { expect(result.status).to.eql('error'); expect(result.message).to.match(/Unreachable Remote Webhook/); }); + it('should handle failing webhook targets', async () => { const webhookActionId = await createWebhookAction(webhookSimulatorURL); const { body: result } = await supertest diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts index 7f67f2f5b60e7..0780efc0fc977 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/index.ts @@ -21,6 +21,5 @@ export default function actionsTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./builtin_action_types/email')); loadTestFile(require.resolve('./builtin_action_types/es_index')); loadTestFile(require.resolve('./builtin_action_types/pagerduty')); - loadTestFile(require.resolve('./builtin_action_types/webhook')); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/index.ts index 4c2dc3cbdf11f..d3d5ca592ce63 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/index.ts @@ -19,7 +19,7 @@ export default function alertingApiIntegrationTests({ const esArchiver = getService('esArchiver'); describe('alerting api integration security and spaces enabled', function() { - this.tags('ciGroup8'); + this.tags('ciGroup3'); before(async () => { for (const space of Spaces) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts index dfbb2cca81a49..8c59e39818619 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts @@ -17,7 +17,7 @@ export default function alertingApiIntegrationTests({ const esArchiver = getService('esArchiver'); describe('alerting api integration spaces only', function() { - this.tags('ciGroup8'); + this.tags('ciGroup3'); before(async () => { for (const space of Object.values(Spaces)) { diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 99694c250ef3f..807a808e7f9d3 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('apis', function () { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./es')); loadTestFile(require.resolve('./security')); diff --git a/x-pack/test/api_integration/apis/security/index.js b/x-pack/test/api_integration/apis/security/index.js index 4d034622427fc..2174b578abff5 100644 --- a/x-pack/test/api_integration/apis/security/index.js +++ b/x-pack/test/api_integration/apis/security/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('security', function () { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./basic_login')); loadTestFile(require.resolve('./builtin_es_privileges')); diff --git a/x-pack/test/api_integration/apis/spaces/index.ts b/x-pack/test/api_integration/apis/spaces/index.ts index adcf70d032e0f..f3f96b891db07 100644 --- a/x-pack/test/api_integration/apis/spaces/index.ts +++ b/x-pack/test/api_integration/apis/spaces/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('spaces', function() { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./saved_objects')); loadTestFile(require.resolve('./space_attributes')); diff --git a/x-pack/test/functional/apps/apm/index.ts b/x-pack/test/functional/apps/apm/index.ts index 945af09183f03..977b6fca549c3 100644 --- a/x-pack/test/functional/apps/apm/index.ts +++ b/x-pack/test/functional/apps/apm/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('APM', function() { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./feature_controls')); }); } diff --git a/x-pack/test/functional/apps/code/code_intelligence.ts b/x-pack/test/functional/apps/code/code_intelligence.ts index 0213cc27fafe8..0dbd6ee8c38a6 100644 --- a/x-pack/test/functional/apps/code/code_intelligence.ts +++ b/x-pack/test/functional/apps/code/code_intelligence.ts @@ -37,7 +37,7 @@ export default function codeIntelligenceFunctionalTests({ expect(spans.length).to.greaterThan(1); const userModelSpan = spans[1]; expect(await userModelSpan.getVisibleText()).to.equal('UserModel'); - await userModelSpan.moveMouseTo(); + await browser.moveMouseTo(userModelSpan); // Expect the go to definition button show up eventually. expect(await exists('codeGoToDefinitionButton')).to.be(true); @@ -180,7 +180,7 @@ export default function codeIntelligenceFunctionalTests({ expect(spans.length).to.greaterThan(1); const userModelSpan = spans[0]; expect(await userModelSpan.getVisibleText()).to.equal('UserModel'); - await userModelSpan.moveMouseTo(); + await browser.moveMouseTo(userModelSpan); // Expect the go to definition button show up eventually. expect(await exists('codeFindReferenceButton')).to.be(true); @@ -231,7 +231,7 @@ export default function codeIntelligenceFunctionalTests({ expect(spans.length).to.greaterThan(1); const asyncSpan = spans[1]; expect(await asyncSpan.getVisibleText()).to.equal('async'); - await asyncSpan.moveMouseTo(); + await browser.moveMouseTo(asyncSpan); // Expect the go to definition button show up eventually. expect(await exists('codeGoToDefinitionButton')).to.be(true); diff --git a/x-pack/test/functional/apps/code/history.ts b/x-pack/test/functional/apps/code/history.ts index 5b2e3cdde336a..8dfde5a0aaad2 100644 --- a/x-pack/test/functional/apps/code/history.ts +++ b/x-pack/test/functional/apps/code/history.ts @@ -25,8 +25,7 @@ export default function manageRepositoriesFunctionalTests({ const existsInvisible = async (selector: string) => await testSubjects.exists(selector, { allowHidden: true }); - // FLAKY: https://github.com/elastic/kibana/issues/37859 - describe.skip('History', function() { + describe('History', function() { this.tags('smoke'); const repositoryListSelector = 'codeRepositoryList codeRepositoryItem'; diff --git a/x-pack/test/functional/apps/dashboard_mode/index.js b/x-pack/test/functional/apps/dashboard_mode/index.js index 2d263834fc311..5612fced7a25d 100644 --- a/x-pack/test/functional/apps/dashboard_mode/index.js +++ b/x-pack/test/functional/apps/dashboard_mode/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('dashboard mode', function () { - this.tags('ciGroup7'); + this.tags('ciGroup2'); loadTestFile(require.resolve('./dashboard_view_mode')); }); diff --git a/x-pack/test/functional/apps/index_lifecycle_management/index.ts b/x-pack/test/functional/apps/index_lifecycle_management/index.ts index 9078a9d681e7e..d85b1af2b2612 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/index.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext) => { describe('Index Lifecycle Management app', function() { - this.tags('ciGroup7'); + this.tags('ciGroup2'); loadTestFile(require.resolve('./home_page')); }); }; diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index b706dc8cce546..b534f6b69fe5b 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext) => { describe('InfraOps app', function() { - this.tags('ciGroup7'); + this.tags('ciGroup2'); loadTestFile(require.resolve('./home_page')); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/functional/apps/license_management/index.ts b/x-pack/test/functional/apps/license_management/index.ts index 7524d00a4b8dd..a41e4f5f4abd1 100644 --- a/x-pack/test/functional/apps/license_management/index.ts +++ b/x-pack/test/functional/apps/license_management/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext) => { describe('License app', function() { - this.tags('ciGroup7'); + this.tags('ciGroup2'); loadTestFile(require.resolve('./home_page')); }); }; diff --git a/x-pack/test/functional/apps/machine_learning/create_single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/create_single_metric_job.ts index a8993f7c92c1e..3973f2aa38257 100644 --- a/x-pack/test/functional/apps/machine_learning/create_single_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/create_single_metric_job.ts @@ -13,15 +13,9 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); const jobId = `fq_single_1_${Date.now()}`; - const jobDescription = - 'Create single metric job based on the farequote dataset with 30m bucketspan and mean(responsetime)'; - const jobGroups = ['automated', 'farequote', 'single-metric']; - const aggAndFieldIdentifier = 'Mean(responsetime)'; - const bucketSpan = '30m'; - const memoryLimit = '15MB'; describe('single metric job creation', function() { - this.tags(['smoke', 'mlqa']); + this.tags('smoke'); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); }); @@ -64,12 +58,14 @@ export default function({ getService }: FtrProviderContext) { }); it('selects field and aggregation', async () => { + const identifier = 'Mean(responsetime)'; await ml.jobWizardCommon.assertAggAndFieldInputExists(); - await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier); - await ml.jobWizardCommon.assertAggAndFieldSelection(aggAndFieldIdentifier); + await ml.jobWizardCommon.selectAggAndField(identifier); + await ml.jobWizardCommon.assertAggAndFieldSelection(identifier); }); it('inputs the bucket span', async () => { + const bucketSpan = '30m'; await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(bucketSpan); await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); @@ -87,12 +83,15 @@ export default function({ getService }: FtrProviderContext) { }); it('inputs the job description', async () => { + const jobDescription = + 'Create single metric job based on the farequote dataset with 30m bucketspan and mean(responsetime)'; await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(jobDescription); await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); }); it('inputs job groups', async () => { + const jobGroups = ['automated', 'farequote', 'single-metric']; await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); @@ -115,6 +114,7 @@ export default function({ getService }: FtrProviderContext) { }); it('inputs the model memory limit', async () => { + const memoryLimit = '15MB'; await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); @@ -138,75 +138,9 @@ export default function({ getService }: FtrProviderContext) { it('displays the created job in the job list', async () => { await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); - - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobId); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobId)).to.have.length(1); - }); - - it('displays details for the created job in the job list', async () => { - const rows = await ml.jobTable.parseJobTable(); - const job = rows.filter(row => row.id === jobId)[0]; - expect(job).to.eql({ - id: jobId, - description: jobDescription, - jobGroups, - recordCount: '2,399', - memoryStatus: 'ok', - jobState: 'closed', - datafeedState: 'stopped', - latestTimestamp: '2016-02-11 23:56:59', - }); - - const countDetails = await ml.jobTable.parseJobCounts(jobId); - const counts = countDetails.counts; - - // last_data_time holds a runtime timestamp and is hard to predict - // the property is only validated to be present and then removed - // so it doesn't make the counts object validation fail - expect(counts).to.have.property('last_data_time'); - delete counts.last_data_time; - - expect(counts).to.eql({ - job_id: jobId, - processed_record_count: '2,399', - processed_field_count: '4,798', - input_bytes: '180.6 KB', - input_field_count: '4,798', - invalid_date_count: '0', - missing_field_count: '0', - out_of_order_timestamp_count: '0', - empty_bucket_count: '0', - sparse_bucket_count: '0', - bucket_count: '239', - earliest_record_timestamp: '2016-02-07 00:02:50', - latest_record_timestamp: '2016-02-11 23:56:59', - input_record_count: '2,399', - latest_bucket_timestamp: '2016-02-11 23:30:00', - }); - - const modelSizeStats = countDetails.modelSizeStats; - - // log_time holds a runtime timestamp and is hard to predict - // the property is only validated to be present and then removed - // so it doesn't make the modelSizeStats object validation fail - expect(modelSizeStats).to.have.property('log_time'); - delete modelSizeStats.log_time; - - expect(modelSizeStats).to.eql({ - job_id: jobId, - result_type: 'model_size_stats', - model_bytes: '47.6 KB', - model_bytes_exceeded: '0', - model_bytes_memory_limit: '15728640', - total_by_field_count: '3', - total_over_field_count: '0', - total_partition_field_count: '2', - bucket_allocation_failures_count: '0', - memory_status: 'ok', - timestamp: '2016-02-11 23:00:00', - }); + await ml.jobManagement.filterJobsTable(jobId); + const jobRow = await ml.jobManagement.getJobRowByJobId(jobId); + expect(jobRow).to.not.be(null); }); }); } diff --git a/x-pack/test/functional/apps/machine_learning/pages.ts b/x-pack/test/functional/apps/machine_learning/pages.ts index b3537abb4044b..fb085f0661f8c 100644 --- a/x-pack/test/functional/apps/machine_learning/pages.ts +++ b/x-pack/test/functional/apps/machine_learning/pages.ts @@ -11,7 +11,7 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('page navigation', function() { - this.tags(['smoke', 'mlqa']); + this.tags('smoke'); before(async () => { await esArchiver.load('empty_kibana'); }); diff --git a/x-pack/test/functional/apps/maps/add_layer_panel.js b/x-pack/test/functional/apps/maps/add_layer_panel.js index a17772987ab8e..60a719f046674 100644 --- a/x-pack/test/functional/apps/maps/add_layer_panel.js +++ b/x-pack/test/functional/apps/maps/add_layer_panel.js @@ -16,7 +16,7 @@ export default function ({ getService, getPageObjects }) { before(async () => { await PageObjects.maps.openNewMap(); await PageObjects.maps.clickAddLayer(); - await PageObjects.maps.selectEMSBoundariesSource(); + await PageObjects.maps.selectVectorSource(); await PageObjects.maps.selectVectorLayer(LAYER_NAME); }); diff --git a/x-pack/test/functional/apps/maps/full_screen_mode.js b/x-pack/test/functional/apps/maps/full_screen_mode.js index 7d89ff1454598..36db159704d21 100644 --- a/x-pack/test/functional/apps/maps/full_screen_mode.js +++ b/x-pack/test/functional/apps/maps/full_screen_mode.js @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['maps', 'common']); + const browser = getService('browser'); const retry = getService('retry'); describe('full screen mode', () => { @@ -39,7 +40,7 @@ export default function ({ getService, getPageObjects }) { it('exits when the text button is clicked on', async () => { const logoButton = await PageObjects.maps.getExitFullScreenLogoButton(); - await logoButton.moveMouseTo(); + await browser.moveMouseTo(logoButton); await PageObjects.maps.clickExitFullScreenTextButton(); await retry.try(async () => { diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js index 9880fe41076d0..76cccc9f5d21a 100644 --- a/x-pack/test/functional/apps/maps/index.js +++ b/x-pack/test/functional/apps/maps/index.js @@ -28,7 +28,7 @@ export default function ({ loadTestFile, getService }) { }); describe('', function () { - this.tags('ciGroup7'); + this.tags('ciGroup2'); loadTestFile(require.resolve('./documents_source')); loadTestFile(require.resolve('./saved_object_management')); loadTestFile(require.resolve('./sample_data')); @@ -38,7 +38,7 @@ export default function ({ loadTestFile, getService }) { }); describe('', function () { - this.tags('ciGroup10'); + this.tags('ciGroup5'); loadTestFile(require.resolve('./es_geo_grid_source')); loadTestFile(require.resolve('./joins')); loadTestFile(require.resolve('./add_layer_panel')); diff --git a/x-pack/test/functional/apps/maps/mapbox_styles.js b/x-pack/test/functional/apps/maps/mapbox_styles.js index 49519b530337e..c7976741f835e 100644 --- a/x-pack/test/functional/apps/maps/mapbox_styles.js +++ b/x-pack/test/functional/apps/maps/mapbox_styles.js @@ -49,7 +49,7 @@ export const MAPBOX_STYLES = { 'coalesce', [ 'feature-state', - '__kbn__dynamic____kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name__fillColor' + '__kbn__scaled(__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name)' ], -1 ], @@ -123,7 +123,7 @@ export const MAPBOX_STYLES = { 'coalesce', [ 'feature-state', - '__kbn__dynamic____kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name__fillColor' + '__kbn__scaled(__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name)' ], -1 ], diff --git a/x-pack/test/functional/apps/spaces/copy_saved_objects.ts b/x-pack/test/functional/apps/spaces/copy_saved_objects.ts index c4e905b3babd0..f62400ad62ee7 100644 --- a/x-pack/test/functional/apps/spaces/copy_saved_objects.ts +++ b/x-pack/test/functional/apps/spaces/copy_saved_objects.ts @@ -16,7 +16,8 @@ export default function spaceSelectorFunctonalTests({ const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['security', 'settings', 'copySavedObjectsToSpace']); - describe('Copy Saved Objects to Space', function() { + // FLAKY: https://github.com/elastic/kibana/issues/44575 + describe.skip('Copy Saved Objects to Space', function() { before(async () => { await esArchiver.load('spaces/copy_saved_objects'); diff --git a/x-pack/test/functional/apps/uptime/index.ts b/x-pack/test/functional/apps/uptime/index.ts index c1bc8f856c467..b5d3c73c96855 100644 --- a/x-pack/test/functional/apps/uptime/index.ts +++ b/x-pack/test/functional/apps/uptime/index.ts @@ -18,7 +18,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => { await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'UTC' }); }); after(async () => await esArchiver.unload(ARCHIVE)); - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./overview')); diff --git a/x-pack/test/functional/es_archives/ml/farequote/data.json.gz b/x-pack/test/functional/es_archives/ml/farequote/data.json.gz index 6d84ef28be658..47e0d55672bfd 100644 Binary files a/x-pack/test/functional/es_archives/ml/farequote/data.json.gz and b/x-pack/test/functional/es_archives/ml/farequote/data.json.gz differ diff --git a/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts b/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts index 69e79d63d5fd5..3908b2ddecf1d 100644 --- a/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts +++ b/x-pack/test/functional/page_objects/copy_saved_objects_to_space_page.ts @@ -31,9 +31,6 @@ export function CopySavedObjectsToSpacePageProvider({ getService }: FtrProviderC 'table.euiTable tbody tr.euiTableRow td.euiTableRowCell:last-child .euiButtonIcon' ); - // Wait for context menu to render - await find.existsByCssSelector('.euiContextMenuPanel'); - const actions = await find.allByCssSelector('.euiContextMenuItem'); for (const action of actions) { diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js index f46abfd28f1fb..553ad6edc72d7 100644 --- a/x-pack/test/functional/page_objects/gis_page.js +++ b/x-pack/test/functional/page_objects/gis_page.js @@ -470,14 +470,14 @@ export function GisPageProvider({ getService, getPageObjects }) { await this.waitForLayersToLoad(); } - async selectEMSBoundariesSource() { - log.debug(`Select EMS boundaries source`); - await testSubjects.click('emsBoundaries'); + async selectVectorSource() { + log.debug(`Select vector source`); + await testSubjects.click('vectorShapes'); } async selectGeoJsonUploadSource() { - log.debug(`Select upload geojson source`); - await testSubjects.click('uploadedGeoJson'); + log.debug(`Select upload geojson vector file`); + await testSubjects.click('uploadGeoJsonVectorFile'); } async uploadJsonFileForIndexing(path) { @@ -601,8 +601,8 @@ export function GisPageProvider({ getService, getPageObjects }) { async lockTooltipAtPosition(xOffset, yOffset) { await retry.try(async () => { const mapContainerElement = await testSubjects.find('mapContainer'); - await mapContainerElement.moveMouseTo({ xOffset, yOffset }); - await mapContainerElement.clickMouseButton({ xOffset, yOffset }); + await browser.moveMouseTo(mapContainerElement, xOffset, yOffset); + await browser.clickMouseButton(mapContainerElement, xOffset, yOffset); // Close button is only displayed with tooltip is locked const hasCloseButton = await testSubjects.exists('mapTooltipCloseButton'); if (!hasCloseButton) { diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js index a0ea73e40f6b0..d3af519a66b57 100644 --- a/x-pack/test/functional/page_objects/security_page.js +++ b/x-pack/test/functional/page_objects/security_page.js @@ -164,7 +164,7 @@ export function SecurityPageProvider({ getService, getPageObjects }) { async clickSaveEditRole() { const saveButton = await retry.try(() => testSubjects.find('roleFormSaveButton')); - await saveButton.moveMouseTo(); + await browser.moveMouseTo(saveButton); await saveButton.click(); await PageObjects.header.waitUntilLoadingHasFinished(); } diff --git a/x-pack/test/functional/services/machine_learning/index.ts b/x-pack/test/functional/services/machine_learning/index.ts index f6fab4af1a119..d2e387d5ea1ba 100644 --- a/x-pack/test/functional/services/machine_learning/index.ts +++ b/x-pack/test/functional/services/machine_learning/index.ts @@ -10,7 +10,6 @@ export { MachineLearningDataFramesProvider } from './data_frames'; export { MachineLearningDataVisualizerProvider } from './data_visualizer'; export { MachineLearningJobManagementProvider } from './job_management'; export { MachineLearningJobSourceSelectionProvider } from './job_source_selection'; -export { MachineLearningJobTableProvider } from './job_table'; export { MachineLearningJobTypeSelectionProvider } from './job_type_selection'; export { MachineLearningJobWizardCommonProvider } from './job_wizard_common'; export { MachineLearningNavigationProvider } from './navigation'; diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts index 29380c85ad7a4..dcec09def1221 100644 --- a/x-pack/test/functional/services/machine_learning/job_management.ts +++ b/x-pack/test/functional/services/machine_learning/job_management.ts @@ -5,11 +5,30 @@ */ import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; export function MachineLearningJobManagementProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); return { + async getJobsTable(): Promise { + const tableContainer = await testSubjects.find('mlJobListTable'); + return await tableContainer.findByTagName('table'); + }, + + async isJobsTableLoadingIndicatorDisplayed(): Promise { + const mlJobListTable = await testSubjects.find('mlJobListTable'); + const innerText = await mlJobListTable.getVisibleText(); + return innerText.includes('Loading jobs...'); + }, + + async isNoItemsFoundMessageDisplayed(): Promise { + const mlJobListTable = await testSubjects.find('mlJobListTable'); + const innerText = await mlJobListTable.getVisibleText(); + return innerText.includes('No jobs found'); + }, + async navigateToNewJobSourceSelection() { await testSubjects.clickWhenNotDisabled('mlCreateNewJobButton'); await testSubjects.existOrFail('mlPageSourceSelection'); @@ -26,5 +45,30 @@ export function MachineLearningJobManagementProvider({ getService }: FtrProvider async assertJobStatsBarExists() { await testSubjects.existOrFail('mlJobStatsBar'); }, + + async waitForJobsTableToLoad() { + await retry.waitFor( + 'jobs table to exist', + async () => await testSubjects.exists('mlJobListTable') + ); + + await retry.waitFor( + 'jobs table loading indicator to be invisible', + async () => (await this.isJobsTableLoadingIndicatorDisplayed()) === false + ); + }, + + async filterJobsTable(jobId: string) { + await this.waitForJobsTableToLoad(); + const searchBar = await testSubjects.find('mlJobListSearchBar'); + const searchBarInput = await searchBar.findByTagName('input'); + await searchBarInput.clearValueWithKeyboard(); + await searchBarInput.type(jobId); + }, + + async getJobRowByJobId(jobId: string): Promise { + const table = await this.getJobsTable(); + return await table.findByCssSelector(`[data-row-id=${jobId}]`); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/job_table.ts b/x-pack/test/functional/services/machine_learning/job_table.ts deleted file mode 100644 index 8e9c74f7c22c7..0000000000000 --- a/x-pack/test/functional/services/machine_learning/job_table.ts +++ /dev/null @@ -1,178 +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 { FtrProviderContext } from '../../ftr_provider_context'; - -export function MachineLearningJobTableProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - - return new (class MlJobTable { - public async parseJobTable() { - const table = await testSubjects.find('mlJobListTable'); - const $ = await table.parseDomContent(); - const rows = []; - - for (const tr of $.findTestSubjects('row').toArray()) { - const $tr = $(tr); - - const $description = $tr.findTestSubject('description').find('.euiTableCellContent'); - const $jobGroups = $description.findTestSubjects('jobGroup'); - const jobGroups = []; - for (const el of $jobGroups.toArray()) { - // collect this group in our array - jobGroups.push( - $(el) - .text() - .trim() - ); - - // remove this element from $description so it doesn't pollute it's text value - $(el).remove(); - } - - rows.push({ - id: $tr - .findTestSubject('id') - .find('.euiTableCellContent') - .text() - .trim(), - description: $description - .text() - .replace(/( $)/g, '') - .trim(), - jobGroups, - recordCount: $tr - .findTestSubject('recordCount') - .find('.euiTableCellContent') - .text() - .trim(), - memoryStatus: $tr - .findTestSubject('memoryStatus') - .find('.euiTableCellContent') - .text() - .trim(), - jobState: $tr - .findTestSubject('jobState') - .find('.euiTableCellContent') - .text() - .trim(), - datafeedState: $tr - .findTestSubject('datafeedState') - .find('.euiTableCellContent') - .text() - .trim(), - latestTimestamp: $tr - .findTestSubject('latestTimestamp') - .find('.euiTableCellContent') - .text() - .trim(), - }); - } - - return rows; - } - - public async parseJobCounts(jobId: string) { - return await this.withDetailsOpen(jobId, async () => { - // click counts tab - await testSubjects.click(this.detailsSelector(jobId, 'tab-counts')); - - const countsTable = await testSubjects.find( - this.detailsSelector(jobId, 'details-counts counts') - ); - const modelSizeStatsTable = await testSubjects.find( - this.detailsSelector(jobId, 'details-counts modelSizeStats') - ); - - // parse a table by reading each row - async function parseTable(el: typeof countsTable) { - const $ = await el.parseDomContent(); - const vars: Record = {}; - - for (const row of $('tr').toArray()) { - const [name, value] = $(row) - .find('td') - .toArray(); - - vars[ - $(name) - .text() - .trim() - ] = $(value) - .text() - .trim(); - } - - return vars; - } - - return { - counts: await parseTable(countsTable), - modelSizeStats: await parseTable(modelSizeStatsTable), - }; - }); - } - - public rowSelector(jobId: string, subSelector?: string) { - const row = `mlJobListTable row-${jobId}`; - return !subSelector ? row : `${row} ${subSelector}`; - } - - public detailsSelector(jobId: string, subSelector?: string) { - const row = `mlJobListTable details-${jobId}`; - return !subSelector ? row : `${row} ${subSelector}`; - } - - public async withDetailsOpen(jobId: string, block: () => Promise): Promise { - await this.ensureDetailsOpen(jobId); - try { - return await block(); - } finally { - await this.ensureDetailsClosed(jobId); - } - } - - public async ensureDetailsOpen(jobId: string) { - await retry.try(async () => { - if (!(await testSubjects.exists(this.detailsSelector(jobId)))) { - await testSubjects.click(this.rowSelector(jobId, 'detailsToggle')); - } - - await testSubjects.existOrFail(this.detailsSelector(jobId)); - }); - } - - public async ensureDetailsClosed(jobId: string) { - await retry.try(async () => { - if (await testSubjects.exists(this.detailsSelector(jobId))) { - await testSubjects.click(this.rowSelector(jobId, 'detailsToggle')); - await testSubjects.missingOrFail(this.detailsSelector(jobId)); - } - }); - } - - public async waitForJobsToLoad() { - await retry.waitFor( - 'jobs table to exist', - async () => await testSubjects.exists('mlJobListTable') - ); - - await retry.waitFor( - 'jobs table to be done loading', - async () => await testSubjects.exists('mlJobListTable&loaded') - ); - } - - public async filterWithSearchString(filter: string) { - await this.waitForJobsToLoad(); - const searchBar = await testSubjects.find('mlJobListSearchBar'); - const searchBarInput = await searchBar.findByTagName('input'); - await searchBarInput.clearValueWithKeyboard(); - await searchBarInput.type(filter); - } - })(); -} diff --git a/x-pack/test/functional/services/ml.ts b/x-pack/test/functional/services/ml.ts index 6e929bd4e21e2..b93ad7c9121bc 100644 --- a/x-pack/test/functional/services/ml.ts +++ b/x-pack/test/functional/services/ml.ts @@ -13,7 +13,6 @@ import { MachineLearningDataVisualizerProvider, MachineLearningJobManagementProvider, MachineLearningJobSourceSelectionProvider, - MachineLearningJobTableProvider, MachineLearningJobTypeSelectionProvider, MachineLearningJobWizardCommonProvider, MachineLearningNavigationProvider, @@ -28,7 +27,6 @@ export function MachineLearningProvider(context: FtrProviderContext) { const dataVisualizer = MachineLearningDataVisualizerProvider(context); const jobManagement = MachineLearningJobManagementProvider(context); const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context); - const jobTable = MachineLearningJobTableProvider(context); const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context); const jobWizardCommon = MachineLearningJobWizardCommonProvider(context); const navigation = MachineLearningNavigationProvider(context); @@ -42,7 +40,6 @@ export function MachineLearningProvider(context: FtrProviderContext) { dataVisualizer, jobManagement, jobSourceSelection, - jobTable, jobTypeSelection, jobWizardCommon, navigation, diff --git a/x-pack/test/kerberos_api_integration/apis/index.ts b/x-pack/test/kerberos_api_integration/apis/index.ts index 00818c2b59eee..6bb924818a672 100644 --- a/x-pack/test/kerberos_api_integration/apis/index.ts +++ b/x-pack/test/kerberos_api_integration/apis/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('apis Kerberos', function() { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./security')); }); } diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.js b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.js index 0ef60bb929826..85f2a82cc9641 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.js +++ b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('apis', function () { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./oidc_auth')); }); } diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts index 22ce3b17a5949..0503efea77eab 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/index.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function({ loadTestFile }: FtrProviderContext) { describe('apis', function() { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./oidc_auth')); }); } diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index 0c77ff3a0640e..4689ed447632f 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -7,14 +7,12 @@ import expect from '@kbn/expect'; import { JSDOM } from 'jsdom'; import request, { Cookie } from 'request'; -import { format as formatURL } from 'url'; import { createTokens, getStateAndNonce } from '../../fixtures/oidc_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); - const config = getService('config'); describe('OpenID Connect Implicit Flow authentication', () => { describe('finishing handshake', () => { @@ -33,30 +31,22 @@ export default function({ getService }: FtrProviderContext) { it('should return an HTML page that will parse URL fragment', async () => { const response = await supertest.get('/api/security/v1/oidc/implicit').expect(200); const dom = new JSDOM(response.text, { - url: formatURL({ ...config.get('servers.kibana'), auth: false }), runScripts: 'dangerously', - resources: 'usable', beforeParse(window) { // JSDOM doesn't support changing of `window.location` and throws an exception if script - // tries to do that and we have to workaround this behaviour. We also need to wait until our - // script is loaded and executed, __isScriptExecuted__ is used exactly for that. - (window as Record).__isScriptExecuted__ = new Promise(resolve => { - Object.defineProperty(window, 'location', { - value: { - href: - 'https://kibana.com/api/security/v1/oidc/implicit#token=some_token&access_token=some_access_token', - replace(newLocation: string) { - this.href = newLocation; - resolve(); - }, + // tries to do that and we have to workaround this behaviour. + Object.defineProperty(window, 'location', { + value: { + href: + 'https://kibana.com/api/security/v1/oidc/implicit#token=some_token&access_token=some_access_token', + replace(newLocation: string) { + this.href = newLocation; }, - }); + }, }); }, }); - await (dom.window as Record).__isScriptExecuted__; - // Check that proxy page is returned with proper headers. expect(response.headers['content-type']).to.be('text/html; charset=utf-8'); expect(response.headers['cache-control']).to.be('private, no-cache, no-store'); diff --git a/x-pack/test/oidc_api_integration/implicit_flow.config.ts b/x-pack/test/oidc_api_integration/implicit_flow.config.ts index 93f2349a40099..a7854488097a6 100644 --- a/x-pack/test/oidc_api_integration/implicit_flow.config.ts +++ b/x-pack/test/oidc_api_integration/implicit_flow.config.ts @@ -20,20 +20,17 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { esTestCluster: { ...oidcAPITestsConfig.get('esTestCluster'), - serverArgs: oidcAPITestsConfig - .get('esTestCluster.serverArgs') - .reduce((serverArgs: string[], arg: string) => { - // We should change `response_type` to `id_token token` and get rid of unnecessary `token_endpoint`. - if (arg.startsWith('xpack.security.authc.realms.oidc.oidc1.rp.response_type')) { - serverArgs.push( - 'xpack.security.authc.realms.oidc.oidc1.rp.response_type=id_token token' - ); - } else if (!arg.startsWith('xpack.security.authc.realms.oidc.oidc1.op.token_endpoint')) { - serverArgs.push(arg); - } + serverArgs: oidcAPITestsConfig.get('esTestCluster.serverArgs').map((arg: string) => { + if (arg.startsWith('xpack.security.authc.realms.oidc.oidc1.rp.response_type')) { + return 'xpack.security.authc.realms.oidc.oidc1.rp.response_type=id_token token'; + } - return serverArgs; - }, []), + if (arg.startsWith('xpack.security.authc.realms.oidc.oidc1.op.token_endpoint')) { + return 'xpack.security.authc.realms.oidc.oidc1.op.token_endpoint=should_not_be_used'; + } + + return arg; + }), }, }; } diff --git a/x-pack/test/pki_api_integration/apis/index.ts b/x-pack/test/pki_api_integration/apis/index.ts index d859ed172ac69..47ffb25835d43 100644 --- a/x-pack/test/pki_api_integration/apis/index.ts +++ b/x-pack/test/pki_api_integration/apis/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('apis PKI', function() { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./security')); }); } diff --git a/x-pack/test/reporting/functional/index.js b/x-pack/test/reporting/functional/index.js index fa473f454a925..17aeb03eeadb5 100644 --- a/x-pack/test/reporting/functional/index.js +++ b/x-pack/test/reporting/functional/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('reporting app', function () { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./reporting')); }); } diff --git a/x-pack/test/saml_api_integration/apis/index.js b/x-pack/test/saml_api_integration/apis/index.js index ac08d2e078abf..b4e6503f201e5 100644 --- a/x-pack/test/saml_api_integration/apis/index.js +++ b/x-pack/test/saml_api_integration/apis/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('apis SAML', function () { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./security')); }); } diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/index.ts b/x-pack/test/saved_object_api_integration/security_only/apis/index.ts index fadefd2743b30..e24de7c7ae77f 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/index.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/index.ts @@ -12,7 +12,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { const supertest = getService('supertest'); describe('saved objects security only enabled', function() { - this.tags('ciGroup9'); + this.tags('ciGroup4'); before(async () => { await createUsersAndRoles(es, supertest); diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts index 4493a5332b62c..b54345d78456f 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts @@ -13,7 +13,7 @@ export default function({ loadTestFile, getService }: TestInvoker) { const supertest = getService('supertest'); describe('spaces api with security', function() { - this.tags('ciGroup8'); + this.tags('ciGroup3'); before(async () => { await createUsersAndRoles(es, supertest); diff --git a/x-pack/test/token_api_integration/auth/index.js b/x-pack/test/token_api_integration/auth/index.js index e7b5a5b46a503..528a5c4bf8cf5 100644 --- a/x-pack/test/token_api_integration/auth/index.js +++ b/x-pack/test/token_api_integration/auth/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('token-based auth', function () { - this.tags('ciGroup6'); + this.tags('ciGroup1'); loadTestFile(require.resolve('./login')); loadTestFile(require.resolve('./logout')); loadTestFile(require.resolve('./header')); diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts index e28ea819bfdfc..f87bb10aaf083 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts @@ -17,7 +17,7 @@ export default function uiCapabilitiesTests({ loadTestFile, getService }: FtrPro const featuresService: FeaturesService = getService('features'); describe('ui capabilities', function() { - this.tags('ciGroup9'); + this.tags('ciGroup4'); before(async () => { const features = await featuresService.get(); diff --git a/x-pack/test/ui_capabilities/security_only/tests/index.ts b/x-pack/test/ui_capabilities/security_only/tests/index.ts index b84c02f9d65c0..a941e64839726 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/index.ts @@ -13,7 +13,7 @@ export default function uiCapabilitesTests({ loadTestFile, getService }: FtrProv const securityService: SecurityService = getService('security'); describe('ui capabilities', function() { - this.tags('ciGroup9'); + this.tags('ciGroup4'); before(async () => { for (const user of UserScenarios) { diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/index.ts b/x-pack/test/ui_capabilities/spaces_only/tests/index.ts index 294f545c7d90f..177ca0064a6bf 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/index.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/index.ts @@ -14,7 +14,7 @@ export default function uiCapabilitesTests({ loadTestFile, getService }: FtrProv const featuresService: FeaturesService = getService('features'); describe('ui capabilities', function() { - this.tags('ciGroup9'); + this.tags('ciGroup4'); before(async () => { const features = await featuresService.get(); diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js index 1b7406b37022a..82e9214f55398 100644 --- a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('upgrade assistant', function () { - this.tags('ciGroup7'); + this.tags('ciGroup2'); loadTestFile(require.resolve('./reindexing')); }); diff --git a/x-pack/test/visual_regression/tests/maps/index.js b/x-pack/test/visual_regression/tests/maps/index.js index de5c50e900ca8..c080e5727b243 100644 --- a/x-pack/test/visual_regression/tests/maps/index.js +++ b/x-pack/test/visual_regression/tests/maps/index.js @@ -26,7 +26,7 @@ export default function ({ loadTestFile, getService }) { await esArchiver.unload('maps/kibana'); }); - this.tags('ciGroup10'); + this.tags('ciGroup5'); loadTestFile(require.resolve('./vector_styling')); }); } diff --git a/yarn.lock b/yarn.lock index 3c82dacf6e9c4..f82b220d9c255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2303,34 +2303,28 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== -"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.6.0.tgz#ec7670432ae9c8eb710400d112c201a362d83393" - integrity sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg== +"@sinonjs/commons@^1.0.2", "@sinonjs/commons@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.3.0.tgz#50a2754016b6f30a994ceda6d9a0a8c36adda849" + integrity sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA== dependencies: type-detect "4.0.8" -"@sinonjs/formatio@^3.2.1": - version "3.2.1" - resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.1.tgz#52310f2f9bcbc67bdac18c94ad4901b95fde267e" - integrity sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ== +"@sinonjs/formatio@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.1.0.tgz#6ac9d1eb1821984d84c4996726e45d1646d8cce5" + integrity sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg== dependencies: - "@sinonjs/commons" "^1" - "@sinonjs/samsam" "^3.1.0" + "@sinonjs/samsam" "^2 || ^3" -"@sinonjs/samsam@^3.1.0", "@sinonjs/samsam@^3.3.3": - version "3.3.3" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.3.tgz#46682efd9967b259b81136b9f120fd54585feb4a" - integrity sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ== +"@sinonjs/samsam@^2 || ^3", "@sinonjs/samsam@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.0.2.tgz#304fb33bd5585a0b2df8a4c801fcb47fa84d8e43" + integrity sha512-m08g4CS3J6lwRQk1pj1EO+KEVWbrbXsmi9Pw0ySmrIbcVxVaedoFgLvFsV8wHLwh01EpROVz3KvVcD1Jmks9FQ== dependencies: - "@sinonjs/commons" "^1.3.0" + "@sinonjs/commons" "^1.0.2" array-from "^2.1.1" - lodash "^4.17.15" - -"@sinonjs/text-encoding@^0.7.1": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" - integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + lodash.get "^4.4.2" "@slack/client@^4.8.0": version "4.8.0" @@ -3039,10 +3033,10 @@ resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6" integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg== -"@types/chroma-js@^1.4.2": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-1.4.2.tgz#3152c8dedfa8621f1ccaaabb40722a8aca808bcf" - integrity sha512-Ni8yCN1vF0yfnfKf5bNrBm+92EdZIX2sUk+A4t4QvO1x/9G04rGyC0nik4i5UcNfx8Q7MhX4XUDcy2nrkKQLFg== +"@types/chroma-js@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-1.4.1.tgz#7c52d461173d569ba1f27e0c2dd26ee76691ec82" + integrity sha512-i9hUiO3bwgmzZUDwBuR65WqsBQ/nwN+H2fKX0bykXCdd8cFQEuIj8vI7FXjyb2f5z5h+pv76I/uakikKSgaqTA== "@types/chromedriver@^2.38.0": version "2.38.0" @@ -3321,10 +3315,10 @@ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220" integrity sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q== -"@types/history@^4.7.3": - version "4.7.3" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a" - integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw== +"@types/history@^4.6.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" + integrity sha512-eVAb52MJ4lfPLiO9VvTgv8KaZDEIqCwhv+lXOMLlt4C1YHTShgmMULEg0RrCbnqfYd6QKfHsMp0MiX0vWISpSw== "@types/hoek@^4.1.3": version "4.1.3" @@ -3573,10 +3567,10 @@ 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" - integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== +"@types/mocha@^5.2.6": + version "5.2.6" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b" + integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw== "@types/moment-timezone@^0.5.8": version "0.5.8" @@ -3640,12 +3634,10 @@ resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.25.tgz#b6f55062827a4787fe4ab151cf3412a468e65271" integrity sha512-ShHzHkYD+Ldw3eyttptCpUhF1/mkInWwasQkCNXZHOsJMJ/UMa8wXrxSrTJaVk0r4pLK/VnESVM0wFsfQzNEKQ== -"@types/object-hash@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.0.tgz#b20db2074129f71829d61ff404e618c4ac3d73cf" - integrity sha512-il4NIe4jTx4lfhkKaksmmGHw5EsVkO8sHWkpJHM9m59r1dtsVadLSrJqdE8zU74NENDAsR3oLIOlooRAXlPLNA== - dependencies: - "@types/node" "*" +"@types/object-hash@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.2.0.tgz#d65904331bd0b05c7d5ece75f9ddfdbe82affd30" + integrity sha512-0JKYQRatHdzijO/ni7JV5eHUJWaMRpGvwiABk8U5iAk5Corm0yLNEfYGNkZWYc+wCyCKKpg0+TsZIvP8AymIYA== "@types/opn@^5.1.0": version "5.1.0" @@ -3902,10 +3894,10 @@ dependencies: "@types/node" "*" -"@types/sinon@^7.0.13": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313" - integrity sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung== +"@types/sinon@^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.3.tgz#f8647e883d873962130f906a6114a4e187755696" + integrity sha512-cjmJQLx2B5Hp9SzO7rdSivipo3kBqRqeYkTW17nLST1tn5YLWBjTdnzdmeTJXA1+KrrBLsEuvKQ0fUPGrfazQg== "@types/stack-utils@^1.0.1": version "1.0.1" @@ -4501,13 +4493,6 @@ agent-base@^4.1.0: dependencies: es6-promisify "^5.0.0" -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - agentkeepalive@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.4.1.tgz#aa95aebc3a749bca5ed53e3880a09f5235b48f0c" @@ -9747,7 +9732,7 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" -del@^4.1.1: +del@^4.0.0, del@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== @@ -14501,7 +14486,7 @@ history-extra@^5.0.1: resolved "https://registry.yarnpkg.com/history-extra/-/history-extra-5.0.1.tgz#95a2e59dda526c4241d0ae1b124a77a5e4675ce8" integrity sha512-6XV1L1lHgporVWgppa/Kq+Fnz4lhBew7iMxYCTfzVmoEywsAKJnTjdw1zOd+EGLHGYp0/V8jSVMEgqx4QbHLTw== -history@4.9.0, history@^4.9.0: +history@4.9.0: version "4.9.0" resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca" integrity sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA== @@ -14819,14 +14804,6 @@ https-proxy-agent@2.2.1, https-proxy-agent@^2.2.0, https-proxy-agent@^2.2.1: agent-base "^4.1.0" debug "^3.1.0" -https-proxy-agent@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz#271ea8e90f836ac9f119daccd39c19ff7dfb0793" - integrity sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -15884,6 +15861,13 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-path-inside@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.0.0.tgz#28f249020fe6906671fe31a76118f2cd490441fd" + integrity sha512-OmUXvSq+P7aI/aRbl1dzwdlyLn8vW7Nr2/11S7y/dcLLgnQ89hgYJp7tfc+A5SRid3rNCLpruOp2CAV68/iOcA== + dependencies: + path-is-inside "^1.0.2" + is-path-inside@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" @@ -18520,10 +18504,15 @@ loglevelnext@^1.0.1: es6-symbol "^3.1.1" object.assign "^4.1.0" -lolex@^4.1.0, lolex@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.2.0.tgz#ddbd7f6213ca1ea5826901ab1222b65d714b3cd7" - integrity sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg== +lolex@^2.3.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.6.0.tgz#cf9166f3c9dece3cdeb5d6b01fce50f14a1203e3" + integrity sha512-e1UtIo1pbrIqEXib/yMjHciyqkng5lc0rrIbytgjmRgDR9+2ceNIAcwOWSgylRjoEP9VdVguCSRwnNmlbnOUwA== + +lolex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-3.0.0.tgz#f04ee1a8aa13f60f1abd7b0e8f4213ec72ec193e" + integrity sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ== long@^2.4.0: version "2.4.0" @@ -19866,16 +19855,16 @@ nigel@3.x.x: hoek "5.x.x" vise "3.x.x" -nise@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.2.tgz#b6d29af10e48b321b307e10e065199338eeb2652" - integrity sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA== +nise@^1.4.7: + version "1.4.8" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.8.tgz#ce91c31e86cf9b2c4cac49d7fcd7f56779bfd6b0" + integrity sha512-kGASVhuL4tlAV0tvA34yJYZIVihrUt/5bDwpp4tTluigxUr2bBlJeDXmivb6NuEdFkqvdv/Ybb9dm16PSKUhtw== dependencies: - "@sinonjs/formatio" "^3.2.1" - "@sinonjs/text-encoding" "^0.7.1" + "@sinonjs/formatio" "^3.1.0" just-extend "^4.0.2" - lolex "^4.1.0" + lolex "^2.3.2" path-to-regexp "^1.7.0" + text-encoding "^0.6.4" no-case@^2.2.0, no-case@^2.3.2: version "2.3.2" @@ -20657,7 +20646,7 @@ opentracing@^0.14.0: resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.3.tgz#23e3ad029fa66a653926adbe57e834469f8550aa" integrity sha1-I+OtAp+mamU5Jq2+V+g0Rp+FUKo= -opn@^5.3.0: +opn@^5.3.0, opn@^5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035" integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw== @@ -25449,17 +25438,17 @@ simplify-js@^1.2.1: resolved "https://registry.yarnpkg.com/simplify-js/-/simplify-js-1.2.3.tgz#a3422c1b9884d60421345eb44d2b872662df27f5" integrity sha512-0IkEqs+5c5vROkHaifGfbqHf5tYDcsTBy6oJPRbFCSwp2uzEr+PpH3dNP7wD8O3d7zdUCjLVq1/xHkwA/JjlFA== -sinon@^7.4.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.4.2.tgz#ecd54158fef2fcfbdb231a3fa55140e8cb02ad6c" - integrity sha512-pY5RY99DKelU3pjNxcWo6XqeB1S118GBcVIIdDi6V+h6hevn1izcg2xv1hTHW/sViRXU7sUOxt4wTUJ3gsW2CQ== +sinon@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.2.2.tgz#388ecabd42fa93c592bfc71d35a70894d5a0ca07" + integrity sha512-WLagdMHiEsrRmee3jr6IIDntOF4kbI6N2pfbi8wkv50qaUQcBglkzkjtoOEbeJ2vf1EsrHhLI+5Ny8//WHdMoA== dependencies: - "@sinonjs/commons" "^1.4.0" - "@sinonjs/formatio" "^3.2.1" - "@sinonjs/samsam" "^3.3.3" + "@sinonjs/commons" "^1.2.0" + "@sinonjs/formatio" "^3.1.0" + "@sinonjs/samsam" "^3.0.2" diff "^3.5.0" - lolex "^4.2.0" - nise "^1.5.2" + lolex "^3.0.0" + nise "^1.4.7" supports-color "^5.5.0" sisteransi@^1.0.0: @@ -26925,6 +26914,11 @@ test-exclude@^5.2.3: read-pkg-up "^4.0.0" require-main-filename "^2.0.0" +text-encoding@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" + integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk= + text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"