diff --git a/.backportrc.json b/.backportrc.json index af064451595c8..77456cf33f625 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -1,5 +1,5 @@ { "upstream": "elastic/kibana", - "branches": [{ "name": "7.x", "checked": true }, "7.5", "7.4", "7.3", "7.2", "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], + "branches": [{ "name": "7.x", "checked": true }, "7.6", "7.5", "7.4", "7.3", "7.2", "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], "labels": ["backport"] } diff --git a/.eslintignore b/.eslintignore index 90155ca9cb681..c4fb806b6d394 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,14 +9,14 @@ bower_components /built_assets /html_docs /src/plugins/data/common/es_query/kuery/ast/_generated_/** -/src/fixtures/vislib/mock_data +src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data /src/legacy/ui/public/angular-bootstrap /src/legacy/ui/public/flot-charts /test/fixtures/scenarios /src/legacy/core_plugins/console/public/webpackShims /src/legacy/core_plugins/console/public/tests/webpackShims /src/legacy/ui/public/utils/decode_geo_hash.js -/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.* +/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.* /src/core/lib/kbn_internal_native_observable /packages/*/target /packages/eslint-config-kibana diff --git a/.eslintrc.js b/.eslintrc.js index a7bb204da4775..2c5804da053a6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -77,7 +77,7 @@ module.exports = { }, }, { - files: ['src/legacy/core_plugins/kbn_vislib_vis_types/**/*.{js,ts,tsx}'], + files: ['src/legacy/core_plugins/vis_type_vislib/**/*.{js,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index acfb7307f49c4..ed5721e8756e8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -117,21 +117,21 @@ /x-pack/test/api_integration/apis/security/ @elastic/kibana-security # Kibana Localization -/src/dev/i18n @elastic/kibana-localization +/src/dev/i18n/ @elastic/kibana-localization # Pulse /packages/kbn-analytics/ @elastic/pulse /src/legacy/core_plugins/ui_metric/ @elastic/pulse /src/plugins/usage_collection/ @elastic/pulse -/x-pack/legacy/plugins/telemetry @elastic/pulse +/x-pack/legacy/plugins/telemetry/ @elastic/pulse # Kibana Alerting Services -/x-pack/legacy/plugins/alerting @elastic/kibana-alerting-services -/x-pack/legacy/plugins/actions @elastic/kibana-alerting-services -/x-pack/legacy/plugins/task_manager @elastic/kibana-alerting-services -/x-pack/test/alerting_api_integration @elastic/kibana-alerting-services -/x-pack/test/plugin_api_integration/plugins/task_manager @elastic/kibana-alerting-services -/x-pack/test/plugin_api_integration/test_suites/task_manager @elastic/kibana-alerting-services +/x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services +/x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services +/x-pack/plugins/task_manager/ @elastic/kibana-alerting-services +/x-pack/test/alerting_api_integration/ @elastic/kibana-alerting-services +/x-pack/test/plugin_api_integration/plugins/task_manager/ @elastic/kibana-alerting-services +/x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/kibana-alerting-services /x-pack/legacy/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services /x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/kibana-alerting-services /x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services diff --git a/.i18nrc.json b/.i18nrc.json index 4bc0f773ee8b5..73acf92cda149 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -21,7 +21,7 @@ "interpreter": "src/legacy/core_plugins/interpreter", "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", - "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", + "kbnVislibVisTypes": "src/legacy/core_plugins/vis_type_vislib", "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", @@ -33,7 +33,7 @@ "statusPage": "src/legacy/core_plugins/status_page", "telemetry": "src/legacy/core_plugins/telemetry", "tileMap": "src/legacy/core_plugins/tile_map", - "timelion": "src/legacy/core_plugins/timelion", + "timelion": ["src/legacy/core_plugins/timelion", "src/legacy/core_plugins/vis_type_timelion"], "uiActions": "src/plugins/ui_actions", "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", diff --git a/.sass-lint.yml b/.sass-lint.yml index f6c0f5bb83fcb..fba2c003484f6 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -2,7 +2,7 @@ files: include: - 'src/legacy/core_plugins/metrics/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss' - - 'src/legacy/ui/public/vislib/**/*.s+(a|c)ss' + - 'src/legacy/core_plugins/vis_type_vislib/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 412019efc7f35..c46ba8a980ce2 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -23,9 +23,9 @@ Want to export multiple workpads? Go to the *Canvas workpads* view, select the w [[create-workpad-pdf]] === Create a PDF -Create a PDF copy of your workpad that you can save and share outside of {kib}. +If you have a license that supports the {report-features}, you can create a PDF copy of your workpad that you can save and share outside {kib}. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. +For more information, refer to <>. . From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. @@ -38,12 +38,10 @@ image::images/canvas-generate-pdf.gif[Generate PDF] [[create-workpad-URL]] === Create a POST URL -Create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. +If you have a license that supports the {report-features}, you can create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. For more information, refer to <>. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. - . From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. . Click *Copy POST URL*. @@ -57,8 +55,6 @@ image::images/canvas-create-URL.gif[Create POST URL] beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. - . From your workpad, click the *Share this workpad* icon in the upper left corner, then select *Share on a website*. . On the *Share on a website* pane, follow the instructions. diff --git a/docs/developer/visualize/development-create-visualization.asciidoc b/docs/developer/visualize/development-create-visualization.asciidoc index b782428b83135..faaa9b36a7a00 100644 --- a/docs/developer/visualize/development-create-visualization.asciidoc +++ b/docs/developer/visualize/development-create-visualization.asciidoc @@ -208,8 +208,8 @@ This is the sidebar editor you see in many of the Kibana visualizations. You can [[development-default-editor]] ==== `default` editor controller -The default editor controller receives an `optionsTemplate` or `optionsTabs` parameter. -These can be either an AngularJS template or React component. +The default editor controller receives an `optionsTemplate` or `optionTabs` parameter. +These tabs should be React components. ["source","js"] ----------- @@ -220,12 +220,9 @@ These can be either an AngularJS template or React component. description: 'Cool new chart', editor: 'default', editorConfig: { - optionsTemplate: '' // or optionsTemplate: MyReactComponent // or if multiple tabs are required: - optionsTabs: [ - { title: 'tab 1', template: '
....
}, - { title: 'tab 2', template: '' }, - { title: 'tab 3', template: MyReactComponent } + optionTabs: [ + { title: 'tab 3', editor: MyReactComponent } ] } } diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md index 2deb9f4a9a151..d1cd2d3b04950 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md @@ -1,25 +1,30 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) - -## ChromeNavLinks.update() method - -Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. - -Signature: - -```typescript -update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| id | string | | -| values | ChromeNavLinkUpdateableFields | | - -Returns: - -`ChromeNavLink | undefined` - + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) + +## ChromeNavLinks.update() method + +> Warning: This API is now obsolete. +> +> Uses the [AppBase.updater$](./kibana-plugin-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-public.applicationsetup.register.md) instead. +> + +Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. + +Signature: + +```typescript +update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | +| values | ChromeNavLinkUpdateableFields | | + +Returns: + +`ChromeNavLink | undefined` + diff --git a/docs/development/core/server/kibana-plugin-server.deprecationsettings.doclinkskey.md b/docs/development/core/server/kibana-plugin-server.deprecationsettings.doclinkskey.md new file mode 100644 index 0000000000000..4296d0d229988 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.deprecationsettings.doclinkskey.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) > [docLinksKey](./kibana-plugin-server.deprecationsettings.doclinkskey.md) + +## DeprecationSettings.docLinksKey property + +Key to documentation links + +Signature: + +```typescript +docLinksKey: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.deprecationsettings.md b/docs/development/core/server/kibana-plugin-server.deprecationsettings.md new file mode 100644 index 0000000000000..64a654c1bcea8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.deprecationsettings.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) + +## DeprecationSettings interface + +UiSettings deprecation field options. + +Signature: + +```typescript +export interface DeprecationSettings +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [docLinksKey](./kibana-plugin-server.deprecationsettings.doclinkskey.md) | string | Key to documentation links | +| [message](./kibana-plugin-server.deprecationsettings.message.md) | string | Deprecation message | + diff --git a/docs/development/core/server/kibana-plugin-server.deprecationsettings.message.md b/docs/development/core/server/kibana-plugin-server.deprecationsettings.message.md new file mode 100644 index 0000000000000..ed52929c3551e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.deprecationsettings.message.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) > [message](./kibana-plugin-server.deprecationsettings.message.md) + +## DeprecationSettings.message property + +Deprecation message + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.imagevalidation.maxsize.md b/docs/development/core/server/kibana-plugin-server.imagevalidation.maxsize.md new file mode 100644 index 0000000000000..9b03924ad2e0f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.imagevalidation.maxsize.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ImageValidation](./kibana-plugin-server.imagevalidation.md) > [maxSize](./kibana-plugin-server.imagevalidation.maxsize.md) + +## ImageValidation.maxSize property + +Signature: + +```typescript +maxSize: { + length: number; + description: string; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.imagevalidation.md b/docs/development/core/server/kibana-plugin-server.imagevalidation.md new file mode 100644 index 0000000000000..8d81a7eae1915 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.imagevalidation.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ImageValidation](./kibana-plugin-server.imagevalidation.md) + +## ImageValidation interface + +Signature: + +```typescript +export interface ImageValidation +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [maxSize](./kibana-plugin-server.imagevalidation.maxsize.md) | {
length: number;
description: string;
} | | + diff --git a/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md index 1bc78dd84571d..42cbc59c536a6 100644 --- a/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md +++ b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md @@ -9,14 +9,14 @@ Generate a `KibanaResponse` which renders an HTML page bootstrapped with the `co Signature: ```typescript -render(options?: IRenderOptions): Promise; +render(options?: Pick): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| options | IRenderOptions | | +| options | Pick<IRenderOptions, 'includeUserSettings'> | | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 5e28643843af3..00ab83123319a 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -56,6 +56,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | | [DeprecationInfo](./kibana-plugin-server.deprecationinfo.md) | | +| [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) | UiSettings deprecation field options. | | [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | | [ElasticsearchError](./kibana-plugin-server.elasticsearcherror.md) | | | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | @@ -69,6 +70,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | | [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | +| [ImageValidation](./kibana-plugin-server.imagevalidation.md) | | | [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | | [IRenderOptions](./kibana-plugin-server.irenderoptions.md) | | | [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | @@ -140,6 +142,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [StringValidation](./kibana-plugin-server.stringvalidation.md) | | | [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | | [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) | | diff --git a/docs/development/core/server/kibana-plugin-server.stringvalidation.md b/docs/development/core/server/kibana-plugin-server.stringvalidation.md new file mode 100644 index 0000000000000..cc52c853ce248 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.stringvalidation.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [StringValidation](./kibana-plugin-server.stringvalidation.md) + +## StringValidation interface + +Signature: + +```typescript +export interface StringValidation +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [message](./kibana-plugin-server.stringvalidation.message.md) | string | | +| [regexString](./kibana-plugin-server.stringvalidation.regexstring.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-server.stringvalidation.message.md b/docs/development/core/server/kibana-plugin-server.stringvalidation.message.md new file mode 100644 index 0000000000000..a15fe8b931403 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.stringvalidation.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [StringValidation](./kibana-plugin-server.stringvalidation.md) > [message](./kibana-plugin-server.stringvalidation.message.md) + +## StringValidation.message property + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.stringvalidation.regexstring.md b/docs/development/core/server/kibana-plugin-server.stringvalidation.regexstring.md new file mode 100644 index 0000000000000..e19560237f77d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.stringvalidation.regexstring.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [StringValidation](./kibana-plugin-server.stringvalidation.md) > [regexString](./kibana-plugin-server.stringvalidation.regexstring.md) + +## StringValidation.regexString property + +Signature: + +```typescript +regexString: string; +``` diff --git a/docs/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index 0ad27e68f7fe9..ecb550d3ab267 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -25,9 +25,9 @@ At the end of the trial period, the platinum features operate in a <>. You can revert to a basic license, extend the trial, or purchase a subscription. -TIP: If {security-features} are enabled, before you revert to a basic license or -install a gold or platinum license, you must configure Transport Layer Security -(TLS) in {es}. See {ref}/encrypting-communications.html[Encrypting communications]. +TIP: If {security-features} are enabled, unless you have a trial license, +you must configure Transport Layer Security (TLS) in {es}. +See {ref}/encrypting-communications.html[Encrypting communications]. {kib} and the {ref}/start-basic.html[start basic API] provide a list of all of the features that will no longer be supported if you revert to a basic license. diff --git a/docs/maps/images/extended_stats_config.png b/docs/maps/images/extended_stats_config.png new file mode 100644 index 0000000000000..018acea96852f Binary files /dev/null and b/docs/maps/images/extended_stats_config.png differ diff --git a/docs/maps/images/gear_icon.png b/docs/maps/images/gear_icon.png new file mode 100644 index 0000000000000..355d55dbbc37a Binary files /dev/null and b/docs/maps/images/gear_icon.png differ diff --git a/docs/maps/images/gs_add_cloropeth_layer.png b/docs/maps/images/gs_add_cloropeth_layer.png index b2ee35f025b6f..2800a5a2d2584 100644 Binary files a/docs/maps/images/gs_add_cloropeth_layer.png and b/docs/maps/images/gs_add_cloropeth_layer.png differ diff --git a/docs/maps/images/gs_add_es_layer.png b/docs/maps/images/gs_add_es_layer.png deleted file mode 100644 index b80ddc47d7e5d..0000000000000 Binary files a/docs/maps/images/gs_add_es_layer.png and /dev/null differ diff --git a/docs/maps/images/gs_link_icon.png b/docs/maps/images/gs_link_icon.png deleted file mode 100644 index 8986648470613..0000000000000 Binary files a/docs/maps/images/gs_link_icon.png and /dev/null differ diff --git a/docs/maps/images/quantitative_data_driven_styling.png b/docs/maps/images/quantitative_data_driven_styling.png new file mode 100644 index 0000000000000..a7852ed202016 Binary files /dev/null and b/docs/maps/images/quantitative_data_driven_styling.png differ diff --git a/docs/maps/images/sample_data_web_logs.png b/docs/maps/images/sample_data_web_logs.png index dda0926a07dfe..3b0c2ba3f12c0 100644 Binary files a/docs/maps/images/sample_data_web_logs.png and b/docs/maps/images/sample_data_web_logs.png differ diff --git a/docs/maps/images/vector_style_class.png b/docs/maps/images/vector_style_class.png index 48658bda73ee8..8c685dfcf0ab6 100644 Binary files a/docs/maps/images/vector_style_class.png and b/docs/maps/images/vector_style_class.png differ diff --git a/docs/maps/images/vector_style_dynamic.png b/docs/maps/images/vector_style_dynamic.png index 90d51144c422d..aeaef412b5220 100644 Binary files a/docs/maps/images/vector_style_dynamic.png and b/docs/maps/images/vector_style_dynamic.png differ diff --git a/docs/maps/images/vector_style_static.png b/docs/maps/images/vector_style_static.png index 6eedb8247e6ba..47d9c3b21fcb6 100644 Binary files a/docs/maps/images/vector_style_static.png and b/docs/maps/images/vector_style_static.png differ diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index 627fd49dafa51..05d8c0c605e7f 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -24,13 +24,13 @@ The *Grid aggregation* source uses {ref}/search-aggregations-bucket-geotilegrid- You can symbolize grid aggregation metrics as: -*Points*:: Creates a <> with a point for each gridded cell. -The point location is the weighted centroid for all geo-points in the gridded cell. - *Grid rectangles*:: Creates a <> with a bounding box polygon for each gridded cell. *Heat map*:: Creates a <> that clusters the weighted centroids for each gridded cell. +*Clusters*:: Creates a <> with a cluster symbol for each gridded cell. +The cluster location is the weighted centroid for all geo-points in the gridded cell. + [role="xpack"] [[maps-top-hits-aggregation]] diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index e6908ca773a2f..b13eeebe56fd8 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -68,14 +68,16 @@ and lighter shades symbolize countries with less traffic. . Click the *EMS Boundaries* data source. . From the *Layer* dropdown menu, select *World Countries*. . Click the *Add layer* button. -. Set *Layer name* to `Total Requests by Country`. -. Set *Layer transparency* to 0.5. +. Set *Name* to `Total Requests by Country`. +. Set *Opacity* to 50%. +. Click *Add* under *Tooltip fields*. +. In the popover, select *ISO 3166-1 alpha-2 code* and *name* and click *Add*. ===== Join the vector layer with the sample web log index You now have a vector layer containing the world countries. To symbolize countries by web traffic, you'll need to augment the world country features with the count of Elasticsearch weblog documents originating from each country. -To do this, you'll create a <> to link the vector source *World Countries* to +To do this, you'll create a <> to link the vector source *World Countries* to the {es} index `kibana_sample_data_logs` on the shared key iso2 = geo.src. . Click plus image:maps/images/gs_plus_icon.png[] to the right of *Term Joins* label. @@ -83,15 +85,18 @@ the {es} index `kibana_sample_data_logs` on the shared key iso2 = geo.src. . Set *Left field* to *ISO 3166-1 alpha-2 code*. . Set *Right source* to *kibana_sample_data_logs*. . Set *Right field* to *geo.src*. +. Click *and use metric count*. +. Set *Custom label* to *web logs count*. ===== Set the layer style All of the world countries are still a single color because the layer is using <>. To shade the world countries based on which country is sending the most requests, you'll need to use <>. -. Click image:maps/images/gs_link_icon.png[] to the right of *Fill color*. +. Under *Fill color*, change the selected value from *Solid* to *By value*. +. In the field select input, select *web logs count*. . Select the grey color ramp. -. In the field select input, select *count of kibana_sample_data_logs:geo.src*. +. Under *Border color*, change the selected color to *white*. . Click *Save & close*. + Your map now looks like this: @@ -119,9 +124,11 @@ The layer is only visible when users zoom in the map past zoom level 9. . Click the *Documents* data source. . Set *Index pattern* to *kibana_sample_data_logs*. . Click the *Add layer* button. -. Set *Layer name* to `Actual Requests`. -. Set *Zoom range for layer visibility* to the range [9, 24]. -. Set *Layer transparency* to 1. +. Set *Name* to `Actual Requests`. +. Set *Visibilty* to the range [9, 24]. +. Set *Opacity* to 100%. +. Click *Add* under *Tooltip fields*. +. In the popover, select *clientip*, *timestamp*, *host*, *request*, *response*, *machine.os*, *agent*, and *bytes* and click *Add*. . Set *Fill color* to *#2200ff*. . Click *Save & close*. + @@ -150,30 +157,30 @@ image::maps/images/grid_metrics_both.png[] . In the map legend, click *Add layer*. . Click the *Grid aggregation* data source. . Set *Index pattern* to *kibana_sample_data_logs*. -. Set *Show as* to *points*. +. Set *Show as* to *clusters*. . Click the *Add layer* button. -. Set *Layer name* to `Total Requests and Bytes`. -. Set *Zoom range for layer visibility* to the range [0, 9]. -. Set *Layer transparency* to 1. +. Set *Name* to `Total Requests and Bytes`. +. Set *Visibility* to the range [0, 9]. +. Set *Opacity* to 100%. ===== Configure the aggregation metrics -. Click plus image:maps/images/gs_plus_icon.png[] to the right of *Metrics* label. +. Click *Add metric* under of *Metrics* label. . Select *Sum* in the aggregation select. . Select *bytes* in the field select. ===== Set the layer style . In *Layer style*, change *Symbol size*: - .. Set *Min size* to 1. + .. Set *Min size* to 7. .. Set *Max size* to 25. - .. In the field select, select *sum of bytes*. + .. Change the field select from *count* to *sum of bytes*. . Click *Save & close* button. + Your map now looks like this between zoom levels 0 and 9: + [role="screenshot"] -image::maps/images/gs_add_es_layer.png[] +image::maps/images/sample_data_web_logs.png[] [role="xpack"] [[maps-save]] diff --git a/docs/maps/vector-layer.asciidoc b/docs/maps/vector-layer.asciidoc index 1d4ba9912529a..17c57c82b0f17 100644 --- a/docs/maps/vector-layer.asciidoc +++ b/docs/maps/vector-layer.asciidoc @@ -19,7 +19,7 @@ NOTE: Document results are limited to the `index.max_result_window` index settin Use <> to plot large data sets. *Grid aggregation*:: Geospatial data grouped in grids with metrics for each gridded cell. -Set *Show as* to *grid rectangles* or *points*. +Set *Show as* to *grid rectangles* or *clusters*. 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]. diff --git a/docs/maps/vector-style.asciidoc b/docs/maps/vector-style.asciidoc index 609cd712e8b13..cd5b086508ae8 100644 --- a/docs/maps/vector-style.asciidoc +++ b/docs/maps/vector-style.asciidoc @@ -5,6 +5,7 @@ When styling a vector layer, you can customize your data by property, such as size and color. For each property, you can specify whether to use a constant or data driven value for the style. + [float] [[maps-vector-style-static]] ==== Static styling @@ -17,13 +18,41 @@ The *kibana_sample_data_logs* layer uses static styling for all properties. [role="screenshot"] image::maps/images/vector_style_static.png[] + [float] [[maps-vector-style-data-driven]] ==== Data driven styling -Use data driven styling to symbolize features from a range of numeric property values. -To enable data driven styling, click image:maps/images/gs_link_icon.png[] next to the property. -This button is only available when vector features contain numeric properties. +Use data driven styling to symbolize features by property values. +To enable data driven styling for a style property, change the selected value from *Fixed* or *Solid* to *By value*. + +The image below shows an example of data driven styling using the <> data set. +The *kibana_sample_data_logs* layer uses data driven styling for fill color and symbol size style properties. + +* The `hour_of_day` property determines the fill color for each feature based on where the value fits on a linear scale. +Light green circles symbolize documents that occur earlier in the day, and dark green circles symbolize documents that occur later in the day. + +* The `bytes` property determines the size of each symbol based on where the value fits on a linear scale. +Smaller circles symbolize documents with smaller payloads, and larger circles symbolize documents with larger payloads. + +[role="screenshot"] +image::maps/images/vector_style_dynamic.png[] + + +[float] +[[maps-vector-style-quantitative-data-driven]] +==== Quantitative data driven styling + +Quantitative data driven styling symbolizes features from a range of numeric property values. + +To ensure symbols are consistent as you pan, zoom, and filter the map, quantitative data driven styling uses {ref}/search-aggregations-metrics-extendedstats-aggregation.html[extended_stats aggregation] to retrieve statistical metadata. + +Click the gear icon image:maps/images/gear_icon.png[] to configure extended_stats. Set *Sigma* to a smaller value to minimize outliers by moving the range minimum and maximum closer to the average. Clear the *Calculate range from indices* checkbox to turn off the extended_stats aggregation request. + +NOTE: When the *Calculate range from indices* checkbox is cleared, symbols might be inconsistent as users pan, zoom, and filter the map. Without extended_stats, the range is calulated with data from the local layer. The range is recalulcated when layer data changes. + +[role="screenshot"] +image::maps/images/extended_stats_config.png[] When the property value is undefined for a feature: @@ -31,22 +60,32 @@ When the property value is undefined for a feature: * *Border width* and *Symbol size* are set to the minimum size. * *Symbol orientation* is set to 0. -When the minimum and maximum are the same and there is no range: +When the symbol range minimum and maximum are the same and there is no range: * *Fill color* and *Border color* are set to last color in the color ramp. * *Border width* and *Symbol size* are set to the maximum size. -The image below shows an example of data driven styling using the <> data set. -The *kibana_sample_data_logs* layer uses data driven styling for fill color and symbol size style properties. -* The `hour_of_day` property determines the fill color for each feature based on where the value fits on a linear scale. -Light green circles symbolize documents that occur earlier in the day, and dark green circles symbolize documents that occur later in the day. +[float] +[[maps-vector-style-qualitative-data-driven]] +==== Qualitative data driven styling -* The `bytes` property determines the size of each symbol based on where the value fits on a linear scale. -Smaller circles symbolize documents with smaller payloads, and larger circles symbolize documents with larger payloads. +Qualitative data driven styling symbolizes non-numeric properties, such as strings and IP addresses, by category. + +Qualitative data driven styling is available for the following styling properties: + +* *Fill color* +* *Border color* +* *Label color* +* *Label border color* + +Qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation] to retrieve the top nine categories for the property. Feature values within the top categories are assigned a unique color. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. + +The image below shows an example of quantitative data driven styling using the <> data set. +The `machine.os.keyword` property determines the color of each symbol based on category. [role="screenshot"] -image::maps/images/vector_style_dynamic.png[] +image::maps/images/quantitative_data_driven_styling.png[] [float] diff --git a/docs/user/security/images/mutual-tls-role-mapping.png b/docs/user/security/images/mutual-tls-role-mapping.png new file mode 100644 index 0000000000000..d95ce41e130c2 Binary files /dev/null and b/docs/user/security/images/mutual-tls-role-mapping.png differ diff --git a/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc b/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc new file mode 100644 index 0000000000000..1c7e7080e676b --- /dev/null +++ b/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc @@ -0,0 +1,120 @@ +[role="xpack"] +[[elasticsearch-mutual-tls]] +=== Mutual TLS authentication between {kib} and {es} +++++ +Mutual TLS with {es} +++++ + +In a standard Transport Layer Security (TLS/SSL) configuration, the server presents a signed certificate to authenticate itself to the +client. In a mutual TLS configuration, the client also presents a signed certificate to authenticate itself to the server. + +When {security} is enabled on your cluster, each request that {kib} makes to {es} must be authenticated. Most requests made through {kib} to +{es} are authenticated by using the credentials of the logged-in user. There are, however, a few internal requests that the {kib} server +needs to make to the {es} cluster. For this reason, you must configure credentials for the {kib} server to use for those requests. + +If {kib} has `elasticsearch.username` and `elasticsearch.password` configured, it will attempt to use these to authenticate to {es} via the +{ref}/native-realm.html[Native realm]. However, {kib} also supports mutual TLS authentication with {es} via a {ref}/pki-realm.html[Public +Key Infrastructure (PKI) realm]. To do so, {es} needs to verify the signature on the {kib} client certificate, and it also needs to map the +certificate's distinguished name (DN) to the appropriate `kibana_system` role. + +NOTE: Using a PKI realm is a gold feature. For a comparison of the Elastic license levels, see https://www.elastic.co/subscriptions[the +subscription page]. + +To configure {kib} and {es} to use mutual TLS authentication: + +. <> with a username and password. + +. <>. At a minimum, this requires a server certificate for {es}. + +. Create a client certificate and private key for {kib} to use when connecting to {es}. ++ +-- +NOTE: This is not the same as the <> that {kib} will present to web browsers. + +You may choose to generate a certificate and private key using {ref}/certutil.html[the {es} certutil tool]. At this point, you will have +already set up a certificate authority (CA) to sign the {es} server certificate. You may choose to use the same CA to sign the {kib} client +certificate. You would do this like so: + +[source,sh] +-------------------------------------------------------------------------------- +bin/elasticsearch-certutil cert -ca elastic-stack-ca.p12 -name kibana-client +-------------------------------------------------------------------------------- + +This will generate a certificate and private key in a PKCS #12 keystore named `kibana-client.p12`. The certificate has a Common Name (CN) of +"kibana-client". + +You will also need to use the CA certificate when setting up the PKI realm in {es}. While you could use the CA keystore in the above example +for this purpose, it is bad practice to expose the CA's private key in such a manner. Instead, you can extract the CA certificate (without +its private key) like so: + +[source,sh] +-------------------------------------------------------------------------------- +openssl pkcs12 -in kibana-client.p12 -cacerts -nokeys -out ca.crt +-------------------------------------------------------------------------------- +-- + +. Configure a PKI realm and a Native realm in your {es} cluster: ++ +-- +By default, {es} provides a Native realm. However, to support both a PKI realm (for {kib}) and a Native realm (for end users), you must +configure each realm in `elasticsearch.yml`: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.security.authc.realms.pki.realm1.order: 1 +xpack.security.authc.realms.pki.realm1.certificate_authorities: "/path/to/ca.crt" +xpack.security.authc.realms.native.realm2.order: 2 +-------------------------------------------------------------------------------- + +-- + +. Configure your {es} cluster to request client certificates: ++ +-- +By default, {es} will not request a client certificate when establishing a TLS connection. To change this, you must set up optional client +certificate authentication in `elasticsearch.yml`: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.security.http.ssl.client_authentication: "optional" +-------------------------------------------------------------------------------- +-- + +. Restart your {es} cluster. + +. Use {kib} to create a <> for your new client certificate: ++ +-- +This role mapping will assign the `kibana_system` role to any user that matches the included mapping rule, which is set to equal the client +certificate's DN attribute: + +[role="screenshot"] +image:user/security/images/mutual-tls-role-mapping.png["Role mapping for the {kib} client certificate"] +-- + +. Configure {kib} to use the client certificate: ++ +-- +Assuming you used the {es} certutil tool to generate a certificate and private key in a PKCS #12 keystore, add the following values to +`kibana.yml`: + +[source,yaml] +-------------------------------------------------------------------------------- +elasticsearch.ssl.keystore.path: "/path/to/kibana-client.p12" +elasticsearch.ssl.keystore.password: "decryption password" +-------------------------------------------------------------------------------- + +The decryption password should match what you entered when prompted by the {es} certutil tool. + +You must also remove the `elasticsearch.username` and `elasticsearch.password` values from the configuration file. Otherwise, {kib} will +attempt to use those to authenticate via the Native realm. + +TIP: Alternatively, {kib} also supports using a client certificate and private key in PEM format with the `elasticsearch.ssl.certificate` +and `elasticsearch.ssl.key` settings. For more information, see <>. +-- + +. Restart {kib}. + +NOTE: The steps above enable {kib} to authenticate to {es} using a certificate. However, end users will only be able to authenticate to +{kib} with a username and password. To allow end users to authenticate to {kib} using certificates, see <>. diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index a68a2ee285ee3..2d07b57bfabe1 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -88,6 +88,8 @@ xpack.security.session.lifespan: "8h" . Optional: <>. +. Optional: <>. + . Restart {kib}. . [[kibana-roles]]Choose an authentication mechanism and grant users the privileges they need to @@ -141,4 +143,5 @@ NOTE: This must be a user who has been assigned <> instead, which offers more functionality and is easier to use. -If you want to create new coordinate map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. +NOTE: Coordinate maps have been replaced with <>, which offers more functionality and is easier to use. -Kibana uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service] -to display map tiles. To use other tile service providers, configure the <> +To create coordinate maps in Visualize: + +* Set `xpack.maps.showMapVisualizationTypes` to `true`. + +* To display map tiles, {kib} uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. +To use other tile service providers, configure the <> in `kibana.yml`. [float] -[[tilemap-configuration]] -=== Configuration +[[coordinate-map-aggregation]] +=== Supported aggregations -[float] -==== Data +Coordinate maps support the metric and bucket aggregations. [float] -===== Metrics - -The default _metrics_ aggregation for a coordinate map is the *Count* aggregation. You can select any of the following -aggregations as the metrics aggregation: - -*Count*:: The {ref}/search-aggregations-metrics-valuecount-aggregation.html[_count_] aggregation returns a raw count of -the elements in the selected index pattern. -*Average*:: This aggregation returns the {ref}/search-aggregations-metrics-avg-aggregation.html[_average_] of a numeric -field. Select a field from the drop-down. -*Sum*:: The {ref}/search-aggregations-metrics-sum-aggregation.html[_sum_] aggregation returns the total sum of a numeric -field. Select a field from the drop-down. -*Min*:: The {ref}/search-aggregations-metrics-min-aggregation.html[_min_] aggregation returns the minimum value of a -numeric field. Select a field from the drop-down. -*Max*:: The {ref}/search-aggregations-metrics-max-aggregation.html[_max_] aggregation returns the maximum value of a -numeric field. Select a field from the drop-down. -*Unique Count*:: The {ref}/search-aggregations-metrics-cardinality-aggregation.html[_cardinality_] aggregation returns -the number of unique values in a field. Select a field from the drop-down. - -Enter a string in the *Custom Label* field to change the display label. +===== Metric aggregations -[float] -===== Buckets +The following metric aggregations are supported: -Coordinate maps use the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html[_geohash_] aggregation. Select a field, typically coordinates, from the -drop-down. +{ref}/search-aggregations-metrics-valuecount-aggregation.html[Count]:: Returns a raw count of +the elements in the index pattern. The default metrics aggregation for a coordinate map is *Count*. -- The _Change precision on map zoom_ box is checked by default. Uncheck the box to disable this behavior. -The _Precision_ slider determines the granularity of the results displayed on the map. See the documentation -for the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[geohash grid] -aggregation for details on the area specified by each precision level. +{ref}/search-aggregations-metrics-avg-aggregation.html[Average]:: Returns the average of a numeric +field. -NOTE: Higher precisions increase memory usage for the browser displaying Kibana as well as for the underlying -Elasticsearch cluster. +{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: Returns the total sum of a numeric +field. -- The _place markers off grid (use {ref}/search-aggregations-metrics-geocentroid-aggregation.html[geocentroid])_ box is checked by default. When this box is checked, the markers are -placed in the center of all the documents in that bucket. When unchecked, the markers are placed in the center -of the geohash grid cell. Leaving this checked generally results in a more accurate visualization. +{ref}/search-aggregations-metrics-min-aggregation.html[Min]:: Returns the minimum value of a +numeric field. +{ref}/search-aggregations-metrics-max-aggregation.html[Max]:: Returns the maximum value of a +numeric field. -Enter a string in the *Custom Label* field to change the display label. +{ref}/search-aggregations-metrics-cardinality-aggregation.html[Unique Count]:: Returns +the number of unique values in a field. [float] -==== Options - -*Map type*:: Select one of the following options from the drop-down. -*_Scaled Circle Markers_*:: Scale the size of the markers based on the metric aggregation's value. -*_Shaded Circle Markers_*:: Displays the markers with different shades based on the metric aggregation's value. -*_Shaded Geohash Grid_*:: Displays the rectangular cells of the geohash grid instead of circular markers, with different -shades based on the metric aggregation's value. -*_Heatmap_*:: A heat map applies blurring to the circle markers and applies shading based on the amount of overlap. -Heatmaps have the following options: - -* *Cluster size*: Adjust the size of the heatmap clustering. -* *Show Tooltip*: Check this box to have a tooltip with the values for a given dot when the cursor is on that dot. - -*Desaturate map tiles*:: Desaturate the map's color in order to make the markers stand out more clearly. -*WMS compliant map server*:: Check this box to enable the use of a third-party mapping service that complies with the Web -Map Service (WMS) standard. Specify the following elements: - -* *WMS url*: The URL for the WMS map service. -* *WMS layers*: A comma-separated list of the layers to use in this visualization. Each map server provides its own list of -layers. -* *WMS version*: The WMS version used by this map service. -* *WMS format*: The image format used by this map service. The two most common formats are `image/png` and `image/jpeg`. -* *WMS attribution*: An optional, user-defined string that identifies the map source. Maps display the attribution string -in the lower right corner. -* *WMS styles*: A comma-separated list of the styles to use in this visualization. Each map server provides its own styling -options. - -After changing options, click the *Apply changes* button to update your visualization, or the grey *Discard -changes* button to keep your visualization in its current state. +[[coordinate-bucket-aggregation]] +===== Bucket aggregation + +Coordinate maps support the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html[_geohash_] bucket aggregation. + +When you deselect *Change precision on map zoom*, the *Precision* slider appears. The *Precision* slider determines the granularity of the results displayed on the map. For details on the area specified by each precision level, refer to {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[geohash grid]. + +NOTE: Higher precisions increase memory usage for the browser that displays {kib} and the underlying +{es} cluster. + +When you select *Place markers off grid (use {ref}/search-aggregations-metrics-geocentroid-aggregation.html[geocentroid])*, the markers are +placed in the center of all documents in the bucket, and a more accurate visualization is created. +NOTE: When you have multiple values in the geo_point, the coordinate map is unable to accurately calculate the geo_centroid. + +When you deselect *Place markers off grid (use {ref}/search-aggregations-metrics-geocentroid-aggregation.html[geocentroid])*, the markers are placed in the center +of the geohash grid cell. [float] [[navigate-map]] -=== Navigating the Map +=== Navigate the coordinate map -Once your tilemap visualization is ready, you can explore the map in several ways: +Use the following navigation options: -* Click and hold anywhere on the map and move the cursor to move the map center. Hold Shift and drag a bounding box -across the map to zoom in on the selection. -* Click the *Zoom In/Out* image:images/viz-zoom.png[] buttons to change the zoom level manually. -* Click the *Fit Data Bounds* image:images/viz-fit-bounds.png[] button to automatically crop the map boundaries to the -geohash buckets that have at least one result. -* Click the *Latitude/Longitude Filter* image:images/viz-lat-long-filter.png[] button, then drag a bounding box across the -map, to create a filter for the box coordinates. +* To move the map center, click and hold anywhere on the map and move the cursor. +* To change the zoom level, click *Zoom In* or *Zoom out* image:images/viz-zoom.png[]. +* To automatically crop the map boundaries to the +geohash buckets that have at least one result, click *Fit Data Bounds* image:images/viz-fit-bounds.png[]. diff --git a/examples/README.md b/examples/README.md index 7cade0b35f820..2b214a8d1eb52 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,4 +5,3 @@ This folder contains example plugins. To run the plugins in this folder, use th ``` yarn start --run-examples ``` - diff --git a/examples/bfetch_explorer/kibana.json b/examples/bfetch_explorer/kibana.json new file mode 100644 index 0000000000000..cbdd9be0e658c --- /dev/null +++ b/examples/bfetch_explorer/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "bfetchExplorer", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["bfetch_explorer"], + "server": true, + "ui": true, + "requiredPlugins": ["bfetch"], + "optionalPlugins": [] +} diff --git a/examples/bfetch_explorer/package.json b/examples/bfetch_explorer/package.json new file mode 100644 index 0000000000000..ea5a1b1848613 --- /dev/null +++ b/examples/bfetch_explorer/package.json @@ -0,0 +1,17 @@ +{ + "name": "bfetch_explorer", + "version": "1.0.0", + "main": "target/examples/bfetch_explorer", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/examples/bfetch_explorer/public/components/count_until/index.tsx b/examples/bfetch_explorer/public/components/count_until/index.tsx new file mode 100644 index 0000000000000..ce48ce9dfe61f --- /dev/null +++ b/examples/bfetch_explorer/public/components/count_until/index.tsx @@ -0,0 +1,93 @@ +/* + * 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, { useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import useList from 'react-use/lib/useList'; +import { EuiForm, EuiSpacer, EuiFieldNumber, EuiFormRow, EuiButton } from '@elastic/eui'; +import { BfetchPublicSetup } from '../../../../../src/plugins/bfetch/public'; + +export interface Props { + fetchStreaming: BfetchPublicSetup['fetchStreaming']; +} + +export const CountUntil: React.FC = ({ fetchStreaming }) => { + const isMounted = useMountedState(); + const [data, setData] = useState(5); + const [showingResults, setShowingResults] = useState(false); + const [results, { push: pushResult, clear: clearList }] = useList([]); + const [completed, setCompleted] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = () => { + setShowingResults(true); + const { stream } = fetchStreaming({ + url: '/bfetch_explorer/count', + body: JSON.stringify({ data }), + }); + stream.subscribe({ + next: (next: string) => { + if (!isMounted()) return; + pushResult(next); + }, + error: (nextError: any) => { + if (!isMounted()) return; + setError(nextError); + }, + complete: () => { + if (!isMounted()) return; + setCompleted(true); + }, + }); + }; + + const handleReset = () => { + setShowingResults(false); + clearList(); + setError(null); + setCompleted(false); + }; + + if (showingResults) { + return ( + +
{JSON.stringify(error || results, null, 4)}
+ + + Reset + +
+ ); + } + + return ( + + + setData(Number(e.target.value))} + /> + + + Start + + + ); +}; diff --git a/examples/bfetch_explorer/public/components/double_integers/index.tsx b/examples/bfetch_explorer/public/components/double_integers/index.tsx new file mode 100644 index 0000000000000..d8fbe33ec73be --- /dev/null +++ b/examples/bfetch_explorer/public/components/double_integers/index.tsx @@ -0,0 +1,105 @@ +/* + * 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, { useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import useList from 'react-use/lib/useList'; +import useCounter from 'react-use/lib/useCounter'; +import { EuiForm, EuiSpacer, EuiTextArea, EuiFormRow, EuiButton } from '@elastic/eui'; +import { ExplorerService } from '../../plugin'; + +interface ResultItem { + num: number; + result?: { + num: number; + }; + error?: any; +} + +const defaultNumbers = [2000, 300, -1, 1000].join('\n'); + +export interface Props { + double: ExplorerService['double']; +} + +export const DoubleIntegers: React.FC = ({ double }) => { + const isMounted = useMountedState(); + const [numbers, setNumbers] = useState(defaultNumbers); + const [showingResults, setShowingResults] = useState(false); + const [numberOfResultsAwaiting, counter] = useCounter(0); + const [results, { push: pushResult, clear: clearList }] = useList([]); + + const handleSubmit = () => { + setShowingResults(true); + const nums = numbers + .split('\n') + .map(num => num.trim()) + .filter(Boolean) + .map(Number); + counter.set(nums.length); + nums.forEach(num => { + double({ num }).then( + result => { + if (!isMounted()) return; + counter.dec(); + pushResult({ num, result }); + }, + error => { + if (!isMounted()) return; + counter.dec(); + pushResult({ num, error }); + } + ); + }); + }; + + const handleReset = () => { + setShowingResults(false); + counter.reset(); + clearList(); + }; + + if (showingResults) { + return ( + +
{JSON.stringify(results, null, 4)}
+ + + Reset + +
+ ); + } + + return ( + + + setNumbers(e.target.value)} + /> + + + Send + + + ); +}; diff --git a/examples/bfetch_explorer/public/components/page/index.tsx b/examples/bfetch_explorer/public/components/page/index.tsx new file mode 100644 index 0000000000000..0e7855178a884 --- /dev/null +++ b/examples/bfetch_explorer/public/components/page/index.tsx @@ -0,0 +1,51 @@ +/* + * 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 * as React from 'react'; +import { + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; + +export interface PageProps { + title?: React.ReactNode; +} + +export const Page: React.FC = ({ title = 'Untitled', children }) => { + return ( + + + + +

{title}

+
+
+
+ + + {children} + + +
+ ); +}; diff --git a/src/plugins/data/public/autocomplete_provider/index.ts b/examples/bfetch_explorer/public/containers/app/index.tsx similarity index 50% rename from src/plugins/data/public/autocomplete_provider/index.ts rename to examples/bfetch_explorer/public/containers/app/index.tsx index 6758bd7f379c1..a448c9e4f3a6a 100644 --- a/src/plugins/data/public/autocomplete_provider/index.ts +++ b/examples/bfetch_explorer/public/containers/app/index.tsx @@ -16,25 +16,33 @@ * specific language governing permissions and limitations * under the License. */ -import { AutocompleteProvider } from './types'; -export class AutocompleteProviderRegister { - private readonly registeredProviders: Map = new Map(); +import React from 'react'; +import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom'; +import { EuiPage } from '@elastic/eui'; +import { useDeps } from '../../hooks/use_deps'; +import { Sidebar } from './sidebar'; +import { routes } from '../../routes'; - /** @public **/ - public addProvider(language: string, provider: AutocompleteProvider): void { - if (language && provider) { - this.registeredProviders.set(language, provider); - } - } +export const App: React.FC = () => { + const { appBasePath } = useDeps(); - /** @public **/ - public getProvider(language: string): AutocompleteProvider | undefined { - return this.registeredProviders.get(language); + const routeElements: React.ReactElement[] = []; + for (const { items } of routes) { + for (const { id, component } of items) { + routeElements.push( component} />); + } } - /** @internal **/ - public clearProviders(): void { - this.registeredProviders.clear(); - } -} + return ( + + + + + {routeElements} + + + + + ); +}; diff --git a/examples/bfetch_explorer/public/containers/app/pages/page_count_until/index.tsx b/examples/bfetch_explorer/public/containers/app/pages/page_count_until/index.tsx new file mode 100644 index 0000000000000..7b4eac6eea44c --- /dev/null +++ b/examples/bfetch_explorer/public/containers/app/pages/page_count_until/index.tsx @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { EuiPanel, EuiText } from '@elastic/eui'; +import { CountUntil } from '../../../../components/count_until'; +import { Page } from '../../../../components/page'; +import { useDeps } from '../../../../hooks/use_deps'; + +// eslint-disable-next-line +export interface Props {} + +export const PageCountUntil: React.FC = () => { + const { plugins } = useDeps(); + + return ( + + + This demo sends a single number N using fetchStreaming to the server. The + server will stream back N number of messages with 1 second delay each containing a number + from 1 to N, after which it will close the stream. + +
+ + + +
+ ); +}; diff --git a/examples/bfetch_explorer/public/containers/app/pages/page_double_integers/index.tsx b/examples/bfetch_explorer/public/containers/app/pages/page_double_integers/index.tsx new file mode 100644 index 0000000000000..7bd5feb836674 --- /dev/null +++ b/examples/bfetch_explorer/public/containers/app/pages/page_double_integers/index.tsx @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { EuiPanel, EuiText } from '@elastic/eui'; +import { DoubleIntegers } from '../../../../components/double_integers'; +import { Page } from '../../../../components/page'; +import { useDeps } from '../../../../hooks/use_deps'; + +// eslint-disable-next-line +export interface Props {} + +export const PageDoubleIntegers: React.FC = () => { + const { explorer } = useDeps(); + + return ( + + + Below is a list of numbers in milliseconds. They are sent as a batch to the server. For each + number server waits given number of milliseconds then doubles the number and streams it + back. + +
+ + + +
+ ); +}; diff --git a/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx b/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx new file mode 100644 index 0000000000000..cc50698e05908 --- /dev/null +++ b/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx @@ -0,0 +1,54 @@ +/* + * 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 { EuiPageSideBar, EuiSideNav } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { routes } from '../../../routes'; + +// eslint-disable-next-line +interface SidebarProps {} + +export const Sidebar: React.FC = () => { + const history = useHistory(); + + return ( + + ({ + id, + name: title, + isSelected: true, + items: items.map(route => ({ + id: route.id, + name: route.title, + onClick: () => history.push(`/${route.id}`), + 'data-test-subj': route.id, + })), + })), + }, + ]} + /> + + ); +}; diff --git a/examples/bfetch_explorer/public/hooks/use_deps.ts b/examples/bfetch_explorer/public/hooks/use_deps.ts new file mode 100644 index 0000000000000..c68b4e759c21c --- /dev/null +++ b/examples/bfetch_explorer/public/hooks/use_deps.ts @@ -0,0 +1,23 @@ +/* + * 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 { useKibana } from '../../../../src/plugins/kibana_react/public'; +import { BfetchDeps } from '../mount'; + +export const useDeps = () => useKibana().services as BfetchDeps; diff --git a/examples/bfetch_explorer/public/index.ts b/examples/bfetch_explorer/public/index.ts new file mode 100644 index 0000000000000..76d0a1d1c6334 --- /dev/null +++ b/examples/bfetch_explorer/public/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { BfetchExplorerPlugin } from './plugin'; + +export const plugin = () => new BfetchExplorerPlugin(); diff --git a/examples/bfetch_explorer/public/mount.tsx b/examples/bfetch_explorer/public/mount.tsx new file mode 100644 index 0000000000000..5ad53ef4a1988 --- /dev/null +++ b/examples/bfetch_explorer/public/mount.tsx @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { CoreSetup, CoreStart, AppMountParameters } from 'kibana/public'; +import { KibanaContextProvider } from '../../../src/plugins/kibana_react/public'; +import { BfetchExplorerStartPlugins, ExplorerService } from './plugin'; +import { App } from './containers/app'; + +export interface BfetchDeps { + appBasePath: string; + core: CoreStart; + plugins: BfetchExplorerStartPlugins; + explorer: ExplorerService; +} + +export const mount = ( + coreSetup: CoreSetup, + explorer: ExplorerService +) => async ({ appBasePath, element }: AppMountParameters) => { + const [core, plugins] = await coreSetup.getStartServices(); + const deps: BfetchDeps = { appBasePath, core, plugins, explorer }; + const reactElement = ( + + + + ); + render(reactElement, element); + return () => unmountComponentAtNode(element); +}; diff --git a/examples/bfetch_explorer/public/plugin.tsx b/examples/bfetch_explorer/public/plugin.tsx new file mode 100644 index 0000000000000..3155354c91fd4 --- /dev/null +++ b/examples/bfetch_explorer/public/plugin.tsx @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Plugin, CoreSetup } from 'kibana/public'; +import { BfetchPublicSetup, BfetchPublicStart } from '../../../src/plugins/bfetch/public'; +import { mount } from './mount'; + +export interface ExplorerService { + double: (number: { num: number }) => Promise<{ num: number }>; +} + +export interface BfetchExplorerSetupPlugins { + bfetch: BfetchPublicSetup; +} + +export interface BfetchExplorerStartPlugins { + bfetch: BfetchPublicStart; +} + +export class BfetchExplorerPlugin implements Plugin { + public setup(core: CoreSetup, plugins: BfetchExplorerSetupPlugins) { + const double = plugins.bfetch.batchedFunction<{ num: number }, { num: number }>({ + url: '/bfetch_explorer/double', + }); + + const explorer: ExplorerService = { + double, + }; + + core.application.register({ + id: 'bfetch-explorer', + title: 'bfetch explorer', + mount: mount(core, explorer), + }); + } + + public start() {} + public stop() {} +} diff --git a/examples/bfetch_explorer/public/routes.tsx b/examples/bfetch_explorer/public/routes.tsx new file mode 100644 index 0000000000000..2008811d75795 --- /dev/null +++ b/examples/bfetch_explorer/public/routes.tsx @@ -0,0 +1,59 @@ +/* + * 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 { PageDoubleIntegers } from './containers/app/pages/page_double_integers'; +import { PageCountUntil } from './containers/app/pages/page_count_until'; + +interface RouteSectionDef { + title: string; + id: string; + items: RouteDef[]; +} + +interface RouteDef { + title: string; + id: string; + component: React.ReactNode; +} + +export const routes: RouteSectionDef[] = [ + { + title: 'fetchStreaming', + id: 'fetchStreaming', + items: [ + { + title: 'Count until', + id: 'count-until', + component: , + }, + ], + }, + { + title: 'batchedFunction', + id: 'batchedFunction', + items: [ + { + title: 'Double integers', + id: 'double-integers', + component: , + }, + ], + }, +]; diff --git a/examples/bfetch_explorer/server/index.ts b/examples/bfetch_explorer/server/index.ts new file mode 100644 index 0000000000000..76d0a1d1c6334 --- /dev/null +++ b/examples/bfetch_explorer/server/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { BfetchExplorerPlugin } from './plugin'; + +export const plugin = () => new BfetchExplorerPlugin(); diff --git a/examples/bfetch_explorer/server/plugin.ts b/examples/bfetch_explorer/server/plugin.ts new file mode 100644 index 0000000000000..bf3b7f50ca6c8 --- /dev/null +++ b/examples/bfetch_explorer/server/plugin.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Subject } from 'rxjs'; +import { Plugin, CoreSetup, CoreStart } from '../../../src/core/server'; +import { BfetchServerSetup, BfetchServerStart } from '../../../src/plugins/bfetch/server'; + +export interface BfetchExplorerSetupPlugins { + bfetch: BfetchServerSetup; +} + +export interface BfetchExplorerStartPlugins { + bfetch: BfetchServerStart; +} + +export class BfetchExplorerPlugin implements Plugin { + public setup(core: CoreSetup, plugins: BfetchExplorerSetupPlugins) { + plugins.bfetch.addStreamingResponseRoute('/bfetch_explorer/count', () => ({ + getResponseStream: ({ data }: any) => { + const subject = new Subject(); + const countTo = Number(data); + for (let cnt = 1; cnt <= countTo; cnt++) { + setTimeout(() => { + subject.next(String(cnt)); + }, cnt * 1000); + } + setTimeout(() => { + subject.complete(); + }, countTo * 1000); + return subject; + }, + })); + + plugins.bfetch.addBatchProcessingRoute<{ num: number }, { num: number }>( + '/bfetch_explorer/double', + () => ({ + onBatchItem: async ({ num }) => { + // Validate inputs. + if (num < 0) throw new Error('Invalid number'); + // Wait number of specified milliseconds. + await new Promise(r => setTimeout(r, num)); + // Double the number and send it back. + return { num: 2 * num }; + }, + }) + ); + } + + public start(core: CoreStart, plugins: BfetchExplorerStartPlugins) {} + + public stop() {} +} diff --git a/examples/bfetch_explorer/tsconfig.json b/examples/bfetch_explorer/tsconfig.json new file mode 100644 index 0000000000000..d508076b33199 --- /dev/null +++ b/examples/bfetch_explorer/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*", + ], + "exclude": [] +} diff --git a/package.json b/package.json index 365f597fe04fe..430ab9e1ba77d 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "kbn:bootstrap": "yarn build:types && node scripts/register_git_hook", "spec_to_console": "node scripts/spec_to_console", "backport-skip-ci": "backport --prDescription \"[skip-ci]\"", + "storybook": "node scripts/storybook", "cover:report": "nyc report --temp-dir target/kibana-coverage/functional --report-dir target/coverage/report --reporter=lcov && open ./target/coverage/report/lcov-report/index.html" }, "repository": { @@ -118,7 +119,7 @@ "@elastic/charts": "^16.1.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "7.6.0", - "@elastic/eui": "18.2.0", + "@elastic/eui": "18.2.1", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -135,6 +136,7 @@ "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", + "@types/flot": "^0.0.31", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", "@types/node-forge": "^0.9.0", @@ -460,10 +462,13 @@ "postcss-url": "^8.0.0", "prettier": "^1.19.1", "proxyquire": "1.8.0", + "react-popper-tooltip": "^2.10.1", + "react-textarea-autosize": "^7.1.2", "regenerate": "^1.4.0", "sass-lint": "^1.12.1", "selenium-webdriver": "^4.0.0-alpha.5", "simple-git": "1.116.0", + "simplebar-react": "^2.1.0", "sinon": "^7.4.2", "strip-ansi": "^3.0.1", "supertest": "^3.1.0", diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 2fc29b71b262e..714ed56ac4703 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -17,7 +17,7 @@ * under the License. */ -export { withProcRunner } from './proc_runner'; +export { withProcRunner, ProcRunner } from './proc_runner'; export { ToolingLog, ToolingLogTextWriter, diff --git a/packages/kbn-dev-utils/src/proc_runner/index.ts b/packages/kbn-dev-utils/src/proc_runner/index.ts index ad5e7c8ee946e..af9dc5c65a3a3 100644 --- a/packages/kbn-dev-utils/src/proc_runner/index.ts +++ b/packages/kbn-dev-utils/src/proc_runner/index.ts @@ -18,3 +18,4 @@ */ export { withProcRunner } from './with_proc_runner'; +export { ProcRunner } from './proc_runner'; diff --git a/packages/kbn-dev-utils/src/run/run.ts b/packages/kbn-dev-utils/src/run/run.ts index 1d28d43575729..e185f86cc3bf7 100644 --- a/packages/kbn-dev-utils/src/run/run.ts +++ b/packages/kbn-dev-utils/src/run/run.ts @@ -23,11 +23,13 @@ import exitHook from 'exit-hook'; import { pickLevelFromFlags, ToolingLog } from '../tooling_log'; import { createFlagError, isFailError } from './fail'; import { Flags, getFlags, getHelp } from './flags'; +import { ProcRunner, withProcRunner } from '../proc_runner'; type CleanupTask = () => void; type RunFn = (args: { log: ToolingLog; flags: Flags; + procRunner: ProcRunner; addCleanupTask: (task: CleanupTask) => void; }) => Promise | void; @@ -102,10 +104,13 @@ export async function run(fn: RunFn, options: Options = {}) { } try { - await fn({ - log, - flags, - addCleanupTask: (task: CleanupTask) => cleanupTasks.push(task), + await withProcRunner(log, async procRunner => { + await fn({ + log, + flags, + procRunner, + addCleanupTask: (task: CleanupTask) => cleanupTasks.push(task), + }); }); } finally { doCleanup(); diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index a3debf78fb8c8..8bded9d403c21 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -4489,6 +4489,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = __webpack_require__(36); var proc_runner_1 = __webpack_require__(37); exports.withProcRunner = proc_runner_1.withProcRunner; +exports.ProcRunner = proc_runner_1.ProcRunner; var tooling_log_1 = __webpack_require__(415); exports.ToolingLog = tooling_log_1.ToolingLog; exports.ToolingLogTextWriter = tooling_log_1.ToolingLogTextWriter; @@ -4761,6 +4762,8 @@ function __importDefault(mod) { Object.defineProperty(exports, "__esModule", { value: true }); var with_proc_runner_1 = __webpack_require__(38); exports.withProcRunner = with_proc_runner_1.withProcRunner; +var proc_runner_1 = __webpack_require__(39); +exports.ProcRunner = proc_runner_1.ProcRunner; /***/ }), @@ -37069,6 +37072,7 @@ const exit_hook_1 = tslib_1.__importDefault(__webpack_require__(348)); const tooling_log_1 = __webpack_require__(415); const fail_1 = __webpack_require__(425); const flags_1 = __webpack_require__(426); +const proc_runner_1 = __webpack_require__(37); async function run(fn, options = {}) { var _a; const flags = flags_1.getFlags(process.argv.slice(2), options); @@ -37118,10 +37122,13 @@ async function run(fn, options = {}) { throw fail_1.createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`); } try { - await fn({ - log, - flags, - addCleanupTask: (task) => cleanupTasks.push(task), + await proc_runner_1.withProcRunner(log, async (procRunner) => { + await fn({ + log, + flags, + procRunner, + addCleanupTask: (task) => cleanupTasks.push(task), + }); }); } finally { diff --git a/packages/kbn-storybook/README.md b/packages/kbn-storybook/README.md new file mode 100644 index 0000000000000..c9195f41ebf26 --- /dev/null +++ b/packages/kbn-storybook/README.md @@ -0,0 +1,33 @@ +# Kibana Storybook + +This package provides ability to add [Storybook](https://storybook.js.org/) to any Kibana plugin. + +- [Setup Instructions](#setup-instructions) + + +## Setup Instructions + +1. Add `storybook.js` launcher file to your plugin. For example, create a file at + `src/plugins//scripts/storybook.js`, with the following contents: + + ```js + import { join } from 'path'; + + // eslint-disable-next-line + require('@kbn/storybook').runStorybookCli({ + name: '', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.examples.tsx')], + }); + ``` +2. Add your plugin alias to `src/dev/storybook/aliases.ts` config. +3. Create sample Storybook stories. For example, in your plugin create create a file at + `src/plugins//public/components/hello_world/__examples__/hello_world.examples.tsx` with + the following contents: + + ```jsx + import * as React from 'react'; + import { storiesOf } from '@storybook/react'; + + storiesOf('Hello world', module).add('default', () =>
Hello world!
); + ``` +4. Launch Storybook with `yarn storybook `. diff --git a/packages/kbn-storybook/index.js b/packages/kbn-storybook/index.js new file mode 100644 index 0000000000000..78e2cf7f5073b --- /dev/null +++ b/packages/kbn-storybook/index.js @@ -0,0 +1,86 @@ +/* + * 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 fs = require('fs'); +const { join } = require('path'); +const Rx = require('rxjs'); +const { first } = require('rxjs/operators'); +const storybook = require('@storybook/react/standalone'); +const { run } = require('@kbn/dev-utils'); +const { generateStorybookEntry } = require('./lib/storybook_entry'); +const { REPO_ROOT, CURRENT_CONFIG } = require('./lib/constants'); +const { buildDll } = require('./lib/dll'); + +exports.runStorybookCli = config => { + const { name, storyGlobs } = config; + run( + async ({ flags, log, procRunner }) => { + log.debug('Global config:\n', require('./lib/constants')); + + const currentConfig = JSON.stringify(config, null, 2); + const currentConfigDir = join(CURRENT_CONFIG, '..'); + await fs.promises.mkdir(currentConfigDir, { recursive: true }); + log.debug('Writing currentConfig:\n', CURRENT_CONFIG + '\n', currentConfig); + await fs.promises.writeFile(CURRENT_CONFIG, `exports.currentConfig = ${currentConfig};`); + + await buildDll({ + rebuildDll: flags.rebuildDll, + log, + procRunner, + }); + + // Build sass and continue when initial build complete + await procRunner.run('watch sass', { + cmd: process.execPath, + args: ['scripts/build_sass', '--watch'], + cwd: REPO_ROOT, + wait: /scss bundles created/, + }); + + const subj = new Rx.ReplaySubject(1); + generateStorybookEntry({ log, storyGlobs }).subscribe(subj); + + await subj.pipe(first()).toPromise(); + + await Promise.all([ + // route errors + subj.toPromise(), + + new Promise(() => { + // storybook never completes, so neither will this promise + const configDir = join(__dirname, 'storybook_config'); + log.debug('Config dir:', configDir); + storybook({ + mode: 'dev', + port: 9001, + configDir, + }); + }), + ]); + }, + { + flags: { + boolean: ['rebuildDll'], + }, + description: ` + Run the storybook examples for ${name} + `, + } + ); +}; diff --git a/packages/kbn-storybook/lib/constants.js b/packages/kbn-storybook/lib/constants.js new file mode 100644 index 0000000000000..9d216d347eada --- /dev/null +++ b/packages/kbn-storybook/lib/constants.js @@ -0,0 +1,27 @@ +/* + * 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 { resolve, dirname } = require('path'); + +exports.REPO_ROOT = dirname(require.resolve('../../../package.json')); +exports.ASSET_DIR = resolve(exports.REPO_ROOT, 'built_assets/storybook'); +exports.CURRENT_CONFIG = resolve(exports.ASSET_DIR, 'current.config.js'); +exports.STORY_ENTRY_PATH = resolve(exports.ASSET_DIR, 'stories.entry.js'); +exports.DLL_DIST_DIR = resolve(exports.ASSET_DIR, 'dll'); +exports.DLL_NAME = 'storybook_dll'; diff --git a/packages/kbn-storybook/lib/dll.js b/packages/kbn-storybook/lib/dll.js new file mode 100644 index 0000000000000..a9154ca972120 --- /dev/null +++ b/packages/kbn-storybook/lib/dll.js @@ -0,0 +1,41 @@ +/* + * 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 { resolve } = require('path'); +const { existsSync } = require('fs'); + +const { REPO_ROOT, DLL_DIST_DIR } = require('./constants'); + +exports.buildDll = async ({ rebuildDll, log, procRunner }) => { + if (rebuildDll) { + log.info('rebuilding dll'); + } else if (!existsSync(resolve(DLL_DIST_DIR, 'dll.js'))) { + log.info('dll missing, rebuilding'); + } else { + log.info('dll exists'); + return; + } + + await procRunner.run('build dll ', { + cmd: require.resolve('webpack/bin/webpack'), + args: ['--config', require.resolve('./webpack.dll.config.js')], + cwd: REPO_ROOT, + wait: true, + }); +}; diff --git a/packages/kbn-storybook/lib/storybook_entry.js b/packages/kbn-storybook/lib/storybook_entry.js new file mode 100644 index 0000000000000..dececef47f40e --- /dev/null +++ b/packages/kbn-storybook/lib/storybook_entry.js @@ -0,0 +1,89 @@ +/* + * 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 { resolve, relative, dirname } = require('path'); +const Fs = require('fs'); +const Rx = require('rxjs'); +const { mergeMap, map, debounceTime } = require('rxjs/operators'); +const normalize = require('normalize-path'); +const { promisify } = require('util'); + +const watch = require('glob-watcher'); +const mkdirp = require('mkdirp'); // eslint-disable-line +const glob = require('fast-glob'); + +const mkdirpAsync = promisify(mkdirp); +const writeFileAsync = promisify(Fs.writeFile); + +const { REPO_ROOT, STORY_ENTRY_PATH } = require('./constants'); +const STORE_ENTRY_DIR = dirname(STORY_ENTRY_PATH); + +exports.generateStorybookEntry = ({ log, storyGlobs }) => { + const globs = ['built_assets/css/**/*.light.css', ...storyGlobs]; + log.info('Storybook globs:\n', globs); + const norm = p => normalize(relative(STORE_ENTRY_DIR, p)); + + return Rx.defer(() => + glob(globs, { + absolute: true, + cwd: REPO_ROOT, + onlyFiles: true, + }) + ).pipe( + map(paths => { + log.info('Discovered Storybook entry points:\n', paths); + return new Set(paths.map(norm)); + }), + mergeMap( + paths => + new Rx.Observable(observer => { + observer.next(paths); + + const chokidar = watch(globs, { cwd: REPO_ROOT }) + .on('add', path => { + observer.next(paths.add(norm(resolve(REPO_ROOT, path)))); + }) + .on('unlink', path => { + observer.next(paths.delete(norm(resolve(REPO_ROOT, path)))); + }); + + return () => { + chokidar.close(); + }; + }) + ), + debounceTime(200), + mergeMap(async (paths, i) => { + await mkdirpAsync(STORE_ENTRY_DIR); + + let content = ''; + for (const path of paths) { + content += `require('${path}');\n`; + } + + await writeFileAsync(STORY_ENTRY_PATH, content); + + if (i === 0) { + log.info('%d paths written to entry file', paths.size); + } else { + log.info('entry file updated'); + } + }) + ); +}; diff --git a/packages/kbn-storybook/lib/webpack.dll.config.js b/packages/kbn-storybook/lib/webpack.dll.config.js new file mode 100644 index 0000000000000..bc871fab471b2 --- /dev/null +++ b/packages/kbn-storybook/lib/webpack.dll.config.js @@ -0,0 +1,149 @@ +/* + * 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 webpack = require('webpack'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const { DLL_NAME, REPO_ROOT, DLL_DIST_DIR } = require('./constants'); + +// This is the Webpack config for the DLL of CSS and JS assets that are +// not expected to change during development. This saves compile and run +// times considerably. +module.exports = { + context: REPO_ROOT, + mode: 'development', + + // This is a (potentially growing) list of modules that can be safely + // included in the DLL. Only add to this list modules or other code + // which Storybook stories and their components would require, but don't + // change during development. + entry: [ + '@elastic/eui/dist/eui_theme_light.css', + '@kbn/ui-framework/dist/kui_light.css', + '@storybook/addon-info', + '@storybook/addon-knobs', + '@storybook/addon-knobs/react', + '@storybook/addon-knobs/register', + '@storybook/addon-options', + '@storybook/addon-options/register', + '@storybook/core', + '@storybook/core/dist/server/common/polyfills.js', + '@storybook/react', + '@storybook/theming', + 'angular-mocks', + 'angular', + 'brace', + 'chroma-js', + 'highlight.js', + 'html-entities', + 'jquery', + 'lodash.clone', + 'lodash', + 'markdown-it', + 'mocha', + 'prop-types', + 'react-ace', + 'react-beautiful-dnd', + 'react-dom', + 'react-focus-lock', + 'react-markdown', + 'react-resize-detector', + 'react-virtualized', + 'react', + 'recompose', + 'redux-actions', + 'remark-parse', + 'rxjs', + 'sinon', + 'tinycolor2', + './src/legacy/ui/public/styles/font_awesome.less', + './src/legacy/ui/public/styles/bootstrap/bootstrap_light.less', + ], + plugins: [ + // Produce the DLL and its manifest + new webpack.DllPlugin({ + name: DLL_NAME, + path: path.resolve(DLL_DIST_DIR, 'manifest.json'), + }), + // Produce the DLL CSS file + new MiniCssExtractPlugin({ + filename: 'dll.css', + }), + ], + // Output the DLL JS file + output: { + path: DLL_DIST_DIR, + filename: 'dll.js', + library: DLL_NAME, + }, + // Include a require alias for legacy UI code and styles + resolve: { + alias: { + ui: path.resolve(REPO_ROOT, 'src/legacy/ui/public'), + }, + mainFields: ['browser', 'main'], + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: {}, + }, + { loader: 'css-loader' }, + { + loader: 'string-replace-loader', + options: { + search: '__REPLACE_WITH_PUBLIC_PATH__', + replace: '/', + flags: 'g', + }, + }, + ], + }, + { + test: /\.less$/, + use: [ + { loader: 'style-loader' }, + { loader: 'css-loader', options: { importLoaders: 2 } }, + { + loader: 'postcss-loader', + options: { + config: { + path: path.resolve(REPO_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + }, + { loader: 'less-loader' }, + ], + }, + { + test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/, + loader: 'file-loader', + }, + ], + }, + node: { + fs: 'empty', + child_process: 'empty', + }, +}; diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json new file mode 100644 index 0000000000000..6948ae81806eb --- /dev/null +++ b/packages/kbn-storybook/package.json @@ -0,0 +1,32 @@ +{ + "name": "@kbn/storybook", + "version": "1.0.0", + "private": true, + "license": "Apache-2.0", + "dependencies": { + "@kbn/babel-preset": "1.0.0", + "@kbn/dev-utils": "1.0.0", + "@storybook/addon-actions": "^5.2.8", + "@storybook/addon-console": "^1.2.1", + "@storybook/addon-info": "^5.2.8", + "@storybook/addon-knobs": "^5.2.8", + "@storybook/addon-options": "^5.2.8", + "@storybook/addon-storyshots": "^5.2.8", + "@storybook/react": "^5.2.8", + "@storybook/theming": "^5.2.8", + "copy-webpack-plugin": "5.0.3", + "execa": "1.0.0", + "fast-glob": "2.2.7", + "glob-watcher": "5.0.3", + "jest-specific-snapshot": "2.0.0", + "jest-styled-components": "6.3.1", + "mkdirp": "0.5.1", + "mini-css-extract-plugin": "0.7.0", + "normalize-path": "3.0.0", + "react-docgen-typescript-loader": "3.1.0", + "rxjs": "6.5.2", + "serve-static": "1.14.1", + "styled-components": "^3", + "webpack": "4.34.0" + } +} \ No newline at end of file diff --git a/packages/kbn-storybook/storybook_config/addons.js b/packages/kbn-storybook/storybook_config/addons.js new file mode 100644 index 0000000000000..f439d1d8892f8 --- /dev/null +++ b/packages/kbn-storybook/storybook_config/addons.js @@ -0,0 +1,23 @@ +/* + * 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 '@storybook/addon-options/register'; +import '@storybook/addon-actions/register'; +import '@storybook/addon-knobs/register'; +import '@storybook/addon-console'; diff --git a/packages/kbn-storybook/storybook_config/config.js b/packages/kbn-storybook/storybook_config/config.js new file mode 100644 index 0000000000000..a7975773e675b --- /dev/null +++ b/packages/kbn-storybook/storybook_config/config.js @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { configure, addDecorator, addParameters } from '@storybook/react'; +import { withKnobs } from '@storybook/addon-knobs/react'; +import { withInfo } from '@storybook/addon-info'; +import { create } from '@storybook/theming'; + +// If we're running Storyshots, be sure to register the require context hook. +// Otherwise, add the other decorators. +if (process.env.NODE_ENV === 'test') { + // eslint-disable-next-line + require('babel-plugin-require-context-hook/register')(); +} else { + // Customize the info for each story. + addDecorator( + withInfo({ + inline: true, + styles: { + infoBody: { + margin: 20, + }, + infoStory: { + margin: '40px 60px', + }, + }, + }) + ); + + // Add optional knobs to customize each story. + addDecorator(withKnobs); +} + +// Set up the Storybook environment with custom settings. +addParameters({ + options: { + theme: create({ + base: 'light', + brandTitle: 'Kibana Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/master/packages/kbn-storybook', + }), + showPanel: true, + isFullscreen: false, + panelPosition: 'bottom', + isToolshown: true, + }, +}); + +configure(() => { + // eslint-disable-next-line + require('../../../built_assets/storybook/stories.entry.js'); +}, module); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.d.ts b/packages/kbn-storybook/storybook_config/middleware.js similarity index 73% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.d.ts rename to packages/kbn-storybook/storybook_config/middleware.js index bf55abfe8161a..f517477b405bd 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.d.ts +++ b/packages/kbn-storybook/storybook_config/middleware.js @@ -17,16 +17,10 @@ * under the License. */ -import { CommonVislibParams } from './types'; +const serve = require('serve-static'); +const path = require('path'); -export interface PieVisParams extends CommonVislibParams { - type: 'pie'; - addLegend: boolean; - isDonut: boolean; - labels: { - show: boolean; - values: boolean; - last_level: boolean; - truncate: number | null; - }; -} +// Extend the Storybook Middleware to include a route to access Legacy UI assets +module.exports = function(router) { + router.get('/ui', serve(path.resolve(__dirname, '../../../../src/legacy/ui/public/assets'))); +}; diff --git a/packages/kbn-storybook/storybook_config/mocks/absolute_to_parsed_url.js b/packages/kbn-storybook/storybook_config/mocks/absolute_to_parsed_url.js new file mode 100644 index 0000000000000..65a27b095f84e --- /dev/null +++ b/packages/kbn-storybook/storybook_config/mocks/absolute_to_parsed_url.js @@ -0,0 +1,23 @@ +/* + * 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 absoluteToParsedUrl = () => { + getAbsoluteUrl: () => + 'http://localhost:5601/kbp/app/canvas#/workpad/workpad-24d56dad-ae70-42b8-9ef1-c5350ecd426c/page/1'; +}; // noop diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/index.js b/packages/kbn-storybook/storybook_config/mocks/noop.js old mode 100644 new mode 100755 similarity index 96% rename from src/legacy/ui/public/vis/editors/default/fancy_forms/index.js rename to packages/kbn-storybook/storybook_config/mocks/noop.js index 927e6d69e3c8a..aaddfb2ed8ac3 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/index.js +++ b/packages/kbn-storybook/storybook_config/mocks/noop.js @@ -17,4 +17,4 @@ * under the License. */ -import './fancy_forms'; +export default function() {} diff --git a/packages/kbn-storybook/storybook_config/mocks/state_store.js b/packages/kbn-storybook/storybook_config/mocks/state_store.js new file mode 100644 index 0000000000000..11bdf6632321d --- /dev/null +++ b/packages/kbn-storybook/storybook_config/mocks/state_store.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function getState() { + return { + assets: { + yay: { value: 'here is your image' }, + }, + }; +} diff --git a/packages/kbn-storybook/storybook_config/mocks/ui_storage.js b/packages/kbn-storybook/storybook_config/mocks/ui_storage.js new file mode 100644 index 0000000000000..4bd8cdeddfc22 --- /dev/null +++ b/packages/kbn-storybook/storybook_config/mocks/ui_storage.js @@ -0,0 +1,28 @@ +/* + * 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 class Storage { + get(key) { + return this[key]; + } + + set(key, value) { + this[key] = value; + } +} diff --git a/packages/kbn-storybook/storybook_config/preview-head.html b/packages/kbn-storybook/storybook_config/preview-head.html new file mode 100644 index 0000000000000..bef08a5120a36 --- /dev/null +++ b/packages/kbn-storybook/storybook_config/preview-head.html @@ -0,0 +1,6 @@ + + + diff --git a/packages/kbn-storybook/storybook_config/webpack.config.js b/packages/kbn-storybook/storybook_config/webpack.config.js new file mode 100644 index 0000000000000..72ff9162ffe6c --- /dev/null +++ b/packages/kbn-storybook/storybook_config/webpack.config.js @@ -0,0 +1,108 @@ +/* + * 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 { resolve } = require('path'); +const webpack = require('webpack'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const { REPO_ROOT, DLL_DIST_DIR } = require('../lib/constants'); +// eslint-disable-next-line import/no-unresolved +const { currentConfig } = require('../../../built_assets/storybook/current.config'); + +// Extend the Storybook Webpack config with some customizations +module.exports = async ({ config }) => { + // Find and alter the CSS rule to replace the Kibana public path string with a path + // to the route we've added in middleware.js + const cssRule = config.module.rules.find(rule => rule.test.source.includes('.css$')); + cssRule.use.push({ + loader: 'string-replace-loader', + options: { + search: '__REPLACE_WITH_PUBLIC_PATH__', + replace: '/', + flags: 'g', + }, + }); + + // Include the React preset from Kibana for Storybook JS files. + config.module.rules.push({ + test: /\.js$/, + exclude: /node_modules/, + loaders: 'babel-loader', + options: { + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }, + }); + + // Handle Typescript files + config.module.rules.push({ + test: /\.tsx?$/, + use: [ + { + loader: 'babel-loader', + options: { + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }, + }, + ], + }); + + // Parse props data for .tsx files + config.module.rules.push({ + test: /\.tsx$/, + // Exclude example files, as we don't display props info for them + exclude: /\.examples.tsx$/, + use: [ + // Parse TS comments to create Props tables in the UI + require.resolve('react-docgen-typescript-loader'), + ], + }); + + // Reference the built DLL file of static(ish) dependencies, which are removed + // during kbn:bootstrap and rebuilt if missing. + config.plugins.push( + new webpack.DllReferencePlugin({ + manifest: resolve(DLL_DIST_DIR, 'manifest.json'), + context: REPO_ROOT, + }) + ); + + // Copy the DLL files to the Webpack build for use in the Storybook UI + config.plugins.push( + new CopyWebpackPlugin([ + { + from: resolve(DLL_DIST_DIR, 'dll.js'), + to: 'dll.js', + }, + { + from: resolve(DLL_DIST_DIR, 'dll.css'), + to: 'dll.css', + }, + ]) + ); + + // Tell Webpack about the ts/x extensions + config.resolve.extensions.push('.ts', '.tsx'); + + // Load custom Webpack config specified by a plugin. + if (currentConfig.webpackHook) { + // eslint-disable-next-line import/no-dynamic-require + config = await require(currentConfig.webpackHook)({ config }); + } + + return config; +}; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index af44991e625a2..488f57b01e168 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,7 +9,7 @@ "kbn:watch": "node scripts/build --watch" }, "devDependencies": { - "@elastic/eui": "18.2.0", + "@elastic/eui": "18.2.1", "@elastic/charts": "^16.1.0", "@kbn/dev-utils": "1.0.0", "@yarnpkg/lockfile": "^1.1.0", diff --git a/renovate.json5 b/renovate.json5 index 7f67fae894110..5af62d0acef85 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -281,6 +281,14 @@ '@types/file-saver', ], }, + { + groupSlug: 'flot', + groupName: 'flot related packages', + packageNames: [ + 'flot', + '@types/flot', + ], + }, { groupSlug: 'getopts', groupName: 'getopts related packages', diff --git a/scripts/storybook.js b/scripts/storybook.js new file mode 100644 index 0000000000000..cc517bd5a4a32 --- /dev/null +++ b/scripts/storybook.js @@ -0,0 +1,21 @@ +/* + * 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. + */ + +require('../src/setup_node_env'); +require('../src/dev/storybook/run_storybook_cli'); diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 18f82766bdbc1..61c5d5b076a44 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -1,6 +1,12 @@ # Kibana Conventions -- [Plugin Structure](#plugin-structure) +- [Kibana Conventions](#kibana-conventions) + - [Plugin Structure](#plugin-structure) + - [The PluginInitializer](#the-plugininitializer) + - [The Plugin class](#the-plugin-class) + - [Applications](#applications) + - [Services](#services) + - [Usage Collection](#usage-collection) ## Plugin Structure diff --git a/src/core/CORE_CONVENTIONS.md b/src/core/CORE_CONVENTIONS.md new file mode 100644 index 0000000000000..76f3be1595258 --- /dev/null +++ b/src/core/CORE_CONVENTIONS.md @@ -0,0 +1,140 @@ +- [Core Conventions](#core-conventions) + - [1. Exposing API Types](#1-exposing-api-types) + - [2. API Structure and nesting](#2-api-structure-and-nesting) + - [3. Tests and mocks](#3-tests-and-mocks) + +# Core Conventions + +This document contains conventions for development inside `src/core`. Although +many of these might be more widely applicable, adoption within the rest of +Kibana is not the primary objective. + +## 1. Exposing API Types +The following section applies to the types that describe the entire surface +area of Core API's and does not apply to internal types. + + - 1.1 All API types must be exported from the top-level `server` or `public` + directories. + + ```ts + // -- good -- + import { IRouter } from 'src/core/server'; + + // -- bad -- + import { IRouter } from 'src/core/server/http/router.ts'; + ``` + + > Why? This is required for generating documentation from our inline + > typescript doc comments, makes it easier for API consumers to find the + > relevant types and creates a clear distinction between external and + > internal types. + + - 1.2 Classes must not be exposed directly. Instead, use a separate type, + prefixed with an 'I', to describe the public contract of the class. + + ```ts + // -- good (alternative 1) -- + /** + * @public + * {@link UiSettingsClient} + */ + export type IUiSettingsClient = PublicContractOf; + + /** internal only */ + export class UiSettingsClient { + constructor(private setting: string) {} + /** Retrieve all settings */ + public getSettings(): { return this.settings; } + }; + + // -- good (alternative 2) -- + export interface IUiSettingsClient { + /** Retrieve all settings */ + public getSettings(): string; + } + + export class UiSettingsClient implements IUiSettingsClient { + public getSettings(): string; + } + + // -- bad -- + /** external */ + export class UiSettingsClient { + constructor(private setting: string) {} + public getSettings(): { return this.settings; } + } + ``` + + > Why? Classes' private members form part of their type signature making it + > impossible to mock a dependency typed as a `class`. + > + > Until we can use ES private field support in Typescript 3.8 + > https://github.com/elastic/kibana/issues/54906 we have two alternatives + > each with their own pro's and cons: + > + > #### Using a derived class (alternative 1) + > + > Pro's: + > - TSDoc comments are located with the source code + > - The class acts as a single source of type information + > + > Con's: + > - "Go to definition" first takes you to where the type gets derived + > requiring a second "Go to definition" to navigate to the type source. + > + > #### Using a separate interface (alternative 2) + > Pro's: + > - Creates an explicit external API contract + > - "Go to definition" will take you directly to the type definition. + > + > Con's: + > - TSDoc comments are located with the interface not next to the + > implementation source code. + > - Creates duplicate type information between the interface and + > implementation class. + +## 2. API Structure and nesting + - 2.1 Nest API methods into their own namespace only if we expect we will be + adding additional methods to that namespace. + + ```ts + // good + core.overlays.openFlyout(...); + core.overlays.openModal(...); + core.overlays.banners.add(...); + core.overlays.banners.remove(...); + core.overlays.banners.replace(...); + + // bad + core.overlays.flyouts.open(...); + core.overlays.modals.open(...); + ``` + + > Why? Nested namespaces should facilitate discovery and navigation for + > consumers of the API. Having namespaces with a single method, effectively + > hides the method under an additional layer without improving the + > organization. However, introducing namespaces early on can avoid API + > churn when we know related API methods will be introduced. + +## 3. Tests and mocks + - 3.1 Declare Jest mocks with a temporary variable to ensure types are + correctly inferred. + + ```ts + // -- good -- + const createMock => { + const mocked: jest.Mocked = { + start: jest.fn(), + }; + mocked.start.mockReturnValue(createStartContractMock()); + return mocked; + }; + // -- bad -- + const createMock = (): jest.Mocked => ({ + start: jest.fn().mockReturnValue(createSetupContractMock()), + }); + ``` + + > Why? Without the temporary variable, Jest types the `start` function as + > `jest` and, as a result, doesn't typecheck the mock return + > value. diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 173d73ffab664..f51afd35586bd 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1131,6 +1131,7 @@ import { npStart: { core } } from 'ui/new_platform'; | Legacy Platform | New Platform | Notes | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `chrome.addBasePath` | [`core.http.basePath.prepend`](/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md) | | +| `chrome.navLinks.update` | [`core.appbase.updater`](/docs/development/core/public/kibana-plugin-public.appbase.updater_.md) | Use the `updater$` property when registering your application via `core.application.register` | | `chrome.breadcrumbs.set` | [`core.chrome.setBreadcrumbs`](/docs/development/core/public/kibana-plugin-public.chromestart.setbreadcrumbs.md) | | | `chrome.getUiSettingsClient` | [`core.uiSettings`](/docs/development/core/public/kibana-plugin-public.uisettingsclient.md) | | | `chrome.helpExtension.set` | [`core.chrome.setHelpExtension`](/docs/development/core/public/kibana-plugin-public.chromestart.sethelpextension.md) | | diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 04535039acbe7..d7964a53358ef 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -15,6 +15,7 @@ APIs to their New Platform equivalents. - [4. New Platform plugin](#4-new-platform-plugin) - [Accessing Services](#accessing-services) - [Chrome](#chrome) + - [Updating an application navlink](#updating-application-navlink) ## Configuration @@ -462,7 +463,59 @@ elsewhere. | `chrome.setVisible` | [`core.chrome.setIsVisible`](/docs/development/core/public/kibana-plugin-public.chromestart.setisvisible.md) | | | `chrome.getInjected` | [`core.injectedMetadata.getInjected`](/docs/development/core/public/kibana-plugin-public.coresetup.injectedmetadata.md) (temporary) | A temporary API is available to read injected vars provided by legacy plugins. This will be removed after [#41990](https://github.com/elastic/kibana/issues/41990) is completed. | | `chrome.setRootTemplate` / `chrome.setRootController` | -- | Use application mounting via `core.application.register` (not currently avaiable to legacy plugins). | +| `chrome.navLinks.update` | [`core.appbase.updater`](/docs/development/core/public/kibana-plugin-public.appbase.updater_.md) | Use the `updater$` property when registering your application via `core.application.register` | In most cases, the most convenient way to access these APIs will be via the [AppMountContext](/docs/development/core/public/kibana-plugin-public.appmountcontext.md) object passed to your application when your app is mounted on the page. + +### Updating an application navlink + +In the legacy platform, the navlink could be updated using `chrome.navLinks.update` + +```ts +uiModules.get('xpack/ml').run(() => { + const showAppLink = xpackInfo.get('features.ml.showLinks', false); + const isAvailable = xpackInfo.get('features.ml.isAvailable', false); + + const navLinkUpdates = { + // hide by default, only show once the xpackInfo is initialized + hidden: !showAppLink, + disabled: !showAppLink || (showAppLink && !isAvailable), + }; + + npStart.core.chrome.navLinks.update('ml', navLinkUpdates); +}); +``` + +In the new platform, navlinks should not be updated directly. Instead, it is now possible to add an `updater` when +registering an application to change the application or the navlink state at runtime. + +```ts +// my_plugin has a required dependencie to the `licensing` plugin +interface MyPluginSetupDeps { + licensing: LicensingPluginSetup; +} + +export class MyPlugin implements Plugin { + setup({ application }, { licensing }: MyPluginSetupDeps) { + const updater$ = licensing.license$.pipe( + map(license => { + const { hidden, disabled } = calcStatusFor(license); + if (hidden) return { navLinkStatus: AppNavLinkStatus.hidden }; + if (disabled) return { navLinkStatus: AppNavLinkStatus.disabled }; + return { navLinkStatus: AppNavLinkStatus.default }; + }) + ); + + application.register({ + id: 'my-app', + title: 'My App', + updater$, + async mount(params) { + const { renderApp } = await import('./application'); + return renderApp(params); + }, + }); + } +``` \ No newline at end of file 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 650ef77b6fe42..fec9322b0d77d 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -72,6 +72,10 @@ export interface ChromeNavLinks { /** * Update the navlink for the given id with the updated attributes. * Returns the updated navlink or `undefined` if it does not exist. + * + * @deprecated Uses the {@link AppBase.updater$} property when registering + * your application with {@link ApplicationSetup.register} instead. + * * @param id * @param values */ diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 610b08708c681..abd39e864bd30 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -279,6 +279,7 @@ export interface ChromeNavLinks { getNavLinks$(): Observable>>; has(id: string): boolean; showOnly(id: string): void; + // @deprecated update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index eccf3985fc495..50d291b173640 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -152,7 +152,7 @@ export { SessionCookieValidationResult, SessionStorageFactory, } from './http'; -export { RenderingServiceSetup, IRenderOptions, LegacyRenderOptions } from './rendering'; +export { RenderingServiceSetup, IRenderOptions } from './rendering'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; export { @@ -216,6 +216,9 @@ export { UiSettingsServiceSetup, UiSettingsServiceStart, UserProvidedValues, + ImageValidation, + DeprecationSettings, + StringValidation, } from './ui_settings'; export { RecursiveReadonly } from '../utils'; diff --git a/src/core/server/legacy/legacy_internals.ts b/src/core/server/legacy/legacy_internals.ts index 3bf54e5f75dce..628ca4ed12f6b 100644 --- a/src/core/server/legacy/legacy_internals.ts +++ b/src/core/server/legacy/legacy_internals.ts @@ -19,7 +19,8 @@ import { Server } from 'hapi'; -import { LegacyRequest } from '../http'; +import { KibanaRequest, LegacyRequest } from '../http'; +import { ensureRawRequest } from '../http/router'; import { mergeVars } from './merge_vars'; import { ILegacyInternals, LegacyVars, VarsInjector, LegacyConfig, LegacyUiExports } from './types'; @@ -51,11 +52,12 @@ export class LegacyInternals implements ILegacyInternals { )); } - private replaceVars(vars: LegacyVars, request: LegacyRequest) { + private replaceVars(vars: LegacyVars, request: KibanaRequest | LegacyRequest) { const { injectedVarsReplacers = [] } = this.uiExports; return injectedVarsReplacers.reduce( - async (injected, replacer) => replacer(await injected, request, this.server), + async (injected, replacer) => + replacer(await injected, ensureRawRequest(request), this.server), Promise.resolve(vars) ); } @@ -78,7 +80,11 @@ export class LegacyInternals implements ILegacyInternals { ); } - public async getVars(id: string, request: LegacyRequest, injected: LegacyVars = {}) { + public async getVars( + id: string, + request: KibanaRequest | LegacyRequest, + injected: LegacyVars = {} + ) { return this.replaceVars( mergeVars(this.defaultVars, await this.getInjectedUiAppVars(id), injected), request diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 7a03cefc38c1a..ffcbf1662ee85 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -31,6 +31,7 @@ import { PathConfigType } from '../path'; import { findLegacyPluginSpecs } from './plugins'; import { convertLegacyDeprecationProvider } from './config'; import { + ILegacyInternals, LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyPlugins, @@ -82,6 +83,7 @@ export class LegacyService implements CoreService { private legacyRawConfig?: LegacyConfig; private legacyPlugins?: LegacyPlugins; private settings?: LegacyVars; + public legacyInternals?: ILegacyInternals; constructor(private readonly coreContext: CoreContext) { const { logger, configService } = coreContext; @@ -183,6 +185,11 @@ export class LegacyService implements CoreService { // propagate the instance uuid to the legacy config, as it was the legacy way to access it. this.legacyRawConfig!.set('server.uuid', setupDeps.core.uuid.getInstanceUuid()); this.setupDeps = setupDeps; + this.legacyInternals = new LegacyInternals( + this.legacyPlugins.uiExports, + this.legacyRawConfig!, + setupDeps.core.http.server + ); } public async start(startDeps: LegacyServiceStartDeps) { @@ -317,7 +324,7 @@ export class LegacyService implements CoreService { rendering: setupDeps.core.rendering, uiSettings: setupDeps.core.uiSettings, savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider, - legacy: new LegacyInternals(legacyPlugins.uiExports, config, setupDeps.core.http.server), + legacy: this.legacyInternals, }, logger: this.coreContext.logger, }, diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 6ec893be9b310..40b8244a31890 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -20,7 +20,7 @@ import { Server } from 'hapi'; import { ChromeNavLink } from '../../public'; -import { LegacyRequest } from '../http'; +import { KibanaRequest, LegacyRequest } from '../http'; import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { PluginsServiceSetup, PluginsServiceStart } from '../plugins'; import { RenderingServiceSetup } from '../rendering'; @@ -198,7 +198,11 @@ export interface ILegacyInternals { /** * Get the metadata vars for a particular plugin */ - getVars(id: string, request: LegacyRequest, injected?: LegacyVars): Promise; + getVars( + id: string, + request: KibanaRequest | LegacyRequest, + injected?: LegacyVars + ): Promise; } /** diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 41810c6a10655..11d1fb271c81d 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -27,10 +27,10 @@ import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Template } from './views'; import { + IRenderOptions, RenderingSetupDeps, RenderingServiceSetup, RenderingMetadata, - LegacyRenderOptions, } from './types'; /** @internal */ @@ -56,7 +56,7 @@ export class RenderingService implements CoreService { app = { getId: () => 'core' }, includeUserSettings = true, vars = {}, - }: LegacyRenderOptions = {} + }: IRenderOptions = {} ) => { const { env } = this.coreContext; const basePath = http.basePath.get(request); diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 31b326bab6c78..3f9f6ff294909 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -84,21 +84,19 @@ export interface IRenderOptions { * `true` by default. */ includeUserSettings?: boolean; -} -/** - * @internal - * @deprecated for legacy use only, remove with ui_render_mixin - */ -export interface LegacyRenderOptions extends IRenderOptions { /** * Render the bootstrapped HTML content for an optional legacy application. * Defaults to `core`. + * @deprecated for legacy use only, remove with ui_render_mixin + * @internal */ app?: { getId(): string }; /** * Inject custom vars into the page metadata. + * @deprecated for legacy use only, remove with ui_render_mixin + * @internal */ vars?: Record; } @@ -123,7 +121,7 @@ export interface IScopedRenderingClient { * ); * ``` */ - render(options?: IRenderOptions): Promise; + render(options?: Pick): Promise; } /** @internal */ @@ -140,6 +138,6 @@ export interface RenderingServiceSetup { render( request: R, uiSettings: IUiSettingsClient, - options?: R extends LegacyRequest ? LegacyRenderOptions : IRenderOptions + options?: IRenderOptions ): Promise; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 7f3a960571012..6a58666716f42 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -635,6 +635,12 @@ export interface DeprecationInfo { url: string; } +// @public +export interface DeprecationSettings { + docLinksKey: string; + message: string; +} + // @public export interface DiscoveredPlugin { readonly configPath: ConfigPath; @@ -795,6 +801,17 @@ export interface IKibanaSocket { getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate | null; } +// Warning: (ae-missing-release-tag) "ImageValidation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ImageValidation { + // (undocumented) + maxSize: { + length: number; + description: string; + }; +} + // @public (undocumented) export interface IndexSettingsDeprecationInfo { // (undocumented) @@ -803,7 +820,13 @@ export interface IndexSettingsDeprecationInfo { // @public (undocumented) export interface IRenderOptions { + // @internal @deprecated + app?: { + getId(): string; + }; includeUserSettings?: boolean; + // @internal @deprecated + vars?: Record; } // @public @@ -832,7 +855,7 @@ export type IScopedClusterClient = Pick; + render(options?: Pick): Promise; } // @public @@ -932,21 +955,13 @@ export class LegacyInternals implements ILegacyInternals { // (undocumented) getInjectedUiAppVars(id: string): Promise>; // (undocumented) - getVars(id: string, request: LegacyRequest, injected?: LegacyVars): Promise>; + getVars(id: string, request: KibanaRequest | LegacyRequest, injected?: LegacyVars): Promise>; // Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts // // (undocumented) injectUiAppVars(id: string, injector: VarsInjector): void; } -// @internal @deprecated (undocumented) -export interface LegacyRenderOptions extends IRenderOptions { - app?: { - getId(): string; - }; - vars?: Record; -} - // @public @deprecated (undocumented) export interface LegacyRequest extends Request { } @@ -1233,7 +1248,7 @@ export type RedirectResponseOptions = HttpResponseOptions & { // @internal (undocumented) export interface RenderingServiceSetup { - render(request: R, uiSettings: IUiSettingsClient, options?: R extends LegacyRequest ? LegacyRenderOptions : IRenderOptions): Promise; + render(request: R, uiSettings: IUiSettingsClient, options?: IRenderOptions): Promise; } // @public @@ -1925,10 +1940,19 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{ path: Pick; }>; +// Warning: (ae-missing-release-tag) "StringValidation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface StringValidation { + // (undocumented) + message: string; + // (undocumented) + regexString: string; +} + // @public export interface UiSettingsParams { category?: string[]; - // Warning: (ae-forgotten-export) The symbol "DeprecationSettings" needs to be exported by the entry point index.d.ts deprecation?: DeprecationSettings; description?: string; name?: string; @@ -1937,9 +1961,6 @@ export interface UiSettingsParams { readonly?: boolean; requiresPageReload?: boolean; type?: UiSettingsType; - // Warning: (ae-forgotten-export) The symbol "ImageValidation" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "StringValidation" needs to be exported by the entry point index.d.ts - // // (undocumented) validation?: ImageValidation | StringValidation; value?: SavedObjectAttribute; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 7c3f9f249db13..89a5bdc4802fd 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -220,7 +220,11 @@ export class Server { return { rendering: { - render: rendering.render.bind(rendering, req, uiSettingsClient), + render: async (options = {}) => + rendering.render(req, uiSettingsClient, { + ...options, + vars: await this.legacy.legacyInternals!.getVars('core', req), + }), }, savedObjects: { client: savedObjectsClient, diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts index f1185474c2160..39eb0046010b3 100644 --- a/src/core/server/ui_settings/index.ts +++ b/src/core/server/ui_settings/index.ts @@ -31,4 +31,7 @@ export { InternalUiSettingsServiceStart, UiSettingsType, UserProvidedValues, + ImageValidation, + DeprecationSettings, + StringValidation, } from './types'; diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index 14eb71a22cefc..a74a31bbbd671 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -78,7 +78,9 @@ export interface UserProvidedValues { * @public * */ export interface DeprecationSettings { + /** Deprecation message */ message: string; + /** Key to documentation links */ docLinksKey: string; } diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 1c95e75396bcc..807a3fbf4782b 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -58,6 +58,7 @@ export default { '^ui/(.*)': '/src/legacy/ui/public/$1', '^uiExports/(.*)': '/src/dev/jest/mocks/file_mock.js', '^test_utils/(.*)': '/src/test_utils/public/$1', + '^fixtures/(.*)': '/src/fixtures/$1', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/dev/jest/mocks/file_mock.js', '\\.(css|less|scss)$': '/src/dev/jest/mocks/style_mock.js', diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index bd084767a723f..8dccb9830526c 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -101,9 +101,6 @@ export const LICENSE_OVERRIDES = { // TODO can be removed if the PR#9 is accepted on the source 'pause-stream@0.0.11': ['MIT'], - // TODO can be removed once we upgrade past or equal pdf-image@2.0.1 - 'pdf-image@1.1.0': ['MIT'], - // TODO can be removed once we upgrade the use of walk dependency past or equal to v2.3.14 'walk@2.3.9': ['MIT'], }; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index e18852e353b00..e5493df0aecf7 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -52,6 +52,9 @@ export const IGNORE_FILE_GLOBS = [ // filename must match language code which requires capital letters '**/translations/*.json', + + // filename is required by storybook + 'packages/kbn-storybook/storybook_config/preview-head.html', ]; /** @@ -117,7 +120,7 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/seriesList.js', 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/tlConfig.js', 'src/fixtures/config_upgrade_from_4.0.0_to_4.0.1-snapshot.json', - 'src/fixtures/vislib/mock_data/terms/_seriesMultiple.js', + 'src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js', 'src/legacy/ui/public/angular-bootstrap/bindHtml/bindHtml.js', 'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-html-unsafe-popup.html', 'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-popup.html', diff --git a/src/dev/register_git_hook/register_git_hook.js b/src/dev/register_git_hook/register_git_hook.js index 99ae351fe383e..8820327d3adc0 100644 --- a/src/dev/register_git_hook/register_git_hook.js +++ b/src/dev/register_git_hook/register_git_hook.js @@ -65,7 +65,7 @@ function getKbnPrecommitGitHookScript(rootPath, nodeHome, platform) { # The correct exit code on that situation # according the linux documentation project is 130 # https://www.tldp.org/LDP/abs/html/exitcodes.html - trap "exit 130" SIGINT + trap "exit 130" INT has_node() { command -v node >/dev/null 2>&1 diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts new file mode 100644 index 0000000000000..1dce53b6c2a84 --- /dev/null +++ b/src/dev/storybook/aliases.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const storybookAliases = { + apm: 'x-pack/legacy/plugins/apm/scripts/storybook.js', + canvas: 'x-pack/legacy/plugins/canvas/scripts/storybook_new.js', + embeddable: 'src/plugins/embeddable/scripts/storybook.js', + infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', + siem: 'x-pack/legacy/plugins/siem/scripts/storybook.js', +}; diff --git a/src/dev/storybook/commands/clean.ts b/src/dev/storybook/commands/clean.ts new file mode 100644 index 0000000000000..328c4d9e2c23c --- /dev/null +++ b/src/dev/storybook/commands/clean.ts @@ -0,0 +1,32 @@ +/* + * 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 { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { join } from 'path'; +import del from 'del'; + +export const clean = async ({ log }: { log: ToolingLog }) => { + log.info('Cleaning Storybook build folder'); + + const dir = join(REPO_ROOT, 'built_assets', 'storybook'); + log.info('Deleting folder:', dir); + await del([join(dir, '*')]); + await del([dir]); +}; diff --git a/src/dev/storybook/run_storybook_cli.ts b/src/dev/storybook/run_storybook_cli.ts new file mode 100644 index 0000000000000..0f7dc40ceef0b --- /dev/null +++ b/src/dev/storybook/run_storybook_cli.ts @@ -0,0 +1,78 @@ +/* + * 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 { join } from 'path'; +import { run, createFlagError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { storybookAliases } from './aliases'; +import { clean } from './commands/clean'; + +run( + async params => { + const { flags, log } = params; + const { + _: [alias], + } = flags; + + if (flags.verbose) { + log.verbose('Flags:', flags); + } + + if (flags.clean) { + await clean({ log }); + return; + } + + if (!alias) { + throw createFlagError('missing alias'); + } + + if (!storybookAliases.hasOwnProperty(alias)) { + throw createFlagError(`unknown alias [${alias}]`); + } + + const relative = (storybookAliases as any)[alias]; + const absolute = join(REPO_ROOT, relative); + + log.verbose('Loading Storybook:', absolute); + process.chdir(join(absolute, '..', '..')); + require(absolute); + }, + { + usage: `node scripts/storybook `, + description: ` + Start a 📕 Storybook for a plugin + + Available aliases: + ${Object.keys(storybookAliases) + .map(alias => `📕 ${alias}`) + .join('\n ')} + + Add your alias in src/dev/storybook/aliases.ts + `, + flags: { + default: {}, + string: [], + boolean: ['clean'], + help: ` + --clean Clean Storybook build folder. + `, + }, + } +); diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js b/src/legacy/core_plugins/console/common/text_object.ts similarity index 58% rename from src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js rename to src/legacy/core_plugins/console/common/text_object.ts index 1f0788cf74d1d..3b3464a77ac4e 100644 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js +++ b/src/legacy/core_plugins/console/common/text_object.ts @@ -17,13 +17,32 @@ * under the License. */ -import { uiModules } from '../../../../modules'; +export const textObjectTypeName = 'text-object'; -import { decorateFormController } from './kbn_form_controller'; -import { decorateModelController } from './kbn_model_controller'; +/** + * Describes the shape of persisted objects that contain information about the current text in the + * text editor. + */ +export interface TextObject { + /** + * An ID that uniquely identifies this object. + */ + id: string; + + /** + * UNIX timestamp of when the object was created. + */ + createdAt: number; + + /** + * UNIX timestamp of when the object was last updated. + */ + updatedAt: number; -uiModules.get('kibana').config(function($provide) { - $provide.decorator('formDirective', decorateFormController); - $provide.decorator('ngFormDirective', decorateFormController); - $provide.decorator('ngModelDirective', decorateModelController); -}); + /** + * Text value input by the user. + * + * Used to re-populate a text editor buffer. + */ + text: string; +} diff --git a/src/legacy/core_plugins/console/common/types.ts b/src/legacy/core_plugins/console/common/types.ts new file mode 100644 index 0000000000000..33d6907ff60b8 --- /dev/null +++ b/src/legacy/core_plugins/console/common/types.ts @@ -0,0 +1,49 @@ +/* + * 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 { TextObject } from './text_object'; + +export interface IdObject { + id: string; +} + +export interface ObjectStorage { + /** + * Creates a new object in the underlying persistance layer. + * + * @remarks Does not accept an ID, a new ID is generated and returned with the newly created object. + */ + create(obj: Omit): Promise; + + /** + * This method should update specific object in the persistance layer. + */ + update(obj: O): Promise; + + /** + * A function that will return all of the objects in the persistance layer. + * + * @remarks Unless an error is thrown this function should always return an array (empty if there are not objects present). + */ + findAll(): Promise; +} + +export interface ObjectStorageClient { + text: ObjectStorage; +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/editor_content_spinner.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_content_spinner.tsx new file mode 100644 index 0000000000000..2ae4545ad24ef --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_content_spinner.tsx @@ -0,0 +1,29 @@ +/* + * 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, { FunctionComponent } from 'react'; +import { EuiLoadingContent, EuiPageContent } from '@elastic/eui'; + +export const EditorContentSpinner: FunctionComponent = () => { + return ( + + + + ); +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts index 0f109e99b0b39..4669e210e7c2d 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts @@ -18,8 +18,10 @@ */ export * from './split_panel'; +export { SomethingWentWrongCallout } from './something_went_wrong_callout'; export { TopNavMenuItem, TopNavMenu } from './top_nav_menu'; export { ConsoleMenu } from './console_menu'; export { WelcomePanel } from './welcome_panel'; export { AutocompleteOptions, DevToolsSettingsModal } from './settings_modal'; export { HelpPanel } from './help_panel'; +export { EditorContentSpinner } from './editor_content_spinner'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx new file mode 100644 index 0000000000000..7b643bc84dd34 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx @@ -0,0 +1,58 @@ +/* + * 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, { FunctionComponent, useEffect } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiText, EuiButton, EuiSpacer } from '@elastic/eui'; + +interface Props { + error: Error; + onButtonClick: () => void; +} + +export const SomethingWentWrongCallout: FunctionComponent = ({ error, onButtonClick }) => { + useEffect(() => { + // eslint-disable-next-line no-console + console.error(error); + }, [error]); + + return ( + + +

+ +

+
+ + onButtonClick()}> + + +
+ ); +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap index 0a40e3e84211d..36f4dec1a1f54 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap @@ -8,13 +8,13 @@ exports[`Split panel should render correctly 1`] = ` "panels": Array [ Object { "getWidth": [Function], - "initialWidth": "100%", "setWidth": [Function], + "width": 100, }, Object { "getWidth": [Function], - "initialWidth": "100%", "setWidth": [Function], + "width": 100, }, ], } @@ -55,15 +55,39 @@ exports[`Split panel should render correctly 1`] = ` -
- ︙ -
+ + + + + +
; +export type ResizerMouseEvent = React.MouseEvent; +export type ResizerKeyDownEvent = React.KeyboardEvent; export interface Props { + onKeyDown: (eve: ResizerKeyDownEvent) => void; onMouseDown: (eve: ResizerMouseEvent) => void; + className?: string; } -/** - * TODO: This component uses styling constants from public UI - should be removed, next iteration should incl. horizontal and vertical resizers. - */ export function Resizer(props: Props) { return ( -
- ︙ -
+ ); } diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx index 80960a7772ba1..2eb39f0808ad0 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx @@ -22,20 +22,26 @@ import { usePanelContext } from '../context'; export interface Props { children: ReactNode[] | ReactNode; - initialWidth?: string; + className?: string; + + /** + * initial width of the panel in percents + */ + initialWidth?: number; style?: CSSProperties; } -export function Panel({ children, initialWidth = '100%', style = {} }: Props) { - const [width, setWidth] = useState(initialWidth); +export function Panel({ children, className, initialWidth = 100, style = {} }: Props) { + const [width, setWidth] = useState(`${initialWidth}%`); const { registry } = usePanelContext(); const divRef = useRef(null); useEffect(() => { registry.registerPanel({ - initialWidth, + width: initialWidth, setWidth(value) { setWidth(value + '%'); + this.width = value; }, getWidth() { return divRef.current!.getBoundingClientRect().width; @@ -44,7 +50,7 @@ export function Panel({ children, initialWidth = '100%', style = {} }: Props) { }, [initialWidth, registry]); return ( -
+
{children}
); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx index fef65a954bd60..c9d7b01f87967 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx @@ -17,14 +17,17 @@ * under the License. */ -import React, { Children, ReactNode, useRef, useState } from 'react'; +import React, { Children, ReactNode, useRef, useState, useCallback } from 'react'; +import { keyCodes } from '@elastic/eui'; import { PanelContextProvider } from '../context'; -import { Resizer } from '../components/resizer'; +import { Resizer, ResizerMouseEvent, ResizerKeyDownEvent } from '../components/resizer'; import { PanelRegistry } from '../registry'; export interface Props { children: ReactNode; + className?: string; + resizerClassName?: string; onPanelWidthChange?: (arrayOfPanelWidths: number[]) => any; } @@ -37,7 +40,12 @@ const initialState: State = { isDragging: false, currentResizerPos: -1 }; const pxToPercent = (proportion: number, whole: number) => (proportion / whole) * 100; -export function PanelsContainer({ children, onPanelWidthChange }: Props) { +export function PanelsContainer({ + children, + className, + onPanelWidthChange, + resizerClassName, +}: Props) { const [firstChild, secondChild] = Children.toArray(children); const registryRef = useRef(new PanelRegistry()); @@ -48,18 +56,48 @@ export function PanelsContainer({ children, onPanelWidthChange }: Props) { return containerRef.current!.getBoundingClientRect().width; }; + const handleMouseDown = useCallback( + (event: ResizerMouseEvent) => { + setState({ + ...state, + isDragging: true, + currentResizerPos: event.clientX, + }); + }, + [state] + ); + + const handleKeyDown = useCallback( + (ev: ResizerKeyDownEvent) => { + const { keyCode } = ev; + + if (keyCode === keyCodes.LEFT || keyCode === keyCodes.RIGHT) { + ev.preventDefault(); + + const { current: registry } = registryRef; + const [left, right] = registry.getPanels(); + + const leftPercent = left.width - (keyCode === keyCodes.LEFT ? 1 : -1); + const rightPercent = right.width - (keyCode === keyCodes.RIGHT ? 1 : -1); + + left.setWidth(leftPercent); + right.setWidth(rightPercent); + + if (onPanelWidthChange) { + onPanelWidthChange([leftPercent, rightPercent]); + } + } + }, + [onPanelWidthChange] + ); + const childrenWithResizer = [ firstChild, { - event.preventDefault(); - setState({ - ...state, - isDragging: true, - currentResizerPos: event.clientX, - }); - }} + className={resizerClassName} + onKeyDown={handleKeyDown} + onMouseDown={handleMouseDown} />, secondChild, ]; @@ -67,6 +105,7 @@ export function PanelsContainer({ children, onPanelWidthChange }: Props) { return (
{ diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts index 5f06ab8915270..e275da9e2ac74 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts @@ -20,7 +20,7 @@ export interface PanelController { setWidth: (percent: number) => void; getWidth: () => number; - initialWidth: string; + width: number; } export class PanelRegistry { @@ -35,6 +35,6 @@ export class PanelRegistry { } getPanels() { - return this.panels.map(panel => ({ ...panel })); + return this.panels; } } diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx index 304535421a78a..02153d1a1d3cd 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx @@ -65,8 +65,8 @@ describe('Split panel', () => { const panelContainer = mount( - {testComponentA} - {testComponentB} + {testComponentA} + {testComponentB} ); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx index c83237c52febd..92f3f32da9a4e 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { EuiTabs, EuiTab } from '@elastic/eui'; export interface TopNavMenuItem { @@ -29,19 +29,26 @@ export interface TopNavMenuItem { } interface Props { + disabled?: boolean; items: TopNavMenuItem[]; } -export function TopNavMenu({ items }: Props) { +export const TopNavMenu: FunctionComponent = ({ items, disabled }) => { return ( {items.map((item, idx) => { return ( - + {item.label} ); })} ); -} +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx index 07b48c083bf61..7be1382760eb9 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx @@ -20,19 +20,26 @@ import React, { useCallback } from 'react'; import { debounce } from 'lodash'; +import { EditorContentSpinner } from '../../components'; import { Panel, PanelsContainer } from '../../components/split_panel'; import { Editor as EditorUI, EditorOutput } from './legacy/console_editor'; import { StorageKeys } from '../../../services'; -import { useServicesContext } from '../../contexts'; +import { useEditorReadContext, useServicesContext } from '../../contexts'; const INITIAL_PANEL_WIDTH = 50; const PANEL_MIN_WIDTH = '100px'; -export const Editor = () => { +interface Props { + loading: boolean; +} + +export const Editor = ({ loading }: Props) => { const { services: { storage }, } = useServicesContext(); + const { currentTextObject } = useEditorReadContext(); + const [firstPanelWidth, secondPanelWidth] = storage.get(StorageKeys.WIDTH, [ INITIAL_PANEL_WIDTH, INITIAL_PANEL_WIDTH, @@ -45,19 +52,25 @@ export const Editor = () => { [] ); + if (!currentTextObject) return null; + return ( - + - + {loading ? ( + + ) : ( + + )} - + {loading ? : } ); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx index 73ee6d160613f..d4079fcea33f8 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -52,7 +52,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { - + @@ -72,6 +72,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { updateCurrentState: jest.fn(), } as any, notifications: notificationServiceMock.createSetupContract(), + objectStorageClient: {} as any, }, docLinkVersion: 'NA', }; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx index 761a252b56a87..759e3dbafb39c 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx @@ -35,7 +35,11 @@ import { autoIndent, getDocumentation } from '../console_menu_actions'; import { registerCommands } from './keyboard_shortcuts'; import { applyCurrentSettings } from './apply_editor_settings'; -import { useSendCurrentRequestToES, useSetInputEditor } from '../../../../hooks'; +import { + useSendCurrentRequestToES, + useSetInputEditor, + useSaveCurrentTextObject, +} from '../../../../hooks'; import * as senseEditor from '../../../../models/sense_editor'; // @ts-ignore @@ -43,6 +47,10 @@ import mappings from '../../../../../lib/mappings/mappings'; import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; +export interface EditorProps { + initialTextValue: string; +} + const abs: CSSProperties = { position: 'absolute', top: '0', @@ -58,7 +66,7 @@ const DEFAULT_INPUT_VALUE = `GET _search } }`; -function EditorUI() { +function EditorUI({ initialTextValue }: EditorProps) { const { services: { history, notifications }, docLinkVersion, @@ -68,6 +76,7 @@ function EditorUI() { const { settings } = useEditorReadContext(); const setInputEditor = useSetInputEditor(); const sendCurrentRequestToES = useSendCurrentRequestToES(); + const saveCurrentTextObject = useSaveCurrentTextObject(); const editorRef = useRef(null); const editorInstanceRef = useRef(null); @@ -132,10 +141,7 @@ function EditorUI() { if (initialQueryParams.load_from) { loadBufferFromRemote(initialQueryParams.load_from); } else { - const { content: text } = history.getSavedEditorState() || { - content: DEFAULT_INPUT_VALUE, - }; - editor.update(text); + editor.update(initialTextValue || DEFAULT_INPUT_VALUE); } function setupAutosave() { @@ -153,7 +159,7 @@ function EditorUI() { function saveCurrentState() { try { const content = editor.getCoreEditor().getValue(); - history.updateCurrentState(content); + saveCurrentTextObject(content); } catch (e) { // Ignoring saving error } @@ -172,7 +178,7 @@ function EditorUI() { mappings.clearSubscriptions(); window.removeEventListener('hashchange', onHashChange); }; - }, [history, setInputEditor]); + }, [saveCurrentTextObject, initialTextValue, history, setInputEditor]); useEffect(() => { const { current: editor } = editorInstanceRef; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx index 764c4b8e87100..902d800b3e56b 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx @@ -19,14 +19,15 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiPageContent } from '@elastic/eui'; import { ConsoleHistory } from '../console_history'; import { Editor } from '../editor'; import { Settings } from '../settings'; -import { TopNavMenu, WelcomePanel, HelpPanel } from '../../components'; +import { TopNavMenu, WelcomePanel, HelpPanel, SomethingWentWrongCallout } from '../../components'; import { useServicesContext, useEditorReadContext } from '../../contexts'; +import { useDataInit } from '../../hooks'; import { getTopNavConfig } from './get_top_nav'; @@ -48,6 +49,15 @@ export function Main() { const renderConsoleHistory = () => { return editorsReady ? setShowHistory(false)} /> : null; }; + const { done, error, retry } = useDataInit(); + + if (error) { + return ( + + + + ); + } return (
@@ -66,6 +76,7 @@ export function Main() { setShowHistory(!showingHistory), onClickSettings: () => setShowSettings(true), @@ -75,11 +86,11 @@ export function Main() { {showingHistory ? {renderConsoleHistory()} : null} - + - {showWelcome ? ( + {done && showWelcome ? ( { storage.set('version_welcome_shown', '@@SENSE_REVISION'); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx index f14685ecd4ac7..d7f036e1aecb6 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx @@ -20,6 +20,7 @@ import React, { createContext, useContext } from 'react'; import { NotificationsSetup } from 'kibana/public'; import { History, Storage, Settings } from '../../services'; +import { ObjectStorageClient } from '../../../../common/types'; import { MetricsTracker } from '../../types'; export interface ContextValue { @@ -28,6 +29,7 @@ export interface ContextValue { storage: Storage; settings: Settings; notifications: NotificationsSetup; + objectStorageClient: ObjectStorageClient; trackUiMetric: MetricsTracker; }; elasticsearchUrl: string; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts index 8c5a8d599a0df..72e83e883553d 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts @@ -20,3 +20,5 @@ export { useSetInputEditor } from './use_set_input_editor'; export { useRestoreRequestFromHistory } from './use_restore_request_from_history'; export { useSendCurrentRequestToES } from './use_send_current_request_to_es'; +export { useSaveCurrentTextObject } from './use_save_current_text_object'; +export { useDataInit } from './use_data_init'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/data_migration.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/data_migration.ts new file mode 100644 index 0000000000000..08acd78ba2b8a --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/data_migration.ts @@ -0,0 +1,46 @@ +/* + * 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 '../../../services'; +import { ObjectStorageClient } from '../../../../../common/types'; + +export interface Dependencies { + history: History; + objectStorageClient: ObjectStorageClient; +} + +/** + * Once off migration to new text object data structure + */ +export async function migrateToTextObjects({ + history, + objectStorageClient: objectStorageClient, +}: Dependencies): Promise { + const legacyTextContent = history.getLegacySavedEditorState(); + + if (!legacyTextContent) return; + + await objectStorageClient.text.create({ + createdAt: Date.now(), + updatedAt: Date.now(), + text: legacyTextContent.content, + }); + + history.deleteLegacySavedEditorState(); +} diff --git a/src/legacy/ui/public/vis/editors/default/agg_params.d.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts similarity index 93% rename from src/legacy/ui/public/vis/editors/default/agg_params.d.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts index 89896c0e1be3e..582aa047f7d40 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_params.d.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts @@ -16,7 +16,4 @@ * specific language governing permissions and limitations * under the License. */ - -export interface AggParams { - [key: string]: unknown; -} +export { useDataInit } from './use_data_init'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/use_data_init.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/use_data_init.ts new file mode 100644 index 0000000000000..2212827c1f598 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/use_data_init.ts @@ -0,0 +1,72 @@ +/* + * 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 { useCallback, useEffect, useState } from 'react'; +import { migrateToTextObjects } from './data_migration'; +import { useEditorActionContext, useServicesContext } from '../../contexts'; + +export const useDataInit = () => { + const [error, setError] = useState(null); + const [done, setDone] = useState(false); + const [retryToken, setRetryToken] = useState({}); + + const retry = useCallback(() => { + setRetryToken({}); + setDone(false); + setError(null); + }, []); + + const { + services: { objectStorageClient, history }, + } = useServicesContext(); + + const dispatch = useEditorActionContext(); + + useEffect(() => { + const load = async () => { + try { + await migrateToTextObjects({ history, objectStorageClient }); + const results = await objectStorageClient.text.findAll(); + if (!results.length) { + const newObject = await objectStorageClient.text.create({ + createdAt: Date.now(), + updatedAt: Date.now(), + text: '', + }); + dispatch({ type: 'setCurrentTextObject', payload: newObject }); + } else { + // For now, we always take the first text object returned. + dispatch({ type: 'setCurrentTextObject', payload: results[0] }); + } + } catch (e) { + setError(e); + } finally { + setDone(true); + } + }; + + load(); + }, [dispatch, objectStorageClient, history, retryToken]); + + return { + error, + done, + retry, + }; +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_save_current_text_object.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_save_current_text_object.ts new file mode 100644 index 0000000000000..ab517ba1bfdd1 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_save_current_text_object.ts @@ -0,0 +1,49 @@ +/* + * 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 { useRef, useCallback } from 'react'; +import { throttle } from 'lodash'; +import { useEditorReadContext, useServicesContext } from '../contexts'; + +const WAIT_MS = 500; + +export const useSaveCurrentTextObject = () => { + const promiseChainRef = useRef(Promise.resolve()); + + const { + services: { objectStorageClient }, + } = useServicesContext(); + + const { currentTextObject } = useEditorReadContext(); + + return useCallback( + throttle( + (text: string) => { + const { current: promise } = promiseChainRef; + if (!currentTextObject) return; + promise.finally(() => + objectStorageClient.text.update({ ...currentTextObject, text, updatedAt: Date.now() }) + ); + }, + WAIT_MS, + { trailing: true } + ), + [objectStorageClient, currentTextObject] + ); +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/index.tsx b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx index 89756513b2b22..efd0f2ba86024 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/index.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx @@ -18,10 +18,11 @@ */ import React from 'react'; -import { NotificationsSetup } from 'kibana/public'; +import { NotificationsSetup } from 'src/core/public'; import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts'; import { Main } from './containers'; import { createStorage, createHistory, createSettings, Settings } from '../services'; +import * as localStorageObjectClient from '../lib/local_storage_object_client'; import { createUsageTracker } from '../services/tracker'; let settingsRef: Settings; @@ -46,6 +47,7 @@ export function boot(deps: { }); const history = createHistory({ storage }); const settings = createSettings({ storage }); + const objectStorageClient = localStorageObjectClient.create(storage); settingsRef = settings; return ( @@ -60,6 +62,7 @@ export function boot(deps: { settings, notifications, trackUiMetric, + objectStorageClient, }, }} > diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js index b7b6c5bade9ad..842736428e8bb 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js @@ -89,6 +89,7 @@ export function InputHighlightRules() { addEOL(['url.amp'], /(&)/, 'start') ), 'url-sql': mergeTokens( + addEOL(['url.part'], /([^?\/,\s]+)/, 'start-sql'), addEOL(['url.comma'], /(,)/, 'start-sql'), addEOL(['url.slash'], /(\/)/, 'start-sql'), addEOL(['url.questionmark'], /(\?)/, 'start-sql', 'urlParams-sql') diff --git a/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts index 339a2f7a2c4af..844eacd9b91a8 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts @@ -21,22 +21,26 @@ import { Reducer } from 'react'; import { produce } from 'immer'; import { identity } from 'fp-ts/lib/function'; import { DevToolsSettings } from '../../services'; +import { TextObject } from '../../../../common/text_object'; export interface Store { ready: boolean; settings: DevToolsSettings; + currentTextObject: TextObject | null; } export const initialValue: Store = produce( { ready: false, settings: null as any, + currentTextObject: null, }, identity ); export type Action = | { type: 'setInputEditor'; payload: any } + | { type: 'setCurrentTextObject'; payload: any } | { type: 'updateSettings'; payload: DevToolsSettings }; export const reducer: Reducer = (state, action) => @@ -53,5 +57,10 @@ export const reducer: Reducer = (state, action) => return; } + if (action.type === 'setCurrentTextObject') { + draft.currentTextObject = action.payload; + return; + } + return draft; }); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss index 159e9f9e8a173..c69440225236b 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss @@ -26,6 +26,10 @@ // Required on IE11 to render ace editor correctly after first input. position: relative; + + &__spinner { + width: 100%; + } } .conApp__output { diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/create.ts b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/create.ts new file mode 100644 index 0000000000000..36948b9acb962 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/create.ts @@ -0,0 +1,29 @@ +/* + * 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 { Storage } from '../../services'; +import { ObjectStorageClient } from '../../../../common/types'; +import { TextObject, textObjectTypeName } from '../../../../common/text_object'; +import { LocalObjectStorage } from './local_storage_object_client'; + +export const create = (storage: Storage): ObjectStorageClient => { + return { + text: new LocalObjectStorage(storage, textObjectTypeName), + }; +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/index.ts b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/index.ts new file mode 100644 index 0000000000000..c170b8721a04d --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { create } from './create'; +export { LocalObjectStorage } from './local_storage_object_client'; diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/local_storage_object_client.ts b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/local_storage_object_client.ts new file mode 100644 index 0000000000000..41c88d23b2533 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/local_storage_object_client.ts @@ -0,0 +1,53 @@ +/* + * 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 uuid from 'uuid'; +import { ObjectStorage, IdObject } from '../../../../common/types'; +import { Storage } from '../../services'; + +export class LocalObjectStorage implements ObjectStorage { + private readonly prefix: string; + + constructor(private readonly client: Storage, type: string) { + this.prefix = `console_local_${type}`; + } + + async create(obj: Omit): Promise { + const id = uuid.v4(); + const newObj = { id, ...obj } as O; + this.client.set(`${this.prefix}_${id}`, newObj); + return newObj; + } + + async update(obj: O): Promise { + this.client.set(`${this.prefix}_${obj.id}`, obj); + } + + async findAll(): Promise { + const allLocalKeys = this.client.keys().filter(key => { + return key.includes(this.prefix); + }); + + const result = []; + for (const key of allLocalKeys) { + result.push(this.client.get(key)); + } + return result; + } +} diff --git a/src/legacy/core_plugins/console/public/np_ready/plugin.ts b/src/legacy/core_plugins/console/public/np_ready/plugin.ts index cbe262b124677..22351ae95ba87 100644 --- a/src/legacy/core_plugins/console/public/np_ready/plugin.ts +++ b/src/legacy/core_plugins/console/public/np_ready/plugin.ts @@ -55,11 +55,11 @@ export class ConsoleUIPlugin implements Plugin { defaultMessage: 'Console', }), enableRouting: false, - async mount(ctx, { element }) { + async mount({ core: { docLinks } }, { element }) { const { boot } = await import('./application'); render( boot({ - docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION, + docLinkVersion: docLinks.DOC_LINK_VERSION, I18nContext, notifications, elasticsearchUrl, diff --git a/src/legacy/core_plugins/console/public/np_ready/services/history.ts b/src/legacy/core_plugins/console/public/np_ready/services/history.ts index 6cb6f7ba35cd4..04dae0beacefe 100644 --- a/src/legacy/core_plugins/console/public/np_ready/services/history.ts +++ b/src/legacy/core_plugins/console/public/np_ready/services/history.ts @@ -74,13 +74,20 @@ export class History { }); } - getSavedEditorState() { + getLegacySavedEditorState() { const saved = this.storage.get('editor_state'); if (!saved) return; const { time, content } = saved; return { time, content }; } + /** + * This function should only ever be called once for a user if they had legacy state. + */ + deleteLegacySavedEditorState() { + this.storage.delete('editor_state'); + } + clearHistory() { this.getHistoryKeys().forEach(key => this.storage.delete(key)); } diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap index 278811ca85df9..249f42a6ebf3f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap @@ -44,11 +44,10 @@ exports[`renders ControlsTab 1`] = ` } } getIndexPattern={[Function]} - handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} handleIndexPatternChange={[Function]} handleLabelChange={[Function]} - handleNumberOptionChange={[Function]} + handleOptionsChange={[Function]} handleParentChange={[Function]} handleRemoveControl={[Function]} key="1" @@ -101,11 +100,10 @@ exports[`renders ControlsTab 1`] = ` } } getIndexPattern={[Function]} - handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} handleIndexPatternChange={[Function]} handleLabelChange={[Function]} - handleNumberOptionChange={[Function]} + handleOptionsChange={[Function]} handleParentChange={[Function]} handleRemoveControl={[Function]} key="2" diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx index dbac5d9d94371..2bd0baea6eff8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx @@ -29,7 +29,6 @@ import { EuiFormRow, EuiPanel, EuiSpacer, - EuiSwitchEvent, } from '@elastic/eui'; import { RangeControlEditor } from './range_control_editor'; @@ -41,33 +40,28 @@ import { InputControlVisDependencies } from '../../plugin'; interface ControlEditorUiProps { controlIndex: number; controlParams: ControlParams; - handleLabelChange: (controlIndex: number, event: ChangeEvent) => void; + handleLabelChange: (controlIndex: number, value: string) => void; moveControl: (controlIndex: number, direction: number) => void; handleRemoveControl: (controlIndex: number) => void; handleIndexPatternChange: (controlIndex: number, indexPatternId: string) => void; handleFieldNameChange: (controlIndex: number, fieldName: string) => void; getIndexPattern: (indexPatternId: string) => Promise; - handleCheckboxOptionChange: ( + handleOptionsChange: ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: EuiSwitchEvent - ) => void; - handleNumberOptionChange: ( - controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent + optionName: T, + value: ControlParamsOptions[T] ) => void; parentCandidates: Array<{ value: string; text: string; }>; - handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + handleParentChange: (controlIndex: number, parent: string) => void; deps: InputControlVisDependencies; } class ControlEditorUi extends PureComponent { changeLabel = (event: ChangeEvent) => { - this.props.handleLabelChange(this.props.controlIndex, event); + this.props.handleLabelChange(this.props.controlIndex, event.target.value); }; removeControl = () => { @@ -101,8 +95,7 @@ class ControlEditorUi extends PureComponent ); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx index 56381ef7d1570..214cff4ddf9d5 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { PureComponent, ChangeEvent } from 'react'; +import React, { PureComponent } from 'react'; import { InjectedIntlProps } from 'react-intl'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; @@ -28,7 +28,6 @@ import { EuiFormRow, EuiPanel, EuiSelect, - EuiSwitchEvent, } from '@elastic/eui'; import { ControlEditor } from './control_editor'; @@ -73,44 +72,44 @@ class ControlsTabUi extends PureComponent this.props.setValue('controls', value); - handleLabelChange = (controlIndex: number, event: ChangeEvent) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.label = event.target.value; + handleLabelChange = (controlIndex: number, label: string) => { + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + label, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleIndexPatternChange = (controlIndex: number, indexPatternId: string) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.indexPattern = indexPatternId; - updatedControl.fieldName = ''; + handleIndexPatternChange = (controlIndex: number, indexPattern: string) => { + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + indexPattern, + fieldName: '', + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; handleFieldNameChange = (controlIndex: number, fieldName: string) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.fieldName = fieldName; + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + fieldName, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleCheckboxOptionChange = ( + handleOptionsChange = ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: EuiSwitchEvent + optionName: T, + value: ControlParamsOptions[T] ) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - // @ts-ignore - updatedControl.options[optionName] = event.target.checked; - this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); - }; - - handleNumberOptionChange = ( - controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent - ) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - // @ts-ignore - updatedControl.options[optionName] = parseFloat(event.target.value); + const control = this.props.stateParams.controls[controlIndex]; + const updatedControl = { + ...control, + options: { + ...control.options, + [optionName]: value, + }, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; @@ -126,9 +125,11 @@ class ControlsTabUi extends PureComponent) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.parent = event.target.value; + handleParentChange = (controlIndex: number, parent: string) => { + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + parent, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; @@ -151,8 +152,7 @@ class ControlsTabUi extends PureComponent { handleFieldNameChange = sinon.spy(); handleIndexPatternChange = sinon.spy(); - handleCheckboxOptionChange = sinon.spy(); - handleNumberOptionChange = sinon.spy(); + handleOptionsChange = sinon.spy(); }); describe('renders', () => { @@ -82,8 +80,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -107,8 +104,7 @@ describe('renders', () => { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={parentCandidates} /> @@ -143,8 +139,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -178,8 +173,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -213,8 +207,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -227,7 +220,7 @@ describe('renders', () => { }); }); -test('handleCheckboxOptionChange - multiselect', async () => { +test('handleOptionsChange - multiselect', async () => { const component = mountWithIntl( { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -249,25 +241,12 @@ test('handleCheckboxOptionChange - multiselect', async () => { checkbox.simulate('click'); sinon.assert.notCalled(handleFieldNameChange); sinon.assert.notCalled(handleIndexPatternChange); - sinon.assert.notCalled(handleNumberOptionChange); const expectedControlIndex = 0; const expectedOptionName = 'multiselect'; - sinon.assert.calledWith( - handleCheckboxOptionChange, - expectedControlIndex, - expectedOptionName, - sinon.match(event => { - // Synthetic `event.target.checked` does not get altered by EuiSwitch, - // but its aria attribute is correctly updated - if (event.target.getAttribute('aria-checked') === 'true') { - return true; - } - return false; - }, 'unexpected checkbox input event') - ); + sinon.assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName); }); -test('handleNumberOptionChange - size', async () => { +test('handleOptionsChange - size', async () => { const component = mountWithIntl( { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -286,23 +264,12 @@ test('handleNumberOptionChange - size', async () => { await updateComponent(component); const input = findTestSubject(component, 'listControlSizeInput'); - input.simulate('change', { target: { value: 7 } }); - sinon.assert.notCalled(handleCheckboxOptionChange); + input.simulate('change', { target: { valueAsNumber: 7 } }); sinon.assert.notCalled(handleFieldNameChange); sinon.assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'size'; - sinon.assert.calledWith( - handleNumberOptionChange, - expectedControlIndex, - expectedOptionName, - sinon.match(event => { - if (event.target.value === 7) { - return true; - } - return false; - }, 'unexpected input event') - ); + sinon.assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 7); }); test('field name change', async () => { @@ -314,8 +281,7 @@ test('field name change', async () => { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx index ed68894d39ae4..9772cb5fc2548 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx @@ -17,10 +17,10 @@ * under the License. */ -import React, { PureComponent, ChangeEvent, ComponentType } from 'react'; +import React, { PureComponent, ComponentType } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect, EuiSwitchEvent } from '@elastic/eui'; +import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect } from '@elastic/eui'; import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; import { FieldSelect } from './field_select'; @@ -45,17 +45,12 @@ interface ListControlEditorProps { controlParams: ControlParams; handleFieldNameChange: (fieldName: string) => void; handleIndexPatternChange: (indexPatternId: string) => void; - handleCheckboxOptionChange: ( + handleOptionsChange: ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: EuiSwitchEvent + optionName: T, + value: ControlParamsOptions[T] ) => void; - handleNumberOptionChange: ( - controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent - ) => void; - handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + handleParentChange: (controlIndex: number, parent: string) => void; parentCandidates: React.ComponentProps['options']; deps: InputControlVisDependencies; } @@ -177,7 +172,7 @@ export class ListControlEditor extends PureComponent< options={parentCandidatesOptions} value={this.props.controlParams.parent} onChange={event => { - this.props.handleParentChange(this.props.controlIndex, event); + this.props.handleParentChange(this.props.controlIndex, event.target.value); }} /> @@ -204,7 +199,11 @@ export class ListControlEditor extends PureComponent< } checked={this.props.controlParams.options.multiselect ?? true} onChange={event => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'multiselect', + event.target.checked + ); }} data-test-subj="listControlMultiselectInput" /> @@ -237,7 +236,11 @@ export class ListControlEditor extends PureComponent< } checked={this.props.controlParams.options.dynamicOptions ?? false} onChange={event => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'dynamicOptions', + event.target.checked + ); }} disabled={this.state.isStringField ? false : true} data-test-subj="listControlDynamicOptionsSwitch" @@ -268,7 +271,11 @@ export class ListControlEditor extends PureComponent< min={1} value={this.props.controlParams.options.size} onChange={event => { - this.props.handleNumberOptionChange(this.props.controlIndex, 'size', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'size', + event.target.valueAsNumber + ); }} data-test-subj="listControlSizeInput" /> diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx index e7f9b6083890c..55c4c71ce430b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SinonSpy, spy, assert, match } from 'sinon'; +import { SinonSpy, spy, assert } from 'sinon'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; // @ts-ignore @@ -46,12 +46,12 @@ const controlParams: ControlParams = { const deps = getDepsMock(); let handleFieldNameChange: SinonSpy; let handleIndexPatternChange: SinonSpy; -let handleNumberOptionChange: SinonSpy; +let handleOptionsChange: SinonSpy; beforeEach(() => { handleFieldNameChange = spy(); handleIndexPatternChange = spy(); - handleNumberOptionChange = spy(); + handleOptionsChange = spy(); }); test('renders RangeControlEditor', async () => { @@ -63,7 +63,7 @@ test('renders RangeControlEditor', async () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} /> ); @@ -72,7 +72,7 @@ test('renders RangeControlEditor', async () => { expect(component).toMatchSnapshot(); // eslint-disable-line }); -test('handleNumberOptionChange - step', async () => { +test('handleOptionsChange - step', async () => { const component = mountWithIntl( { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} /> ); await updateComponent(component); findTestSubject(component, 'rangeControlSizeInput0').simulate('change', { - target: { value: 0.5 }, + target: { valueAsNumber: 0.5 }, }); assert.notCalled(handleFieldNameChange); assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'step'; - assert.calledWith( - handleNumberOptionChange, - expectedControlIndex, - expectedOptionName, - match(event => { - if (event.target.value === 0.5) { - return true; - } - return false; - }, 'unexpected input event') - ); + assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 0.5); }); -test('handleNumberOptionChange - decimalPlaces', async () => { +test('handleOptionsChange - decimalPlaces', async () => { const component = mountWithIntl( { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} /> ); await updateComponent(component); findTestSubject(component, 'rangeControlDecimalPlacesInput0').simulate('change', { - target: { value: 2 }, + target: { valueAsNumber: 2 }, }); assert.notCalled(handleFieldNameChange); assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'decimalPlaces'; - assert.calledWith( - handleNumberOptionChange, - expectedControlIndex, - expectedOptionName, - match(event => { - if (event.target.value === 2) { - return true; - } - return false; - }, 'unexpected input event') - ); + assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 2); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx index 44477eafda6b1..97850879a2d38 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { Component, Fragment, ChangeEvent, ComponentType } from 'react'; +import React, { Component, Fragment, ComponentType } from 'react'; import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -37,10 +37,10 @@ interface RangeControlEditorProps { getIndexPattern: (indexPatternId: string) => Promise; handleFieldNameChange: (fieldName: string) => void; handleIndexPatternChange: (indexPatternId: string) => void; - handleNumberOptionChange: ( + handleOptionsChange: ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent + optionName: T, + value: ControlParamsOptions[T] ) => void; deps: InputControlVisDependencies; } @@ -109,7 +109,11 @@ export class RangeControlEditor extends Component< { - this.props.handleNumberOptionChange(this.props.controlIndex, 'step', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'step', + event.target.valueAsNumber + ); }} data-test-subj={`rangeControlSizeInput${this.props.controlIndex}`} /> @@ -128,7 +132,11 @@ export class RangeControlEditor extends Component< min={0} value={this.props.controlParams.options.decimalPlaces} onChange={event => { - this.props.handleNumberOptionChange(this.props.controlIndex, 'decimalPlaces', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'decimalPlaces', + event.target.valueAsNumber + ); }} data-test-subj={`rangeControlDecimalPlacesInput${this.props.controlIndex}`} /> diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts index b6774aa87b43c..9473ea5a20b35 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts @@ -50,7 +50,6 @@ export function createInputControlVisTypeDefinition(deps: InputControlVisDepende pinFilters: false, }, }, - editor: 'default', editorConfig: { optionTabs: [ { diff --git a/src/legacy/core_plugins/interpreter/init.ts b/src/legacy/core_plugins/interpreter/init.ts index 768d76fbf744e..46da1539afadb 100644 --- a/src/legacy/core_plugins/interpreter/init.ts +++ b/src/legacy/core_plugins/interpreter/init.ts @@ -22,35 +22,10 @@ // @ts-ignore import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common'; -// @ts-ignore -import { routes } from './server/routes'; - -import { typeSpecs as types, Type } from '../../../plugins/expressions/common'; import { Legacy } from '../../../../kibana'; -export class TypesRegistry extends Registry { - wrapper(obj: any) { - return new (Type as any)(obj); - } -} - -export class FunctionsRegistry extends Registry { - wrapper(obj: any) { - return new Fn(obj); - } -} - -export const registries = { - types: new TypesRegistry(), - serverFunctions: new FunctionsRegistry(), -}; - export async function init(server: Legacy.Server /* options */) { server.injectUiAppVars('canvas', () => { - register(registries, { - types, - }); - const config = server.config(); const basePath = config.get('server.basePath'); const reportingBrowserType = (() => { @@ -63,7 +38,9 @@ export async function init(server: Legacy.Server /* options */) { return { kbnIndex: config.get('kibana.index'), - serverFunctions: registries.serverFunctions.toArray(), + serverFunctions: (server.newPlatform.setup.plugins.expressions as any).__LEGACY + .registries() + .serverFunctions.toArray(), basePath, reportingBrowserType, }; @@ -71,7 +48,5 @@ export async function init(server: Legacy.Server /* options */) { // Expose server.plugins.interpreter.register(specs) and // server.plugins.interpreter.registries() (a getter). - server.expose(registryFactory(registries)); - - routes(server); + server.expose((server.newPlatform.setup.plugins.expressions as any).__LEGACY); } diff --git a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts b/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts index 2c2f79b3d6f51..fed157846a1a1 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts +++ b/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts @@ -28,62 +28,6 @@ * server side, it should be respective function's internal implementation detail. */ -import { get, identity } from 'lodash'; -// @ts-ignore -import { npSetup, npStart } from 'ui/new_platform'; -import { FUNCTIONS_URL } from './consts'; -import { batchedFetch } from './batched_fetch'; +import { npSetup } from 'ui/new_platform'; -export function getType(node: any) { - if (node == null) return 'null'; - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; - } - return typeof node; -} - -export function serializeProvider(types: any) { - return { - serialize: provider('serialize'), - deserialize: provider('deserialize'), - }; - - function provider(key: any) { - return (context: any) => { - const type = getType(context); - const typeDef = types[type]; - const fn: any = get(typeDef, key) || identity; - return fn(context); - }; - } -} - -let cached: Promise | null = null; - -export const loadLegacyServerFunctionWrappers = async () => { - if (!cached) { - cached = (async () => { - const serverFunctionList = await npSetup.core.http.get(FUNCTIONS_URL); - const types = npSetup.plugins.expressions.__LEGACY.types.toJS(); - const { serialize } = serializeProvider(types); - const batch = batchedFetch({ - fetchStreaming: npStart.plugins.bfetch.fetchStreaming, - serialize, - }); - - // For every sever-side function, register a client-side - // function that matches its definition, but which simply - // calls the server-side function endpoint. - Object.keys(serverFunctionList).forEach(functionName => { - const fn = () => ({ - ...serverFunctionList[functionName], - fn: (context: any, args: any) => batch({ functionName, args, context }), - }); - npSetup.plugins.expressions.registerFunction(fn); - }); - })(); - } - - return cached; -}; +export const { loadLegacyServerFunctionWrappers } = npSetup.plugins.expressions.__LEGACY; diff --git a/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts b/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts index 66c51167c7b59..0f37f33cc1b13 100644 --- a/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts +++ b/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts @@ -26,6 +26,7 @@ export const registries = { browserFunctions: functionsRegistry, renderers: renderersRegistry, types: typesRegistry, + loadLegacyServerFunctionWrappers: () => Promise.resolve(), }; const resetRegistry = (registry: any) => { diff --git a/src/legacy/core_plugins/interpreter/server/lib/__tests__/create_handlers.ts b/src/legacy/core_plugins/interpreter/server/lib/__tests__/create_handlers.ts deleted file mode 100644 index 0088663080774..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/lib/__tests__/create_handlers.ts +++ /dev/null @@ -1,65 +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 expect from '@kbn/expect'; -import { createHandlers } from '../create_handlers'; - -const mockRequest = { - headers: 'i can haz headers', -}; - -const mockServer = { - plugins: { - elasticsearch: { - getCluster: () => ({ - callWithRequest: (...args: any) => Promise.resolve(args), - }), - }, - }, - config: () => ({ - has: () => false, - get: (val: any) => val, - }), - info: { - uri: 'serveruri', - }, -}; - -describe('server createHandlers', () => { - it('provides helper methods and properties', () => { - const handlers = createHandlers(mockRequest, mockServer); - - expect(handlers).to.have.property('environment', 'server'); - expect(handlers).to.have.property('serverUri'); - expect(handlers).to.have.property('elasticsearchClient'); - }); - - describe('elasticsearchClient', () => { - it('executes callWithRequest', async () => { - const handlers = createHandlers(mockRequest, mockServer); - const [request, endpoint, payload] = await handlers.elasticsearchClient( - 'endpoint', - 'payload' - ); - expect(request).to.equal(mockRequest); - expect(endpoint).to.equal('endpoint'); - expect(payload).to.equal('payload'); - }); - }); -}); diff --git a/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts b/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts deleted file mode 100644 index e03ad361b5555..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts +++ /dev/null @@ -1,166 +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 Boom from 'boom'; -import Joi from 'joi'; -import { serializeProvider } from '../../../../../plugins/expressions/common'; -import { createHandlers } from '../lib/create_handlers'; - -const API_ROUTE = '/api/interpreter'; - -/** - * Register the Canvas function endopints. - * - * @param {*} server - The Kibana server - */ -export function registerServerFunctions(server: any) { - getServerFunctions(server); - runServerFunctions(server); -} - -/** - * Register the endpoint that executes a batch of functions, and sends the result back as a single response. - * - * @param {*} server - The Kibana server - */ -function runServerFunctions(server: any) { - server.route({ - method: 'POST', - path: `${API_ROUTE}/fns`, - options: { - payload: { - allow: 'application/json', - maxBytes: 26214400, // 25MB payload limit - }, - validate: { - payload: Joi.object({ - functions: Joi.array() - .items( - Joi.object().keys({ - id: Joi.number().required(), - functionName: Joi.string().required(), - args: Joi.object().default({}), - context: Joi.any().default(null), - }) - ) - .required(), - }).required(), - }, - }, - async handler(req: any) { - const handlers = await createHandlers(req, server); - const { functions } = req.payload; - - // Grab the raw Node response object. - const res = req.raw.res; - - // Tell Hapi not to manage the response https://github.com/hapijs/hapi/issues/3884 - req._isReplied = true; - - // Send the initial headers. - res.writeHead(200, { - 'Content-Type': 'application/x-ndjson', - Connection: 'keep-alive', - 'Transfer-Encoding': 'chunked', - 'Cache-Control': 'no-cache', - }); - - // Write a length-delimited response - const streamResult = (result: any) => { - res.write(JSON.stringify(result) + '\n'); - }; - - // Tries to run an interpreter function, and ensures a consistent error payload on failure. - const tryFunction = async (id: any, fnCall: any) => { - try { - const result = await runFunction(server, handlers, fnCall); - - if (typeof result === 'undefined') { - return batchError(id, `Function ${fnCall.functionName} did not return anything.`); - } - - return { id, statusCode: 200, result }; - } catch (err) { - if (Boom.isBoom(err)) { - return batchError(id, err.output.payload, (err as any).statusCode); - } else if (err instanceof Error) { - return batchError(id, err.message); - } - - server.log(['interpreter', 'error'], err); - return batchError(id, 'See server logs for details.'); - } - }; - - // Process each function individually, and stream the responses back to the client - await Promise.all( - functions.map(({ id, ...fnCall }: any) => tryFunction(id, fnCall).then(streamResult)) - ); - - // All of the responses have been written, so we can close the response. - res.end(); - }, - }); -} - -/** - * A helper function for bundling up errors. - */ -function batchError(id: any, message: any, statusCode = 500) { - return { - id, - statusCode, - result: { statusCode, message }, - }; -} - -/** - * Register the endpoint that returns the list of server-only functions. - * @param {*} server - The Kibana server - */ -function getServerFunctions(server: any) { - server.route({ - method: 'GET', - path: `${API_ROUTE}/fns`, - handler() { - return server.plugins.interpreter.registries().serverFunctions.toJS(); - }, - }); -} - -/** - * Run a single Canvas function. - * - * @param {*} server - The Kibana server object - * @param {*} handlers - The Canvas handlers - * @param {*} fnCall - Describes the function being run `{ functionName, args, context }` - */ -async function runFunction(server: any, handlers: any, fnCall: any) { - const registries = server.plugins.interpreter.registries(); - const { functionName, args, context } = fnCall; - const types = registries.types.toJS(); - const { deserialize } = serializeProvider(types); - const fnDef = registries.serverFunctions.toJS()[functionName]; - - if (!fnDef) { - throw Boom.notFound(`Function "${functionName}" could not be found.`); - } - - return fnDef.fn(deserialize(context), args, handlers); -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/package.json b/src/legacy/core_plugins/kbn_vislib_vis_types/package.json deleted file mode 100644 index b6df6eb794de8..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "kbn_vislib_vis_types", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js deleted file mode 100644 index 46f2376cb03eb..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js +++ /dev/null @@ -1,140 +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 'jquery'; -import React from 'react'; - -import { - CUSTOM_LEGEND_VIS_TYPES, - VisLegend, -} from '../../../ui/public/vis/vis_types/vislib_vis_legend'; -import { VislibVisProvider } from '../../../ui/public/vislib/vis'; -import chrome from '../../../ui/public/chrome'; -import { mountReactNode } from '../../../../core/public/utils'; - -const legendClassName = { - top: 'visLib--legend-top', - bottom: 'visLib--legend-bottom', - left: 'visLib--legend-left', - right: 'visLib--legend-right', -}; - -export class vislibVisController { - constructor(el, vis) { - this.el = el; - this.vis = vis; - this.unmount = null; - this.legendRef = React.createRef(); - - // vis mount point - this.container = document.createElement('div'); - this.container.className = 'visLib'; - this.el.appendChild(this.container); - - // chart mount point - this.chartEl = document.createElement('div'); - this.chartEl.className = 'visLib__chart'; - this.container.appendChild(this.chartEl); - // legend mount point - this.legendEl = document.createElement('div'); - this.legendEl.className = 'visLib__legend'; - this.container.appendChild(this.legendEl); - } - - render(esResponse, visParams) { - if (this.vislibVis) { - this.destroy(); - } - - return new Promise(async resolve => { - if (!this.vislib) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - this.Vislib = Private(VislibVisProvider); - } - - if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { - return resolve(); - } - - this.vislibVis = new this.Vislib(this.chartEl, visParams); - this.vislibVis.on('brush', this.vis.API.events.brush); - this.vislibVis.on('click', this.vis.API.events.filter); - this.vislibVis.on('renderComplete', resolve); - - this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); - - if (visParams.addLegend) { - $(this.container) - .attr('class', (i, cls) => { - return cls.replace(/visLib--legend-\S+/g, ''); - }) - .addClass(legendClassName[visParams.legendPosition]); - - this.mountLegend(esResponse, visParams.legendPosition); - } - - this.vislibVis.render(esResponse, this.vis.getUiState()); - - // refreshing the legend after the chart is rendered. - // this is necessary because some visualizations - // provide data necessary for the legend only after a render cycle. - if ( - visParams.addLegend && - CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) - ) { - this.unmountLegend(); - this.mountLegend(esResponse, visParams.legendPosition); - this.vislibVis.render(esResponse, this.vis.getUiState()); - } - }); - } - - mountLegend(visData, position) { - this.unmount = mountReactNode( - - )(this.legendEl); - } - - unmountLegend() { - if (this.unmount) { - this.unmount(); - } - } - - destroy() { - if (this.unmount) { - this.unmount(); - } - - if (this.vislibVis) { - this.vislibVis.off('brush', this.vis.API.events.brush); - this.vislibVis.off('click', this.vis.API.events.filter); - this.vislibVis.destroy(); - delete this.vislibVis; - } - } -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts deleted file mode 100644 index ff8345e9a5b25..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.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. - */ - -import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; -import { Alignments, GaugeTypes } from './utils/collections'; -import { ColorSchemaVislibParams, Labels, Style } from './types'; - -interface Gauge extends ColorSchemaVislibParams { - backStyle: 'Full'; - gaugeStyle: 'Full'; - orientation: 'vertical'; - type: 'meter'; - alignment: Alignments; - colorsRange: RangeValues[]; - extendRange: boolean; - gaugeType: GaugeTypes; - labels: Labels; - percentageMode: boolean; - outline?: boolean; - scale: { - show: boolean; - labels: false; - color: 'rgba(105,112,125,0.2)'; - }; - style: Style; -} - -export interface GaugeVisParams { - type: 'gauge'; - addTooltip: boolean; - addLegend: boolean; - isDisplayWarning: boolean; - gauge: Gauge; -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.d.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.d.ts deleted file mode 100644 index 13c676854ead2..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.d.ts +++ /dev/null @@ -1,36 +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 { ColorSchemas } from 'ui/vislib/components/color/colormaps'; -import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; -import { TimeMarker } from 'ui/vislib/visualizations/time_marker'; -import { CommonVislibParams, ColorSchemaVislibParams, ValueAxis } from './types'; -import { Positions } from './utils/collections'; - -export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibParams { - type: 'heatmap'; - addLegend: boolean; - enableHover: boolean; - colorsNumber: number | ''; - colorsRange: RangeValues[]; - valueAxes: ValueAxis[]; - setColorRange: boolean; - percentageMode: boolean; - times: TimeMarker[]; -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js deleted file mode 100644 index c82073ff582b8..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js +++ /dev/null @@ -1,38 +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 { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; - -import { histogramDefinition } from './histogram'; -import { lineDefinition } from './line'; -import { pieDefinition } from './pie'; -import { areaDefinition } from './area'; -import { heatmapDefinition } from './heatmap'; -import { horizontalBarDefinition } from './horizontal_bar'; -import { gaugeDefinition } from './gauge'; -import { goalDefinition } from './goal'; - -visualizations.types.createBaseVisualization(histogramDefinition); -visualizations.types.createBaseVisualization(lineDefinition); -visualizations.types.createBaseVisualization(pieDefinition); -visualizations.types.createBaseVisualization(areaDefinition); -visualizations.types.createBaseVisualization(heatmapDefinition); -visualizations.types.createBaseVisualization(horizontalBarDefinition); -visualizations.types.createBaseVisualization(gaugeDefinition); -visualizations.types.createBaseVisualization(goalDefinition); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 111b74d8aa6af..e6a0420534ef2 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -61,10 +61,7 @@ export default function(kibana) { uiExports: { hacks: ['plugins/kibana/discover', 'plugins/kibana/dev_tools', 'plugins/kibana/visualize'], - savedObjectTypes: [ - 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register', - 'plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register', - ], + savedObjectTypes: ['plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register'], app: { id: 'kibana', title: 'Kibana', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index ba01919431080..b44d1993db23a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -65,6 +65,6 @@ export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; export { SavedObjectLoader } from 'ui/saved_objects'; -export { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize_embeddable'; +export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts index 4d5101e1f9e5f..d9a93b2ceedc3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts @@ -28,6 +28,12 @@ import { ViewMode } from 'src/plugins/embeddable/public'; jest.mock('ui/state_management/state', () => ({ State: {}, })); +jest.mock('ui/agg_types', () => ({ + aggTypes: { + metrics: [], + buckets: [], + }, +})); describe('DashboardState', function() { let dashboardState: DashboardStateManager; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts index 2e360567c4653..73383f2ff3f68 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts @@ -25,11 +25,9 @@ import { DashboardConstants } from './dashboard_constants'; */ export function getUrlVars(url: string): Record { const vars: Record = {}; - // @ts-ignore - url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(_, key, value) { - // @ts-ignore + for (const [, key, value] of url.matchAll(/[?&]+([^=&]+)=([^&]*)/gi)) { vars[key] = decodeURIComponent(value); - }); + } return vars; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 78ac99567d10e..2927565e61dce 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -826,9 +826,14 @@ function discoverController( }; $scope.updateQueryAndFetch = function({ query, dateRange }) { + const oldDateRange = timefilter.getTime(); timefilter.setTime(dateRange); $state.query = query; - $scope.fetch(); + // storing the updated timerange in the state will trigger a fetch + // call automatically, so only trigger fetch in case this is a refresh call (no changes in parameters). + if (_.isEqual(oldDateRange, dateRange)) { + $scope.fetch(); + } }; function onResults(resp) { diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/ibmmq_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/ibmmq_metrics/screenshot.png new file mode 100644 index 0000000000000..c4f202ad13bb7 Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/ibmmq_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg index ad0cb64b161dd..e474d93359beb 100644 --- a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index dfe4aa1fd3b9f..324458c0814d9 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -7,6 +7,9 @@ // Public UI styles @import 'src/legacy/ui/public/index'; +// vis_type_vislib UI styles +@import 'src/legacy/core_plugins/vis_type_vislib/public/index'; + // Dev tools styles @import './dev_tools/index'; @@ -18,7 +21,6 @@ // Visualize styles @import './visualize/index'; -@import './visualize_embeddable/index'; // Has to come after visualize because of some // bad cascading in the Editor layout @import 'src/legacy/ui/public/vis/index'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 334397c479a1d..4100ae7205869 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -52,7 +52,7 @@ import './visualize'; import './dashboard'; import './management'; import './dev_tools'; -import 'ui/vislib'; +import 'ui/color_maps'; import 'ui/agg_response'; import 'ui/agg_types'; import { showAppRedirectNotification } from 'ui/notify'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index a389a44197baf..f113c81256f8e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -17,9 +17,6 @@ * under the License. */ -import 'ui/collapsible_sidebar'; // used in default editor -import 'ui/vis/editors/default/sidebar'; - import { IPrivate, legacyChrome, diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 141063adcbd6a..7d0a07323378a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -66,12 +66,9 @@ export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -// @ts-ignore -export { defaultEditor } from 'ui/vis/editors/default/default'; export { VisType } from 'ui/vis'; export { wrapInI18nContext } from 'ui/i18n'; export { DashboardConstants } from '../dashboard/np_ready/dashboard_constants'; -export { VisSavedObject } from '../visualize_embeddable/visualize_embeddable'; -export { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize_embeddable'; -export { VisualizeEmbeddableFactory } from '../visualize_embeddable/visualize_embeddable_factory'; +export { VisSavedObject } from '../../../visualizations/public/embeddable/visualize_embeddable'; +export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss index f738820677beb..2f48ecc322fea 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss @@ -30,9 +30,4 @@ a tilemap in an iframe: https://github.com/elastic/kibana/issues/16457 */ @include flex-parent(); width: 100%; z-index: 0; - - // overrides for tablet and desktop - @include euiBreakpoint('l', 'xl') { - flex-direction: row; - } } diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index 64653730473cd..0e085b8553bf0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -116,6 +116,11 @@ function VisualizeAppController( dirty: !savedVis.id, }); + vis.on('dirtyStateChange', ({ isDirty }) => { + vis.dirty = isDirty; + $scope.$digest(); + }); + $scope.topNavMenu = [ ...(visualizeCapabilities.save ? [ @@ -357,7 +362,10 @@ function VisualizeAppController( }; $scope.showQueryBarTimePicker = () => { - return vis.type.options.showTimePicker; + // tsvb loads without an indexPattern initially (TODO investigate). + // hide timefilter only if timeFieldName is explicitly undefined. + const hasTimeField = $scope.indexPattern ? !!$scope.indexPattern.timeFieldName : true; + return vis.type.options.showTimePicker && hasTimeField; }; $scope.timeRange = timefilter.getTime(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts index f47a54baac9a1..2e0eaeb484c0a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts @@ -17,7 +17,16 @@ * under the License. */ -import { VisSavedObject } from '../legacy_imports'; +import { TimeRange, Query, esFilters } from 'src/plugins/data/public'; +import { VisSavedObject, AppState, PersistedState } from '../legacy_imports'; + +export interface EditorRenderProps { + appState: AppState; + filters: esFilters.Filter[]; + uiState: PersistedState; + timeRange: TimeRange; + query?: Query; +} export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 9ea26f129895c..80e17b1631f3e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -27,6 +27,9 @@ import { SavedObjectsClientContract, } from 'kibana/public'; +// @ts-ignore +import { uiModules } from 'ui/modules'; + import { Storage } from '../../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; @@ -40,14 +43,10 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../../plugins/home/public'; -import { - defaultEditor, - VisEditorTypesRegistryProvider, - VisualizeEmbeddableFactory, - VISUALIZE_EMBEDDABLE_TYPE, -} from './legacy_imports'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { createSavedVisLoader } from './saved_visualizations/saved_visualizations'; +// @ts-ignore +import { savedObjectManagementRegistry } from '../management/saved_object_registry'; export interface LegacyAngularInjectedDependencies { legacyChrome: any; @@ -113,6 +112,7 @@ export class VisualizePlugin implements Plugin { indexPatterns: data.indexPatterns, chrome: contextCore.chrome, overlays: contextCore.overlays, + visualizations, }); const deps: VisualizeKibanaServices = { ...angularDependencies, @@ -154,24 +154,35 @@ export class VisualizePlugin implements Plugin { showOnHomePage: true, category: FeatureCatalogueCategory.DATA, }); - - VisEditorTypesRegistryProvider.register(defaultEditor); } public start( - { savedObjects: { client: savedObjectsClient } }: CoreStart, + core: CoreStart, { embeddables, navigation, data, share, visualizations }: VisualizePluginStartDependencies ) { this.startDependencies = { data, embeddables, navigation, - savedObjectsClient, + savedObjectsClient: core.savedObjects.client, share, visualizations, }; - const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); - embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + const savedVisualizations = createSavedVisLoader({ + savedObjectsClient: core.savedObjects.client, + indexPatterns: data.indexPatterns, + chrome: core.chrome, + overlays: core.overlays, + visualizations, + }); + + // TODO: remove once savedobjectregistry is refactored + savedObjectManagementRegistry.register({ + service: 'savedVisualizations', + title: 'visualizations', + }); + + uiModules.get('app/visualize').service('savedVisualizations', () => savedVisualizations); } } diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts index 9f7ba342d803f..a0a6f8ea1c8a2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts @@ -34,7 +34,7 @@ import { IIndexPattern } from '../../../../../../plugins/data/public'; import { VisSavedObject } from '../legacy_imports'; import { createSavedSearchesService } from '../../discover'; -import { VisualizeConstants } from '..'; +import { VisualizeConstants } from '../np_ready/visualize_constants'; async function _afterEsResp(savedVis: VisSavedObject, services: any) { await _getLinkedSavedSearch(savedVis, services); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts index b71a10ab000d8..98f5458d5eecc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts @@ -18,7 +18,7 @@ */ import { extractReferences, injectReferences } from './saved_visualization_references'; -import { VisSavedObject } from '../../visualize_embeddable/visualize_embeddable'; +import { VisSavedObject } from '../../../../visualizations/public/embeddable/visualize_embeddable'; describe('extractReferences', () => { test('extracts nothing if savedSearchId is empty', () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts index 0c76aaff4345d..403e9c5a8172d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts @@ -17,7 +17,7 @@ * under the License. */ import { SavedObjectAttributes, SavedObjectReference } from 'kibana/server'; -import { VisSavedObject } from '../../visualize_embeddable/visualize_embeddable'; +import { VisSavedObject } from '../../../../visualizations/public/embeddable/visualize_embeddable'; export function extractReferences({ attributes, diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts deleted file mode 100644 index cbf72339804ce..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts +++ /dev/null @@ -1,43 +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 { npStart } from 'ui/new_platform'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -// @ts-ignore -import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; -import './saved_visualizations'; -import { createSavedVisLoader } from './saved_visualizations'; - -const services = { - savedObjectsClient: npStart.core.savedObjects.client, - indexPatterns: npStart.plugins.data.indexPatterns, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, -}; - -const savedObjectLoaderVisualize = createSavedVisLoader(services); - -// Register this service with the saved object registry so it can be -// edited by the object editor. -savedObjectManagementRegistry.register({ - service: 'savedVisualizations', - title: 'visualizations', -}); - -uiModules.get('app/visualize').service('savedVisualizations', () => savedObjectLoaderVisualize); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts index c19c7818c1fbd..d51fae7428939 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts @@ -19,14 +19,18 @@ import { SavedObjectLoader } from 'ui/saved_objects'; import { SavedObjectKibanaServices } from 'ui/saved_objects/types'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; // @ts-ignore import { findListItems } from './find_list_items'; import { createSavedVisClass } from './_saved_vis'; -import { createVisualizeEditUrl } from '..'; +import { createVisualizeEditUrl } from '../np_ready/visualize_constants'; +import { VisualizationsStart } from '../../../../visualizations/public/np_ready/public'; -export function createSavedVisLoader(services: SavedObjectKibanaServices) { - const { savedObjectsClient } = services; +interface SavedObjectKibanaServicesWithVisualizations extends SavedObjectKibanaServices { + visualizations: VisualizationsStart; +} + +export function createSavedVisLoader(services: SavedObjectKibanaServicesWithVisualizations) { + const { savedObjectsClient, visualizations } = services; class SavedObjectLoaderVisualize extends SavedObjectLoader { mapHitSource = (source: Record, id: string) => { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js new file mode 100644 index 0000000000000..b2824832dc14c --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js @@ -0,0 +1,67 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../../../common/tutorials/metricbeat_instructions'; + +export function ibmmqMetricsSpecProvider(context) { + const moduleName = 'ibmmq'; + return { + id: 'ibmmqMetrics', + name: i18n.translate('kbn.server.tutorials.ibmmqMetrics.nameTitle', { + defaultMessage: 'IBM MQ metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.ibmmqMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics from IBM MQ instances.', + }), + longDescription: i18n.translate('kbn.server.tutorials.ibmmqMetrics.longDescription', { + defaultMessage: + 'The `ibmmq` Metricbeat module fetches monitoring metrics from IBM MQ instances \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-ibmmq.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/ibmmq.svg', + isBeta: true, + artifacts: { + application: { + label: i18n.translate('kbn.server.tutorials.ibmmqMetrics.artifacts.application.label', { + defaultMessage: 'Discover', + }), + path: '/app/kibana#/discover', + }, + dashboards: [], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-ibmmq.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/ibmmq_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName), + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index 69a6ac76e4a8f..2f69e7dbcbc7d 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -84,6 +84,7 @@ import { activemqLogsSpecProvider } from './activemq_logs'; import { activemqMetricsSpecProvider } from './activemq_metrics'; import { azureMetricsSpecProvider } from './azure_metrics'; import { ibmmqLogsSpecProvider } from './ibmmq_logs'; +import { ibmmqMetricsSpecProvider } from './ibmmq_metrics'; import { stanMetricsSpecProvider } from './stan_metrics'; import { envoyproxyMetricsSpecProvider } from './envoyproxy_metrics'; @@ -158,6 +159,7 @@ export function registerTutorials(server) { server.newPlatform.setup.plugins.home.tutorials.registerTutorial(activemqMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(azureMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(ibmmqLogsSpecProvider); + server.newPlatform.setup.plugins.home.tutorials.registerTutorial(ibmmqMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(stanMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(envoyproxyMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/region_map/public/choropleth_layer.js b/src/legacy/core_plugins/region_map/public/choropleth_layer.js index f4c6bfd6bedf5..8132976fcbc69 100644 --- a/src/legacy/core_plugins/region_map/public/choropleth_layer.js +++ b/src/legacy/core_plugins/region_map/public/choropleth_layer.js @@ -23,7 +23,7 @@ import _ from 'lodash'; import d3 from 'd3'; import { i18n } from '@kbn/i18n'; import { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; -import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorMaps } from 'ui/color_maps'; import * as topojson from 'topojson-client'; import { toastNotifications } from 'ui/notify'; import * as colorUtil from 'ui/vis/map/color_util'; diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index 8306b3274a914..73fe07ec60102 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -28,7 +28,7 @@ import { NumberInputOption, SelectOption, SwitchOption, -} from '../../../kbn_vislib_vis_types/public/components'; +} from '../../../vis_type_vislib/public/components'; import { WmsOptions } from '../../../tile_map/public/components/wms_options'; import { RegionMapVisParams } from '../types'; 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 6f83ae912e184..39353a379ce52 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 @@ -19,7 +19,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorSchemas as colorSchemas } from 'ui/color_maps'; import { mapToLayerWithId } from './util'; import { createRegionMapVisualization } from './region_map_visualization'; import { Status } from '../../visualizations/public'; diff --git a/src/legacy/core_plugins/region_map/public/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/region_map_visualization.js index b58de5d9c6ab7..f9a5793ca8137 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/region_map_visualization.js @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import ChoroplethLayer from './choropleth_layer'; -import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorMaps } from 'ui/color_maps'; import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { toastNotifications } from 'ui/notify'; 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 15c92f4617497..e57cea8467d12 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 @@ -27,7 +27,7 @@ import { RangeOption, SelectOption, SwitchOption, -} from '../../../kbn_vislib_vis_types/public/components'; +} from '../../../vis_type_vislib/public/components'; import { WmsOptions } from './wms_options'; import { TileMapVisParams } from '../types'; import { MapTypes } from '../map_types'; diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx index f2cf69bb63ba4..2989f6ce7ebd5 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TextInputOption } from '../../../kbn_vislib_vis_types/public/components'; +import { TextInputOption } from '../../../vis_type_vislib/public/components'; import { WMSOptions } from '../types'; interface WmsInternalOptions { diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index c5ccc3acba610..d9dca5afd7377 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { TmsLayer } from 'ui/vis/map/service_settings'; import { Vis } from 'ui/vis'; import { RegionMapVisParams } from '../../../region_map/public/types'; -import { SelectOption, SwitchOption } from '../../../kbn_vislib_vis_types/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; import { WmsInternalOptions } from './wms_internal_options'; import { WMSOptions, TileMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js b/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js index e9334ff91def9..fe29d9b6aad21 100644 --- a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js +++ b/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js @@ -22,7 +22,7 @@ import _ from 'lodash'; import d3 from 'd3'; import $ from 'jquery'; import { EventEmitter } from 'events'; -import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorMaps } from 'ui/color_maps'; import * as colorUtil from 'ui/vis/map/color_util'; export class ScaledCirclesMarkers extends EventEmitter { 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 f2e6469e768e7..c58c226f0aba0 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 @@ -21,7 +21,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorSchemas as colorSchemas } from 'ui/color_maps'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; import { createTileMapVisualization } from './tile_map_visualization'; diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 7ef722ee3a277..084e497761e43 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -59,8 +59,6 @@ document.title = 'Timelion - Kibana'; const app = require('ui/modules').get('apps/timelion', []); -require('./vis'); - require('ui/routes').enable(); require('ui/routes').when('/:id?', { diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index 231330b898edb..ea2d44bcaefe0 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -19,10 +19,13 @@ import expect from '@kbn/expect'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../../chain.peg'; +import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { getArgValueSuggestions } from '../../services/arg_value_suggestions'; -import { setIndexPatterns, setSavedObjectsClient } from '../../services/plugin_services'; +import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions'; +import { + setIndexPatterns, + setSavedObjectsClient, +} from '../../../../vis_type_timelion/public/helpers/plugin_services'; describe('Timelion expression suggestions', () => { setIndexPatterns({}); diff --git a/src/legacy/core_plugins/timelion/public/directives/_index.scss b/src/legacy/core_plugins/timelion/public/directives/_index.scss index 6ee2f81539032..cd46a1a0a369e 100644 --- a/src/legacy/core_plugins/timelion/public/directives/_index.scss +++ b/src/legacy/core_plugins/timelion/public/directives/_index.scss @@ -1,6 +1,5 @@ @import './timelion_expression_input'; @import './cells/index'; -@import './chart/index'; @import './timelion_expression_suggestions/index'; @import './timelion_help/index'; @import './timelion_interval/index'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss b/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss deleted file mode 100644 index 33c188decd4f1..0000000000000 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './chart'; diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 449c0489fea25..1fec243a277f8 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -43,7 +43,7 @@ import _ from 'lodash'; import $ from 'jquery'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../chain.peg'; +import grammar from 'raw-loader!../../../vis_type_timelion/public/chain.peg'; import timelionExpressionInputTemplate from './timelion_expression_input.html'; import { SUGGESTION_TYPE, @@ -52,7 +52,7 @@ import { insertAtLocation, } from './timelion_expression_input_helpers'; import { comboBoxKeyCodes } from '@elastic/eui'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { getArgValueSuggestions } from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/index.scss b/src/legacy/core_plugins/timelion/public/index.scss index 7ccc6c300bc40..ebf000d160b54 100644 --- a/src/legacy/core_plugins/timelion/public/index.scss +++ b/src/legacy/core_plugins/timelion/public/index.scss @@ -11,6 +11,4 @@ // timChart__legend-isLoading @import './app'; -@import './components/index'; @import './directives/index'; -@import './vis/index'; diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index 1cf6bb65cdc02..63030fcbce387 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -20,15 +20,10 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; -import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; import { TimelionPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; const setupPlugins: Readonly = { - visualizations, - data: npSetup.plugins.data, - expressions: npSetup.plugins.expressions, - // Temporary solution // It will be removed when all dependent services are migrated to the new platform. __LEGACY: new LegacyDependenciesPlugin(), @@ -37,4 +32,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 0bbda4bf3646f..57ee99f5268b0 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -17,19 +17,18 @@ * under the License. */ -import './flot'; +import '../../../../vis_type_timelion/public/flot'; import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment-timezone'; import { timefilter } from 'ui/timefilter'; // @ts-ignore import observeResize from '../../lib/observe_resize'; -// @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib'; +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../../vis_type_timelion/common/lib'; +import { tickFormatters } from '../../../../vis_type_timelion/public/helpers/tick_formatters'; import { TimelionVisualizationDependencies } from '../../plugin'; -import { tickFormatters } from '../../services/tick_formatters'; -import { xaxisFormatterProvider } from './xaxis_formatter'; -import { generateTicksProvider } from './tick_generator'; +import { xaxisFormatterProvider } from '../../../../vis_type_timelion/public/helpers/xaxis_formatter'; +import { generateTicksProvider } from '../../../../vis_type_timelion/public/helpers/tick_generator'; const DEBOUNCE_DELAY = 50; diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 42f0ee3ad4725..636b8bf8e128a 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -22,33 +22,19 @@ import { Plugin, PluginInitializerContext, IUiSettingsClient, - HttpSetup, } from 'kibana/public'; -import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; -import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; -import { PluginsStart } from 'ui/new_platform/new_platform'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; -import { getTimelionVisualizationConfig } from './timelion_vis_fn'; -import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; -import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { uiSettings: IUiSettingsClient; - http: HttpSetup; timelionPanels: Map; - timefilter: TimefilterContract; } /** @internal */ export interface TimelionPluginSetupDependencies { - expressions: ReturnType; - visualizations: VisualizationsSetup; - data: DataPublicPluginSetup; - // Temporary solution __LEGACY: LegacyDependenciesPlugin; } @@ -61,24 +47,16 @@ export class TimelionPlugin implements Plugin, void> { this.initializerContext = initializerContext; } - public async setup( - core: CoreSetup, - { __LEGACY, expressions, visualizations, data }: TimelionPluginSetupDependencies - ) { + public async setup(core: CoreSetup, { __LEGACY }: TimelionPluginSetupDependencies) { const timelionPanels: Map = new Map(); const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, - http: core.http, timelionPanels, - timefilter: data.query.timefilter.timefilter, ...(await __LEGACY.setup(core, timelionPanels)), }; this.registerPanels(dependencies); - - expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { @@ -87,15 +65,12 @@ export class TimelionPlugin implements Plugin, void> { dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); } - public start(core: CoreStart, plugins: PluginsStart) { + public start(core: CoreStart) { const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); if (timelionUiEnabled === false) { core.chrome.navLinks.update('timelion', { hidden: true }); } - - setIndexPatterns(plugins.data.indexPatterns); - setSavedObjectsClient(core.savedObjects.client); } public stop(): void {} diff --git a/src/legacy/core_plugins/timelion/public/vis/_index.scss b/src/legacy/core_plugins/timelion/public/vis/_index.scss deleted file mode 100644 index 17a2018f7a56a..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './timelion_vis'; -@import './timelion_editor'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html b/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html deleted file mode 100644 index 8bfb9e91a2523..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js index 9514e479d36f4..9056362cb723a 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js +++ b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js @@ -26,7 +26,7 @@ import parseSheet from './lib/parse_sheet.js'; import repositionArguments from './lib/reposition_arguments.js'; import indexArguments from './lib/index_arguments.js'; import validateTime from './lib/validate_time.js'; -import { calculateInterval } from '../../common/lib'; +import { calculateInterval } from '../../../vis_type_timelion/common/lib'; export default function chainRunner(tlConfig) { const preprocessChain = require('./lib/preprocess_chain')(tlConfig); diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js index 74ef76d1a50cd..4957d3cb78b85 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js @@ -21,7 +21,10 @@ import { i18n } from '@kbn/i18n'; import fs from 'fs'; import path from 'path'; import _ from 'lodash'; -const grammar = fs.readFileSync(path.resolve(__dirname, '../../../public/chain.peg'), 'utf8'); +const grammar = fs.readFileSync( + path.resolve(__dirname, '../../../../vis_type_timelion/public/chain.peg'), + 'utf8' +); import PEG from 'pegjs'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js index 8b1f8998557be..db924e33be5e9 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import toMS from '../../lib/to_milliseconds.js'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default function validateTime(time, tlConfig) { const span = moment.duration(moment(time.to).diff(moment(time.from))).asMilliseconds(); diff --git a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts index 798902aa133de..08358b9d81f78 100644 --- a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts +++ b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TimelionFunctionArgs } from '../../../common/types'; +import { TimelionFunctionArgs } from '../../../../vis_type_timelion/common/types'; export interface TimelionFunctionInterface extends TimelionFunctionConfig { chainable: boolean; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js index 970d146c45b91..0cc41df933e8c 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js @@ -23,7 +23,7 @@ import Chainable from '../../lib/classes/chainable'; import ses from './lib/ses'; import des from './lib/des'; import tes from './lib/tes'; -import toMilliseconds from '../../lib/to_milliseconds'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default new Chainable('holt', { args: [ @@ -125,9 +125,7 @@ export default new Chainable('holt', { }) ); } - const season = Math.round( - toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval) - ); + const season = Math.round(toMS(args.byName.season) / toMS(tlConfig.time.interval)); points = tes(points, alpha, beta, gamma, season, sample); } diff --git a/src/legacy/core_plugins/timelion/server/series_functions/legend.js b/src/legacy/core_plugins/timelion/server/series_functions/legend.js index b467318686729..fd9ff53a1391f 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/legend.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/legend.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import Chainable from '../lib/classes/chainable'; -import { DEFAULT_TIME_FORMAT } from '../../common/lib'; +import { DEFAULT_TIME_FORMAT } from '../../../vis_type_timelion/common/lib'; export default new Chainable('legend', { args: [ diff --git a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js index 361cd1f9dfb67..a4b458991c1bc 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; const validPositions = ['left', 'right', 'center']; const defaultPosition = 'center'; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js index 778c91d30f2cb..b604015624dfd 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss b/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss index da38d6d2ed211..fb0a3d05e5e85 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss +++ b/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss @@ -4,13 +4,8 @@ } .visEditor--markdown { - vis-editor-vis-options, vis-options-react-wrapper { - flex-grow: 1; - display: flex; - flex-direction: column; - } - - .visEditor--markdown__textarea { + .visEditorSidebar__config > *, + .visEditor--markdown__textarea { flex-grow: 1; } diff --git a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx index cce92a08f2c2b..125577815c207 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx @@ -22,7 +22,7 @@ import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { RangeOption, SwitchOption } from '../../kbn_vislib_vis_types/public/components'; +import { RangeOption, SwitchOption } from '../../vis_type_vislib/public/components'; import { MarkdownVisParams } from './types'; function SettingsOptions({ stateParams, setValue }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap b/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap index d0fba4d164dbf..44414cedf966a 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap +++ b/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap @@ -13,7 +13,7 @@ Object { "metrics": undefined, }, "metric": Object { - "colorSchema": "\\"Green to Red\\"", + "colorSchema": "Green to Red", "colorsRange": "{range from=0 to=10000}", "invertColors": false, "labels": Object { 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 deleted file mode 100644 index f4786123f7b9d..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js +++ /dev/null @@ -1,95 +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 'jquery'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; - -import { Vis } from 'ui/vis'; -import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; - -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; - -describe('metric_vis - createMetricVisTypeDefinition', () => { - let setup = null; - let vis; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(Private => { - setup = () => { - const metricVisType = visualizations.types.get('metric'); - 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(); - - const ip = '235.195.237.208'; - render({ - columns: [{ id: 'col-0', name: 'ip' }], - rows: [{ 'col-0': ip }], - }); - - const $link = $(el) - .find('a[href]') - .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/__tests__/metric_vis_controller.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js deleted file mode 100644 index 367c5f4699937..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js +++ /dev/null @@ -1,71 +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 expect from '@kbn/expect'; -import { MetricVisComponent } from '../components/metric_vis_controller'; - -describe('metric_vis - controller', function() { - const vis = { - params: { - metric: { - colorSchema: 'Green to Red', - colorsRange: [{ from: 0, to: 1000 }], - style: {}, - }, - dimensions: { - metrics: [{ accessor: 0 }], - bucket: null, - }, - }, - }; - - let metricController; - - beforeEach(() => { - metricController = new MetricVisComponent({ vis: vis, visParams: vis.params }); - }); - - it('should set the metric label and value', function() { - const metrics = metricController._processTableGroups({ - columns: [{ id: 'col-0', name: 'Count' }], - rows: [{ 'col-0': 4301021 }], - }); - - expect(metrics.length).to.be(1); - expect(metrics[0].label).to.be('Count'); - expect(metrics[0].value).to.be('4301021'); - }); - - it('should support multi-value metrics', function() { - vis.params.dimensions.metrics.push({ accessor: 1 }); - const metrics = metricController._processTableGroups({ - columns: [ - { id: 'col-0', name: '1st percentile of bytes' }, - { id: 'col-1', name: '99th percentile of bytes' }, - ], - rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }], - }); - - expect(metrics.length).to.be(2); - expect(metrics[0].label).to.be('1st percentile of bytes'); - expect(metrics[0].value).to.be('182'); - expect(metrics[1].label).to.be('99th percentile of bytes'); - expect(metrics[1].value).to.be('445842.4634666484'); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap b/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap new file mode 100644 index 0000000000000..d84424cc6179a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MetricVisComponent should render correct structure for single metric 1`] = ` +
+ 4301021", + } + } + showLabel={true} + /> +
+`; + +exports[`MetricVisComponent should render correct structure for multi-value metrics 1`] = ` +
+ 182", + } + } + showLabel={true} + /> + 445842.4634666484", + } + } + showLabel={true} + /> +
+`; diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx new file mode 100644 index 0000000000000..901273ccbeb95 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx @@ -0,0 +1,93 @@ +/* + * 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 { MetricVisComponent } from './metric_vis_component'; +import { Vis } from '../legacy_imports'; + +jest.mock('ui/new_platform'); + +type Props = MetricVisComponent['props']; + +const baseVisData = { + columns: [{ id: 'col-0', name: 'Count' }], + rows: [{ 'col-0': 4301021 }], +} as any; + +describe('MetricVisComponent', function() { + const vis: Vis = { + params: { + metric: { + colorSchema: 'Green to Red', + colorsRange: [{ from: 0, to: 1000 }], + style: {}, + labels: { + show: true, + }, + }, + dimensions: { + metrics: [{ accessor: 0 }], + bucket: null, + }, + }, + } as any; + + const getComponent = (propOverrides: Partial = {} as Partial) => { + const props: Props = { + vis, + visParams: vis.params, + visData: baseVisData, + renderComplete: jest.fn(), + ...propOverrides, + }; + + return shallow(); + }; + + it('should render component', () => { + expect(getComponent().exists()).toBe(true); + }); + + it('should render correct structure for single metric', function() { + expect(getComponent()).toMatchSnapshot(); + }); + + it('should render correct structure for multi-value metrics', function() { + const component = getComponent({ + visData: { + columns: [ + { id: 'col-0', name: '1st percentile of bytes' }, + { id: 'col-1', name: '99th percentile of bytes' }, + ], + rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }], + }, + visParams: { + ...vis.params, + dimensions: { + ...vis.params.dimensions, + metrics: [{ accessor: 0 }, { accessor: 1 }], + }, + }, + } as any); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx similarity index 64% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx index ecaf6b5d70d36..f8398f5c83146 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -19,20 +19,33 @@ import { last, findIndex, isNaN } from 'lodash'; import React, { Component } from 'react'; + import { isColorDark } from '@elastic/eui'; -import { getHeatmapColors } from 'ui/vislib/components/color/heatmap_color'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +import { getHeatmapColors, getFormat, Vis } from '../legacy_imports'; import { MetricVisValue } from './metric_vis_value'; +import { FieldFormat, ContentType } from '../../../../../plugins/data/public'; +import { Context } from '../metric_vis_fn'; +import { KibanaDatatable } from '../../../../../plugins/expressions/public'; +import { VisParams, MetricVisMetric } from '../types'; +import { SchemaConfig } from '../../../visualizations/public'; + +interface MetricVisComponentProps { + visParams: VisParams; + visData: Context; + vis: Vis; + renderComplete: () => void; +} -export class MetricVisComponent extends Component { - _getLabels() { +export class MetricVisComponent extends Component { + private getLabels() { const config = this.props.visParams.metric; const isPercentageMode = config.percentageMode; const colorsRange = config.colorsRange; const max = last(colorsRange).to; - const labels = []; - colorsRange.forEach(range => { + const labels: string[] = []; + + colorsRange.forEach((range: any) => { const from = isPercentageMode ? Math.round((100 * range.from) / max) : range.from; const to = isPercentageMode ? Math.round((100 * range.to) / max) : range.to; labels.push(`${from} - ${to}`); @@ -41,13 +54,13 @@ export class MetricVisComponent extends Component { return labels; } - _getColors() { + private getColors() { const config = this.props.visParams.metric; const invertColors = config.invertColors; const colorSchema = config.colorSchema; const colorsRange = config.colorsRange; - const labels = this._getLabels(); - const colors = {}; + const labels = this.getLabels(); + const colors: any = {}; for (let i = 0; i < labels.length; i += 1) { const divider = Math.max(colorsRange.length - 1, 1); const val = invertColors ? 1 - i / divider : i / divider; @@ -56,9 +69,9 @@ export class MetricVisComponent extends Component { return colors; } - _getBucket(val) { + private getBucket(val: number) { const config = this.props.visParams.metric; - let bucket = findIndex(config.colorsRange, range => { + let bucket = findIndex(config.colorsRange, (range: any) => { return range.from <= val && range.to > val; }); @@ -70,59 +83,65 @@ export class MetricVisComponent extends Component { return bucket; } - _getColor(val, labels, colors) { - const bucket = this._getBucket(val); + private getColor(val: number, labels: string[], colors: { [label: string]: string }) { + const bucket = this.getBucket(val); const label = labels[bucket]; return colors[label]; } - _needsLightText(bgColor) { - const color = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); - if (!color) { + private needsLightText(bgColor: string) { + const colors = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); + if (!colors) { return false; } - return isColorDark(parseInt(color[1]), parseInt(color[2]), parseInt(color[3])); + + const [red, green, blue] = colors.slice(1).map(parseInt); + return isColorDark(red, green, blue); } - _getFormattedValue = (fieldFormatter, value, format = 'text') => { + private getFormattedValue = ( + fieldFormatter: FieldFormat, + value: any, + format: ContentType = 'text' + ) => { if (isNaN(value)) return '-'; return fieldFormatter.convert(value, format); }; - _processTableGroups(table) { + private processTableGroups(table: KibanaDatatable) { const config = this.props.visParams.metric; const dimensions = this.props.visParams.dimensions; const isPercentageMode = config.percentageMode; const min = config.colorsRange[0].from; const max = last(config.colorsRange).to; - const colors = this._getColors(); - const labels = this._getLabels(); - const metrics = []; + const colors = this.getColors(); + const labels = this.getLabels(); + const metrics: MetricVisMetric[] = []; - let bucketColumnId; - let bucketFormatter; + let bucketColumnId: string; + let bucketFormatter: FieldFormat; if (dimensions.bucket) { bucketColumnId = table.columns[dimensions.bucket.accessor].id; bucketFormatter = getFormat(dimensions.bucket.format); } - dimensions.metrics.forEach(metric => { + dimensions.metrics.forEach((metric: SchemaConfig) => { const columnIndex = metric.accessor; - const column = table.columns[columnIndex]; + const column = table?.columns[columnIndex]; const formatter = getFormat(metric.format); table.rows.forEach((row, rowIndex) => { let title = column.name; - let value = row[column.id]; - const color = this._getColor(value, labels, colors); + let value: any = row[column.id]; + const color = this.getColor(value, labels, colors); if (isPercentageMode) { value = (value - min) / (max - min); } - value = this._getFormattedValue(formatter, value, 'html'); + value = this.getFormattedValue(formatter, value, 'html'); if (bucketColumnId) { - const bucketValue = this._getFormattedValue(bucketFormatter, row[bucketColumnId]); + const bucketValue = this.getFormattedValue(bucketFormatter, row[bucketColumnId]); title = `${bucketValue} - ${title}`; } @@ -130,11 +149,11 @@ export class MetricVisComponent extends Component { metrics.push({ label: title, - value: value, - color: shouldColor && config.style.labelColor ? color : null, - bgColor: shouldColor && config.style.bgColor ? color : null, - lightText: shouldColor && config.style.bgColor && this._needsLightText(color), - rowIndex: rowIndex, + value, + color: shouldColor && config.style.labelColor ? color : undefined, + bgColor: shouldColor && config.style.bgColor ? color : undefined, + lightText: shouldColor && config.style.bgColor && this.needsLightText(color), + rowIndex, }); }); }); @@ -142,7 +161,7 @@ export class MetricVisComponent extends Component { return metrics; } - _filterBucket = metric => { + private filterBucket = (metric: MetricVisMetric) => { const dimensions = this.props.visParams.dimensions; if (!dimensions.bucket) { return; @@ -155,27 +174,18 @@ export class MetricVisComponent extends Component { }); }; - _renderMetric = (metric, index) => { + private renderMetric = (metric: MetricVisMetric, index: number) => { return ( ); }; - render() { - let metricsHtml; - if (this.props.visData) { - const metrics = this._processTableGroups(this.props.visData); - metricsHtml = metrics.map(this._renderMetric); - } - return
{metricsHtml}
; - } - componentDidMount() { this.props.renderComplete(); } @@ -183,4 +193,13 @@ export class MetricVisComponent extends Component { componentDidUpdate() { this.props.renderComplete(); } + + render() { + let metricsHtml; + if (this.props.visData) { + const metrics = this.processTableGroups(this.props.visData); + metricsHtml = metrics.map(this.renderMetric); + } + return
{metricsHtml}
; + } } diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx index 9649588976c0d..032f66d92624c 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -29,16 +29,17 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../legacy_imports'; import { ColorRanges, ColorSchemaOptions, SwitchOption, RangeOption, SetColorSchemaOptionsValue, -} from '../../../kbn_vislib_vis_types/public/components'; -import { ColorModes } from '../../../kbn_vislib_vis_types/public/utils/collections'; +} from '../../../vis_type_vislib/public/components'; +import { ColorModes } from '../../../vis_type_vislib/public/utils/collections'; import { MetricVisParam, VisParams } from '../types'; +import { SetColorRangeValue } from '../../../vis_type_vislib/public/components/common/color_ranges'; function MetricVisOptions({ stateParams, @@ -135,7 +136,7 @@ function MetricVisOptions({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx similarity index 76% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx index 2fc436cb829bd..2ee80e92f129b 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx @@ -21,34 +21,32 @@ import { shallow } from 'enzyme'; import { MetricVisValue } from './metric_vis_value'; +const baseMetric = { label: 'Foo', value: 'foo' } as any; + describe('MetricVisValue', () => { it('should be wrapped in EuiKeyboardAccessible if having a click listener', () => { const component = shallow( - {}} /> + {}} /> ); expect(component.find('EuiKeyboardAccessible').exists()).toBe(true); }); it('should not be wrapped in EuiKeyboardAccessible without having a click listener', () => { - const component = shallow( - - ); + const component = shallow(); expect(component.find('EuiKeyboardAccessible').exists()).toBe(false); }); it('should add -isfilterable class if onFilter is provided', () => { const onFilter = jest.fn(); const component = shallow( - + ); component.simulate('click'); expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(1); }); it('should not add -isfilterable class if onFilter is not provided', () => { - const component = shallow( - - ); + const component = shallow(); component.simulate('click'); expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(0); }); @@ -56,9 +54,9 @@ describe('MetricVisValue', () => { it('should call onFilter callback if provided', () => { const onFilter = jest.fn(); const component = shallow( - + ); component.find('.mtrVis__container-isfilterable').simulate('click'); - expect(onFilter).toHaveBeenCalledWith({ label: 'Foo', value: 'foo' }); + expect(onFilter).toHaveBeenCalledWith(baseMetric); }); }); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx similarity index 72% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx index 0776dd13a9868..79876377c8e44 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx @@ -17,26 +17,36 @@ * under the License. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, KeyboardEvent } from 'react'; import classNames from 'classnames'; import { EuiKeyboardAccessible, keyCodes } from '@elastic/eui'; -class MetricVisValue extends Component { +import { MetricVisMetric } from '../types'; + +interface MetricVisValueProps { + metric: MetricVisMetric; + fontSize: number; + onFilter?: (metric: MetricVisMetric) => void; + showLabel?: boolean; +} + +export class MetricVisValue extends Component { onClick = () => { - this.props.onFilter(this.props.metric); + if (this.props.onFilter) { + this.props.onFilter(this.props.metric); + } }; - onKeyPress = e => { - if (e.keyCode === keyCodes.ENTER) { + onKeyPress = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ENTER) { this.onClick(); } }; render() { const { fontSize, metric, onFilter, showLabel } = this.props; - const hasFilter = !!onFilter; + const hasFilter = Boolean(onFilter); const metricValueStyle = { fontSize: `${fontSize}pt`, @@ -52,10 +62,10 @@ class MetricVisValue extends Component {
{showLabel &&
{metric.label}
}
@@ -81,12 +91,3 @@ class MetricVisValue extends Component { return metricComponent; } } - -MetricVisValue.propTypes = { - fontSize: PropTypes.number.isRequired, - metric: PropTypes.object.isRequired, - onFilter: PropTypes.func, - showLabel: PropTypes.bool, -}; - -export { MetricVisValue }; diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts new file mode 100644 index 0000000000000..93dfd76e16b16 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts @@ -0,0 +1,27 @@ +/* + * 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 { Vis, VisParams } from 'ui/vis'; +export { vislibColorMaps, colorSchemas, ColorSchemas } from 'ui/color_maps'; +export { getHeatmapColors } from 'ui/color_maps'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +// @ts-ignore +export { Schemas } from 'ui/vis/editors/default/schemas'; +export { AggGroupNames } from 'ui/vis/editors/default'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index b64361f17c470..45110ca113003 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { vislibColorMaps, ColorSchemas } from 'ui/vislib/components/color/colormaps'; +import { vislibColorMaps, ColorSchemas } from './legacy_imports'; import { ExpressionFunction, KibanaDatatable, @@ -27,16 +27,16 @@ import { Render, Style, } from '../../../../plugins/expressions/public'; -import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; +import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; import { visType, DimensionsVisParam, VisParams } from './types'; -type Context = KibanaDatatable; +export type Context = KibanaDatatable; const name = 'metricVis'; interface Arguments { - percentage: boolean; - colorScheme: ColorSchemas; + percentageMode: boolean; + colorSchema: ColorSchemas; colorMode: ColorModes; useRanges: boolean; invertColors: boolean; @@ -73,19 +73,19 @@ export const createMetricVisFn = (): ExpressionFunction< defaultMessage: 'Metric visualization', }), args: { - percentage: { + percentageMode: { types: ['boolean'], default: false, - help: i18n.translate('visTypeMetric.function.percentage.help', { + help: i18n.translate('visTypeMetric.function.percentageMode.help', { defaultMessage: 'Shows metric in percentage mode. Requires colorRange to be set.', }), }, - colorScheme: { + colorSchema: { types: ['string'], default: '"Green to Red"', options: Object.values(vislibColorMaps).map((value: any) => value.id), - help: i18n.translate('visTypeMetric.function.colorScheme.help', { - defaultMessage: 'Color scheme to use', + help: i18n.translate('visTypeMetric.function.colorSchema.help', { + defaultMessage: 'Color schema to use', }), }, colorMode: { @@ -174,8 +174,8 @@ export const createMetricVisFn = (): ExpressionFunction< dimensions.bucket = args.bucket; } - if (args.percentage && (!args.colorRange || args.colorRange.length === 0)) { - throw new Error('colorRange must be provided when using percentage'); + if (args.percentageMode && (!args.colorRange || args.colorRange.length === 0)) { + throw new Error('colorRange must be provided when using percentageMode'); } const fontSize = Number.parseInt(args.font.spec.fontSize || '', 10); @@ -188,9 +188,9 @@ export const createMetricVisFn = (): ExpressionFunction< visType, visConfig: { metric: { - percentageMode: args.percentage, + percentageMode: args.percentageMode, useRanges: args.useRanges, - colorSchema: args.colorScheme, + colorSchema: args.colorSchema, metricColorMode: args.colorMode, colorsRange: args.colorRange, labels: { diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts new file mode 100644 index 0000000000000..649959054416c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -0,0 +1,106 @@ +/* + * 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 'jquery'; + +import { npStart } from 'ui/new_platform'; +// @ts-ignore +import getStubIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; + +import { Vis } from '../../visualizations/public'; +import { UrlFormat } from '../../../../plugins/data/public'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; +import { metricVisTypeDefinition } from './metric_vis_type'; + +jest.mock('ui/new_platform'); + +describe('metric_vis - createMetricVisTypeDefinition', () => { + let vis: Vis; + + beforeAll(() => { + visualizationsSetup.types.createReactVisualization(metricVisTypeDefinition); + (npStart.plugins.data.fieldFormats.getType as jest.Mock).mockImplementation(() => { + return UrlFormat; + }); + }); + + const setup = () => { + const stubIndexPattern = getStubIndexPattern(); + + stubIndexPattern.stubSetFieldFormat('ip', 'url', { + urlTemplate: 'http://ip.info?address={{value}}', + labelTemplate: 'ip[{{value}}]', + }); + + // TODO: remove when Vis is converted to typescript. Only importing Vis as type + // @ts-ignore + vis = new Vis(stubIndexPattern, { + 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 metricVisType = visualizationsStart.types.get('metric'); + const Controller = metricVisType.visualization; + const controller = new Controller(el, vis); + const render = (esResponse: any) => { + controller.render(esResponse, vis.params); + }; + + return { el, render }; + }; + + it('renders html value from field formatter', () => { + const { el, render } = setup(); + + const ip = '235.195.237.208'; + render({ + columns: [{ id: 'col-0', name: 'ip' }], + rows: [{ 'col-0': ip }], + }); + + const links = $(el) + .find('a[href]') + .filter(function() { + // @ts-ignore + return this.href.includes('ip.info'); + }); + + expect(links.length).toBe(1); + expect(links.text()).toBe(`ip[${ip}]`); + }); +}); 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 ceab5dafe1f06..0d9019ee0579c 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 @@ -19,19 +19,12 @@ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { Schemas } from 'ui/vis/editors/default/schemas'; - -import { AggGroupNames } from 'ui/vis/editors/default'; -import { colorSchemas, ColorSchemas } from 'ui/vislib/components/color/colormaps'; - -// @ts-ignore -import { MetricVisComponent } from './components/metric_vis_controller'; - +import { MetricVisComponent } from './components/metric_vis_component'; import { MetricVisOptions } from './components/metric_vis_options'; -import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; +import { Schemas, AggGroupNames, colorSchemas, ColorSchemas } from './legacy_imports'; +import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; -export const metricVisDefinition = { +export const metricVisTypeDefinition = { name: 'metric', title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }), icon: 'visMetric', 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 f5c152ce888c0..413f846d78991 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -22,7 +22,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressio import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; -import { metricVisDefinition } from './metric_vis_type'; +import { metricVisTypeDefinition } from './metric_vis_type'; /** @internal */ export interface MetricVisPluginSetupDependencies { @@ -40,7 +40,7 @@ export class MetricVisPlugin implements Plugin { public setup(core: CoreSetup, { expressions, visualizations }: MetricVisPluginSetupDependencies) { expressions.registerFunction(createMetricVisFn); - visualizations.types.createReactVisualization(metricVisDefinition); + visualizations.types.createReactVisualization(metricVisTypeDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts index ce0e78140a86a..71c1c12b4f8f0 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -17,17 +17,17 @@ * under the License. */ -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; -import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; +import { ColorSchemas } from './legacy_imports'; +import { Range } from '../../../../plugins/expressions/public'; import { SchemaConfig } from '../../visualizations/public'; -import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; -import { Labels, Style } from '../../kbn_vislib_vis_types/public/types'; +import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; +import { Labels, Style } from '../../vis_type_vislib/public/types'; export const visType = 'metric'; export interface DimensionsVisParam { metrics: SchemaConfig[]; - bucket?: SchemaConfig[]; + bucket?: SchemaConfig; } export interface MetricVisParam { @@ -35,7 +35,7 @@ export interface MetricVisParam { useRanges: boolean; colorSchema: ColorSchemas; metricColorMode: ColorModes; - colorsRange: RangeValues[]; + colorsRange: Range[]; labels: Labels; invertColors: boolean; style: Style; @@ -48,3 +48,12 @@ export interface VisParams { metric: MetricVisParam; type: typeof visType; } + +export interface MetricVisMetric { + value: any; + label: string; + color?: string; + bgColor?: string; + lightText: boolean; + rowIndex: number; +} diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index 4d69af59b0c99..33d7480de5a8e 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -28,13 +28,12 @@ import { NumberInputOption, SwitchOption, SelectOption, -} from '../../../kbn_vislib_vis_types/public/components/common'; +} from '../../../vis_type_vislib/public/components/common'; import { TableVisParams } from '../types'; import { totalAggregations, isAggConfigNumeric } from './utils'; function TableOptions({ aggs, - aggsLabels, stateParams, setValidity, setValue, @@ -51,7 +50,7 @@ function TableOptions({ .filter(col => isAggConfigNumeric(get(col, 'aggConfig.type.name'), stateParams.dimensions)) .map(({ name }) => ({ value: name, text: name })), ], - [aggs, aggsLabels, stateParams.percentageCol, stateParams.dimensions] + [aggs, stateParams.percentageCol, stateParams.dimensions] ); const isPerPageValid = stateParams.perPage === '' || stateParams.perPage > 0; diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts index 7adaa21cac593..a792fc98842f1 100644 --- a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts +++ b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts @@ -19,6 +19,7 @@ import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; import $ from 'jquery'; +import { isEqual } from 'lodash'; import { Vis, VisParams } from '../../visualizations/public'; import { npStart } from './legacy_imports'; @@ -75,6 +76,11 @@ export class TableVisualizationController { this.$scope.vis = this.vis; this.$scope.visState = this.vis.getState(); this.$scope.esResponse = esResponse; + + if (!isEqual(this.$scope.visParams, visParams)) { + this.vis.emit('updateEditorStateParams', visParams); + } + this.$scope.visParams = visParams; this.$scope.renderComplete = resolve; this.$scope.renderFailed = reject; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx index 9e6a2d1a24a85..c500b5d888b05 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { ValidatedDualRange } from 'ui/validated_range'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { SelectOption, SwitchOption } from '../../../kbn_vislib_vis_types/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; import { TagCloudVisParams } from '../types'; function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts similarity index 78% rename from src/legacy/core_plugins/timelion/common/lib/calculate_interval.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts index 7c6b3c2816e67..328c634ea5153 100644 --- a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts @@ -17,11 +17,11 @@ * under the License. */ -import toMS from '../../server/lib/to_milliseconds.js'; +import { toMS } from './to_milliseconds'; // Totally cribbed this from Kibana 3. // I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. -function roundInterval(interval) { +function roundInterval(interval: number) { switch (true) { case interval <= 500: // <= 0.5s return '100ms'; @@ -58,9 +58,24 @@ function roundInterval(interval) { } } -export function calculateInterval(from, to, size, interval, min) { - if (interval !== 'auto') return interval; - const dateMathInterval = roundInterval((to - from) / size); - if (toMS(dateMathInterval) < toMS(min)) return min; +export function calculateInterval( + from: number, + to: number, + size: number, + interval: string, + min: string +) { + if (interval !== 'auto') { + return interval; + } + + const dateMathInterval: string = roundInterval((to - from) / size); + const dateMathIntervalMs = toMS(dateMathInterval); + const minMs = toMS(min); + + if (dateMathIntervalMs !== undefined && minMs !== undefined && dateMathIntervalMs < minMs) { + return min; + } + return dateMathInterval; } diff --git a/src/legacy/core_plugins/timelion/common/lib/index.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts similarity index 95% rename from src/legacy/core_plugins/timelion/common/lib/index.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts index 927331043f0b3..1901b8224f607 100644 --- a/src/legacy/core_plugins/timelion/common/lib/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts @@ -18,4 +18,6 @@ */ export { calculateInterval } from './calculate_interval'; +export { toMS } from './to_milliseconds'; + export const DEFAULT_TIME_FORMAT = 'MMMM Do YYYY, HH:mm:ss.SSS'; diff --git a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts similarity index 55% rename from src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts index 0d62d848daba5..f6fcb08b48b25 100644 --- a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts @@ -17,42 +17,45 @@ * under the License. */ -import _ from 'lodash'; -import moment from 'moment'; +import { keys } from 'lodash'; +import moment, { unitOfTime } from 'moment'; + +type Units = unitOfTime.Base | unitOfTime._quarter; +type Values = { [key in Units]: number }; // map of moment's short/long unit ids and elasticsearch's long unit ids // to their value in milliseconds -const vals = _.transform( - [ - ['ms', 'milliseconds', 'millisecond'], - ['s', 'seconds', 'second', 'sec'], - ['m', 'minutes', 'minute', 'min'], - ['h', 'hours', 'hour'], - ['d', 'days', 'day'], - ['w', 'weeks', 'week'], - ['M', 'months', 'month'], - ['quarter'], - ['y', 'years', 'year'], - ], - function(vals, units) { - const normal = moment.normalizeUnits(units[0]); - const val = moment.duration(1, normal).asMilliseconds(); - [].concat(normal, units).forEach(function(unit) { - vals[unit] = val; - }); - }, - {} -); +const unitMappings = [ + ['ms', 'milliseconds', 'millisecond'], + ['s', 'seconds', 'second', 'sec'], + ['m', 'minutes', 'minute', 'min'], + ['h', 'hours', 'hour'], + ['d', 'days', 'day'], + ['w', 'weeks', 'week'], + ['M', 'months', 'month'], + ['quarter'], + ['y', 'years', 'year'], +] as Units[][]; + +const vals = {} as Values; +unitMappings.forEach(units => { + const normal = moment.normalizeUnits(units[0]) as Units; + const val = moment.duration(1, normal).asMilliseconds(); + ([] as Units[]).concat(normal, units).forEach((unit: Units) => { + vals[unit] = val; + }); +}); + // match any key from the vals object preceded by an optional number -const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$'); +const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + keys(vals).join('|') + ')$'); -export default function(expr) { +export function toMS(expr: string) { const match = expr.match(parseRE); if (match) { if (match[2] === 'M' && match[1] !== '1') { throw new Error('Invalid interval. 1M is only valid monthly interval.'); } - return parseFloat(match[1] || 1) * vals[match[2]]; + return parseFloat(match[1] || '1') * vals[match[2] as Units]; } } diff --git a/src/legacy/core_plugins/timelion/common/types.ts b/src/legacy/core_plugins/vis_type_timelion/common/types.ts similarity index 100% rename from src/legacy/core_plugins/timelion/common/types.ts rename to src/legacy/core_plugins/vis_type_timelion/common/types.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts new file mode 100644 index 0000000000000..4664bebb4f38a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -0,0 +1,44 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; + +const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'timelion_vis', + require: ['kibana', 'elasticsearch', 'visualizations', 'data'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({}), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + }); + +// eslint-disable-next-line import/no-default-export +export default timelionVisPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_timelion/package.json b/src/legacy/core_plugins/vis_type_timelion/package.json new file mode 100644 index 0000000000000..9b09f98ce6caf --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/package.json @@ -0,0 +1,4 @@ +{ + "name": "timelion_vis", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss diff --git a/src/legacy/core_plugins/timelion/public/chain.peg b/src/legacy/core_plugins/vis_type_timelion/public/chain.peg similarity index 100% rename from src/legacy/core_plugins/timelion/public/chain.peg rename to src/legacy/core_plugins/vis_type_timelion/public/chain.peg diff --git a/src/legacy/core_plugins/timelion/public/components/_index.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss similarity index 67% rename from src/legacy/core_plugins/timelion/public/components/_index.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss index f2458a367e176..1d887f43ff9a1 100644 --- a/src/legacy/core_plugins/timelion/public/components/_index.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss @@ -1 +1,2 @@ +@import './panel'; @import './timelion_expression_input'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss similarity index 97% rename from src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss index 39713cd05ab37..c4d591bc82cad 100644 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss @@ -33,6 +33,7 @@ .ngLegendValue { color: $euiTextColor; + cursor: pointer; &:focus, &:hover { diff --git a/src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss diff --git a/src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx similarity index 70% rename from src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index d214abecb9c0c..a8b03bdbc8b7e 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -18,14 +18,22 @@ */ import React from 'react'; -import { VisOptionsProps } from './vis_options_props'; -interface VisOptionsReactWrapperProps extends VisOptionsProps { - component: React.ComponentType; +import { Sheet } from '../helpers/timelion_request_handler'; +import { Panel } from './panel'; + +interface ChartComponentProp { + interval: string; + renderComplete(): void; + seriesList: Sheet; } -function VisOptionsReactWrapper({ component: Component, ...rest }: VisOptionsReactWrapperProps) { - return ; +function ChartComponent(props: ChartComponentProp) { + if (!props.seriesList) { + return null; + } + + return ; } -export { VisOptionsReactWrapper }; +export { ChartComponent }; diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts similarity index 96% rename from src/legacy/core_plugins/timelion/public/components/index.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/index.ts index 8d7d32a3ba262..c70caab8dd70c 100644 --- a/src/legacy/core_plugins/timelion/public/components/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts @@ -19,3 +19,4 @@ export * from './timelion_expression_input'; export * from './timelion_interval'; +export * from './timelion_vis'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx new file mode 100644 index 0000000000000..6095ba28443b8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -0,0 +1,386 @@ +/* + * 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, { useState, useEffect, useMemo, useCallback } from 'react'; +import $ from 'jquery'; +import moment from 'moment-timezone'; +import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; + +import { useKibana } from '../../../../../plugins/kibana_react/public'; +import '../flot'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; + +import { + buildSeriesData, + buildOptions, + SERIES_ID_ATTR, + colors, + Axis, +} from '../helpers/panel_utils'; +import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { tickFormatters } from '../helpers/tick_formatters'; +import { generateTicksProvider } from '../helpers/tick_generator'; +import { TimelionVisDependencies } from '../plugin'; + +interface PanelProps { + interval: string; + seriesList: Sheet; + renderComplete(): void; +} + +interface Position { + x: number; + x1: number; + y: number; + y1: number; + pageX: number; + pageY: number; +} + +interface Range { + to: number; + from: number; +} + +interface Ranges { + xaxis: Range; + yaxis: Range; +} + +const DEBOUNCE_DELAY = 50; +// ensure legend is the same height with or without a caption so legend items do not move around +const emptyCaption = '
'; + +function Panel({ interval, seriesList, renderComplete }: PanelProps) { + const kibana = useKibana(); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const [canvasElem, setCanvasElem] = useState(); + const [chartElem, setChartElem] = useState(); + + const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); + + const [highlightedSeries, setHighlightedSeries] = useState(null); + const [focusedSeries, setFocusedSeries] = useState(); + const [plot, setPlot] = useState(); + + // Used to toggle the series, and for displaying values on hover + const [legendValueNumbers, setLegendValueNumbers] = useState(); + const [legendCaption, setLegendCaption] = useState(); + + const canvasRef = useCallback(node => { + if (node !== null) { + setCanvasElem(node); + } + }, []); + + const elementRef = useCallback(node => { + if (node !== null) { + setChartElem(node); + } + }, []); + + useEffect( + () => () => { + $(chartElem) + .off('plotselected') + .off('plothover') + .off('mouseleave'); + }, + [chartElem] + ); + + const highlightSeries = useCallback( + debounce(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + if (highlightedSeries === id) { + return; + } + + setHighlightedSeries(id); + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + series.color = + seriesIndex === id + ? originalColorMap.get(series) // color it like it was + : 'rgba(128,128,128,0.1)'; // mark as grey + + return series; + }) + ); + }, DEBOUNCE_DELAY), + [originalColorMap, highlightedSeries] + ); + + const focusSeries = useCallback( + (event: JQuery.TriggeredEvent) => { + const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); + setFocusedSeries(id); + highlightSeries(event); + }, + [highlightSeries] + ); + + const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + if (seriesIndex === id) { + series._hide = !series._hide; + } + return series; + }) + ); + }, []); + + const updateCaption = useCallback( + (plotData: any) => { + if (get(plotData, '[0]._global.legend.showTime', true)) { + const caption = $(''); + caption.html(emptyCaption); + setLegendCaption(caption); + + const canvasNode = $(canvasElem); + canvasNode.find('div.legend table').append(caption); + setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + + const legend = $(canvasElem).find('.ngLegendValue'); + if (legend) { + legend.click(toggleSeries); + legend.focus(focusSeries); + legend.mouseover(highlightSeries); + } + + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + canvasNode + .find('div.legend table .legendLabel>span') + .get(focusedSeries) + .focus(); + } + } + }, + [focusedSeries, canvasElem, toggleSeries, focusSeries, highlightSeries] + ); + + const updatePlot = useCallback( + (chartValue: Series[], grid?: boolean) => { + if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { + const options = buildOptions( + interval, + kibana.services.timefilter, + kibana.services.uiSettings, + chartElem && chartElem.clientWidth, + grid + ); + const updatedSeries = buildSeriesData(chartValue, options); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: Axis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + + const newPlot = $.plot(canvasElem, updatedSeries, options); + setPlot(newPlot); + renderComplete(); + + updateCaption(newPlot.getData()); + } + }, + [canvasElem, chartElem, renderComplete, kibana.services, interval, updateCaption] + ); + + useEffect(() => { + updatePlot(chart, seriesList.render && seriesList.render.grid); + }, [chart, updatePlot, seriesList.render]); + + useEffect(() => { + const colorsSet: Array<[Series, string]> = []; + const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; + } + colorsSet.push([newSeries, newSeries.color]); + return newSeries; + }); + setChart(newChart); + setOriginalColorMap(new Map(colorsSet)); + }, [seriesList.list]); + + const unhighlightSeries = useCallback(() => { + if (highlightedSeries === null) { + return; + } + + setHighlightedSeries(null); + setFocusedSeries(null); + + setChart(chartState => + chartState.map((series: Series) => { + series.color = originalColorMap.get(series); // reset the colors + return series; + }) + ); + }, [originalColorMap, highlightedSeries]); + + // Shamelessly borrowed from the flotCrosshairs example + const setLegendNumbers = useCallback( + (pos: Position) => { + unhighlightSeries(); + + const axes = plot.getAxes(); + if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { + return; + } + + const dataset = plot.getData(); + if (legendCaption) { + legendCaption.text( + moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) + ); + } + for (let i = 0; i < dataset.length; ++i) { + const series = dataset[i]; + const useNearestPoint = series.lines.show && !series.lines.steps; + const precision = get(series, '_meta.precision', 2); + + if (series._hide) { + continue; + } + + const currentPoint = series.data.find((point: [number, number], index: number) => { + if (index + 1 === series.data.length) { + return true; + } + if (useNearestPoint) { + return pos.x - point[0] < series.data[index + 1][0] - pos.x; + } else { + return pos.x < series.data[index + 1][0]; + } + }); + + const y = currentPoint[1]; + + if (y != null && legendValueNumbers) { + let label = y.toFixed(precision); + if (series.yaxis.tickFormatter) { + label = series.yaxis.tickFormatter(Number(label), series.yaxis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } else { + legendValueNumbers.eq(i).empty(); + } + } + }, + [plot, legendValueNumbers, unhighlightSeries, legendCaption] + ); + + const debouncedSetLegendNumbers = useCallback( + debounce(setLegendNumbers, DEBOUNCE_DELAY, { + maxWait: DEBOUNCE_DELAY, + leading: true, + trailing: false, + }), + [setLegendNumbers] + ); + + const clearLegendNumbers = useCallback(() => { + if (legendCaption) { + legendCaption.html(emptyCaption); + } + each(legendValueNumbers, (num: Node) => { + $(num).empty(); + }); + }, [legendCaption, legendValueNumbers]); + + const plotHoverHandler = useCallback( + (event: JQuery.TriggeredEvent, pos: Position) => { + if (!plot) { + return; + } + plot.setCrosshair(pos); + debouncedSetLegendNumbers(pos); + }, + [plot, debouncedSetLegendNumbers] + ); + const mouseLeaveHandler = useCallback(() => { + if (!plot) { + return; + } + plot.clearCrosshair(); + clearLegendNumbers(); + }, [plot, clearLegendNumbers]); + + const plotSelectedHandler = useCallback( + (event: JQuery.TriggeredEvent, ranges: Ranges) => { + kibana.services.timefilter.setTime({ + from: moment(ranges.xaxis.from), + to: moment(ranges.xaxis.to), + }); + }, + [kibana.services.timefilter] + ); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plotselected') + .on('plotselected', plotSelectedHandler); + } + }, [chartElem, plotSelectedHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('mouseleave') + .on('mouseleave', mouseLeaveHandler); + } + }, [chartElem, mouseLeaveHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plothover') + .on('plothover', plotHoverHandler); + } + }, [chartElem, plotHoverHandler]); + + const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ + seriesList.list, + ]); + + return ( +
+
{title}
+
+
+ ); +} + +export { Panel }; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx index c695d09ca822b..fa79e4eb6871a 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx @@ -25,7 +25,7 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; import { suggest, getSuggestion } from './timelion_expression_input_helpers'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { getArgValueSuggestions } from '../helpers/arg_value_suggestions'; const LANGUAGE_ID = 'timelion_expression'; monacoEditor.languages.register({ id: LANGUAGE_ID }); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index fc90c276eeca2..5aa05fb16466b 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -26,7 +26,7 @@ import grammar from 'raw-loader!../chain.peg'; import { i18n } from '@kbn/i18n'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { ArgValueSuggestions, FunctionArg, Location } from '../services/arg_value_suggestions'; +import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx index 6294e51e54788..4bfa5d424ed85 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx @@ -21,8 +21,8 @@ import React, { useMemo, useCallback } from 'react'; import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; import { isValidEsInterval } from '../../../../core_plugins/data/common'; +import { useValidation } from '../legacy_imports'; const intervalOptions = [ { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx new file mode 100644 index 0000000000000..ae55e11380b78 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -0,0 +1,49 @@ +/* + * 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 { IUiSettingsClient } from 'kibana/public'; +import { Vis } from '../legacy_imports'; +import { ChartComponent } from './chart'; +import { VisParams } from '../timelion_vis_fn'; +import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; + +export interface TimelionVisComponentProp { + config: IUiSettingsClient; + renderComplete(): void; + updateStatus: object; + vis: Vis; + visData: TimelionSuccessResponse; + visParams: VisParams; +} + +function TimelionVisComponent(props: TimelionVisComponentProp) { + return ( +
+ +
+ ); +} + +export { TimelionVisComponent }; diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/flot.js b/src/legacy/core_plugins/vis_type_timelion/public/flot.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/panels/timechart/flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/flot.js diff --git a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts new file mode 100644 index 0000000000000..db29d9112be8e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -0,0 +1,187 @@ +/* + * 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 { cloneDeep, defaults, merge, compact } from 'lodash'; +import moment, { Moment } from 'moment-timezone'; + +import { TimefilterContract } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'kibana/public'; + +import { calculateInterval } from '../../common/lib'; +import { xaxisFormatterProvider } from './xaxis_formatter'; +import { Series } from './timelion_request_handler'; + +export interface Axis { + delta?: number; + max?: number; + min?: number; + mode: string; + options?: { + units: { prefix: string; suffix: string }; + }; + tickSize?: number; + ticks: number; + tickLength: number; + timezone: string; + tickDecimals?: number; + tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); + tickGenerator?(axis: Axis): number[]; + units?: { type: string }; +} + +interface TimeRangeBounds { + min: Moment | undefined; + max: Moment | undefined; +} + +const colors = [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', +]; + +const SERIES_ID_ATTR = 'data-series-id'; + +function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { + const seriesData = chart.map((series: Series, seriesIndex: number) => { + const newSeries: Series = cloneDeep( + defaults(series, { + shadowSize: 0, + lines: { + lineWidth: 3, + }, + }) + ); + + newSeries._id = seriesIndex; + + if (series.color) { + const span = document.createElement('span'); + span.style.color = series.color; + newSeries.color = span.style.color; + } + + if (series._hide) { + newSeries.data = []; + newSeries.stack = false; + newSeries.label = `(hidden) ${series.label}`; + } + + if (series._global) { + merge(options, series._global, (objVal, srcVal) => { + // This is kind of gross, it means that you can't replace a global value with a null + // best you can do is an empty string. Deal with it. + if (objVal == null) { + return srcVal; + } + if (srcVal == null) { + return objVal; + } + }); + } + + return newSeries; + }); + + return compact(seriesData); +} + +function buildOptions( + intervalValue: string, + timefilter: TimefilterContract, + uiSettings: IUiSettingsClient, + clientWidth = 0, + showGrid?: boolean +) { + // Get the X-axis tick format + const time: TimeRangeBounds = timefilter.getBounds(); + const interval = calculateInterval( + (time.min && time.min.valueOf()) || 0, + (time.max && time.max.valueOf()) || 0, + uiSettings.get('timelion:target_buckets') || 200, + intervalValue, + uiSettings.get('timelion:min_interval') || '1ms' + ); + const format = xaxisFormatterProvider(uiSettings)(interval); + + const tickLetterWidth = 7; + const tickPadding = 45; + + const options = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + // Calculate how many ticks can fit on the axis + ticks: Math.floor(clientWidth / (format.length * tickLetterWidth + tickPadding)), + // Use moment to format ticks so we get timezone correction + tickFormatter: (val: number) => moment(val).format(format), + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + colors, + grid: { + show: showGrid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: string, series: { _id: number }) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'ngLegendValue'); + wrapperSpan.setAttribute(SERIES_ID_ATTR, `${series._id}`); + + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + } as jquery.flot.plotOptions & { yaxes?: Axis[] }; + + return options; +} + +export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; diff --git a/src/legacy/core_plugins/timelion/public/services/plugin_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/plugin_services.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts diff --git a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts similarity index 56% rename from src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index 40840c4cd2610..01734f2f5888a 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -17,124 +17,123 @@ * under the License. */ -import expect from '@kbn/expect'; -import { tickFormatters } from '../../services/tick_formatters'; +import { tickFormatters } from './tick_formatters'; describe('Tick Formatters', function() { - let formatters; + let formatters: any; beforeEach(function() { formatters = tickFormatters(); }); describe('Bits mode', function() { - let bitFormatter; + let bitFormatter: any; beforeEach(function() { bitFormatter = formatters.bits; }); it('is a function', function() { - expect(bitFormatter).to.be.a('function'); + expect(bitFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitFormatter(7)).to.equal('7b'); - expect(bitFormatter(4 * 1000)).to.equal('4kb'); - expect(bitFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb'); - expect(bitFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb'); + expect(bitFormatter(7)).toEqual('7b'); + expect(bitFormatter(4 * 1000)).toEqual('4kb'); + expect(bitFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb'); + expect(bitFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb'); }); it('formats negative values with b/kb/mb/gb', () => { - expect(bitFormatter(-7)).to.equal('-7b'); - expect(bitFormatter(-4 * 1000)).to.equal('-4kb'); - expect(bitFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb'); - expect(bitFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb'); + expect(bitFormatter(-7)).toEqual('-7b'); + expect(bitFormatter(-4 * 1000)).toEqual('-4kb'); + expect(bitFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb'); + expect(bitFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb'); }); }); describe('Bits/s mode', function() { - let bitsFormatter; + let bitsFormatter: any; beforeEach(function() { bitsFormatter = formatters['bits/s']; }); it('is a function', function() { - expect(bitsFormatter).to.be.a('function'); + expect(bitsFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitsFormatter(7)).to.equal('7b/s'); - expect(bitsFormatter(4 * 1000)).to.equal('4kb/s'); - expect(bitsFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb/s'); - expect(bitsFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb/s'); + expect(bitsFormatter(7)).toEqual('7b/s'); + expect(bitsFormatter(4 * 1000)).toEqual('4kb/s'); + expect(bitsFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb/s'); + expect(bitsFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb/s'); }); it('formats negative values with b/kb/mb/gb', function() { - expect(bitsFormatter(-7)).to.equal('-7b/s'); - expect(bitsFormatter(-4 * 1000)).to.equal('-4kb/s'); - expect(bitsFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb/s'); - expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb/s'); + expect(bitsFormatter(-7)).toEqual('-7b/s'); + expect(bitsFormatter(-4 * 1000)).toEqual('-4kb/s'); + expect(bitsFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb/s'); + expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb/s'); }); }); describe('Bytes mode', function() { - let byteFormatter; + let byteFormatter: any; beforeEach(function() { byteFormatter = formatters.bytes; }); it('is a function', function() { - expect(byteFormatter).to.be.a('function'); + expect(byteFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(byteFormatter(10)).to.equal('10B'); - expect(byteFormatter(10 * 1024)).to.equal('10KB'); - expect(byteFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB'); - expect(byteFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB'); + expect(byteFormatter(10)).toEqual('10B'); + expect(byteFormatter(10 * 1024)).toEqual('10KB'); + expect(byteFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB'); + expect(byteFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(byteFormatter(-10)).to.equal('-10B'); - expect(byteFormatter(-10 * 1024)).to.equal('-10KB'); - expect(byteFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB'); - expect(byteFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB'); + expect(byteFormatter(-10)).toEqual('-10B'); + expect(byteFormatter(-10 * 1024)).toEqual('-10KB'); + expect(byteFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB'); + expect(byteFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB'); }); }); describe('Bytes/s mode', function() { - let bytesFormatter; + let bytesFormatter: any; beforeEach(function() { bytesFormatter = formatters['bytes/s']; }); it('is a function', function() { - expect(bytesFormatter).to.be.a('function'); + expect(bytesFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(bytesFormatter(10)).to.equal('10B/s'); - expect(bytesFormatter(10 * 1024)).to.equal('10KB/s'); - expect(bytesFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB/s'); - expect(bytesFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB/s'); + expect(bytesFormatter(10)).toEqual('10B/s'); + expect(bytesFormatter(10 * 1024)).toEqual('10KB/s'); + expect(bytesFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB/s'); + expect(bytesFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB/s'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(bytesFormatter(-10)).to.equal('-10B/s'); - expect(bytesFormatter(-10 * 1024)).to.equal('-10KB/s'); - expect(bytesFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB/s'); - expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB/s'); + expect(bytesFormatter(-10)).toEqual('-10B/s'); + expect(bytesFormatter(-10 * 1024)).toEqual('-10KB/s'); + expect(bytesFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB/s'); + expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB/s'); }); }); describe('Currency mode', function() { - let currencyFormatter; + let currencyFormatter: any; beforeEach(function() { currencyFormatter = formatters.currency; }); it('is a function', function() { - expect(currencyFormatter).to.be.a('function'); + expect(currencyFormatter).toEqual(expect.any(Function)); }); it('formats with $ by default', function() { @@ -143,7 +142,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(currencyFormatter(10.2, axis)).to.equal('$10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('$10.20'); }); it('accepts currency in ISO 4217', function() { @@ -155,18 +154,18 @@ describe('Tick Formatters', function() { }, }; - expect(currencyFormatter(10.2, axis)).to.equal('CN¥10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('CN¥10.20'); }); }); describe('Percent mode', function() { - let percentFormatter; + let percentFormatter: any; beforeEach(function() { percentFormatter = formatters.percent; }); it('is a function', function() { - expect(percentFormatter).to.be.a('function'); + expect(percentFormatter).toEqual(expect.any(Function)); }); it('formats with %', function() { @@ -175,7 +174,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(percentFormatter(0.1234, axis)).to.equal('12%'); + expect(percentFormatter(0.1234, axis)).toEqual('12%'); }); it('formats with % with decimal precision', function() { @@ -189,18 +188,18 @@ describe('Tick Formatters', function() { }, }, }; - expect(percentFormatter(0.12345, axis)).to.equal('12.345%'); + expect(percentFormatter(0.12345, axis)).toEqual('12.345%'); }); }); describe('Custom mode', function() { - let customFormatter; + let customFormatter: any; beforeEach(function() { customFormatter = formatters.custom; }); it('is a function', function() { - expect(customFormatter).to.be.a('function'); + expect(customFormatter).toEqual(expect.any(Function)); }); it('accepts prefix and suffix', function() { @@ -214,7 +213,7 @@ describe('Tick Formatters', function() { tickDecimals: 1, }; - expect(customFormatter(10.2, axis)).to.equal('prefix10.2suffix'); + expect(customFormatter(10.2, axis)).toEqual('prefix10.2suffix'); }); it('correctly renders small values', function() { @@ -228,7 +227,7 @@ describe('Tick Formatters', function() { tickDecimals: 3, }; - expect(customFormatter(0.00499999999999999, axis)).to.equal('prefix0.005suffix'); + expect(customFormatter(0.00499999999999999, axis)).toEqual('prefix0.005suffix'); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts similarity index 79% rename from src/legacy/core_plugins/timelion/public/services/tick_formatters.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts index 2c78d2423cc06..c80f9c3ed5f4b 100644 --- a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -17,9 +17,11 @@ * under the License. */ -import _ from 'lodash'; +import { get } from 'lodash'; -function baseTickFormatter(value: any, axis: any) { +import { Axis } from './panel_utils'; + +function baseTickFormatter(value: number, axis: Axis) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -40,8 +42,8 @@ function baseTickFormatter(value: any, axis: any) { return formatted; } -function unitFormatter(divisor: any, units: any) { - return (val: any) => { +function unitFormatter(divisor: number, units: string[]) { + return (val: number) => { let index = 0; const isNegative = val < 0; val = Math.abs(val); @@ -55,20 +57,20 @@ function unitFormatter(divisor: any, units: any) { } export function tickFormatters() { - const formatters = { + return { bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: any, axis: any) { + currency(val: number, axis: Axis) { return val.toLocaleString('en', { style: 'currency', - currency: axis.options.units.prefix || 'USD', + currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: any, axis: any) { + percent(val: number, axis: Axis) { let precision = - _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0); + get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 if (precision < 0) { precision = 0; @@ -78,13 +80,11 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: any, axis: any) { + custom(val: number, axis: Axis) { const formattedVal = baseTickFormatter(val, axis); - const prefix = axis.options.units.prefix; - const suffix = axis.options.units.suffix; + const prefix = axis && axis.options && axis.options.units.prefix; + const suffix = axis && axis.options && axis.options.units.suffix; return prefix + formattedVal + suffix; }, }; - - return formatters; } diff --git a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts similarity index 72% rename from src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts index e42657374af4c..d1d959dee9501 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts @@ -17,11 +17,10 @@ * under the License. */ -import expect from '@kbn/expect'; -import { generateTicksProvider } from '../panels/timechart/tick_generator'; +import { generateTicksProvider } from './tick_generator'; describe('Tick Generator', function() { - let generateTicks; + let generateTicks: any; beforeEach(function() { generateTicks = generateTicksProvider(); @@ -29,7 +28,7 @@ describe('Tick Generator', function() { describe('generateTicksProvider()', function() { it('should return a function', function() { - expect(generateTicks).to.be.a('function'); + expect(generateTicks).toEqual(expect.any(Function)); }); }); @@ -58,14 +57,14 @@ describe('Tick Generator', function() { let n = 1; while (Math.pow(2, n) < axis.delta) n++; const expectedDelta = Math.pow(2, n); - const expectedNr = parseInt((axis.max - axis.min) / expectedDelta) + 2; - expect(ticks instanceof Array).to.be(true); - expect(ticks.length).to.be(expectedNr); - expect(ticks[0]).to.equal(axis.min); - expect(ticks[parseInt(ticks.length / 2)]).to.equal( - axis.min + expectedDelta * parseInt(ticks.length / 2) + const expectedNr = Math.floor((axis.max - axis.min) / expectedDelta) + 2; + expect(ticks instanceof Array).toBeTruthy(); + expect(ticks.length).toBe(expectedNr); + expect(ticks[0]).toEqual(axis.min); + expect(ticks[Math.floor(ticks.length / 2)]).toEqual( + axis.min + expectedDelta * Math.floor(ticks.length / 2) ); - expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1)); + expect(ticks[ticks.length - 1]).toEqual(axis.min + expectedDelta * (ticks.length - 1)); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts similarity index 82% rename from src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts index f7d696a0316db..6321ad01418ac 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts @@ -17,15 +17,17 @@ * under the License. */ +import { Axis } from './panel_utils'; + export function generateTicksProvider() { - function floorInBase(n: any, base: any) { + function floorInBase(n: number, base: number) { return base * Math.floor(n / base); } - function generateTicks(axis: any) { + function generateTicks(axis: Axis) { const returnTicks = []; let tickSize = 2; - let delta = axis.delta; + let delta = axis.delta || 0; let steps = 0; let tickVal; let tickCount = 0; @@ -46,16 +48,14 @@ export function generateTicksProvider() { axis.tickSize = tickSize * Math.pow(1024, steps); // Calculate the new ticks - const tickMin = floorInBase(axis.min, axis.tickSize); + const tickMin = floorInBase(axis.min || 0, axis.tickSize); do { tickVal = tickMin + tickCount++ * axis.tickSize; returnTicks.push(tickVal); - } while (tickVal < axis.max); + } while (tickVal < (axis.max || 0)); return returnTicks; } - return function(axis: any) { - return generateTicks(axis); - }; + return (axis: Axis) => generateTicks(axis); } diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts similarity index 83% rename from src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 14cd3d0083e6a..de066b474d987 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -17,13 +17,11 @@ * under the License. */ -// @ts-ignore -import { timezoneProvider } from 'ui/vis/lib/timezone'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; -import { TimelionVisualizationDependencies } from '../plugin'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; +import { timezoneProvider, VisParams } from '../legacy_imports'; +import { TimelionVisDependencies } from '../plugin'; interface Stats { cacheCount: number; @@ -33,9 +31,25 @@ interface Stats { sheetTime: number; } -interface Sheet { - list: Array>; - render: Record; +export interface Series { + _global?: boolean; + _hide?: boolean; + _id?: number; + _title?: string; + color?: string; + data: Array>; + fit: string; + label: string; + split: string; + stack?: boolean; + type: string; +} + +export interface Sheet { + list: Series[]; + render?: { + grid?: boolean; + }; type: string; } @@ -46,8 +60,11 @@ export interface TimelionSuccessResponse { type: KIBANA_CONTEXT_NAME; } -export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { uiSettings, http, timefilter } = dependencies; +export function getTimelionRequestHandler({ + uiSettings, + http, + timefilter, +}: TimelionVisDependencies) { const timezone = timezoneProvider(uiSettings)(); return async function({ diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts similarity index 86% rename from src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts index db3408dae33db..5350b1cb26957 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts @@ -20,12 +20,13 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'kibana/public'; -export function xaxisFormatterProvider(config: any) { +export function xaxisFormatterProvider(config: IUiSettingsClient) { function getFormat(esInterval: any) { const parts = esInterval.match(/(\d+)(ms|s|m|h|d|w|M|y|)/); - if (parts == null || parts[1] == null || parts[2] == null) { + if (parts === null || parts[1] === null || parts[2] === null) { throw new Error( i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', { defaultMessage: 'Unknown interval', @@ -48,7 +49,5 @@ export function xaxisFormatterProvider(config: any) { return config.get('dateFormat'); } - return function(esInterval: any) { - return getFormat(esInterval); - }; + return (esInterval: any) => getFormat(esInterval); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.scss b/src/legacy/core_plugins/vis_type_timelion/public/index.scss new file mode 100644 index 0000000000000..313f14a8acf69 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.scss @@ -0,0 +1,5 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './timelion_vis'; +@import './timelion_editor'; +@import './components/index'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/index.ts new file mode 100644 index 0000000000000..98cc35877094e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../../core/public'; +import { TimelionVisPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts new file mode 100644 index 0000000000000..9935f3d92f6bd --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'kibana/public'; + +import { npSetup, npStart } from './legacy_imports'; + +import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; +import { TimelionVisSetupDependencies } from './plugin'; +import { plugin } from '.'; + +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + data: npSetup.plugins.data, + visualizations: visualizationsSetup, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts new file mode 100644 index 0000000000000..8d1156862d27e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts @@ -0,0 +1,29 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; +export { PluginsStart } from 'ui/new_platform/new_platform'; + +// @ts-ignore +export { DefaultEditorSize } from 'ui/vis/editor_size'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; +export { VisParams, Vis } from 'ui/vis'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +export { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts new file mode 100644 index 0000000000000..69a2ad3c1351a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -0,0 +1,76 @@ +/* + * 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 { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, + IUiSettingsClient, + HttpSetup, +} from 'kibana/public'; +import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; +import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; + +import { PluginsStart } from './legacy_imports'; +import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; + +import { getTimelionVisualizationConfig } from './timelion_vis_fn'; +import { getTimelionVisDefinition } from './timelion_vis_type'; +import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_services'; + +type TimelionVisCoreSetup = CoreSetup; + +/** @internal */ +export interface TimelionVisDependencies extends Partial { + uiSettings: IUiSettingsClient; + http: HttpSetup; + timefilter: TimefilterContract; +} + +/** @internal */ +export interface TimelionVisSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export class TimelionVisPlugin implements Plugin { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: TimelionVisCoreSetup, + { expressions, visualizations, data }: TimelionVisSetupDependencies + ) { + const dependencies: TimelionVisDependencies = { + uiSettings: core.uiSettings, + http: core.http, + timefilter: data.query.timefilter.timefilter, + }; + + expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); + visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); + } + + public start(core: CoreStart, plugins: PluginsStart) { + setIndexPatterns(plugins.data.indexPatterns); + setSavedObjectsClient(core.savedObjects.client); + } +} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx similarity index 89% rename from src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index 527fcc3bc6ce8..be6829a76ac58 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -20,9 +20,9 @@ import React, { useCallback } from 'react'; import { EuiPanel } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { VisParams } from '../timelion_vis_fn'; -import { TimelionInterval, TimelionExpressionInput } from '../components'; +import { VisOptionsProps } from './legacy_imports'; +import { VisParams } from './timelion_vis_fn'; +import { TimelionInterval, TimelionExpressionInput } from './components'; function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps) { const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts similarity index 91% rename from src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index 206f9f5d8368d..8a517b6cecbc7 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -20,9 +20,9 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; -import { getTimelionRequestHandler } from './vis/timelion_request_handler'; -import { TimelionVisualizationDependencies } from './plugin'; -import { TIMELION_VIS_NAME } from './vis'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TIMELION_VIS_NAME } from './timelion_vis_type'; +import { TimelionVisDependencies } from './plugin'; const name = 'timelion_vis'; @@ -42,7 +42,7 @@ export type VisParams = Arguments; type Return = Promise>; export const getTimelionVisualizationConfig = ( - dependencies: TimelionVisualizationDependencies + dependencies: TimelionVisDependencies ): ExpressionFunction => ({ name, type: 'render', diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx similarity index 73% rename from src/legacy/core_plugins/timelion/public/vis/index.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 1edcb0a5ce71c..51540eea0223c 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -19,21 +19,18 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { DefaultEditorSize } from 'ui/vis/editor_size'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; -import { getTimelionRequestHandler } from './timelion_request_handler'; -import visConfigTemplate from './timelion_vis.html'; -import { TimelionVisualizationDependencies } from '../plugin'; -// @ts-ignore -import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; + +import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; +import { DefaultEditorSize, VisOptionsProps } from './legacy_imports'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TimelionVisComponent, TimelionVisComponentProp } from './components'; import { TimelionOptions } from './timelion_options'; -import { VisParams } from '../timelion_vis_fn'; +import { VisParams } from './timelion_vis_fn'; +import { TimelionVisDependencies } from './plugin'; export const TIMELION_VIS_NAME = 'timelion'; -export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { +export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) { const { http, uiSettings } = dependencies; const timelionRequestHandler = getTimelionRequestHandler(dependencies); @@ -46,13 +43,16 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe description: i18n.translate('timelion.timelionDescription', { defaultMessage: 'Build time-series using functional expressions', }), - visualization: AngularVisController, visConfig: { defaults: { expression: '.es(*)', interval: 'auto', }, - template: visConfigTemplate, + component: (props: TimelionVisComponentProp) => ( + + + + ), }, editorConfig: { optionsTemplate: (props: VisOptionsProps) => ( diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js b/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js deleted file mode 100644 index f78f0a5660419..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js +++ /dev/null @@ -1,61 +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 { expect } from 'chai'; -import { createOptions, isBasicAgg } from '../agg_lookup'; - -describe('aggLookup', () => { - describe('isBasicAgg(metric)', () => { - it('returns true for a basic metric (count)', () => { - expect(isBasicAgg({ type: 'count' })).to.equal(true); - }); - it('returns false for a pipeline metric (derivative)', () => { - expect(isBasicAgg({ type: 'derivative' })).to.equal(false); - }); - }); - - describe('createOptions(type, siblings)', () => { - it('returns options for all aggs', () => { - const options = createOptions(); - expect(options).to.have.length(30); - options.forEach(option => { - expect(option).to.have.property('label'); - expect(option).to.have.property('value'); - expect(option).to.have.property('disabled'); - }); - }); - - it('returns options for basic', () => { - const options = createOptions('basic'); - expect(options).to.have.length(15); - expect(options.every(opt => isBasicAgg({ type: opt.value }))).to.equal(true); - }); - - it('returns options for pipeline', () => { - const options = createOptions('pipeline'); - expect(options).to.have.length(15); - expect(options.every(opt => !isBasicAgg({ type: opt.value }))).to.equal(true); - }); - - it('returns options for all if given unknown key', () => { - const options = createOptions('foo'); - expect(options).to.have.length(30); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js index 0f6eba4add596..4dfdc83dcfabb 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js +++ b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js @@ -127,25 +127,3 @@ const byType = { export function isBasicAgg(item) { return _.includes(Object.keys(byType.basic), item.type); } - -export function createOptions(type = '_all', siblings = []) { - let aggs = byType[type]; - if (!aggs) aggs = byType._all; - let enablePipelines = siblings.some(isBasicAgg); - if (siblings.length <= 1) enablePipelines = false; - return _(aggs) - .map((label, value) => { - const disabled = _.includes(pipeline, value) ? !enablePipelines : false; - return { - label: disabled - ? i18n.translate('visTypeTimeseries.aggLookup.addPipelineAggDescription', { - defaultMessage: '{label} (use the "+" button to add this pipeline agg)', - values: { label }, - }) - : label, - value, - disabled, - }; - }) - .value(); -} diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js new file mode 100644 index 0000000000000..a7c5d362e669c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js @@ -0,0 +1,31 @@ +/* + * 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 { isBasicAgg } from './agg_lookup'; + +describe('aggLookup', () => { + describe('isBasicAgg(metric)', () => { + test('returns true for a basic metric (count)', () => { + expect(isBasicAgg({ type: 'count' })).toEqual(true); + }); + test('returns false for a pipeline metric (derivative)', () => { + expect(isBasicAgg({ type: 'derivative' })).toEqual(false); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js b/src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js similarity index 59% rename from src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js rename to src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js index 955c052a3906e..a5af6d114c894 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js +++ b/src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js @@ -17,51 +17,50 @@ * under the License. */ -import { expect } from 'chai'; -import { calculateLabel } from '../calculate_label'; +import { calculateLabel } from './calculate_label'; describe('calculateLabel(metric, metrics)', () => { - it('returns "Unknown" for empty metric', () => { - expect(calculateLabel()).to.equal('Unknown'); + test('returns "Unknown" for empty metric', () => { + expect(calculateLabel()).toEqual('Unknown'); }); - it('returns the metric.alias if set', () => { - expect(calculateLabel({ alias: 'Example' })).to.equal('Example'); + test('returns the metric.alias if set', () => { + expect(calculateLabel({ alias: 'Example' })).toEqual('Example'); }); - it('returns "Count" for a count metric', () => { - expect(calculateLabel({ type: 'count' })).to.equal('Count'); + test('returns "Count" for a count metric', () => { + expect(calculateLabel({ type: 'count' })).toEqual('Count'); }); - it('returns "Calculation" for a bucket script metric', () => { - expect(calculateLabel({ type: 'calculation' })).to.equal('Bucket Script'); + test('returns "Calculation" for a bucket script metric', () => { + expect(calculateLabel({ type: 'calculation' })).toEqual('Bucket Script'); }); - it('returns formated label for series_agg', () => { + test('returns formated label for series_agg', () => { const label = calculateLabel({ type: 'series_agg', function: 'max' }); - expect(label).to.equal('Series Agg (max)'); + expect(label).toEqual('Series Agg (max)'); }); - it('returns formated label for basic aggs', () => { + test('returns formated label for basic aggs', () => { const label = calculateLabel({ type: 'avg', field: 'memory' }); - expect(label).to.equal('Average of memory'); + expect(label).toEqual('Average of memory'); }); - it('returns formated label for pipeline aggs', () => { + test('returns formated label for pipeline aggs', () => { const metric = { id: 2, type: 'derivative', field: 1 }; const metrics = [{ id: 1, type: 'max', field: 'network.out.bytes' }, metric]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Max of network.out.bytes'); + expect(label).toEqual('Derivative of Max of network.out.bytes'); }); - it('returns formated label for derivative of percentile', () => { + test('returns formated label for derivative of percentile', () => { const metric = { id: 2, type: 'derivative', field: '1[50.0]' }; const metrics = [{ id: 1, type: 'percentile', field: 'network.out.bytes' }, metric]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Percentile of network.out.bytes (50.0)'); + expect(label).toEqual('Derivative of Percentile of network.out.bytes (50.0)'); }); - it('returns formated label for pipeline aggs (deep)', () => { + test('returns formated label for pipeline aggs (deep)', () => { const metric = { id: 3, type: 'derivative', field: 2 }; const metrics = [ { id: 1, type: 'max', field: 'network.out.bytes' }, @@ -69,16 +68,16 @@ describe('calculateLabel(metric, metrics)', () => { metric, ]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Moving Average of Max of network.out.bytes'); + expect(label).toEqual('Derivative of Moving Average of Max of network.out.bytes'); }); - it('returns formated label for pipeline aggs uses alias for field metric', () => { + test('returns formated label for pipeline aggs uses alias for field metric', () => { const metric = { id: 2, type: 'derivative', field: 1 }; const metrics = [ { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' }, metric, ]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Outbound Traffic'); + expect(label).toEqual('Derivative of Outbound Traffic'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js similarity index 88% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js index 40807399a35a2..4f343022c4b0f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js @@ -17,11 +17,10 @@ * under the License. */ -import { calculateSiblings } from '../calculate_siblings'; -import { expect } from 'chai'; +import { calculateSiblings } from './calculate_siblings'; describe('calculateSiblings(metrics, metric)', () => { - it('should return all siblings', () => { + test('should return all siblings', () => { const metrics = [ { id: 1, type: 'max', field: 'network.bytes' }, { id: 2, type: 'derivative', field: 1 }, @@ -30,7 +29,7 @@ describe('calculateSiblings(metrics, metric)', () => { { id: 5, type: 'count' }, ]; const siblings = calculateSiblings(metrics, { id: 2 }); - expect(siblings).to.eql([ + expect(siblings).toEqual([ { id: 1, type: 'max', field: 'network.bytes' }, { id: 5, type: 'count' }, ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js index 5f97f559aa4bd..c76943cc8d6d7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js @@ -17,37 +17,35 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { handleChange, handleAdd, handleDelete } from '../collection_actions'; +import { handleChange, handleAdd, handleDelete } from './collection_actions'; describe('collection actions', () => { - it('handleChange() calls props.onChange() with updated collection', () => { - const fn = sinon.spy(); + test('handleChange() calls props.onChange() with updated collection', () => { + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleChange.call(null, props, { id: 1, title: 'bar' }); - expect(fn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [{ id: 1, title: 'bar' }], }); }); - it('handleAdd() calls props.onChange() with update collection', () => { - const newItemFn = sinon.stub().returns({ id: 2, title: 'example' }); - const fn = sinon.spy(); + test('handleAdd() calls props.onChange() with update collection', () => { + const newItemFn = jest.fn(() => ({ id: 2, title: 'example' })); + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleAdd.call(null, props, newItemFn); - expect(fn.calledOnce).to.equal(true); - expect(newItemFn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(newItemFn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [ { id: 1, title: 'foo' }, { id: 2, title: 'example' }, @@ -55,16 +53,16 @@ describe('collection actions', () => { }); }); - it('handleDelete() calls props.onChange() with update collection', () => { - const fn = sinon.spy(); + test('handleDelete() calls props.onChange() with update collection', () => { + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleDelete.call(null, props, { id: 1 }); - expect(fn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [], }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js index 40e3b302c1cf6..689b62962256a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js @@ -18,5 +18,5 @@ */ describe('convertSeriesToVars(series, model)', () => { - it('returns and object', () => {}); + test('returns and object', () => {}); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js index fdbef9eac2d02..e24fe1f1d88d3 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createNumberHandler } from '../create_number_handler'; +import { createNumberHandler } from './create_number_handler'; describe('createNumberHandler()', () => { let handleChange; @@ -27,17 +25,17 @@ describe('createNumberHandler()', () => { let event; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createNumberHandler(handleChange); - event = { preventDefault: sinon.spy(), target: { value: '1' } }; + event = { preventDefault: jest.fn(), target: { value: '1' } }; const fn = changeHandler('test'); fn(event); }); - it('calls handleChange() function with partial', () => { - expect(event.preventDefault.calledOnce).to.equal(true); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(event.preventDefault.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 1, }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js similarity index 77% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js index 7bc9ae5de0162..a8d5351341c17 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js @@ -17,24 +17,22 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createSelectHandler } from '../create_select_handler'; +import { createSelectHandler } from './create_select_handler'; describe('createSelectHandler()', () => { let handleChange; let changeHandler; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createSelectHandler(handleChange); const fn = changeHandler('test'); fn([{ value: 'foo' }]); }); - it('calls handleChange() function with partial', () => { - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 'foo', }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js index 689fc5c4d7df0..a8a5377b4b084 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createTextHandler } from '../create_text_handler'; +import { createTextHandler } from './create_text_handler'; describe('createTextHandler()', () => { let handleChange; @@ -27,17 +25,17 @@ describe('createTextHandler()', () => { let event; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createTextHandler(handleChange); - event = { preventDefault: sinon.spy(), target: { value: 'foo' } }; + event = { preventDefault: jest.fn(), target: { value: 'foo' } }; const fn = changeHandler('test'); fn(event); }); - it('calls handleChange() function with partial', () => { - expect(event.preventDefault.calledOnce).to.equal(true); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(event.preventDefault.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 'foo', }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js index 455a5b262c929..cfbd5ecb7bf65 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js @@ -17,17 +17,18 @@ * under the License. */ -import { expect } from 'chai'; -import { getAxisLabelString } from '../get_axis_label_string'; +import { getAxisLabelString } from './get_axis_label_string'; + +jest.mock('ui/new_platform'); describe('getAxisLabelString(interval)', () => { - it('should return a valid label for 10 seconds', () => { - expect(getAxisLabelString(10000)).to.equal('per 10 seconds'); + test('should return a valid label for 10 seconds', () => { + expect(getAxisLabelString(10000)).toEqual('per 10 seconds'); }); - it('should return a valid label for 2 minutes', () => { - expect(getAxisLabelString(120000)).to.equal('per 2 minutes'); + test('should return a valid label for 2 minutes', () => { + expect(getAxisLabelString(120000)).toEqual('per 2 minutes'); }); - it('should return a valid label for 2 hour', () => { - expect(getAxisLabelString(7200000)).to.equal('per 2 hours'); + test('should return a valid label for 2 hour', () => { + expect(getAxisLabelString(7200000)).toEqual('per 2 hours'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js similarity index 64% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js index b629e284a0fe4..7c646c7dde2e8 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js @@ -18,24 +18,24 @@ */ import uuid from 'uuid'; -import { expect } from 'chai'; -import { reIdSeries } from '../re_id_series'; + +import { reIdSeries } from './re_id_series'; describe('reIdSeries()', () => { - it('reassign ids for series with just basic metrics', () => { + test('reassign ids for series with just basic metrics', () => { const series = { id: uuid.v1(), metrics: [{ id: uuid.v1() }, { id: uuid.v1() }], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); newSeries.metrics.forEach((val, key) => { - expect(val.id).to.not.equal(series.metrics[key].id); + expect(val.id).not.toEqual(series.metrics[key].id); }); }); - it('reassign ids for series with just basic metrics and group by', () => { + test('reassign ids for series with just basic metrics and group by', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), @@ -43,27 +43,27 @@ describe('reIdSeries()', () => { terms_order_by: firstMetricId, }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); newSeries.metrics.forEach((val, key) => { - expect(val.id).to.not.equal(series.metrics[key].id); + expect(val.id).not.toEqual(series.metrics[key].id); }); - expect(newSeries.terms_order_by).to.equal(newSeries.metrics[0].id); + expect(newSeries.terms_order_by).toEqual(newSeries.metrics[0].id); }); - it('reassign ids for series with pipeline metrics', () => { + test('reassign ids for series with pipeline metrics', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), metrics: [{ id: firstMetricId }, { id: uuid.v1(), field: firstMetricId }], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); - expect(newSeries.metrics[0].id).to.equal(newSeries.metrics[1].field); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); + expect(newSeries.metrics[0].id).toEqual(newSeries.metrics[1].field); }); - it('reassign ids for series with calculation vars', () => { + test('reassign ids for series with calculation vars', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), @@ -77,8 +77,8 @@ describe('reIdSeries()', () => { ], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); - expect(newSeries.metrics[1].variables[0].field).to.equal(newSeries.metrics[0].id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); + expect(newSeries.metrics[1].variables[0].field).toEqual(newSeries.metrics[0].id); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js similarity index 74% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js index 0ac7427f6facc..517958692dd2c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js @@ -17,26 +17,25 @@ * under the License. */ -import { expect } from 'chai'; -import { replaceVars } from '../replace_vars'; +import { replaceVars } from './replace_vars'; describe('replaceVars(str, args, vars)', () => { - it('replaces vars with values', () => { + test('replaces vars with values', () => { const vars = { total: 100 }; const args = { host: 'test-01' }; const template = '# {{args.host}} {{total}}'; - expect(replaceVars(template, args, vars)).to.equal('# test-01 100'); + expect(replaceVars(template, args, vars)).toEqual('# test-01 100'); }); - it('replaces args override vars', () => { + test('replaces args override vars', () => { const vars = { total: 100, args: { test: 'foo-01' } }; const args = { test: 'bar-01' }; const template = '# {{args.test}} {{total}}'; - expect(replaceVars(template, args, vars)).to.equal('# bar-01 100'); + expect(replaceVars(template, args, vars)).toEqual('# bar-01 100'); }); - it('returns original string if error', () => { + test('returns original string if error', () => { const vars = { total: 100 }; const args = { host: 'test-01' }; const template = '# {{args.host}} {{total'; - expect(replaceVars(template, args, vars)).to.equal('# {{args.host}} {{total'); + expect(replaceVars(template, args, vars)).toEqual('# {{args.host}} {{total'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js similarity index 50% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js index f31af4e846305..76d3cff17343e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js @@ -17,64 +17,93 @@ * under the License. */ -import { expect } from 'chai'; -import { createTickFormatter } from '../tick_formatter'; +import { npStart } from 'ui/new_platform'; +import { createTickFormatter } from './tick_formatter'; +import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats'; + +const mockUiSettings = { + get: item => { + return mockUiSettings[item]; + }, + getUpdate$: () => ({ + subscribe: jest.fn(), + }), + 'query:allowLeadingWildcards': true, + 'query:queryString:options': {}, + 'courier:ignoreFilterIfFieldNotInIndex': true, + 'dateFormat:tz': 'Browser', + 'format:defaultTypeMap': {}, +}; + +const mockCore = { + chrome: {}, + uiSettings: mockUiSettings, + http: { + basePath: { + get: jest.fn(), + }, + }, +}; describe('createTickFormatter(format, template)', () => { - it('returns a number with two decimal place by default', () => { + npStart.plugins.data = { + fieldFormats: getFieldFormatsRegistry(mockCore), + }; + + test('returns a number with two decimal place by default', () => { const fn = createTickFormatter(); - expect(fn(1.5556)).to.equal('1.56'); + expect(fn(1.5556)).toEqual('1.56'); }); - it('returns a percent with percent formatter', () => { + test('returns a percent with percent formatter', () => { const config = { 'format:percent:defaultPattern': '0.[00]%', }; const fn = createTickFormatter('percent', null, key => config[key]); - expect(fn(0.5556)).to.equal('55.56%'); + expect(fn(0.5556)).toEqual('55.56%'); }); - it('returns a byte formatted string with byte formatter', () => { + test('returns a byte formatted string with byte formatter', () => { const config = { 'format:bytes:defaultPattern': '0.0b', }; const fn = createTickFormatter('bytes', null, key => config[key]); - expect(fn(1500 ^ 10)).to.equal('1.5KB'); + expect(fn(1500 ^ 10)).toEqual('1.5KB'); }); - it('returns a custom formatted string with custom formatter', () => { + test('returns a custom formatted string with custom formatter', () => { const fn = createTickFormatter('0.0a'); - expect(fn(1500)).to.equal('1.5k'); + expect(fn(1500)).toEqual('1.5k'); }); - it('returns a located string with custom locale setting', () => { + test('returns a located string with custom locale setting', () => { const config = { 'format:number:defaultLocale': 'fr', }; const fn = createTickFormatter('0,0.0', null, key => config[key]); - expect(fn(1500)).to.equal('1 500,0'); + expect(fn(1500)).toEqual('1 500,0'); }); - it('returns a custom formatted string with custom formatter and template', () => { + test('returns a custom formatted string with custom formatter and template', () => { const fn = createTickFormatter('0.0a', '{{value}}/s'); - expect(fn(1500)).to.equal('1.5k/s'); + expect(fn(1500)).toEqual('1.5k/s'); }); - it('returns "foo" if passed a string', () => { + test('returns "foo" if passed a string', () => { const fn = createTickFormatter(); - expect(fn('foo')).to.equal('foo'); + expect(fn('foo')).toEqual('foo'); }); - it('returns value if passed a bad formatter', () => { + test('returns value if passed a bad formatter', () => { const fn = createTickFormatter('102'); - expect(fn(100)).to.equal('100'); + expect(fn(100)).toEqual('100'); }); - it('returns formatted value if passed a bad template', () => { + test('returns formatted value if passed a bad template', () => { const config = { 'format:number:defaultPattern': '0,0.[00]', }; const fn = createTickFormatter('number', '{{value', key => config[key]); - expect(fn(1.5556)).to.equal('1.56'); + expect(fn(1.5556)).toEqual('1.56'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixture.json b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixture.json deleted file mode 100644 index 178704a36372d..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixture.json +++ /dev/null @@ -1,984 +0,0 @@ -{ - "_shards": { - "failed": 0, - "successful": 5, - "total": 5 - }, - "aggregations": { - "c9b5f9c0-e403-11e6-be91-6f7688e9fac7": { - "doc_count": 128145, - "timeseries": { - "buckets": [ - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057 - }, - "doc_count": 368, - "key": 1485549090000, - "key_as_string": "2017-01-27T20:31:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.07466666666666667 - }, - "doc_count": 1106, - "key": 1485549120000, - "key_as_string": "2017-01-27T20:32:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.08033333333333335 - }, - "doc_count": 1107, - "key": 1485549150000, - "key_as_string": "2017-01-27T20:32:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.066 - }, - "doc_count": 1109, - "key": 1485549180000, - "key_as_string": "2017-01-27T20:33:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.05366666666666667 - }, - "doc_count": 1093, - "key": 1485549210000, - "key_as_string": "2017-01-27T20:33:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1086, - "key": 1485549240000, - "key_as_string": "2017-01-27T20:34:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1086, - "key": 1485549270000, - "key_as_string": "2017-01-27T20:34:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1090, - "key": 1485549300000, - "key_as_string": "2017-01-27T20:35:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1085, - "key": 1485549330000, - "key_as_string": "2017-01-27T20:35:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1082, - "key": 1485549360000, - "key_as_string": "2017-01-27T20:36:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1080, - "key": 1485549390000, - "key_as_string": "2017-01-27T20:36:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1082, - "key": 1485549420000, - "key_as_string": "2017-01-27T20:37:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1079, - "key": 1485549450000, - "key_as_string": "2017-01-27T20:37:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1080, - "key": 1485549480000, - "key_as_string": "2017-01-27T20:38:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1080, - "key": 1485549510000, - "key_as_string": "2017-01-27T20:38:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1082, - "key": 1485549540000, - "key_as_string": "2017-01-27T20:39:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.042666666666666665 - }, - "doc_count": 1079, - "key": 1485549570000, - "key_as_string": "2017-01-27T20:39:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485549600000, - "key_as_string": "2017-01-27T20:40:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1075, - "key": 1485549630000, - "key_as_string": "2017-01-27T20:40:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485549660000, - "key_as_string": "2017-01-27T20:41:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1076, - "key": 1485549690000, - "key_as_string": "2017-01-27T20:41:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485549720000, - "key_as_string": "2017-01-27T20:42:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485549750000, - "key_as_string": "2017-01-27T20:42:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1067, - "key": 1485549780000, - "key_as_string": "2017-01-27T20:43:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1065, - "key": 1485549810000, - "key_as_string": "2017-01-27T20:43:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1065, - "key": 1485549840000, - "key_as_string": "2017-01-27T20:44:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036333333333333336 - }, - "doc_count": 1062, - "key": 1485549870000, - "key_as_string": "2017-01-27T20:44:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1063, - "key": 1485549900000, - "key_as_string": "2017-01-27T20:45:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1065, - "key": 1485549930000, - "key_as_string": "2017-01-27T20:45:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1065, - "key": 1485549960000, - "key_as_string": "2017-01-27T20:46:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1069, - "key": 1485549990000, - "key_as_string": "2017-01-27T20:46:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550020000, - "key_as_string": "2017-01-27T20:47:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550050000, - "key_as_string": "2017-01-27T20:47:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550080000, - "key_as_string": "2017-01-27T20:48:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1074, - "key": 1485550110000, - "key_as_string": "2017-01-27T20:48:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1074, - "key": 1485550140000, - "key_as_string": "2017-01-27T20:49:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1074, - "key": 1485550170000, - "key_as_string": "2017-01-27T20:49:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485550200000, - "key_as_string": "2017-01-27T20:50:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485550230000, - "key_as_string": "2017-01-27T20:50:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1074, - "key": 1485550260000, - "key_as_string": "2017-01-27T20:51:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.031 - }, - "doc_count": 1074, - "key": 1485550290000, - "key_as_string": "2017-01-27T20:51:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485550320000, - "key_as_string": "2017-01-27T20:52:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1073, - "key": 1485550350000, - "key_as_string": "2017-01-27T20:52:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550380000, - "key_as_string": "2017-01-27T20:53:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485550410000, - "key_as_string": "2017-01-27T20:53:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1069, - "key": 1485550440000, - "key_as_string": "2017-01-27T20:54:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1069, - "key": 1485550470000, - "key_as_string": "2017-01-27T20:54:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1068, - "key": 1485550500000, - "key_as_string": "2017-01-27T20:55:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1067, - "key": 1485550530000, - "key_as_string": "2017-01-27T20:55:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1065, - "key": 1485550560000, - "key_as_string": "2017-01-27T20:56:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1069, - "key": 1485550590000, - "key_as_string": "2017-01-27T20:56:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1068, - "key": 1485550620000, - "key_as_string": "2017-01-27T20:57:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550650000, - "key_as_string": "2017-01-27T20:57:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1068, - "key": 1485550680000, - "key_as_string": "2017-01-27T20:58:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1071, - "key": 1485550710000, - "key_as_string": "2017-01-27T20:58:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1074, - "key": 1485550740000, - "key_as_string": "2017-01-27T20:59:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1074, - "key": 1485550770000, - "key_as_string": "2017-01-27T20:59:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1074, - "key": 1485550800000, - "key_as_string": "2017-01-27T21:00:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1076, - "key": 1485550830000, - "key_as_string": "2017-01-27T21:00:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1078, - "key": 1485550860000, - "key_as_string": "2017-01-27T21:01:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1077, - "key": 1485550890000, - "key_as_string": "2017-01-27T21:01:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550920000, - "key_as_string": "2017-01-27T21:02:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550950000, - "key_as_string": "2017-01-27T21:02:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1073, - "key": 1485550980000, - "key_as_string": "2017-01-27T21:03:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551010000, - "key_as_string": "2017-01-27T21:03:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1069, - "key": 1485551040000, - "key_as_string": "2017-01-27T21:04:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1068, - "key": 1485551070000, - "key_as_string": "2017-01-27T21:04:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1075, - "key": 1485551100000, - "key_as_string": "2017-01-27T21:05:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485551130000, - "key_as_string": "2017-01-27T21:05:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485551160000, - "key_as_string": "2017-01-27T21:06:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551190000, - "key_as_string": "2017-01-27T21:06:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1075, - "key": 1485551220000, - "key_as_string": "2017-01-27T21:07:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1071, - "key": 1485551250000, - "key_as_string": "2017-01-27T21:07:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04733333333333334 - }, - "doc_count": 1081, - "key": 1485551280000, - "key_as_string": "2017-01-27T21:08:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.044333333333333336 - }, - "doc_count": 1078, - "key": 1485551310000, - "key_as_string": "2017-01-27T21:08:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1079, - "key": 1485551340000, - "key_as_string": "2017-01-27T21:09:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1077, - "key": 1485551370000, - "key_as_string": "2017-01-27T21:09:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1077, - "key": 1485551400000, - "key_as_string": "2017-01-27T21:10:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1075, - "key": 1485551430000, - "key_as_string": "2017-01-27T21:10:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038000000000000006 - }, - "doc_count": 1078, - "key": 1485551460000, - "key_as_string": "2017-01-27T21:11:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037 - }, - "doc_count": 1074, - "key": 1485551490000, - "key_as_string": "2017-01-27T21:11:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036666666666666674 - }, - "doc_count": 1074, - "key": 1485551520000, - "key_as_string": "2017-01-27T21:12:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1076, - "key": 1485551550000, - "key_as_string": "2017-01-27T21:12:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03733333333333333 - }, - "doc_count": 1075, - "key": 1485551580000, - "key_as_string": "2017-01-27T21:13:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1077, - "key": 1485551610000, - "key_as_string": "2017-01-27T21:13:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1080, - "key": 1485551640000, - "key_as_string": "2017-01-27T21:14:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057666666666666665 - }, - "doc_count": 1080, - "key": 1485551670000, - "key_as_string": "2017-01-27T21:14:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.045000000000000005 - }, - "doc_count": 1080, - "key": 1485551700000, - "key_as_string": "2017-01-27T21:15:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1080, - "key": 1485551730000, - "key_as_string": "2017-01-27T21:15:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551760000, - "key_as_string": "2017-01-27T21:16:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038 - }, - "doc_count": 1080, - "key": 1485551790000, - "key_as_string": "2017-01-27T21:16:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551820000, - "key_as_string": "2017-01-27T21:17:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1080, - "key": 1485551850000, - "key_as_string": "2017-01-27T21:17:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1080, - "key": 1485551880000, - "key_as_string": "2017-01-27T21:18:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1080, - "key": 1485551910000, - "key_as_string": "2017-01-27T21:18:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03766666666666666 - }, - "doc_count": 1080, - "key": 1485551940000, - "key_as_string": "2017-01-27T21:19:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485551970000, - "key_as_string": "2017-01-27T21:19:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1077, - "key": 1485552000000, - "key_as_string": "2017-01-27T21:20:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1077, - "key": 1485552030000, - "key_as_string": "2017-01-27T21:20:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.029666666666666664 - }, - "doc_count": 1077, - "key": 1485552060000, - "key_as_string": "2017-01-27T21:21:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.02766666666666667 - }, - "doc_count": 1077, - "key": 1485552090000, - "key_as_string": "2017-01-27T21:21:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.028 - }, - "doc_count": 1077, - "key": 1485552120000, - "key_as_string": "2017-01-27T21:22:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.030666666666666665 - }, - "doc_count": 1077, - "key": 1485552150000, - "key_as_string": "2017-01-27T21:22:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1083, - "key": 1485552180000, - "key_as_string": "2017-01-27T21:23:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1083, - "key": 1485552210000, - "key_as_string": "2017-01-27T21:23:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.041 - }, - "doc_count": 1082, - "key": 1485552240000, - "key_as_string": "2017-01-27T21:24:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1087, - "key": 1485552270000, - "key_as_string": "2017-01-27T21:24:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1083, - "key": 1485552300000, - "key_as_string": "2017-01-27T21:25:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03933333333333333 - }, - "doc_count": 1083, - "key": 1485552330000, - "key_as_string": "2017-01-27T21:25:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1083, - "key": 1485552360000, - "key_as_string": "2017-01-27T21:26:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1083, - "key": 1485552390000, - "key_as_string": "2017-01-27T21:26:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1082, - "key": 1485552420000, - "key_as_string": "2017-01-27T21:27:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333334 - }, - "doc_count": 1083, - "key": 1485552450000, - "key_as_string": "2017-01-27T21:27:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1083, - "key": 1485552480000, - "key_as_string": "2017-01-27T21:28:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1084, - "key": 1485552510000, - "key_as_string": "2017-01-27T21:28:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1083, - "key": 1485552540000, - "key_as_string": "2017-01-27T21:29:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1083, - "key": 1485552570000, - "key_as_string": "2017-01-27T21:29:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04466666666666667 - }, - "doc_count": 1083, - "key": 1485552600000, - "key_as_string": "2017-01-27T21:30:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1083, - "key": 1485552630000, - "key_as_string": "2017-01-27T21:30:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.0505 - }, - "doc_count": 722, - "key": 1485552660000, - "key_as_string": "2017-01-27T21:31:00.000Z" - } - ] - } - } - }, - "hits": { - "hits": [], - "max_score": 0, - "total": 128145 - }, - "status": 200, - "timed_out": false, - "took": 28 -} diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixtures/std_metric_fixture.json b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixtures/std_metric_fixture.json deleted file mode 100644 index d587e48b31881..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixtures/std_metric_fixture.json +++ /dev/null @@ -1,985 +0,0 @@ -{ - "_shards": { - "failed": 0, - "successful": 5, - "total": 5 - }, - "aggregations": { - "c9b5f9c0-e403-11e6-be91-6f7688e9fac7": { - "doc_count": 128145, - "timeseries": { - "buckets": [ - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057 - }, - "doc_count": 368, - "key": 1485549090000, - "key_as_string": "2017-01-27T20:31:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.07466666666666667 - }, - "doc_count": 1106, - "key": 1485549120000, - "key_as_string": "2017-01-27T20:32:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.08033333333333335 - }, - "doc_count": 1107, - "key": 1485549150000, - "key_as_string": "2017-01-27T20:32:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.066 - }, - "doc_count": 1109, - "key": 1485549180000, - "key_as_string": "2017-01-27T20:33:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.05366666666666667 - }, - "doc_count": 1093, - "key": 1485549210000, - "key_as_string": "2017-01-27T20:33:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1086, - "key": 1485549240000, - "key_as_string": "2017-01-27T20:34:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1086, - "key": 1485549270000, - "key_as_string": "2017-01-27T20:34:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1090, - "key": 1485549300000, - "key_as_string": "2017-01-27T20:35:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1085, - "key": 1485549330000, - "key_as_string": "2017-01-27T20:35:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1082, - "key": 1485549360000, - "key_as_string": "2017-01-27T20:36:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1080, - "key": 1485549390000, - "key_as_string": "2017-01-27T20:36:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1082, - "key": 1485549420000, - "key_as_string": "2017-01-27T20:37:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1079, - "key": 1485549450000, - "key_as_string": "2017-01-27T20:37:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1080, - "key": 1485549480000, - "key_as_string": "2017-01-27T20:38:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1080, - "key": 1485549510000, - "key_as_string": "2017-01-27T20:38:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1082, - "key": 1485549540000, - "key_as_string": "2017-01-27T20:39:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.042666666666666665 - }, - "doc_count": 1079, - "key": 1485549570000, - "key_as_string": "2017-01-27T20:39:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485549600000, - "key_as_string": "2017-01-27T20:40:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1075, - "key": 1485549630000, - "key_as_string": "2017-01-27T20:40:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485549660000, - "key_as_string": "2017-01-27T20:41:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1076, - "key": 1485549690000, - "key_as_string": "2017-01-27T20:41:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485549720000, - "key_as_string": "2017-01-27T20:42:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485549750000, - "key_as_string": "2017-01-27T20:42:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1067, - "key": 1485549780000, - "key_as_string": "2017-01-27T20:43:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1065, - "key": 1485549810000, - "key_as_string": "2017-01-27T20:43:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1065, - "key": 1485549840000, - "key_as_string": "2017-01-27T20:44:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036333333333333336 - }, - "doc_count": 1062, - "key": 1485549870000, - "key_as_string": "2017-01-27T20:44:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1063, - "key": 1485549900000, - "key_as_string": "2017-01-27T20:45:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1065, - "key": 1485549930000, - "key_as_string": "2017-01-27T20:45:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1065, - "key": 1485549960000, - "key_as_string": "2017-01-27T20:46:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1069, - "key": 1485549990000, - "key_as_string": "2017-01-27T20:46:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550020000, - "key_as_string": "2017-01-27T20:47:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550050000, - "key_as_string": "2017-01-27T20:47:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550080000, - "key_as_string": "2017-01-27T20:48:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1074, - "key": 1485550110000, - "key_as_string": "2017-01-27T20:48:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1074, - "key": 1485550140000, - "key_as_string": "2017-01-27T20:49:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1074, - "key": 1485550170000, - "key_as_string": "2017-01-27T20:49:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485550200000, - "key_as_string": "2017-01-27T20:50:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485550230000, - "key_as_string": "2017-01-27T20:50:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1074, - "key": 1485550260000, - "key_as_string": "2017-01-27T20:51:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.031 - }, - "doc_count": 1074, - "key": 1485550290000, - "key_as_string": "2017-01-27T20:51:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485550320000, - "key_as_string": "2017-01-27T20:52:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1073, - "key": 1485550350000, - "key_as_string": "2017-01-27T20:52:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550380000, - "key_as_string": "2017-01-27T20:53:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485550410000, - "key_as_string": "2017-01-27T20:53:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1069, - "key": 1485550440000, - "key_as_string": "2017-01-27T20:54:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1069, - "key": 1485550470000, - "key_as_string": "2017-01-27T20:54:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1068, - "key": 1485550500000, - "key_as_string": "2017-01-27T20:55:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1067, - "key": 1485550530000, - "key_as_string": "2017-01-27T20:55:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1065, - "key": 1485550560000, - "key_as_string": "2017-01-27T20:56:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1069, - "key": 1485550590000, - "key_as_string": "2017-01-27T20:56:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1068, - "key": 1485550620000, - "key_as_string": "2017-01-27T20:57:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550650000, - "key_as_string": "2017-01-27T20:57:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1068, - "key": 1485550680000, - "key_as_string": "2017-01-27T20:58:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1071, - "key": 1485550710000, - "key_as_string": "2017-01-27T20:58:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1074, - "key": 1485550740000, - "key_as_string": "2017-01-27T20:59:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1074, - "key": 1485550770000, - "key_as_string": "2017-01-27T20:59:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1074, - "key": 1485550800000, - "key_as_string": "2017-01-27T21:00:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1076, - "key": 1485550830000, - "key_as_string": "2017-01-27T21:00:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1078, - "key": 1485550860000, - "key_as_string": "2017-01-27T21:01:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1077, - "key": 1485550890000, - "key_as_string": "2017-01-27T21:01:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550920000, - "key_as_string": "2017-01-27T21:02:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550950000, - "key_as_string": "2017-01-27T21:02:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1073, - "key": 1485550980000, - "key_as_string": "2017-01-27T21:03:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551010000, - "key_as_string": "2017-01-27T21:03:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1069, - "key": 1485551040000, - "key_as_string": "2017-01-27T21:04:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1068, - "key": 1485551070000, - "key_as_string": "2017-01-27T21:04:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1075, - "key": 1485551100000, - "key_as_string": "2017-01-27T21:05:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485551130000, - "key_as_string": "2017-01-27T21:05:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485551160000, - "key_as_string": "2017-01-27T21:06:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551190000, - "key_as_string": "2017-01-27T21:06:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1075, - "key": 1485551220000, - "key_as_string": "2017-01-27T21:07:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1071, - "key": 1485551250000, - "key_as_string": "2017-01-27T21:07:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04733333333333334 - }, - "doc_count": 1081, - "key": 1485551280000, - "key_as_string": "2017-01-27T21:08:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.044333333333333336 - }, - "doc_count": 1078, - "key": 1485551310000, - "key_as_string": "2017-01-27T21:08:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1079, - "key": 1485551340000, - "key_as_string": "2017-01-27T21:09:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1077, - "key": 1485551370000, - "key_as_string": "2017-01-27T21:09:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1077, - "key": 1485551400000, - "key_as_string": "2017-01-27T21:10:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1075, - "key": 1485551430000, - "key_as_string": "2017-01-27T21:10:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038000000000000006 - }, - "doc_count": 1078, - "key": 1485551460000, - "key_as_string": "2017-01-27T21:11:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037 - }, - "doc_count": 1074, - "key": 1485551490000, - "key_as_string": "2017-01-27T21:11:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036666666666666674 - }, - "doc_count": 1074, - "key": 1485551520000, - "key_as_string": "2017-01-27T21:12:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1076, - "key": 1485551550000, - "key_as_string": "2017-01-27T21:12:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03733333333333333 - }, - "doc_count": 1075, - "key": 1485551580000, - "key_as_string": "2017-01-27T21:13:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1077, - "key": 1485551610000, - "key_as_string": "2017-01-27T21:13:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1080, - "key": 1485551640000, - "key_as_string": "2017-01-27T21:14:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057666666666666665 - }, - "doc_count": 1080, - "key": 1485551670000, - "key_as_string": "2017-01-27T21:14:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.045000000000000005 - }, - "doc_count": 1080, - "key": 1485551700000, - "key_as_string": "2017-01-27T21:15:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1080, - "key": 1485551730000, - "key_as_string": "2017-01-27T21:15:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551760000, - "key_as_string": "2017-01-27T21:16:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038 - }, - "doc_count": 1080, - "key": 1485551790000, - "key_as_string": "2017-01-27T21:16:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551820000, - "key_as_string": "2017-01-27T21:17:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1080, - "key": 1485551850000, - "key_as_string": "2017-01-27T21:17:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1080, - "key": 1485551880000, - "key_as_string": "2017-01-27T21:18:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1080, - "key": 1485551910000, - "key_as_string": "2017-01-27T21:18:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03766666666666666 - }, - "doc_count": 1080, - "key": 1485551940000, - "key_as_string": "2017-01-27T21:19:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485551970000, - "key_as_string": "2017-01-27T21:19:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1077, - "key": 1485552000000, - "key_as_string": "2017-01-27T21:20:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1077, - "key": 1485552030000, - "key_as_string": "2017-01-27T21:20:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.029666666666666664 - }, - "doc_count": 1077, - "key": 1485552060000, - "key_as_string": "2017-01-27T21:21:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.02766666666666667 - }, - "doc_count": 1077, - "key": 1485552090000, - "key_as_string": "2017-01-27T21:21:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.028 - }, - "doc_count": 1077, - "key": 1485552120000, - "key_as_string": "2017-01-27T21:22:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.030666666666666665 - }, - "doc_count": 1077, - "key": 1485552150000, - "key_as_string": "2017-01-27T21:22:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1083, - "key": 1485552180000, - "key_as_string": "2017-01-27T21:23:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1083, - "key": 1485552210000, - "key_as_string": "2017-01-27T21:23:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.041 - }, - "doc_count": 1082, - "key": 1485552240000, - "key_as_string": "2017-01-27T21:24:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1087, - "key": 1485552270000, - "key_as_string": "2017-01-27T21:24:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1083, - "key": 1485552300000, - "key_as_string": "2017-01-27T21:25:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03933333333333333 - }, - "doc_count": 1083, - "key": 1485552330000, - "key_as_string": "2017-01-27T21:25:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1083, - "key": 1485552360000, - "key_as_string": "2017-01-27T21:26:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1083, - "key": 1485552390000, - "key_as_string": "2017-01-27T21:26:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1082, - "key": 1485552420000, - "key_as_string": "2017-01-27T21:27:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333334 - }, - "doc_count": 1083, - "key": 1485552450000, - "key_as_string": "2017-01-27T21:27:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1083, - "key": 1485552480000, - "key_as_string": "2017-01-27T21:28:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1084, - "key": 1485552510000, - "key_as_string": "2017-01-27T21:28:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1083, - "key": 1485552540000, - "key_as_string": "2017-01-27T21:29:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1083, - "key": 1485552570000, - "key_as_string": "2017-01-27T21:29:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04466666666666667 - }, - "doc_count": 1083, - "key": 1485552600000, - "key_as_string": "2017-01-27T21:30:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1083, - "key": 1485552630000, - "key_as_string": "2017-01-27T21:30:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.0505 - }, - "doc_count": 722, - "key": 1485552660000, - "key_as_string": "2017-01-27T21:31:00.000Z" - } - ] - } - } - }, - "hits": { - "hits": [], - "max_score": 0, - "total": 128145 - }, - "status": 200, - "timed_out": false, - "took": 28 -} - diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/bucket_transform.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/bucket_transform.js deleted file mode 100644 index 1414435017c86..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/bucket_transform.js +++ /dev/null @@ -1,388 +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 { expect } from 'chai'; -import { bucketTransform } from '../../helpers/bucket_transform'; - -describe('bucketTransform', () => { - describe('count', () => { - it('returns count agg', () => { - const metric = { id: 'test', type: 'count' }; - const fn = bucketTransform.count; - expect(fn(metric)).to.eql({ - bucket_script: { - buckets_path: { count: '_count' }, - script: { source: 'count * 1', lang: 'expression' }, - gap_policy: 'skip', - }, - }); - }); - }); - - describe('std metric', () => { - ['avg', 'max', 'min', 'sum', 'cardinality', 'value_count'].forEach(type => { - it(`returns ${type} agg`, () => { - const metric = { id: 'test', type: type, field: 'cpu.pct' }; - const fn = bucketTransform[type]; - const result = {}; - result[type] = { field: 'cpu.pct' }; - expect(fn(metric)).to.eql(result); - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.avg({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.avg({ id: 'test', type: 'avg' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('extended stats', () => { - ['std_deviation', 'variance', 'sum_of_squares'].forEach(type => { - it(`returns ${type} agg`, () => { - const fn = bucketTransform[type]; - const metric = { id: 'test', type: type, field: 'cpu.pct' }; - expect(fn(metric)).to.eql({ extended_stats: { field: 'cpu.pct' } }); - }); - }); - - it('returns std_deviation agg with sigma', () => { - const fn = bucketTransform.std_deviation; - const metric = { - id: 'test', - type: 'std_deviation', - field: 'cpu.pct', - sigma: 2, - }; - expect(fn(metric)).to.eql({ - extended_stats: { field: 'cpu.pct', sigma: 2 }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.std_deviation({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.std_deviation({ id: 'test', type: 'avg' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('percentiles', () => { - it('returns percentiles agg', () => { - const metric = { - id: 'test', - type: 'percentile', - field: 'cpu.pct', - percentiles: [ - { value: 50, mode: 'line' }, - { value: 10, mode: 'band', percentile: 90 }, - ], - }; - const fn = bucketTransform.percentile; - expect(fn(metric)).to.eql({ - percentiles: { - field: 'cpu.pct', - percents: [50, 10, 90], - }, - }); - }); - - it('define a default 0 value if it was not provided', () => { - const metric = { - id: 'test', - type: 'percentile', - field: 'cpu.pct', - percentiles: [ - { value: 50, mode: 'line' }, - { mode: 'line' }, - { value: undefined, mode: 'line' }, - { value: '', mode: 'line' }, - { value: null, mode: 'line' }, - { value: 0, mode: 'line' }, - ], - }; - expect(bucketTransform.percentile(metric)).to.eql({ - percentiles: { - field: 'cpu.pct', - percents: [50, 0, 0, 0, 0, 0], - }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => - bucketTransform.percentile({ - id: 'test', - field: 'cpu.pct', - percentiles: [{ value: 50, mode: 'line' }], - }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => - bucketTransform.percentile({ - id: 'test', - type: 'avg', - percentiles: [{ value: 50, mode: 'line' }], - }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - - it('throws error if percentiles is missing', () => { - const run = () => - bucketTransform.percentile({ - id: 'test', - type: 'avg', - field: 'cpu.pct', - }); - expect(run).to.throw(Error, 'Metric missing percentiles'); - }); - }); - - describe('derivative', () => { - it('returns derivative agg with defaults', () => { - const metric = { - id: '2', - type: 'derivative', - field: '1', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.derivative; - expect(fn(metric, metrics, '10s')).is.eql({ - derivative: { - buckets_path: '1', - gap_policy: 'skip', - unit: '10s', - }, - }); - }); - - it('returns derivative agg with unit', () => { - const metric = { - id: '2', - type: 'derivative', - field: '1', - unit: '1s', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.derivative; - expect(fn(metric, metrics, '10s')).is.eql({ - derivative: { - buckets_path: '1', - gap_policy: 'skip', - unit: '1s', - }, - }); - }); - - it('returns derivative agg with gap_policy', () => { - const metric = { - id: '2', - type: 'derivative', - field: '1', - gap_policy: 'zero_fill', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.derivative; - expect(fn(metric, metrics, '10s')).is.eql({ - derivative: { - buckets_path: '1', - gap_policy: 'zero_fill', - unit: '10s', - }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.derivative({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.derivative({ id: 'test', type: 'derivative' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('serial_diff', () => { - it('returns serial_diff agg with defaults', () => { - const metric = { - id: '2', - type: 'serial_diff', - field: '1', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.serial_diff; - expect(fn(metric, metrics)).is.eql({ - serial_diff: { - buckets_path: '1', - gap_policy: 'skip', - lag: 1, - }, - }); - }); - - it('returns serial_diff agg with lag', () => { - const metric = { - id: '2', - type: 'serial_diff', - field: '1', - lag: 10, - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.serial_diff; - expect(fn(metric, metrics)).is.eql({ - serial_diff: { - buckets_path: '1', - gap_policy: 'skip', - lag: 10, - }, - }); - }); - - it('returns serial_diff agg with gap_policy', () => { - const metric = { - id: '2', - type: 'serial_diff', - field: '1', - gap_policy: 'zero_fill', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.serial_diff; - expect(fn(metric, metrics)).is.eql({ - serial_diff: { - buckets_path: '1', - gap_policy: 'zero_fill', - lag: 1, - }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.serial_diff({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.serial_diff({ id: 'test', type: 'serial_diff' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('cumulative_sum', () => { - it('returns cumulative_sum agg', () => { - const metric = { id: '2', type: 'cumulative_sum', field: '1' }; - const metrics = [{ id: '1', type: 'sum', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.cumulative_sum; - expect(fn(metric, metrics, '10s')).is.eql({ - cumulative_sum: { buckets_path: '1' }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.cumulative_sum({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.cumulative_sum({ id: 'test', type: 'cumulative_sum' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('calculation', () => { - it('returns calculation(bucket_script)', () => { - const metric = { - id: '2', - type: 'calculation', - script: 'params.idle != null ? 1 - params.idle : 0', - variables: [{ name: 'idle', field: '1' }], - }; - const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; - const fn = bucketTransform.calculation; - expect(fn(metric, metrics, '10s')).is.eql({ - bucket_script: { - buckets_path: { - idle: '1', - }, - gap_policy: 'skip', - script: { - source: 'params.idle != null ? 1 - params.idle : 0', - lang: 'painless', - params: { - _interval: 10000, - }, - }, - }, - }); - }); - - it('throws error if variables is missing', () => { - const run = () => - bucketTransform.calculation({ - id: 'test', - type: 'calculation', - script: 'params.idle != null ? 1 - params.idle : null', - }); - expect(run).to.throw(Error, 'Metric missing variables'); - }); - - it('throws error if script is missing', () => { - const run = () => - bucketTransform.calculation({ - id: 'test', - type: 'calculation', - variables: [{ field: '1', name: 'idle' }], - }); - expect(run).to.throw(Error, 'Metric missing script'); - }); - }); - - describe('positive_only', () => { - it('returns bucket_script', () => { - const metric = { - id: '2', - type: 'positive_only', - field: '1', - }; - const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; - const fn = bucketTransform.positive_only; - expect(fn(metric, metrics, '10s')).is.eql({ - bucket_script: { - buckets_path: { - value: '1', - }, - gap_policy: 'skip', - script: { - source: 'params.value > 0.0 ? params.value : 0.0', - lang: 'painless', - }, - }, - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/build_processor_function.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.test.ts similarity index 58% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/build_processor_function.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.test.ts index 10ad9e467610e..cf5244f8e5a7d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/build_processor_function.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.test.ts @@ -17,43 +17,41 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { buildProcessorFunction } from '../build_processor_function'; +import { buildProcessorFunction } from './build_processor_function'; describe('buildProcessorFunction(chain, ...args)', () => { const req = {}; const panel = {}; const series = {}; - it('should call each processor', () => { - const first = sinon.spy(() => next => doc => next(doc)); - const second = sinon.spy(() => next => doc => next(doc)); + test('should call each processor', () => { + const first = jest.fn(() => (next: any) => (doc: any) => next(doc)); + const second = jest.fn(() => (next: any) => (doc: any) => next(doc)); buildProcessorFunction([first, second], req, panel, series); - expect(first.calledOnce).to.equal(true); - expect(second.calledOnce).to.equal(true); + expect(first.mock.calls.length).toEqual(1); + expect(second.mock.calls.length).toEqual(1); }); - it('should chain each processor', () => { - const first = sinon.spy(next => doc => next(doc)); - const second = sinon.spy(next => doc => next(doc)); + test('should chain each processor', () => { + const first = jest.fn(() => (next: any) => (doc: any) => next(doc)); + const second = jest.fn(() => (next: any) => (doc: any) => next(doc)); buildProcessorFunction([() => first, () => second], req, panel, series); - expect(first.calledOnce).to.equal(true); - expect(second.calledOnce).to.equal(true); + expect(first.mock.calls.length).toEqual(1); + expect(second.mock.calls.length).toEqual(1); }); - it('should next of each processor', () => { - const first = sinon.spy(); - const second = sinon.spy(); + test('should next of each processor', () => { + const first = jest.fn(); + const second = jest.fn(); const fn = buildProcessorFunction( [ - () => next => doc => { + () => (next: any) => (doc: any) => { first(); next(doc); }, - () => next => doc => { + () => (next: any) => (doc: any) => { second(); next(doc); }, @@ -63,7 +61,7 @@ describe('buildProcessorFunction(chain, ...args)', () => { series ); fn({}); - expect(first.calledOnce).to.equal(true); - expect(second.calledOnce).to.equal(true); + expect(first.mock.calls.length).toEqual(1); + expect(second.mock.calls.length).toEqual(1); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.ts similarity index 86% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.ts index 5896a089bafe2..b898f4dbf7a5e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.ts @@ -17,11 +17,9 @@ * under the License. */ -export function buildProcessorFunction(chain, ...args) { +export function buildProcessorFunction(chain: any[], ...args: any) { return chain.reduceRight( - (next, fn) => { - return fn(...args)(next); - }, - doc => doc + (next, fn) => fn(...args)(next), + (doc: any) => doc ); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/get_interval_and_timefield.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js similarity index 79% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/get_interval_and_timefield.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js index dc2c2b2cfb1f2..f3e15f2fc65b6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/get_interval_and_timefield.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js @@ -17,27 +17,26 @@ * under the License. */ -import { expect } from 'chai'; -import { getIntervalAndTimefield } from '../get_interval_and_timefield'; +import { getIntervalAndTimefield } from './get_interval_and_timefield'; describe('getIntervalAndTimefield(panel, series)', () => { - it('returns the panel interval and timefield', () => { + test('returns the panel interval and timefield', () => { const panel = { time_field: '@timestamp', interval: 'auto' }; const series = {}; - expect(getIntervalAndTimefield(panel, series)).to.eql({ + expect(getIntervalAndTimefield(panel, series)).toEqual({ timeField: '@timestamp', interval: 'auto', }); }); - it('returns the series interval and timefield', () => { + test('returns the series interval and timefield', () => { const panel = { time_field: '@timestamp', interval: 'auto' }; const series = { override_index_pattern: true, series_interval: '1m', series_time_field: 'time', }; - expect(getIntervalAndTimefield(panel, series)).to.eql({ + expect(getIntervalAndTimefield(panel, series)).toEqual({ timeField: 'time', interval: '1m', }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js index 3def18997863e..db0e8fa3d6bb9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js @@ -94,4 +94,371 @@ describe('src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/bucket_tra }); }); }); + + describe('bucketTransform additional', () => { + describe('count', () => { + test('returns count agg', () => { + const metric = { id: 'test', type: 'count' }; + const fn = bucketTransform.count; + expect(fn(metric)).toEqual({ + bucket_script: { + buckets_path: { count: '_count' }, + script: { source: 'count * 1', lang: 'expression' }, + gap_policy: 'skip', + }, + }); + }); + }); + + describe('std metric', () => { + ['avg', 'max', 'min', 'sum', 'cardinality', 'value_count'].forEach(type => { + test(`returns ${type} agg`, () => { + const metric = { id: 'test', type: type, field: 'cpu.pct' }; + const fn = bucketTransform[type]; + const result = {}; + result[type] = { field: 'cpu.pct' }; + expect(fn(metric)).toEqual(result); + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.avg({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.avg({ id: 'test', type: 'avg' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('extended stats', () => { + ['std_deviation', 'variance', 'sum_of_squares'].forEach(type => { + test(`returns ${type} agg`, () => { + const fn = bucketTransform[type]; + const metric = { id: 'test', type: type, field: 'cpu.pct' }; + expect(fn(metric)).toEqual({ extended_stats: { field: 'cpu.pct' } }); + }); + }); + + test('returns std_deviation agg with sigma', () => { + const fn = bucketTransform.std_deviation; + const metric = { + id: 'test', + type: 'std_deviation', + field: 'cpu.pct', + sigma: 2, + }; + expect(fn(metric)).toEqual({ + extended_stats: { field: 'cpu.pct', sigma: 2 }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.std_deviation({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.std_deviation({ id: 'test', type: 'avg' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('percentiles', () => { + test('returns percentiles agg', () => { + const metric = { + id: 'test', + type: 'percentile', + field: 'cpu.pct', + percentiles: [ + { value: 50, mode: 'line' }, + { value: 10, mode: 'band', percentile: 90 }, + ], + }; + const fn = bucketTransform.percentile; + expect(fn(metric)).toEqual({ + percentiles: { + field: 'cpu.pct', + percents: [50, 10, 90], + }, + }); + }); + + test('define a default 0 value if it was not provided', () => { + const metric = { + id: 'test', + type: 'percentile', + field: 'cpu.pct', + percentiles: [ + { value: 50, mode: 'line' }, + { mode: 'line' }, + { value: undefined, mode: 'line' }, + { value: '', mode: 'line' }, + { value: null, mode: 'line' }, + { value: 0, mode: 'line' }, + ], + }; + expect(bucketTransform.percentile(metric)).toEqual({ + percentiles: { + field: 'cpu.pct', + percents: [50, 0, 0, 0, 0, 0], + }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => + bucketTransform.percentile({ + id: 'test', + field: 'cpu.pct', + percentiles: [{ value: 50, mode: 'line' }], + }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => + bucketTransform.percentile({ + id: 'test', + type: 'avg', + percentiles: [{ value: 50, mode: 'line' }], + }); + expect(run).toThrow(new Error('Metric missing field')); + }); + + test('throws error if percentiles is missing', () => { + const run = () => + bucketTransform.percentile({ + id: 'test', + type: 'avg', + field: 'cpu.pct', + }); + expect(run).toThrow(new Error('Metric missing percentiles')); + }); + }); + + describe('derivative', () => { + test('returns derivative agg with defaults', () => { + const metric = { + id: '2', + type: 'derivative', + field: '1', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.derivative; + expect(fn(metric, metrics, '10s')).toEqual({ + derivative: { + buckets_path: '1', + gap_policy: 'skip', + unit: '10s', + }, + }); + }); + + test('returns derivative agg with unit', () => { + const metric = { + id: '2', + type: 'derivative', + field: '1', + unit: '1s', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.derivative; + expect(fn(metric, metrics, '10s')).toEqual({ + derivative: { + buckets_path: '1', + gap_policy: 'skip', + unit: '1s', + }, + }); + }); + + test('returns derivative agg with gap_policy', () => { + const metric = { + id: '2', + type: 'derivative', + field: '1', + gap_policy: 'zero_fill', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.derivative; + expect(fn(metric, metrics, '10s')).toEqual({ + derivative: { + buckets_path: '1', + gap_policy: 'zero_fill', + unit: '10s', + }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.derivative({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.derivative({ id: 'test', type: 'derivative' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('serial_diff', () => { + test('returns serial_diff agg with defaults', () => { + const metric = { + id: '2', + type: 'serial_diff', + field: '1', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.serial_diff; + expect(fn(metric, metrics)).toEqual({ + serial_diff: { + buckets_path: '1', + gap_policy: 'skip', + lag: 1, + }, + }); + }); + + test('returns serial_diff agg with lag', () => { + const metric = { + id: '2', + type: 'serial_diff', + field: '1', + lag: 10, + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.serial_diff; + expect(fn(metric, metrics)).toEqual({ + serial_diff: { + buckets_path: '1', + gap_policy: 'skip', + lag: 10, + }, + }); + }); + + test('returns serial_diff agg with gap_policy', () => { + const metric = { + id: '2', + type: 'serial_diff', + field: '1', + gap_policy: 'zero_fill', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.serial_diff; + expect(fn(metric, metrics)).toEqual({ + serial_diff: { + buckets_path: '1', + gap_policy: 'zero_fill', + lag: 1, + }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.serial_diff({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.serial_diff({ id: 'test', type: 'serial_diff' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('cumulative_sum', () => { + test('returns cumulative_sum agg', () => { + const metric = { id: '2', type: 'cumulative_sum', field: '1' }; + const metrics = [{ id: '1', type: 'sum', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.cumulative_sum; + expect(fn(metric, metrics, '10s')).toEqual({ + cumulative_sum: { buckets_path: '1' }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.cumulative_sum({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.cumulative_sum({ id: 'test', type: 'cumulative_sum' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('calculation', () => { + test('returns calculation(bucket_script)', () => { + const metric = { + id: '2', + type: 'calculation', + script: 'params.idle != null ? 1 - params.idle : 0', + variables: [{ name: 'idle', field: '1' }], + }; + const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; + const fn = bucketTransform.calculation; + expect(fn(metric, metrics, '10s')).toEqual({ + bucket_script: { + buckets_path: { + idle: '1', + }, + gap_policy: 'skip', + script: { + source: 'params.idle != null ? 1 - params.idle : 0', + lang: 'painless', + params: { + _interval: 10000, + }, + }, + }, + }); + }); + + test('throws error if variables is missing', () => { + const run = () => + bucketTransform.calculation({ + id: 'test', + type: 'calculation', + script: 'params.idle != null ? 1 - params.idle : null', + }); + expect(run).toThrow(new Error('Metric missing variables')); + }); + + test('throws error if script is missing', () => { + const run = () => + bucketTransform.calculation({ + id: 'test', + type: 'calculation', + variables: [{ field: '1', name: 'idle' }], + }); + expect(run).toThrow(new Error('Metric missing script')); + }); + }); + + describe('positive_only', () => { + test('returns bucket_script', () => { + const metric = { + id: '2', + type: 'positive_only', + field: '1', + }; + const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; + const fn = bucketTransform.positive_only; + expect(fn(metric, metrics, '10s')).toEqual({ + bucket_script: { + buckets_path: { + value: '1', + }, + gap_policy: 'skip', + script: { + source: 'params.value > 0.0 ? params.value : 0.0', + lang: 'painless', + }, + }, + }); + }); + }); + }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_agg_value.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_agg_value.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js index 234953c8f4617..5f5e5ebafa560 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_agg_value.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js @@ -17,16 +17,15 @@ * under the License. */ -import { expect } from 'chai'; -import { getAggValue } from '../../helpers/get_agg_value'; +import { getAggValue } from './get_agg_value'; function testAgg(row, metric, expected) { let name = metric.type; if (metric.mode) name += `(${metric.mode})`; if (metric.percent) name += `(${metric.percent})`; - it(`it should return ${name}(${expected})`, () => { + test(`it should return ${name}(${expected})`, () => { const value = getAggValue(row, metric); - expect(value).to.eql(expected); + expect(value).toEqual(expected); }); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_bucket_size.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.test.js similarity index 54% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_bucket_size.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.test.js index e62cfff1083cf..99bef2de6b72d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_bucket_size.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { getBucketSize } from '../../helpers/get_bucket_size'; +import { getBucketSize } from './get_bucket_size'; describe('getBucketSize', () => { const req = { @@ -30,39 +29,39 @@ describe('getBucketSize', () => { }, }; - it('returns auto calculated buckets', () => { + test('returns auto calculated buckets', () => { const result = getBucketSize(req, 'auto'); - expect(result).to.have.property('bucketSize', 30); - expect(result).to.have.property('intervalString', '30s'); + expect(result).toHaveProperty('bucketSize', 30); + expect(result).toHaveProperty('intervalString', '30s'); }); - it('returns overridden buckets (1s)', () => { + test('returns overridden buckets (1s)', () => { const result = getBucketSize(req, '1s'); - expect(result).to.have.property('bucketSize', 1); - expect(result).to.have.property('intervalString', '1s'); + expect(result).toHaveProperty('bucketSize', 1); + expect(result).toHaveProperty('intervalString', '1s'); }); - it('returns overridden buckets (10m)', () => { + test('returns overridden buckets (10m)', () => { const result = getBucketSize(req, '10m'); - expect(result).to.have.property('bucketSize', 600); - expect(result).to.have.property('intervalString', '10m'); + expect(result).toHaveProperty('bucketSize', 600); + expect(result).toHaveProperty('intervalString', '10m'); }); - it('returns overridden buckets (1d)', () => { + test('returns overridden buckets (1d)', () => { const result = getBucketSize(req, '1d'); - expect(result).to.have.property('bucketSize', 86400); - expect(result).to.have.property('intervalString', '1d'); + expect(result).toHaveProperty('bucketSize', 86400); + expect(result).toHaveProperty('intervalString', '1d'); }); - it('returns overridden buckets (>=2d)', () => { + test('returns overridden buckets (>=2d)', () => { const result = getBucketSize(req, '>=2d'); - expect(result).to.have.property('bucketSize', 86400 * 2); - expect(result).to.have.property('intervalString', '2d'); + expect(result).toHaveProperty('bucketSize', 86400 * 2); + expect(result).toHaveProperty('intervalString', '2d'); }); - it('returns overridden buckets (>=10s)', () => { + test('returns overridden buckets (>=10s)', () => { const result = getBucketSize(req, '>=10s'); - expect(result).to.have.property('bucketSize', 30); - expect(result).to.have.property('intervalString', '30s'); + expect(result).toHaveProperty('bucketSize', 30); + expect(result).toHaveProperty('intervalString', '30s'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_buckets_path.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_buckets_path.test.js similarity index 53% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_buckets_path.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_buckets_path.test.js index f5c31fa8ba7ce..0a0e18a696588 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_buckets_path.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_buckets_path.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { getBucketsPath } from '../../helpers/get_buckets_path'; +import { getBucketsPath } from './get_buckets_path'; describe('getBucketsPath', () => { const metrics = [ @@ -33,43 +32,43 @@ describe('getBucketsPath', () => { { id: 9, type: 'max' }, ]; - it('return path for derivative', () => { - expect(getBucketsPath(1, metrics)).to.equal('1[normalized_value]'); + test('return path for derivative', () => { + expect(getBucketsPath(1, metrics)).toEqual('1[normalized_value]'); }); - it('return path for percentile(50)', () => { - expect(getBucketsPath(2, metrics)).to.equal('2[50.0]'); + test('return path for percentile(50)', () => { + expect(getBucketsPath(2, metrics)).toEqual('2[50.0]'); }); - it('return path for percentile(20.0)', () => { - expect(getBucketsPath(3, metrics)).to.equal('3[20.0]'); + test('return path for percentile(20.0)', () => { + expect(getBucketsPath(3, metrics)).toEqual('3[20.0]'); }); - it('return path for percentile(10.0) with alt id', () => { - expect(getBucketsPath('3[10.0]', metrics)).to.equal('3[10.0]'); + test('return path for percentile(10.0) with alt id', () => { + expect(getBucketsPath('3[10.0]', metrics)).toEqual('3[10.0]'); }); - it('return path for std_deviation(raw)', () => { - expect(getBucketsPath(4, metrics)).to.equal('4[std_deviation]'); + test('return path for std_deviation(raw)', () => { + expect(getBucketsPath(4, metrics)).toEqual('4[std_deviation]'); }); - it('return path for std_deviation(upper)', () => { - expect(getBucketsPath(5, metrics)).to.equal('5[std_upper]'); + test('return path for std_deviation(upper)', () => { + expect(getBucketsPath(5, metrics)).toEqual('5[std_upper]'); }); - it('return path for std_deviation(lower)', () => { - expect(getBucketsPath(6, metrics)).to.equal('6[std_lower]'); + test('return path for std_deviation(lower)', () => { + expect(getBucketsPath(6, metrics)).toEqual('6[std_lower]'); }); - it('return path for sum_of_squares', () => { - expect(getBucketsPath(7, metrics)).to.equal('7[sum_of_squares]'); + test('return path for sum_of_squares', () => { + expect(getBucketsPath(7, metrics)).toEqual('7[sum_of_squares]'); }); - it('return path for variance', () => { - expect(getBucketsPath(8, metrics)).to.equal('8[variance]'); + test('return path for variance', () => { + expect(getBucketsPath(8, metrics)).toEqual('8[variance]'); }); - it('return path for basic metric', () => { - expect(getBucketsPath(9, metrics)).to.equal('9'); + test('return path for basic metric', () => { + expect(getBucketsPath(9, metrics)).toEqual('9'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_default_decoration.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_default_decoration.test.js similarity index 51% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_default_decoration.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_default_decoration.test.js index 5cc94dda6d21a..2529c8c649485 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_default_decoration.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_default_decoration.test.js @@ -17,44 +17,43 @@ * under the License. */ -import { expect } from 'chai'; -import { getDefaultDecoration } from '../../helpers/get_default_decoration'; +import { getDefaultDecoration } from './get_default_decoration'; describe('getDefaultDecoration', () => { describe('stack option', () => { - it('should set a stack option to none', () => { + test('should set a stack option to none', () => { const series = { id: 'test_id', stacked: 'none', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'none'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'none'); }); - it('should set a stack option to stacked/percent', () => { + test('should set a stack option to stacked/percent', () => { const series = { stacked: 'stacked', id: 'test_id', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'stacked'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'stacked'); series.stacked = 'percent'; - expect(getDefaultDecoration(series)).to.have.property('stack', 'percent'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'percent'); }); - it('should set a stack option to stacked_within_series', () => { + test('should set a stack option to stacked_within_series', () => { const series = { stacked: 'stacked_within_series', id: 'test_id', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'stacked_within_series'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'stacked_within_series'); }); }); describe('lines', () => { - it('return decoration for lines', () => { + test('return decoration for lines', () => { const series = { point_size: 10, chart_type: 'line', @@ -62,29 +61,29 @@ describe('getDefaultDecoration', () => { fill: 1, }; const result = getDefaultDecoration(series); - expect(result.lines).to.have.property('show', true); - expect(result.lines).to.have.property('fill', 1); - expect(result.lines).to.have.property('lineWidth', 10); - expect(result.points).to.have.property('show', true); - expect(result.points).to.have.property('radius', 1); - expect(result.points).to.have.property('lineWidth', 10); - expect(result.bars).to.have.property('show', false); - expect(result.bars).to.have.property('fill', 1); - expect(result.bars).to.have.property('lineWidth', 10); + expect(result.lines).toHaveProperty('show', true); + expect(result.lines).toHaveProperty('fill', 1); + expect(result.lines).toHaveProperty('lineWidth', 10); + expect(result.points).toHaveProperty('show', true); + expect(result.points).toHaveProperty('radius', 1); + expect(result.points).toHaveProperty('lineWidth', 10); + expect(result.bars).toHaveProperty('show', false); + expect(result.bars).toHaveProperty('fill', 1); + expect(result.bars).toHaveProperty('lineWidth', 10); }); - it('return decoration for lines without points', () => { + test('return decoration for lines without points', () => { const series = { chart_type: 'line', line_width: 10, fill: 1, }; const result = getDefaultDecoration(series); - expect(result.points).to.have.property('show', true); - expect(result.points).to.have.property('lineWidth', 10); + expect(result.points).toHaveProperty('show', true); + expect(result.points).toHaveProperty('lineWidth', 10); }); - it('return decoration for lines with points set to zero (off)', () => { + test('return decoration for lines with points set to zero (off)', () => { const series = { chart_type: 'line', line_width: 10, @@ -92,32 +91,32 @@ describe('getDefaultDecoration', () => { point_size: 0, }; const result = getDefaultDecoration(series); - expect(result.points).to.have.property('show', false); + expect(result.points).toHaveProperty('show', false); }); - it('return decoration for lines (off)', () => { + test('return decoration for lines (off)', () => { const series = { chart_type: 'line', line_width: 0, }; const result = getDefaultDecoration(series); - expect(result.lines).to.have.property('show', false); + expect(result.lines).toHaveProperty('show', false); }); }); describe('bars', () => { - it('return decoration for bars', () => { + test('return decoration for bars', () => { const series = { chart_type: 'bar', line_width: 10, fill: 1, }; const result = getDefaultDecoration(series); - expect(result.lines).to.have.property('show', false); - expect(result.points).to.have.property('show', false); - expect(result.bars).to.have.property('show', true); - expect(result.bars).to.have.property('fill', 1); - expect(result.bars).to.have.property('lineWidth', 10); + expect(result.lines).toHaveProperty('show', false); + expect(result.points).toHaveProperty('show', false); + expect(result.bars).toHaveProperty('show', true); + expect(result.bars).toHaveProperty('fill', 1); + expect(result.bars).toHaveProperty('lineWidth', 10); }); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.test.js similarity index 82% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.test.js index b19f6a3241597..13f62739a5485 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.test.js @@ -17,11 +17,10 @@ * under the License. */ -import { expect } from 'chai'; -import { getEsShardTimeout } from '../../helpers/get_es_shard_timeout'; +import { getEsShardTimeout } from './get_es_shard_timeout'; describe('getEsShardTimeout', () => { - it('should return the elasticsearch.shardTimeout', async () => { + test('should return the elasticsearch.shardTimeout', async () => { const req = { getEsShardTimeout: async () => { return 12345; @@ -30,6 +29,6 @@ describe('getEsShardTimeout', () => { const timeout = await getEsShardTimeout(req); - expect(timeout).to.equal(12345); + expect(timeout).toEqual(12345); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_last_metric.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_last_metric.test.js similarity index 76% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_last_metric.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_last_metric.test.js index be99f81dfccc4..42e6cc2c01836 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_last_metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_last_metric.test.js @@ -17,26 +17,25 @@ * under the License. */ -import { expect } from 'chai'; -import { getLastMetric } from '../../helpers/get_last_metric'; +import { getLastMetric } from './get_last_metric'; describe('getLastMetric(series)', () => { - it('returns the last metric', () => { + test('returns the last metric', () => { const series = { metrics: [ { id: 1, type: 'avg' }, { id: 2, type: 'moving_average' }, ], }; - expect(getLastMetric(series)).to.eql({ id: 2, type: 'moving_average' }); + expect(getLastMetric(series)).toEqual({ id: 2, type: 'moving_average' }); }); - it('returns the last metric that not a series_agg', () => { + test('returns the last metric that not a series_agg', () => { const series = { metrics: [ { id: 1, type: 'avg' }, { id: 2, type: 'series_agg' }, ], }; - expect(getLastMetric(series)).to.eql({ id: 1, type: 'avg' }); + expect(getLastMetric(series)).toEqual({ id: 1, type: 'avg' }); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_sibling_agg_value.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_sibling_agg_value.test.js similarity index 69% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_sibling_agg_value.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_sibling_agg_value.test.js index 68bfa77e25ea3..755568546949c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_sibling_agg_value.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_sibling_agg_value.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { getSiblingAggValue } from '../../helpers/get_sibling_agg_value'; +import { getSiblingAggValue } from './get_sibling_agg_value'; describe('getSiblingAggValue', () => { const row = { @@ -32,23 +31,23 @@ describe('getSiblingAggValue', () => { }, }; - it('returns the value for std_deviation_bounds.upper', () => { + test('returns the value for std_deviation_bounds.upper', () => { const metric = { id: 'test', type: 'std_deviation_bucket', mode: 'upper' }; - expect(getSiblingAggValue(row, metric)).to.equal(2); + expect(getSiblingAggValue(row, metric)).toEqual(2); }); - it('returns the value for std_deviation_bounds.lower', () => { + test('returns the value for std_deviation_bounds.lower', () => { const metric = { id: 'test', type: 'std_deviation_bucket', mode: 'lower' }; - expect(getSiblingAggValue(row, metric)).to.equal(1); + expect(getSiblingAggValue(row, metric)).toEqual(1); }); - it('returns the value for std_deviation', () => { + test('returns the value for std_deviation', () => { const metric = { id: 'test', type: 'std_deviation_bucket', mode: 'raw' }; - expect(getSiblingAggValue(row, metric)).to.equal(1.5); + expect(getSiblingAggValue(row, metric)).toEqual(1.5); }); - it('returns the value for basic (max)', () => { + test('returns the value for basic (max)', () => { const metric = { id: 'test', type: 'max_bucket' }; - expect(getSiblingAggValue(row, metric)).to.equal(3); + expect(getSiblingAggValue(row, metric)).toEqual(3); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_splits.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js similarity index 90% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_splits.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js index 1057248d2f362..0874d944033f5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_splits.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js @@ -17,11 +17,10 @@ * under the License. */ -import { expect } from 'chai'; -import { getSplits } from '../../helpers/get_splits'; +import { getSplits } from './get_splits'; describe('getSplits(resp, panel, series)', () => { - it('should return a splits for everything/filter group bys', () => { + test('should return a splits for everything/filter group bys', () => { const resp = { aggregations: { SERIES: { @@ -41,7 +40,7 @@ describe('getSplits(resp, panel, series)', () => { { id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' }, ], }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES', label: 'Overall Average of Average of cpu', @@ -53,7 +52,7 @@ describe('getSplits(resp, panel, series)', () => { ]); }); - it('should return a splits for terms group bys for top_n', () => { + test('should return a splits for terms group bys for top_n', () => { const resp = { aggregations: { SERIES: { @@ -85,7 +84,7 @@ describe('getSplits(resp, panel, series)', () => { ], }; const panel = { type: 'top_n' }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES:example-01', key: 'example-01', @@ -107,7 +106,7 @@ describe('getSplits(resp, panel, series)', () => { ]); }); - it('should return a splits for terms group bys', () => { + test('should return a splits for terms group bys', () => { const resp = { aggregations: { SERIES: { @@ -139,7 +138,7 @@ describe('getSplits(resp, panel, series)', () => { ], }; const panel = { type: 'timeseries' }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES:example-01', key: 'example-01', @@ -161,7 +160,7 @@ describe('getSplits(resp, panel, series)', () => { ]); }); - it('should return a splits for filters group bys', () => { + test('should return a splits for filters group bys', () => { const resp = { aggregations: { SERIES: { @@ -188,7 +187,7 @@ describe('getSplits(resp, panel, series)', () => { metrics: [{ id: 'COUNT', type: 'count' }], }; const panel = { type: 'timeseries' }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES:filter-1', key: 'filter-1', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_timerange.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.js similarity index 72% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_timerange.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.js index f79d1002b8546..1a1b12c651992 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_timerange.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.js @@ -17,12 +17,11 @@ * under the License. */ -import { expect } from 'chai'; -import { getTimerange } from '../../helpers/get_timerange'; +import { getTimerange } from './get_timerange'; import moment from 'moment'; describe('getTimerange(req)', () => { - it('should return a moment object for to and from', () => { + test('should return a moment object for to and from', () => { const req = { payload: { timerange: { @@ -32,9 +31,9 @@ describe('getTimerange(req)', () => { }, }; const { from, to } = getTimerange(req); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); - expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).to.equal(true); - expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).to.equal(true); + expect(moment.isMoment(from)).toEqual(true); + expect(moment.isMoment(to)).toEqual(true); + expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).toEqual(true); + expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).toEqual(true); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/map_bucket.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js similarity index 72% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/map_bucket.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js index e3223ef7b618f..0b007416681ee 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/map_bucket.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js @@ -17,40 +17,39 @@ * under the License. */ -import { mapBucket } from '../../helpers/map_bucket'; -import { expect } from 'chai'; +import { mapBucket } from './map_bucket'; describe('mapBucket(metric)', () => { - it('returns bucket key and value for basic metric', () => { + test('returns bucket key and value for basic metric', () => { const metric = { id: 'AVG', type: 'avg' }; const bucket = { key: 1234, AVG: { value: 1 }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); - it('returns bucket key and value for std_deviation', () => { + test('returns bucket key and value for std_deviation', () => { const metric = { id: 'STDDEV', type: 'std_deviation' }; const bucket = { key: 1234, STDDEV: { std_deviation: 1 }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); - it('returns bucket key and value for percentiles', () => { + test('returns bucket key and value for percentiles', () => { const metric = { id: 'PCT', type: 'percentile', percent: 50 }; const bucket = { key: 1234, PCT: { values: { '50.0': 1 } }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); - it('returns bucket key and value for derivative', () => { + test('returns bucket key and value for derivative', () => { const metric = { id: 'DERV', type: 'derivative', field: 'io', unit: '1s' }; const bucket = { key: 1234, DERV: { value: 100, normalized_value: 1 }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/parse_settings.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/parse_settings.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/parse_settings.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/parse_settings.test.js index bf16b6e22e8ef..eab06cdccfa66 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/parse_settings.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/parse_settings.test.js @@ -17,41 +17,40 @@ * under the License. */ -import { expect } from 'chai'; -import { parseSettings } from '../../helpers/parse_settings'; +import { parseSettings } from './parse_settings'; describe('parseSettings', () => { - it('returns the true for "true"', () => { + test('returns the true for "true"', () => { const settings = 'pad=true'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: true, }); }); - it('returns the false for "false"', () => { + test('returns the false for "false"', () => { const settings = 'pad=false'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: false, }); }); - it('returns the true for 1', () => { + test('returns the true for 1', () => { const settings = 'pad=1'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: true, }); }); - it('returns the false for 0', () => { + test('returns the false for 0', () => { const settings = 'pad=0'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: false, }); }); - it('returns the settings as an object', () => { + test('returns the settings as an object', () => { const settings = 'alpha=0.9 beta=0.4 gamma=0.2 period=5 pad=false type=add'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ alpha: 0.9, beta: 0.4, gamma: 0.2, diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/offset_time.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/offset_time.test.js similarity index 64% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/offset_time.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/offset_time.test.js index 55e43cf7bd8ce..b9b73002bafb3 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/offset_time.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/offset_time.test.js @@ -17,12 +17,11 @@ * under the License. */ -import { expect } from 'chai'; import moment from 'moment'; -import { offsetTime } from '../offset_time'; +import { offsetTime } from './offset_time'; describe('offsetTime(req, by)', () => { - it('should return a moment object for to and from', () => { + test('should return a moment object for to and from', () => { const req = { payload: { timerange: { @@ -32,13 +31,13 @@ describe('offsetTime(req, by)', () => { }, }; const { from, to } = offsetTime(req, ''); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); - expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).to.equal(true); - expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).to.equal(true); + expect(moment.isMoment(from)).toBeTruthy(); + expect(moment.isMoment(to)).toBeTruthy(); + expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).toBeTruthy(); + expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).toBeTruthy(); }); - it('should return a moment object for to and from offset by 1 hour', () => { + test('should return a moment object for to and from offset by 1 hour', () => { const req = { payload: { timerange: { @@ -48,23 +47,23 @@ describe('offsetTime(req, by)', () => { }, }; const { from, to } = offsetTime(req, '1h'); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); + expect(moment.isMoment(from)).toBeTruthy(); + expect(moment.isMoment(to)).toBeTruthy(); expect( moment .utc('2017-01-01T00:00:00Z') .subtract(1, 'h') .isSame(from) - ).to.equal(true); + ).toBeTruthy(); expect( moment .utc('2017-01-01T01:00:00Z') .subtract(1, 'h') .isSame(to) - ).to.equal(true); + ).toBeTruthy(); }); - it('should return a moment object for to and from offset by -2 minute', () => { + test('should return a moment object for to and from offset by -2 minute', () => { const req = { payload: { timerange: { @@ -74,13 +73,13 @@ describe('offsetTime(req, by)', () => { }, }; const { from, to } = offsetTime(req, '-2m'); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); - expect(moment.utc('2017-01-10T01:02:00Z').isSame(from)).to.equal(true); - expect(moment.utc('2017-01-10T02:02:00Z').isSame(to)).to.equal(true); + expect(moment.isMoment(from)).toBeTruthy(); + expect(moment.isMoment(to)).toBeTruthy(); + expect(moment.utc('2017-01-10T01:02:00Z').isSame(from)).toBeTruthy(); + expect(moment.utc('2017-01-10T02:02:00Z').isSame(to)).toBeTruthy(); }); - it('should work when prefixing positive offsets with the plus sign', () => { + test('should work when prefixing positive offsets with the plus sign', () => { const req = { payload: { timerange: { @@ -92,7 +91,7 @@ describe('offsetTime(req, by)', () => { const { from: fromSigned, to: toSigned } = offsetTime(req, '+1m'); const { from, to } = offsetTime(req, '1m'); - expect(fromSigned.isSame(from)).to.equal(true); - expect(toSigned.isSame(to)).to.equal(true); + expect(fromSigned.isSame(from)).toBeTruthy(); + expect(toSigned.isSame(to)).toBeTruthy(); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js similarity index 85% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index 40eaba621aabb..7f309b44d13f4 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -17,10 +17,8 @@ * under the License. */ -import { expect } from 'chai'; -import sinon from 'sinon'; -import { DefaultSearchCapabilities } from '../../../../search_strategies/default_search_capabilities'; -import { dateHistogram } from '../date_histogram'; +import { DefaultSearchCapabilities } from '../../../search_strategies/default_search_capabilities'; +import { dateHistogram } from './date_histogram'; describe('dateHistogram(req, panel, series)', () => { let panel; @@ -54,18 +52,18 @@ describe('dateHistogram(req, panel, series)', () => { capabilities = new DefaultSearchCapabilities(req); }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns valid date histogram', () => { + test('returns valid date histogram', () => { const next = doc => doc; const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -93,13 +91,13 @@ describe('dateHistogram(req, panel, series)', () => { }); }); - it('returns valid date histogram (offset by 1h)', () => { + test('returns valid date histogram (offset by 1h)', () => { series.offset_time = '1h'; const next = doc => doc; const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -127,7 +125,7 @@ describe('dateHistogram(req, panel, series)', () => { }); }); - it('returns valid date histogram with overridden index pattern', () => { + test('returns valid date histogram with overridden index pattern', () => { series.override_index_pattern = 1; series.series_index_pattern = '*'; series.series_time_field = 'timestamp'; @@ -136,7 +134,7 @@ describe('dateHistogram(req, panel, series)', () => { const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -165,7 +163,7 @@ describe('dateHistogram(req, panel, series)', () => { }); describe('dateHistogram for entire time range mode', () => { - it('should ignore entire range mode for timeseries', () => { + test('should ignore entire range mode for timeseries', () => { panel.time_range_mode = 'entire_time_range'; panel.type = 'timeseries'; @@ -174,18 +172,18 @@ describe('dateHistogram(req, panel, series)', () => { {} ); - expect(doc.aggs.test.aggs.timeseries.auto_date_histogram).to.eql(undefined); - expect(doc.aggs.test.aggs.timeseries.date_histogram).to.exist; + expect(doc.aggs.test.aggs.timeseries.auto_date_histogram).toBeUndefined(); + expect(doc.aggs.test.aggs.timeseries.date_histogram).toBeDefined(); }); - it('should returns valid date histogram for entire range mode', () => { + test('should returns valid date histogram for entire range mode', () => { panel.time_range_mode = 'entire_time_range'; const next = doc => doc; const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/filter_ratios.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/filter_ratios.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js index 0449ee440da81..f3d25a8e44ba7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/filter_ratios.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { ratios } from '../filter_ratios'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { ratios } from './filter_ratios'; describe('ratios(req, panel, series)', () => { let panel; @@ -55,16 +53,16 @@ describe('ratios(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); ratios(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns filter ratio aggs', () => { + test('returns filter ratio aggs', () => { const next = doc => doc; const doc = ratios(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -119,11 +117,11 @@ describe('ratios(req, panel, series)', () => { }); }); - it('returns empty object when field is not set', () => { + test('returns empty object when field is not set', () => { delete series.metrics[0].field; const next = doc => doc; const doc = ratios(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/metric_buckets.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.test.js similarity index 88% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/metric_buckets.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.test.js index 9393882f12b7e..eaf8ee1549f0b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/metric_buckets.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { metricBuckets } from '../metric_buckets'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { metricBuckets } from './metric_buckets'; describe('metricBuckets(req, panel, series)', () => { let panel; @@ -63,16 +61,16 @@ describe('metricBuckets(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); metricBuckets(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns metric aggs', () => { + test('returns metric aggs', () => { const next = doc => doc; const doc = metricBuckets(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/query.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js similarity index 91% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/query.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js index 2f2e5f46f834e..06da636f818d5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/query.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { query } from '../query'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { query } from './query'; describe('query(req, panel, series)', () => { let panel; @@ -47,16 +45,16 @@ describe('query(req, panel, series)', () => { series = { id: 'test' }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); query(req, panel, series, config)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns doc with query for timerange', () => { + test('returns doc with query for timerange', () => { const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -79,11 +77,11 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with query for timerange (offset by 1h)', () => { + test('returns doc with query for timerange (offset by 1h)', () => { series.offset_time = '1h'; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -106,7 +104,7 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with global query', () => { + test('returns doc with global query', () => { req.payload.filters = [ { bool: { @@ -122,7 +120,7 @@ describe('query(req, panel, series)', () => { ]; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -157,11 +155,11 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with series filter', () => { + test('returns doc with series filter', () => { series.filter = { query: 'host:web-server', language: 'lucene' }; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -198,7 +196,7 @@ describe('query(req, panel, series)', () => { }, }); }); - it('returns doc with panel filter and global', () => { + test('returns doc with panel filter and global', () => { req.payload.filters = [ { bool: { @@ -215,7 +213,7 @@ describe('query(req, panel, series)', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -265,7 +263,7 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with panel filter (ignoring globals)', () => { + test('returns doc with panel filter (ignoring globals)', () => { req.payload.filters = [ { bool: { @@ -283,7 +281,7 @@ describe('query(req, panel, series)', () => { panel.ignore_global_filter = true; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/sibling_buckets.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.test.js similarity index 86% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/sibling_buckets.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.test.js index 0f02c755cabcd..8e1c9b0f9ecd9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/sibling_buckets.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { siblingBuckets } from '../sibling_buckets'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { siblingBuckets } from './sibling_buckets'; describe('siblingBuckets(req, panel, series)', () => { let panel; @@ -57,16 +55,16 @@ describe('siblingBuckets(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); siblingBuckets(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns sibling aggs', () => { + test('returns sibling aggs', () => { const next = doc => doc; const doc = siblingBuckets(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_everything.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_everything.test.js similarity index 83% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_everything.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_everything.test.js index 4e2775d3608e6..5c2468f6b7c35 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_everything.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_everything.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByEverything } from '../split_by_everything'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByEverything } from './split_by_everything'; describe('splitByEverything(req, panel, series)', () => { let panel; @@ -39,15 +37,15 @@ describe('splitByEverything(req, panel, series)', () => { }); it('calls next when finished', () => { - const next = sinon.spy(); + const next = jest.fn(); splitByEverything(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); it('returns a valid filter with match_all', () => { const next = doc => doc; const doc = splitByEverything(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { filter: { @@ -61,9 +59,9 @@ describe('splitByEverything(req, panel, series)', () => { it('calls next and does not add a filter', () => { series.split_mode = 'terms'; series.terms_field = 'host'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByEverything(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filter.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js similarity index 79% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filter.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js index 782310ba4d2bf..75ca782a6d7e7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByFilter } from '../split_by_filter'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByFilter } from './split_by_filter'; describe('splitByFilter(req, panel, series)', () => { let panel; @@ -42,16 +40,16 @@ describe('splitByFilter(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); splitByFilter(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns a valid filter with a query_string', () => { + test('returns a valid filter with a query_string', () => { const next = doc => doc; const doc = splitByFilter(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { filter: { @@ -73,11 +71,11 @@ describe('splitByFilter(req, panel, series)', () => { }); }); - it('calls next and does not add a filter', () => { + test('calls next and does not add a filter', () => { series.split_mode = 'terms'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByFilter(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filters.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js similarity index 85% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filters.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js index 018cb4b003ecf..6108b2bf2cf2b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filters.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByFilters } from '../split_by_filters'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByFilters } from './split_by_filters'; describe('splitByFilters(req, panel, series)', () => { let panel; @@ -58,16 +56,16 @@ describe('splitByFilters(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); splitByFilters(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns a valid terms agg', () => { + test('returns a valid terms agg', () => { const next = doc => doc; const doc = splitByFilters(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { filters: { @@ -107,11 +105,11 @@ describe('splitByFilters(req, panel, series)', () => { }); }); - it('calls next and does not add a terms agg', () => { + test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByFilters(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_terms.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js similarity index 80% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_terms.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js index eb1a81baa7fc2..ff7139c380af9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_terms.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByTerms } from '../split_by_terms'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByTerms } from './split_by_terms'; describe('splitByTerms(req, panel, series)', () => { let panel; @@ -46,16 +44,16 @@ describe('splitByTerms(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); splitByTerms(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns a valid terms agg', () => { + test('returns a valid terms agg', () => { const next = doc => doc; const doc = splitByTerms(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { terms: { @@ -70,12 +68,12 @@ describe('splitByTerms(req, panel, series)', () => { }); }); - it('returns a valid terms agg sort by terms', () => { + test('returns a valid terms agg sort by terms', () => { const next = doc => doc; series.terms_order_by = '_key'; series.terms_direction = 'asc'; const doc = splitByTerms(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { terms: { @@ -90,11 +88,11 @@ describe('splitByTerms(req, panel, series)', () => { }); }); - it('returns a valid terms agg with custom sort', () => { + test('returns a valid terms agg with custom sort', () => { series.terms_order_by = 'avgmetric'; const next = doc => doc; const doc = splitByTerms(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { terms: { @@ -116,11 +114,11 @@ describe('splitByTerms(req, panel, series)', () => { }); }); - it('calls next and does not add a terms agg', () => { + test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByTerms(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/_series_agg.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js similarity index 68% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/_series_agg.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js index 582466f8eb9fd..22ea2c852e254 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/_series_agg.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { SeriesAgg as seriesAgg } from '../_series_agg'; +import { SeriesAgg as seriesAgg } from './_series_agg'; describe('seriesAgg', () => { const series = [ @@ -40,8 +39,8 @@ describe('seriesAgg', () => { ]; describe('basic', () => { - it('returns the series sum', () => { - expect(seriesAgg.sum(series)).to.eql([ + test('returns the series sum', () => { + expect(seriesAgg.sum(series)).toEqual([ [ [0, 8], [1, 4], @@ -50,8 +49,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series max', () => { - expect(seriesAgg.max(series)).to.eql([ + test('returns the series max', () => { + expect(seriesAgg.max(series)).toEqual([ [ [0, 4], [1, 2], @@ -60,8 +59,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series min', () => { - expect(seriesAgg.min(series)).to.eql([ + test('returns the series min', () => { + expect(seriesAgg.min(series)).toEqual([ [ [0, 2], [1, 1], @@ -70,8 +69,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series mean', () => { - expect(seriesAgg.mean(series)).to.eql([ + test('returns the series mean', () => { + expect(seriesAgg.mean(series)).toEqual([ [ [0, 8 / 3], [1, 4 / 3], @@ -82,8 +81,8 @@ describe('seriesAgg', () => { }); describe('overall', () => { - it('returns the series overall sum', () => { - expect(seriesAgg.overall_sum(series)).to.eql([ + test('returns the series overall sum', () => { + expect(seriesAgg.overall_sum(series)).toEqual([ [ [0, 21], [1, 21], @@ -92,8 +91,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series overall max', () => { - expect(seriesAgg.overall_max(series)).to.eql([ + test('returns the series overall max', () => { + expect(seriesAgg.overall_max(series)).toEqual([ [ [0, 4], [1, 4], @@ -102,8 +101,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series overall min', () => { - expect(seriesAgg.overall_min(series)).to.eql([ + test('returns the series overall min', () => { + expect(seriesAgg.overall_min(series)).toEqual([ [ [0, 1], [1, 1], @@ -112,9 +111,9 @@ describe('seriesAgg', () => { ]); }); - it('returns the series overall mean', () => { + test('returns the series overall mean', () => { const value = (8 + 4 + 9) / 3; - expect(seriesAgg.overall_avg(series)).to.eql([ + expect(seriesAgg.overall_avg(series)).toEqual([ [ [0, value], [1, value], @@ -125,8 +124,8 @@ describe('seriesAgg', () => { }); describe('cumulative sum', () => { - it('returns the series cumulative sum', () => { - expect(seriesAgg.cumulative_sum(series)).to.eql([ + test('returns the series cumulative sum', () => { + expect(seriesAgg.cumulative_sum(series)).toEqual([ [ [0, 8], [1, 12], diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/percentile.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js similarity index 57% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/percentile.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js index d40a75c903602..9cb08de8dad23 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/percentile.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { percentile } from '../percentile'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { percentile } from './percentile'; describe('percentile(resp, panel, series)', () => { let panel; @@ -82,72 +80,72 @@ describe('percentile(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); percentile(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = percentile(resp, panel, series)(next)([]); - expect(results).to.have.length(3); + expect(results).toHaveLength(3); - expect(results[0]).to.have.property('id', 'test:10-90'); - expect(results[0]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[0]).to.have.property('fillBetween', 'test:10-90:90'); - expect(results[0]).to.have.property('label', 'Percentile of cpu (10)'); - expect(results[0]).to.have.property('legend', false); - expect(results[0]).to.have.property('lines'); - expect(results[0].lines).to.eql({ + expect(results[0]).toHaveProperty('id', 'test:10-90'); + expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[0]).toHaveProperty('fillBetween', 'test:10-90:90'); + expect(results[0]).toHaveProperty('label', 'Percentile of cpu (10)'); + expect(results[0]).toHaveProperty('legend', false); + expect(results[0]).toHaveProperty('lines'); + expect(results[0].lines).toEqual({ fill: 0.2, lineWidth: 0, show: true, }); - expect(results[0]).to.have.property('points'); - expect(results[0].points).to.eql({ show: false }); - expect(results[0].data).to.eql([ + expect(results[0]).toHaveProperty('points'); + expect(results[0].points).toEqual({ show: false }); + expect(results[0].data).toEqual([ [1, 1], [2, 1.2], ]); - expect(results[1]).to.have.property('id', 'test:10-90:90'); - expect(results[1]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[1]).to.have.property('label', 'Percentile of cpu (10)'); - expect(results[1]).to.have.property('legend', false); - expect(results[1]).to.have.property('lines'); - expect(results[1].lines).to.eql({ + expect(results[1]).toHaveProperty('id', 'test:10-90:90'); + expect(results[1]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[1]).toHaveProperty('label', 'Percentile of cpu (10)'); + expect(results[1]).toHaveProperty('legend', false); + expect(results[1]).toHaveProperty('lines'); + expect(results[1].lines).toEqual({ fill: false, lineWidth: 0, show: true, }); - expect(results[1]).to.have.property('points'); - expect(results[1].points).to.eql({ show: false }); - expect(results[1].data).to.eql([ + expect(results[1]).toHaveProperty('points'); + expect(results[1].points).toEqual({ show: false }); + expect(results[1].data).toEqual([ [1, 5], [2, 5.3], ]); - expect(results[2]).to.have.property('id', 'test:50'); - expect(results[2]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[2]).to.have.property('label', 'Percentile of cpu (50)'); - expect(results[2]).to.have.property('stack', false); - expect(results[2]).to.have.property('lines'); - expect(results[2].lines).to.eql({ + expect(results[2]).toHaveProperty('id', 'test:50'); + expect(results[2]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[2]).toHaveProperty('label', 'Percentile of cpu (50)'); + expect(results[2]).toHaveProperty('stack', false); + expect(results[2]).toHaveProperty('lines'); + expect(results[2].lines).toEqual({ fill: 0, lineWidth: 1, show: true, steps: false, }); - expect(results[2]).to.have.property('bars'); - expect(results[2].bars).to.eql({ + expect(results[2]).toHaveProperty('bars'); + expect(results[2].bars).toEqual({ fill: 0, lineWidth: 1, show: false, }); - expect(results[2]).to.have.property('points'); - expect(results[2].points).to.eql({ show: true, lineWidth: 1, radius: 1 }); - expect(results[2].data).to.eql([ + expect(results[2]).toHaveProperty('points'); + expect(results[2].points).toEqual({ show: true, lineWidth: 1, radius: 1 }); + expect(results[2].data).toEqual([ [1, 2.5], [2, 2.7], ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/series_agg.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.test.js similarity index 88% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/series_agg.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.test.js index 2391cd3bc7698..3e09c51d9184f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/series_agg.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.test.js @@ -17,10 +17,8 @@ * under the License. */ -import { seriesAgg } from '../series_agg'; -import { stdMetric } from '../std_metric'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { seriesAgg } from './series_agg'; +import { stdMetric } from './std_metric'; describe('seriesAgg(resp, panel, series)', () => { let panel; @@ -93,18 +91,18 @@ describe('seriesAgg(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); seriesAgg(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = seriesAgg(resp, panel, series)(results => results); const results = stdMetric(resp, panel, series)(next)([]); - expect(results).to.have.length(1); + expect(results).toHaveLength(1); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test', color: '#F00', label: 'Total CPU', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_bands.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_bands.test.js similarity index 87% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_bands.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_bands.test.js index 488ae4a8351b7..77949ff94dc4c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_bands.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_bands.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { stdDeviationBands } from '../std_deviation_bands'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { stdDeviationBands } from './std_deviation_bands'; describe('stdDeviationBands(resp, panel, series)', () => { let panel; @@ -79,18 +77,18 @@ describe('stdDeviationBands(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdDeviationBands(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdDeviationBands(resp, panel, series)(next)([]); - expect(results).to.have.length(2); + expect(results).toHaveLength(2); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test:upper', label: 'Std. Deviation of cpu', color: 'rgb(255, 0, 0)', @@ -103,7 +101,7 @@ describe('stdDeviationBands(resp, panel, series)', () => { ], }); - expect(results[1]).to.eql({ + expect(results[1]).toEqual({ id: 'test:lower', color: 'rgb(255, 0, 0)', lines: { show: true, fill: false, lineWidth: 0 }, diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_sibling.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_sibling.test.js similarity index 87% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_sibling.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_sibling.test.js index 14c2c16ef728f..adc5a3a4a991b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_sibling.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_sibling.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { stdDeviationSibling } from '../std_deviation_sibling'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { stdDeviationSibling } from './std_deviation_sibling'; describe('stdDeviationSibling(resp, panel, series)', () => { let panel; @@ -79,18 +77,18 @@ describe('stdDeviationSibling(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdDeviationSibling(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdDeviationSibling(resp, panel, series)(next)([]); - expect(results).to.have.length(2); + expect(results).toHaveLength(2); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test:lower', color: 'rgb(255, 0, 0)', lines: { show: true, fill: false, lineWidth: 0 }, @@ -101,7 +99,7 @@ describe('stdDeviationSibling(resp, panel, series)', () => { ], }); - expect(results[1]).to.eql({ + expect(results[1]).toEqual({ id: 'test:upper', label: 'Overall Std. Deviation of Average of cpu', color: 'rgb(255, 0, 0)', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_metric.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_metric.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.test.js index 5039a406a6acc..6ddd6f907fa97 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { stdMetric } from '../std_metric'; +import { stdMetric } from './std_metric'; describe('stdMetric(resp, panel, series)', () => { let panel; @@ -60,41 +58,41 @@ describe('stdMetric(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdMetric(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('calls next when finished (percentile)', () => { + test('calls next when finished (percentile)', () => { series.metrics[0].type = 'percentile'; - const next = sinon.spy(d => d); + const next = jest.fn(d => d); const results = stdMetric(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); - expect(results).to.have.length(0); + expect(next.mock.calls.length).toEqual(1); + expect(results).toHaveLength(0); }); - it('calls next when finished (std_deviation band)', () => { + test('calls next when finished (std_deviation band)', () => { series.metrics[0].type = 'std_deviation'; series.metrics[0].mode = 'band'; - const next = sinon.spy(d => d); + const next = jest.fn(d => d); const results = stdMetric(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); - expect(results).to.have.length(0); + expect(next.mock.calls.length).toEqual(1); + expect(results).toHaveLength(0); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdMetric(resp, panel, series)(next)([]); - expect(results).to.have.length(1); - expect(results[0]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[0]).to.have.property('id', 'test'); - expect(results[0]).to.have.property('label', 'Average of cpu'); - expect(results[0]).to.have.property('lines'); - expect(results[0]).to.have.property('stack'); - expect(results[0]).to.have.property('bars'); - expect(results[0]).to.have.property('points'); - expect(results[0].data).to.eql([ + expect(results).toHaveLength(1); + expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[0]).toHaveProperty('id', 'test'); + expect(results[0]).toHaveProperty('label', 'Average of cpu'); + expect(results[0]).toHaveProperty('lines'); + expect(results[0]).toHaveProperty('stack'); + expect(results[0]).toHaveProperty('bars'); + expect(results[0]).toHaveProperty('points'); + expect(results[0].data).toEqual([ [1, 1], [2, 2], ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_sibling.test.js similarity index 82% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_sibling.test.js index a243ad525332c..9ed60a6d16492 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_sibling.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { stdSibling } from '../std_sibling'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { stdSibling } from './std_sibling'; describe('stdSibling(resp, panel, series)', () => { let panel; @@ -74,26 +72,26 @@ describe('stdSibling(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdSibling(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('calls next when std. deviation bands set', () => { + test('calls next when std. deviation bands set', () => { series.metrics[1].mode = 'band'; - const next = sinon.spy(results => results); + const next = jest.fn(results => results); const results = stdSibling(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); - expect(results).to.have.length(0); + expect(next.mock.calls.length).toEqual(1); + expect(results).toHaveLength(0); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdSibling(resp, panel, series)(next)([]); - expect(results).to.have.length(1); + expect(results).toHaveLength(1); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test', label: 'Overall Std. Deviation of Average of cpu', color: 'rgb(255, 0, 0)', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/time_shift.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/time_shift.test.js similarity index 71% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/time_shift.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/time_shift.test.js index 1e6da85f88164..7047c54fa1c0e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/time_shift.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/time_shift.test.js @@ -17,10 +17,8 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { timeShift } from '../time_shift'; -import { stdMetric } from '../std_metric'; +import { timeShift } from './time_shift'; +import { stdMetric } from './std_metric'; describe('timeShift(resp, panel, series)', () => { let panel; @@ -62,24 +60,24 @@ describe('timeShift(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); timeShift(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = timeShift(resp, panel, series)(results => results); const results = stdMetric(resp, panel, series)(next)([]); - expect(results).to.have.length(1); - expect(results[0]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[0]).to.have.property('id', 'test'); - expect(results[0]).to.have.property('label', 'Average of cpu'); - expect(results[0]).to.have.property('lines'); - expect(results[0]).to.have.property('stack'); - expect(results[0]).to.have.property('bars'); - expect(results[0]).to.have.property('points'); - expect(results[0].data).to.eql([ + expect(results).toHaveLength(1); + expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[0]).toHaveProperty('id', 'test'); + expect(results[0]).toHaveProperty('label', 'Average of cpu'); + expect(results[0]).toHaveProperty('lines'); + expect(results[0]).toHaveProperty('stack'); + expect(results[0]).toHaveProperty('bars'); + expect(results[0]).toHaveProperty('points'); + expect(results[0].data).toEqual([ [1483225200000 + 3600000, 1], [1483225210000 + 3600000, 2], ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/__tests__/build_request_body.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/__tests__/build_request_body.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts index f21bf3d197969..0c75e6ef1c5bd 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/__tests__/build_request_body.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts @@ -17,6 +17,8 @@ * under the License. */ +import { buildRequestBody } from './build_request_body'; + const body = JSON.parse(` { "filters": [ @@ -75,15 +77,11 @@ const body = JSON.parse(` } `); -import sinon from 'sinon'; -import { expect } from 'chai'; -import { buildRequestBody } from '../build_request_body'; - describe('buildRequestBody(req)', () => { - it('returns a valid body', () => { + test('returns a valid body', () => { const panel = body.panels[0]; const series = panel.series[0]; - const getValidTimeInterval = sinon.spy(() => '10s'); + const getValidTimeInterval = jest.fn(() => '10s'); const capabilities = { searchTimezone: 'UTC', getValidTimeInterval, @@ -102,7 +100,7 @@ describe('buildRequestBody(req)', () => { capabilities ); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts index fe3137a8f86ba..85e1f8f7eb12b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts @@ -18,6 +18,7 @@ */ import { buildProcessorFunction } from '../build_processor_function'; +// @ts-ignore import { processors } from '../request_processors/series'; /** @@ -33,7 +34,7 @@ import { processors } from '../request_processors/series'; * ] * @returns {Object} doc - processed body */ -export function buildRequestBody(...args) { +export function buildRequestBody(...args: any[]) { const processor = buildProcessorFunction(processors, ...args); const doc = processor({}); return doc; diff --git a/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss b/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss index f4276541d5d9e..709aaa2030f68 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss +++ b/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss @@ -1,13 +1,6 @@ .visEditor--vega { .visEditorSidebar__config { padding: 0; - position: relative; - } - - .visEditorSidebar__options { - @include euiScrollBar; - flex-shrink: 1; - overflow-y: auto; } } @@ -22,8 +15,8 @@ .vgaEditor__aceEditorActions { position: absolute; z-index: $euiZLevel1; - top: 0; - // Adjust for possible scrollbars - right: $euiSize; + top: $euiSizeS; + // Adjust for sidebar collapse button + right: $euiSizeXXL; line-height: 1; } 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 9ab5f820cec31..81c98c6ddb96b 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 @@ -19,10 +19,8 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore -import { DefaultEditorSize } from 'ui/vis/editor_size'; -// @ts-ignore import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -import { Status } from '../../visualizations/public'; +import { Status, DefaultEditorSize } from '../../visualizations/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; diff --git a/src/legacy/core_plugins/vis_type_vislib/index.ts b/src/legacy/core_plugins/vis_type_vislib/index.ts new file mode 100644 index 0000000000000..8c24368f43ab1 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/index.ts @@ -0,0 +1,44 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../types'; + +const kbnVislibVisTypesPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'vis_type_vislib', + require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'], + publicDir: resolve(__dirname, 'public'), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + uiExports: { + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({}), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + } as Legacy.PluginSpecOptions); + +// eslint-disable-next-line import/no-default-export +export default kbnVislibVisTypesPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_vislib/package.json b/src/legacy/core_plugins/vis_type_vislib/package.json new file mode 100644 index 0000000000000..e30a9e2b35834 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/package.json @@ -0,0 +1,4 @@ +{ + "name": "vis_type_vislib", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/__snapshots__/pie_fn.test.js.snap b/src/legacy/core_plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/__snapshots__/pie_fn.test.js.snap rename to src/legacy/core_plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap diff --git a/src/legacy/core_plugins/vis_type_vislib/public/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/_index.scss new file mode 100644 index 0000000000000..64445648ba84a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/_index.scss @@ -0,0 +1 @@ +@import './vislib/index' diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js b/src/legacy/core_plugins/vis_type_vislib/public/area.ts similarity index 89% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js rename to src/legacy/core_plugins/vis_type_vislib/public/area.ts index fd13067c84cc0..9484ddc16fe62 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/area.ts @@ -18,8 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; import { Positions, ChartTypes, @@ -33,17 +37,17 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const areaDefinition = { +export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'area', title: i18n.translate('kbnVislibVisTypes.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', description: i18n.translate('kbnVislibVisTypes.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'area', @@ -132,7 +136,9 @@ export const areaDefinition = { { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { defaultMessage: 'Y-axis' }), + title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { + defaultMessage: 'Y-axis', + }), aggFilter: ['!geo_centroid', '!geo_bounds'], min: 1, defaults: [{ schema: 'metric', type: 'count' }], @@ -140,7 +146,9 @@ export const areaDefinition = { { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { defaultMessage: 'Dot size' }), + title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { + defaultMessage: 'Dot size', + }), min: 0, max: 1, aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'], @@ -148,7 +156,9 @@ export const areaDefinition = { { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { defaultMessage: 'X-axis' }), + title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { + defaultMessage: 'X-axis', + }), min: 0, max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], @@ -175,4 +185,4 @@ export const areaDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/basic_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/basic_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx index 2bffcb383dde3..81174d63060e5 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/basic_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../legacy_imports'; import { SwitchOption } from './switch'; import { SelectOption } from './select'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx index 947c7ae7e6e36..acbb93c8da189 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx @@ -19,14 +19,17 @@ import React, { useCallback } from 'react'; import { last } from 'lodash'; + import { i18n } from '@kbn/i18n'; -import { RangeValues, RangesParamEditor } from 'ui/vis/editors/default/controls/ranges'; +import { RangeValues, RangesParamEditor } from '../../legacy_imports'; + +export type SetColorRangeValue = (paramName: string, value: RangeValues[]) => void; interface ColorRangesProps { 'data-test-subj'?: string; colorsRange: RangeValues[]; - setValue(paramName: string, value: RangeValues[]): void; + setValue: SetColorRangeValue; setValidity?(isValid: boolean): void; setTouched?(isTouched: boolean): void; } diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx index 44837c514d4cf..7b4679ba25e8f 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx @@ -22,8 +22,7 @@ import { i18n } from '@kbn/i18n'; import { EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { ColorSchema } from 'ui/vislib/components/color/colormaps'; +import { VisOptionsProps, ColorSchema } from '../../legacy_imports'; import { SelectOption } from './select'; import { SwitchOption } from './switch'; import { ColorSchemaVislibParams } from '../../types'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/number_input.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/number_input.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/range.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/range.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/range.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/range.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/required_number_input.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/required_number_input.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/select.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/select.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/switch.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/switch.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/switch.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/switch.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/text_input.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/text_input.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/text_input.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/text_input.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/truncate_labels.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/truncate_labels.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/common/utils.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/utils.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx index 1dd1ab49d9a47..b38c65d086823 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx @@ -18,7 +18,8 @@ */ import React, { useEffect, useState, useCallback } from 'react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; + +import { VisOptionsProps } from '../../legacy_imports'; export interface ValidationVisOptionsProps extends VisOptionsProps { setMultipleValidity(paramName: string, isValid: boolean): void; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/index.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx index e3805027d658c..2ba4319a82a95 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx @@ -20,7 +20,7 @@ import React, { useCallback } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { GaugeVisParams } from '../../../gauge'; import { RangesPanel } from './ranges_panel'; import { StylePanel } from './style_panel'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/labels_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/labels_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx index 1045543512c6b..b54d9a5703fcd 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx @@ -22,11 +22,12 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; +import { ColorSchemas } from '../../../legacy_imports'; import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common'; import { GaugeOptionsInternalProps } from '.'; import { ColorSchemaVislibParams } from '../../../types'; import { Gauge } from '../../../gauge'; +import { SetColorRangeValue } from '../../common/color_ranges'; function RangesPanel({ setGaugeValue, @@ -68,7 +69,7 @@ function RangesPanel({ diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/style_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/style_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx index a76171673d9a8..0b4986ae8ad80 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/style_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx @@ -22,7 +22,7 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AggGroupNames } from 'ui/vis/editors/default'; +import { AggGroupNames } from '../../../legacy_imports'; import { SelectOption } from '../../common'; import { GaugeOptionsInternalProps } from '.'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx index 659bcf47f7a0b..d8ca7acda7cbc 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback, useEffect, useState } from 'react'; + import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicOptions, ColorRanges, @@ -35,6 +36,7 @@ import { SetColorSchemaOptionsValue } from '../../common/color_schema'; import { HeatmapVisParams } from '../../../heatmap'; import { ValueAxis } from '../../../types'; import { LabelsPanel } from './labels_panel'; +import { SetColorRangeValue } from '../../common/color_ranges'; function HeatmapOptions(props: VisOptionsProps) { const { stateParams, vis, uiState, setValue, setValidity, setTouched } = props; @@ -170,7 +172,7 @@ function HeatmapOptions(props: VisOptionsProps) { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/labels_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/labels_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx index c0e9a70e8b11e..6ca0bb26ef535 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/labels_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback } from 'react'; + import { EuiColorPicker, EuiFormRow, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { ValueAxis } from '../../../types'; import { HeatmapVisParams } from '../../../heatmap'; import { SwitchOption } from '../../common'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap index 256df603a7f33..d34cf930f7e61 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap @@ -17,11 +17,10 @@ exports[`MetricsAxisOptions component should init with the default set of props "bySchemaName": [Function], } } - aggsLabels="" changeValueAxis={[Function]} + isTabSelected={true} setParamByIndex={[Function]} setValue={[MockFunction]} - setVisType={[MockFunction]} stateParams={ Object { "categoryAxes": Array [ @@ -95,6 +94,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "setVisType": [MockFunction], "type": Object { "schemas": Object { "metrics": Array [ @@ -127,13 +127,12 @@ exports[`MetricsAxisOptions component should init with the default set of props "bySchemaName": [Function], } } - aggsLabels="" isCategoryAxisHorizontal={true} + isTabSelected={true} onValueAxisPositionChanged={[Function]} removeValueAxis={[Function]} setParamByIndex={[Function]} setValue={[MockFunction]} - setVisType={[MockFunction]} stateParams={ Object { "categoryAxes": Array [ @@ -207,6 +206,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "setVisType": [MockFunction], "type": Object { "schemas": Object { "metrics": Array [ @@ -238,7 +238,6 @@ exports[`MetricsAxisOptions component should init with the default set of props "bySchemaName": [Function], } } - aggsLabels="" axis={ Object { "id": "CategoryAxis-1", @@ -260,10 +259,10 @@ exports[`MetricsAxisOptions component should init with the default set of props "type": "category", } } + isTabSelected={true} onPositionChanged={[Function]} setCategoryAxis={[Function]} setValue={[MockFunction]} - setVisType={[MockFunction]} stateParams={ Object { "categoryAxes": Array [ @@ -337,6 +336,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "setVisType": [MockFunction], "type": Object { "schemas": Object { "metrics": Array [ diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx index a32e48baf4588..69622bb3666a6 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx @@ -25,6 +25,8 @@ import { Positions, getPositions } from '../../../utils/collections'; import { LabelOptions } from './label_options'; import { categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); + const positions = getPositions(); describe('CategoryAxisPanel component', () => { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx index 11946a5a6bccd..b83508f3f0896 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback } from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams, Axis } from '../../../types'; import { SelectOption, SwitchOption } from '../../common'; import { LabelOptions } from './label_options'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.test.tsx index ba1a46ba7d89e..9679728a2a3d1 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.test.tsx @@ -31,6 +31,8 @@ import { } from '../../../utils/collections'; import { valueAxis, seriesParam } from './mocks'; +jest.mock('ui/new_platform'); + const interpolationModes = getInterpolationModes(); const chartTypes = getChartTypes(); const chartModes = getChartModes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx index 1c9357c67c2f0..8830c9164c751 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx @@ -18,10 +18,11 @@ */ import React, { useMemo, useCallback } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams, SeriesParam, ValueAxis } from '../../../types'; import { ChartTypes } from '../../../utils/collections'; import { SelectOption } from '../../common'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx index 5f1779ad35304..a112b9a3db708 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx @@ -28,6 +28,8 @@ const DEFAULT_Y_EXTENTS = 'defaultYExtents'; const SCALE = 'scale'; const SET_Y_EXTENTS = 'setYExtents'; +jest.mock('ui/new_platform'); + describe('CustomExtentsOptions component', () => { let setValueAxis: jest.Mock; let setValueAxisScale: jest.Mock; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx similarity index 80% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx index dc5cf42277603..514b957765a99 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; + import { MetricsAxisOptions } from './index'; import { BasicVislibParams, SeriesParam, ValueAxis } from '../../../types'; import { ValidationVisOptionsProps } from '../../common'; @@ -26,10 +27,10 @@ import { Positions } from '../../../utils/collections'; import { ValueAxesPanel } from './value_axes_panel'; import { CategoryAxisPanel } from './category_axis_panel'; import { ChartTypes } from '../../../utils/collections'; -import { AggConfig } from 'ui/vis'; -import { AggType } from 'ui/agg_types'; +import { AggConfig, AggType } from '../../../legacy_imports'; import { defaultValueAxisId, valueAxis, seriesParam, categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); jest.mock('./series_panel', () => ({ SeriesPanel: () => 'SeriesPanel', })); @@ -62,7 +63,6 @@ const createAggs = (aggs: any[]) => ({ describe('MetricsAxisOptions component', () => { let setValue: jest.Mock; - let setVisType: jest.Mock; let defaultProps: ValidationVisOptionsProps; let axis: ValueAxis; let axisRight: ValueAxis; @@ -70,7 +70,6 @@ describe('MetricsAxisOptions component', () => { beforeEach(() => { setValue = jest.fn(); - setVisType = jest.fn(); axis = { ...valueAxis, @@ -90,12 +89,13 @@ describe('MetricsAxisOptions component', () => { defaultProps = { aggs: createAggs([aggCount]), - aggsLabels: '', + isTabSelected: true, vis: { type: { type: ChartTypes.AREA, schemas: { metrics: [{ name: 'metric' }] }, }, + setVisType: jest.fn(), }, stateParams: { valueAxes: [axis], @@ -104,7 +104,6 @@ describe('MetricsAxisOptions component', () => { grid: { valueAxis: defaultValueAxisId }, }, setValue, - setVisType, } as any; }); @@ -119,7 +118,6 @@ describe('MetricsAxisOptions component', () => { const comp = mount(); comp.setProps({ aggs: createAggs([aggCount, aggAverage]), - aggsLabels: `${aggCount.makeLabel()}, ${aggAverage.makeLabel()}`, }); const updatedSeries = [chart, { ...chart, data: { id: '2', label: aggAverage.makeLabel() } }]; @@ -134,7 +132,7 @@ describe('MetricsAxisOptions component', () => { }); const updatedSeries = [{ ...chart, data: { id: agg.id, label: agg.makeLabel() } }]; - expect(setValue).toHaveBeenLastCalledWith(SERIES_PARAMS, updatedSeries); + expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, updatedSeries); }); it('should update visType when one seriesParam', () => { @@ -148,7 +146,7 @@ describe('MetricsAxisOptions component', () => { }, }); - expect(setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE); + expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE); }); it('should set histogram visType when multiple seriesParam', () => { @@ -162,7 +160,7 @@ describe('MetricsAxisOptions component', () => { }, }); - expect(setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM); + expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM); }); }); @@ -176,7 +174,6 @@ describe('MetricsAxisOptions component', () => { }; comp.setProps({ aggs: createAggs([newAgg]), - aggsLabels: `${newAgg.makeLabel()}`, }); const updatedValues = [{ ...axis, title: { text: newAgg.makeLabel() } }]; expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES, updatedValues); @@ -191,11 +188,14 @@ describe('MetricsAxisOptions component', () => { }; comp.setProps({ aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), }); + const updatedSeriesParams = [{ ...chart, data: { ...chart.data, label: agg.makeLabel() } }]; const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues); + + expect(setValue).toHaveBeenCalledTimes(3); + expect(setValue).toHaveBeenNthCalledWith(2, SERIES_PARAMS, updatedSeriesParams); + expect(setValue).toHaveBeenNthCalledWith(3, VALUE_AXES, updatedValues); }); it('should not set the custom title to match the value axis label when more than one agg exists for that axis', () => { @@ -203,7 +203,6 @@ describe('MetricsAxisOptions component', () => { const agg = { id: aggCount.id, makeLabel: () => 'Custom label' }; comp.setProps({ aggs: createAggs([agg, aggAverage]), - aggsLabels: `${agg.makeLabel()}, ${aggAverage.makeLabel()}`, stateParams: { ...defaultProps.stateParams, seriesParams: [chart, chart], @@ -223,48 +222,10 @@ describe('MetricsAxisOptions component', () => { }; comp.setProps({ aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), }); expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES); }); - - it('should overwrite the custom title when the agg type changes', () => { - defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; - const comp = mount(); - const agg = { - id: aggCount.id, - type: { name: 'max' }, - makeLabel: () => 'Max', - }; - comp.setProps({ - aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), - }); - - const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues); - }); - - it('should overwrite the custom title when the agg field changes', () => { - defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; - const agg = { - id: aggCount.id, - type: { name: 'max' }, - makeLabel: () => 'Max', - } as AggConfig; - defaultProps.aggs = createAggs([agg]) as any; - const comp = mount(); - agg.params = { field: { name: 'Field' } }; - agg.makeLabel = () => 'Max, Field'; - comp.setProps({ - aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), - }); - - const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues); - }); }); it('should add value axis', () => { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx index 2ca4ed1e2343d..c4dcbfaa47265 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx @@ -21,7 +21,7 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { cloneDeep, uniq, get } from 'lodash'; import { EuiSpacer } from '@elastic/eui'; -import { AggConfig } from 'ui/vis'; +import { AggConfig } from '../../../legacy_imports'; import { BasicVislibParams, ValueAxis, SeriesParam, Axis } from '../../../types'; import { ValidationVisOptionsProps } from '../../common'; import { SeriesPanel } from './series_panel'; @@ -52,7 +52,7 @@ export type ChangeValueAxis = ( const VALUE_AXIS_PREFIX = 'ValueAxis-'; function MetricsAxisOptions(props: ValidationVisOptionsProps) { - const { stateParams, setValue, aggs, aggsLabels, setVisType, vis } = props; + const { stateParams, setValue, aggs, vis, isTabSelected } = props; const [isCategoryAxisHorizontal, setIsCategoryAxisHorizontal] = useState(true); @@ -89,9 +89,11 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) } ); - const updateAxisTitle = () => { + const updateAxisTitle = (seriesParams?: SeriesParam[]) => { + const series = seriesParams || stateParams.seriesParams; const axes = cloneDeep(stateParams.valueAxes); let isAxesChanged = false; + let lastValuesChanged = false; const lastLabels = { ...lastCustomLabels }; const lastMatchingSeriesAgg = { ...lastSeriesAgg }; @@ -99,8 +101,8 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) let newCustomLabel = ''; const matchingSeries: AggConfig[] = []; - stateParams.seriesParams.forEach((series, seriesIndex) => { - if ((axisNumber === 0 && !series.valueAxis) || series.valueAxis === axis.id) { + series.forEach((serie, seriesIndex) => { + if ((axisNumber === 0 && !serie.valueAxis) || serie.valueAxis === axis.id) { const aggByIndex = aggs.bySchemaName('metric')[seriesIndex]; matchingSeries.push(aggByIndex); } @@ -125,6 +127,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) field: matchingSeriesAggField, }; lastLabels[axis.id] = newCustomLabel; + lastValuesChanged = true; if ( Object.keys(lastCustomLabels).length !== 0 && @@ -147,8 +150,10 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) setValue('valueAxes', axes); } - setLastSeriesAgg(lastMatchingSeriesAgg); - setLastCustomLabels(lastLabels); + if (lastValuesChanged) { + setLastSeriesAgg(lastMatchingSeriesAgg); + setLastCustomLabels(lastLabels); + } }; const onValueAxisPositionChanged = useCallback( @@ -242,7 +247,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) const metrics = useMemo(() => { const schemaName = vis.type.schemas.metrics[0].name; return aggs.bySchemaName(schemaName); - }, [vis.type.schemas.metrics[0].name, aggs, aggsLabels]); + }, [vis.type.schemas.metrics[0].name, aggs]); const firstValueAxesId = stateParams.valueAxes[0].id; @@ -272,7 +277,8 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) }); setValue('seriesParams', updatedSeries); - }, [aggsLabels, metrics, firstValueAxesId]); + updateAxisTitle(updatedSeries); + }, [metrics, firstValueAxesId]); const visType = useMemo(() => { const types = uniq(stateParams.seriesParams.map(({ type }) => type)); @@ -280,14 +286,10 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) }, [stateParams.seriesParams]); useEffect(() => { - setVisType(visType); - }, [visType]); - - useEffect(() => { - updateAxisTitle(); - }, [aggsLabels]); + vis.setVisType(visType); + }, [vis, visType]); - return ( + return isTabSelected ? ( <> @@ -307,7 +309,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) setCategoryAxis={setCategoryAxis} /> - ); + ) : null; } export { MetricsAxisOptions }; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.test.tsx index abb3a2455f9f9..91d9987c77f3b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.test.tsx @@ -23,6 +23,8 @@ import { LabelOptions, LabelOptionsProps } from './label_options'; import { TruncateLabelsOption } from '../../common'; import { valueAxis, categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); + const FILTER = 'filter'; const ROTATE = 'rotate'; const DISABLED = 'disabled'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx index 427d43e18cca6..6a94eabe25243 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback, useMemo } from 'react'; + import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams, Axis } from '../../../types'; import { SelectOption, SwitchOption, TruncateLabelsOption } from '../../common'; import { getRotateOptions } from '../../../utils/collections'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx index 0e603814493fa..98ef8a094a260 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx @@ -24,6 +24,8 @@ import { NumberInputOption } from '../../common'; import { getInterpolationModes } from '../../../utils/collections'; import { seriesParam } from './mocks'; +jest.mock('ui/new_platform'); + const LINE_WIDTH = 'lineWidth'; const DRAW_LINES = 'drawLinesBetweenPoints'; const interpolationModes = getInterpolationModes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx index 9514b69a20b04..0848b708b9094 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx @@ -18,10 +18,11 @@ */ import React, { useCallback } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { Vis } from 'ui/vis'; +import { Vis } from '../../../legacy_imports'; import { SeriesParam } from '../../../types'; import { NumberInputOption, SelectOption, SwitchOption } from '../../common'; import { SetChart } from './chart_options'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/series_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/series_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx index 5a455f4adde31..4d87cc61797fc 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/series_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx @@ -18,11 +18,12 @@ */ import React from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer, EuiAccordion } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams } from '../../../types'; import { ChartOptions } from './chart_options'; import { SetParamByIndex, ChangeValueAxis } from './'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/utils.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/utils.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts index 7144b0ad4902e..7c4f3b3ec8843 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/utils.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts @@ -44,7 +44,7 @@ const makeSerie = ( }; const isAxisHorizontal = (position: Positions) => - [Positions.TOP, Positions.BOTTOM].includes(position); + [Positions.TOP, Positions.BOTTOM].includes(position as any); const RADIX = 10; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx index 080c64db7ff85..7524c7a13435b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx @@ -25,6 +25,8 @@ import { Positions, getScaleTypes, getAxisModes, getPositions } from '../../../u import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { valueAxis, seriesParam } from './mocks'; +jest.mock('ui/new_platform'); + const positions = getPositions(); const axisModes = getAxisModes(); const scaleTypes = getScaleTypes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx index 8cb476508c78b..bd512e9365783 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx @@ -32,6 +32,8 @@ import { } from '../../../utils/collections'; import { valueAxis, categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); + const POSITION = 'position'; const positions = getPositions(); const axisModes = getAxisModes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx index 243950b762390..b4ea4cb42ee60 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx @@ -100,7 +100,7 @@ function ValueAxisOptions(props: ValueAxisOptionsParams) { if (isCategoryAxisHorizontal) { return isAxisHorizontal(position); } - return [Positions.LEFT, Positions.RIGHT].includes(position); + return [Positions.LEFT, Positions.RIGHT].includes(position as any); }, [isCategoryAxisHorizontal] ); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx index 2df17b6e34985..17c47b35b20dc 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx @@ -23,6 +23,8 @@ import { YExtents, YExtentsProps } from './y_extents'; import { ScaleTypes } from '../../../utils/collections'; import { NumberInputOption } from '../../common'; +jest.mock('ui/new_platform'); + describe('YExtents component', () => { let setMultipleValidity: jest.Mock; let setScale: jest.Mock; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/pie.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/pie.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx index 53dde185ec09f..056eb70cb256b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/pie.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx @@ -17,11 +17,12 @@ * under the License. */ import React from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../legacy_imports'; import { BasicOptions, TruncateLabelsOption, SwitchOption } from '../common'; import { PieVisParams } from '../../pie'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/grid_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/grid_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx index 63b2449b823d5..bdb4d3f39c12b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/grid_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx @@ -17,11 +17,12 @@ * under the License. */ import React, { useMemo, useEffect, useCallback } from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { SelectOption, SwitchOption } from '../../common'; import { BasicVislibParams, ValueAxis } from '../../../types'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts similarity index 77% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js rename to src/legacy/core_plugins/vis_type_vislib/public/gauge.ts index a472d20e07224..5dcc8ad16918d 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts @@ -18,14 +18,43 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; + +import { Schemas, AggGroupNames, ColorSchemas, RangeValues } from './legacy_imports'; import { GaugeOptions } from './components/options'; import { getGaugeCollections, Alignments, ColorModes, GaugeTypes } from './utils/collections'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { ColorSchemaVislibParams, Labels, Style } from './types'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +export interface Gauge extends ColorSchemaVislibParams { + backStyle: 'Full'; + gaugeStyle: 'Full'; + orientation: 'vertical'; + type: 'meter'; + alignment: Alignments; + colorsRange: RangeValues[]; + extendRange: boolean; + gaugeType: GaugeTypes; + labels: Labels; + percentageMode: boolean; + outline?: boolean; + scale: { + show: boolean; + labels: false; + color: 'rgba(105,112,125,0.2)'; + }; + style: Style; +} + +export interface GaugeVisParams { + type: 'gauge'; + addTooltip: boolean; + addLegend: boolean; + isDisplayWarning: boolean; + gauge: Gauge; +} -export const gaugeDefinition = { +export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'gauge', title: i18n.translate('kbnVislibVisTypes.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), icon: 'visGauge', @@ -79,7 +108,7 @@ export const gaugeDefinition = { }, }, }, - visualization: vislibVisController, + visualization: createVislibVisController(deps), editorConfig: { collections: getGaugeCollections(), optionsTemplate: GaugeOptions, @@ -115,4 +144,4 @@ export const gaugeDefinition = { ]), }, useCustomNoDataScreen: true, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts similarity index 90% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js rename to src/legacy/core_plugins/vis_type_vislib/public/goal.ts index 71fcd6593e72c..302d5f6393ef9 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts @@ -18,21 +18,21 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; + +import { Schemas, AggGroupNames, ColorSchemas } from './legacy_imports'; import { GaugeOptions } from './components/options'; import { getGaugeCollections, GaugeTypes, ColorModes } from './utils/collections'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const goalDefinition = { +export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'goal', title: i18n.translate('kbnVislibVisTypes.goal.goalTitle', { defaultMessage: 'Goal' }), icon: 'visGoal', description: i18n.translate('kbnVislibVisTypes.goal.goalDescription', { defaultMessage: 'A goal chart indicates how close you are to your final goal.', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { addTooltip: true, @@ -108,4 +108,4 @@ export const goalDefinition = { ]), }, useCustomNoDataScreen: true, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts similarity index 81% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js rename to src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts index 292e108053598..eb5f84b409838 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts @@ -18,21 +18,35 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; + +import { Schemas, AggGroupNames, ColorSchemas, RangeValues } from './legacy_imports'; import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections'; import { HeatmapOptions } from './components/options'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { TimeMarker } from './vislib/visualizations/time_marker'; +import { CommonVislibParams, ColorSchemaVislibParams, ValueAxis } from './types'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibParams { + type: 'heatmap'; + addLegend: boolean; + enableHover: boolean; + colorsNumber: number | ''; + colorsRange: RangeValues[]; + valueAxes: ValueAxis[]; + setColorRange: boolean; + percentageMode: boolean; + times: TimeMarker[]; +} -export const heatmapDefinition = { +export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'heatmap', title: i18n.translate('kbnVislibVisTypes.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), icon: 'visHeatmap', description: i18n.translate('kbnVislibVisTypes.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'heatmap', @@ -122,4 +136,4 @@ export const heatmapDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js rename to src/legacy/core_plugins/vis_type_vislib/public/histogram.ts index bc017b5a1a871..f92875a62cfd7 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts @@ -18,8 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; + import { Positions, ChartTypes, @@ -32,10 +37,10 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const histogramDefinition = { +export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'histogram', title: i18n.translate('kbnVislibVisTypes.histogram.histogramTitle', { defaultMessage: 'Vertical Bar', @@ -44,7 +49,7 @@ export const histogramDefinition = { description: i18n.translate('kbnVislibVisTypes.histogram.histogramDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'histogram', @@ -183,4 +188,4 @@ export const histogramDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js rename to src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts index ee3570314618a..ada0c6b44ff70 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts @@ -18,8 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; + import { Positions, ChartTypes, @@ -32,10 +37,10 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const horizontalBarDefinition = { +export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'horizontal_bar', title: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal Bar', @@ -44,7 +49,7 @@ export const horizontalBarDefinition = { description: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'histogram', @@ -182,4 +187,4 @@ export const horizontalBarDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/index.js b/src/legacy/core_plugins/vis_type_vislib/public/index.ts similarity index 73% rename from src/legacy/core_plugins/kbn_vislib_vis_types/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/index.ts index 6e1f17c941ece..3b4bcb6bc3a7e 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/index.ts @@ -17,14 +17,11 @@ * under the License. */ -export default function(kibana) { - return new kibana.Plugin({ - uiExports: { - visTypes: ['plugins/kbn_vislib_vis_types/kbn_vislib_vis_types'], - interpreter: [ - 'plugins/kbn_vislib_vis_types/pie_fn', - 'plugins/kbn_vislib_vis_types/vislib_fn', - ], - }, - }); +import { PluginInitializerContext } from '../../../../core/public'; +import { KbnVislibVisTypesPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); } + +export { ColorModes } from './utils/collections'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts new file mode 100644 index 0000000000000..3d4cf55adc5e0 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts @@ -0,0 +1,74 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; +import { PluginInitializerContext } from 'kibana/public'; + +/* eslint-disable prettier/prettier */ +import { + initializeHierarchicalTooltipFormatter, + getHierarchicalTooltipFormatter, + // @ts-ignore +} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; +import { + initializePointSeriesTooltipFormatter, + getPointSeriesTooltipFormatter, + // @ts-ignore +} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; +import { + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, + // @ts-ignore +} from 'ui/vis/response_handlers/vislib'; +// @ts-ignore +import { vislibColor } from 'ui/vis/components/color/color'; + +import { plugin } from '.'; +import { + KbnVislibVisTypesPluginSetupDependencies, + KbnVislibVisTypesPluginStartDependencies, +} from './plugin'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; + +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + visualizations: visualizationsSetup, + __LEGACY: { + initializeHierarchicalTooltipFormatter, + getHierarchicalTooltipFormatter, + initializePointSeriesTooltipFormatter, + getPointSeriesTooltipFormatter, + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, + vislibColor, + }, +}; + +const startPlugins: Readonly = { + expressions: npStart.plugins.expressions, + visualizations: visualizationsStart, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, startPlugins); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts new file mode 100644 index 0000000000000..2970942f221e8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -0,0 +1,35 @@ +/* + * 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 { AggGroupNames, VisOptionsProps } from 'ui/vis/editors/default'; +export { Schemas } from 'ui/vis/editors/default/schemas'; +export { RangeValues, RangesParamEditor } from 'ui/vis/editors/default/controls/ranges'; +export { ColorSchema, ColorSchemas, colorSchemas, getHeatmapColors } from 'ui/color_maps'; +export { AggConfig, Vis, VisParams } from 'ui/vis'; +export { AggType } from 'ui/agg_types'; +export { CUSTOM_LEGEND_VIS_TYPES, VisLegend } from 'ui/vis/vis_types/vislib_vis_legend'; +// @ts-ignore +export { Tooltip } from 'ui/vis/components/tooltip'; +// @ts-ignore +export { SimpleEmitter } from 'ui/utils/simple_emitter'; +// @ts-ignore +export { Binder } from 'ui/binder'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +// @ts-ignore +export { tabifyAggResponse } from 'ui/agg_response/tabify'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js b/src/legacy/core_plugins/vis_type_vislib/public/line.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js rename to src/legacy/core_plugins/vis_type_vislib/public/line.ts index d6d075f452fed..35a059fadddcb 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/line.ts @@ -18,8 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; import { Positions, ChartTypes, @@ -32,18 +36,18 @@ import { InterpolationModes, getConfigCollections, } from './utils/collections'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const lineDefinition = { +export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'line', title: i18n.translate('kbnVislibVisTypes.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', description: i18n.translate('kbnVislibVisTypes.line.lineDescription', { defaultMessage: 'Emphasize trends', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'line', @@ -175,4 +179,4 @@ export const lineDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts similarity index 81% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js rename to src/legacy/core_plugins/vis_type_vislib/public/pie.ts index 1cef3114f047d..32307b7a117a1 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts @@ -18,20 +18,34 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; + +import { Schemas, AggGroupNames } from './legacy_imports'; import { PieOptions } from './components/options'; import { getPositions, Positions } from './utils/collections'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { CommonVislibParams } from './types'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +export interface PieVisParams extends CommonVislibParams { + type: 'pie'; + addLegend: boolean; + isDonut: boolean; + labels: { + show: boolean; + values: boolean; + last_level: boolean; + truncate: number | null; + }; +} -export const pieDefinition = { +export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'pie', title: i18n.translate('kbnVislibVisTypes.pie.pieTitle', { defaultMessage: 'Pie' }), icon: 'visPie', description: i18n.translate('kbnVislibVisTypes.pie.pieDescription', { defaultMessage: 'Compare parts of a whole', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'pie', @@ -89,4 +103,4 @@ export const pieDefinition = { }, hierarchicalData: true, responseHandler: 'vislib_slices', -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts index 4d932e6e9518e..786de0cc79b86 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts @@ -19,10 +19,14 @@ // eslint-disable-next-line import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; -import { kibanaPie } from './pie_fn'; +import { createPieVisFn } from './pie_fn'; +import { KbnVislibVisTypesDependencies } from './plugin'; jest.mock('ui/new_platform'); +const deps: KbnVislibVisTypesDependencies = { + vislibSlicesResponseHandlerProvider: () => ({ handler: mockResponseHandler }), +} as any; const mockResponseHandler = jest.fn().mockReturnValue( Promise.resolve({ hits: 1, @@ -39,20 +43,15 @@ const mockResponseHandler = jest.fn().mockReturnValue( }, }) ); -jest.mock('ui/vis/response_handlers/vislib', () => ({ - vislibSlicesResponseHandlerProvider: () => ({ handler: mockResponseHandler }), -})); describe('interpreter/functions#pie', () => { - const fn = functionWrapper(kibanaPie); + const fn = functionWrapper(createPieVisFn(deps)); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], }; const visConfig = { - addTooltip: true, - addLegend: false, type: 'pie', addTooltip: true, addLegend: true, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.js b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts similarity index 68% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.js rename to src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts index f66fd53e59efe..4b536caedb121 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts @@ -17,11 +17,37 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; -import { vislibSlicesResponseHandlerProvider as vislibSlicesResponseHandler } from 'ui/vis/response_handlers/vislib'; import { i18n } from '@kbn/i18n'; -export const kibanaPie = () => ({ +import { + ExpressionFunction, + KibanaDatatable, + Render, +} from '../../../../plugins/expressions/public'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +const name = 'kibana_pie'; + +type Context = KibanaDatatable; + +interface Arguments { + visConfig: string; +} + +type VisParams = Required; + +interface RenderValue { + visConfig: VisParams; +} + +type Return = Promise>; + +export const createPieVisFn = (deps: KbnVislibVisTypesDependencies) => (): ExpressionFunction< + typeof name, + Context, + Arguments, + Return +> => ({ name: 'kibana_pie', type: 'render', context: { @@ -32,14 +58,15 @@ export const kibanaPie = () => ({ }), args: { visConfig: { - types: ['string', 'null'], + types: ['string'], default: '"{}"', + help: '', }, }, async fn(context, args) { const visConfig = JSON.parse(args.visConfig); - const responseHandler = vislibSlicesResponseHandler().handler; + const responseHandler = deps.vislibSlicesResponseHandlerProvider().handler; const convertedData = await responseHandler(context, visConfig.dimensions); return { @@ -56,5 +83,3 @@ export const kibanaPie = () => ({ }; }, }); - -functionsRegistry.register(kibanaPie); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts new file mode 100644 index 0000000000000..a2e8512b2201b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -0,0 +1,106 @@ +/* + * 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 { + CoreSetup, + CoreStart, + Plugin, + IUiSettingsClient, + PluginInitializerContext, +} from 'kibana/public'; + +import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; +import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { createKbnVislibVisTypesFn } from './vis_type_vislib_vis_fn'; +import { createPieVisFn } from './pie_fn'; +import { + createHistogramVisTypeDefinition, + createLineVisTypeDefinition, + createPieVisTypeDefinition, + createAreaVisTypeDefinition, + createHeatmapVisTypeDefinition, + createHorizontalBarVisTypeDefinition, + createGaugeVisTypeDefinition, + createGoalVisTypeDefinition, +} from './vis_type_vislib_vis_types'; + +type ResponseHandlerProvider = () => { + name: string; + handler: (response: any, dimensions: any) => Promise; +}; +type KbnVislibVisTypesCoreSetup = CoreSetup; + +export interface LegacyDependencies { + initializeHierarchicalTooltipFormatter: () => Promise; + getHierarchicalTooltipFormatter: () => Promise; + initializePointSeriesTooltipFormatter: () => void; + getPointSeriesTooltipFormatter: () => void; + vislibSeriesResponseHandlerProvider: ResponseHandlerProvider; + vislibSlicesResponseHandlerProvider: ResponseHandlerProvider; + vislibColor: (colors: Array, mappings: any) => (value: any) => any; +} + +export type KbnVislibVisTypesDependencies = LegacyDependencies & { + uiSettings: IUiSettingsClient; +}; + +/** @internal */ +export interface KbnVislibVisTypesPluginSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + __LEGACY: LegacyDependencies; +} + +/** @internal */ +export interface KbnVislibVisTypesPluginStartDependencies { + expressions: ReturnType; + visualizations: VisualizationsStart; +} + +/** @internal */ +export class KbnVislibVisTypesPlugin implements Plugin, void> { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: KbnVislibVisTypesCoreSetup, + { expressions, visualizations, __LEGACY }: KbnVislibVisTypesPluginSetupDependencies + ) { + const visualizationDependencies: Readonly = { + ...__LEGACY, + uiSettings: core.uiSettings, + }; + + expressions.registerFunction(createKbnVislibVisTypesFn(visualizationDependencies)); + expressions.registerFunction(createPieVisFn(visualizationDependencies)); + + [ + createHistogramVisTypeDefinition, + createLineVisTypeDefinition, + createPieVisTypeDefinition, + createAreaVisTypeDefinition, + createHeatmapVisTypeDefinition, + createHorizontalBarVisTypeDefinition, + createGaugeVisTypeDefinition, + createGoalVisTypeDefinition, + ].forEach(vis => visualizations.types.createBaseVisualization(vis(visualizationDependencies))); + } + + public start(core: CoreStart, deps: KbnVislibVisTypesPluginStartDependencies) { + // nothing to do here + } +} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts b/src/legacy/core_plugins/vis_type_vislib/public/types.ts similarity index 95% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts rename to src/legacy/core_plugins/vis_type_vislib/public/types.ts index b023eb54f7295..b6928be89f648 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/types.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; -import { TimeMarker } from 'ui/vislib/visualizations/time_marker'; +import { ColorSchemas } from './legacy_imports'; +import { TimeMarker } from './vislib/visualizations/time_marker'; import { Positions, ChartModes, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts similarity index 73% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts rename to src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts index 6fe30483a32e8..810ddeea73834 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts @@ -18,14 +18,16 @@ */ import { i18n } from '@kbn/i18n'; -import { colorSchemas } from 'ui/vislib/components/color/colormaps'; +import { $Values } from '@kbn/utility-types'; +import { colorSchemas } from '../legacy_imports'; -export enum Positions { - RIGHT = 'right', - LEFT = 'left', - TOP = 'top', - BOTTOM = 'bottom', -} +export const Positions = Object.freeze({ + RIGHT: 'right' as 'right', + LEFT: 'left' as 'left', + TOP: 'top' as 'top', + BOTTOM: 'bottom' as 'bottom', +}); +export type Positions = $Values; const getPositions = () => [ { @@ -54,11 +56,12 @@ const getPositions = () => [ }, ]; -export enum ChartTypes { - LINE = 'line', - AREA = 'area', - HISTOGRAM = 'histogram', -} +export const ChartTypes = Object.freeze({ + LINE: 'line' as 'line', + AREA: 'area' as 'area', + HISTOGRAM: 'histogram' as 'histogram', +}); +export type ChartTypes = $Values; const getChartTypes = () => [ { @@ -81,10 +84,11 @@ const getChartTypes = () => [ }, ]; -export enum ChartModes { - NORMAL = 'normal', - STACKED = 'stacked', -} +export const ChartModes = Object.freeze({ + NORMAL: 'normal' as 'normal', + STACKED: 'stacked' as 'stacked', +}); +export type ChartModes = $Values; const getChartModes = () => [ { @@ -101,11 +105,12 @@ const getChartModes = () => [ }, ]; -export enum InterpolationModes { - LINEAR = 'linear', - CARDINAL = 'cardinal', - STEP_AFTER = 'step-after', -} +export const InterpolationModes = Object.freeze({ + LINEAR: 'linear' as 'linear', + CARDINAL: 'cardinal' as 'cardinal', + STEP_AFTER: 'step-after' as 'step-after', +}); +export type InterpolationModes = $Values; const getInterpolationModes = () => [ { @@ -128,16 +133,18 @@ const getInterpolationModes = () => [ }, ]; -export enum AxisTypes { - CATEGORY = 'category', - VALUE = 'value', -} +export const AxisTypes = Object.freeze({ + CATEGORY: 'category' as 'category', + VALUE: 'value' as 'value', +}); +export type AxisTypes = $Values; -export enum ScaleTypes { - LINEAR = 'linear', - LOG = 'log', - SQUARE_ROOT = 'square root', -} +export const ScaleTypes = Object.freeze({ + LINEAR: 'linear' as 'linear', + LOG: 'log' as 'log', + SQUARE_ROOT: 'square root' as 'square root', +}); +export type ScaleTypes = $Values; const getScaleTypes = () => [ { @@ -160,12 +167,13 @@ const getScaleTypes = () => [ }, ]; -export enum AxisModes { - NORMAL = 'normal', - PERCENTAGE = 'percentage', - WIGGLE = 'wiggle', - SILHOUETTE = 'silhouette', -} +export const AxisModes = Object.freeze({ + NORMAL: 'normal' as 'normal', + PERCENTAGE: 'percentage' as 'percentage', + WIGGLE: 'wiggle' as 'wiggle', + SILHOUETTE: 'silhouette' as 'silhouette', +}); +export type AxisModes = $Values; const getAxisModes = () => [ { @@ -194,17 +202,19 @@ const getAxisModes = () => [ }, ]; -export enum Rotates { - HORIZONTAL = 0, - VERTICAL = 90, - ANGLED = 75, -} +export const Rotates = Object.freeze({ + HORIZONTAL: 0, + VERTICAL: 90, + ANGLED: 75, +}); +export type Rotates = $Values; -export enum ThresholdLineStyles { - FULL = 'full', - DASHED = 'dashed', - DOT_DASHED = 'dot-dashed', -} +export const ThresholdLineStyles = Object.freeze({ + FULL: 'full' as 'full', + DASHED: 'dashed' as 'dashed', + DOT_DASHED: 'dot-dashed' as 'dot-dashed', +}); +export type ThresholdLineStyles = $Values; const getThresholdLineStyles = () => [ { @@ -248,16 +258,18 @@ const getRotateOptions = () => [ }, ]; -export enum GaugeTypes { - ARC = 'Arc', - CIRCLE = 'Circle', -} +export const GaugeTypes = Object.freeze({ + ARC: 'Arc' as 'Arc', + CIRCLE: 'Circle' as 'Circle', +}); +export type GaugeTypes = $Values; -export enum ColorModes { - BACKGROUND = 'Background', - LABELS = 'Labels', - NONE = 'None', -} +export const ColorModes = Object.freeze({ + BACKGROUND: 'Background' as 'Background', + LABELS: 'Labels' as 'Labels', + NONE: 'None' as 'None', +}); +export type ColorModes = $Values; const getGaugeTypes = () => [ { @@ -274,11 +286,12 @@ const getGaugeTypes = () => [ }, ]; -export enum Alignments { - AUTOMATIC = 'automatic', - HORIZONTAL = 'horizontal', - VERTICAL = 'vertical', -} +export const Alignments = Object.freeze({ + AUTOMATIC: 'automatic' as 'automatic', + HORIZONTAL: 'horizontal' as 'horizontal', + VERTICAL: 'vertical' as 'vertical', +}); +export type Alignments = $Values; const getAlignments = () => [ { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/common_config.tsx b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/common_config.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx index 7e4140606762f..adb93ca8011b2 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/common_config.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx @@ -18,8 +18,9 @@ */ import React from 'react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; import { i18n } from '@kbn/i18n'; + +import { VisOptionsProps } from '../legacy_imports'; import { PointSeriesOptions, MetricsAxisOptions } from '../components/options'; import { ValidationWrapper } from '../components/common'; import { BasicVislibParams } from '../types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx new file mode 100644 index 0000000000000..cff9a0a2e8551 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx @@ -0,0 +1,147 @@ +/* + * 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 'jquery'; +import React, { RefObject } from 'react'; + +import { CUSTOM_LEGEND_VIS_TYPES, VisLegend, Vis, VisParams } from './legacy_imports'; +// @ts-ignore +import { Vis as Vislib } from './vislib/vis'; +import { Positions } from './utils/collections'; +import { KbnVislibVisTypesDependencies } from './plugin'; +import { mountReactNode } from '../../../../core/public/utils'; + +const legendClassName = { + top: 'visLib--legend-top', + bottom: 'visLib--legend-bottom', + left: 'visLib--legend-left', + right: 'visLib--legend-right', +}; + +export const createVislibVisController = (deps: KbnVislibVisTypesDependencies) => { + return class VislibVisController { + unmount: (() => void) | null = null; + visParams?: VisParams; + legendRef: RefObject; + container: HTMLDivElement; + chartEl: HTMLDivElement; + legendEl: HTMLDivElement; + vislibVis: any; + + constructor(public el: Element, public vis: Vis) { + this.el = el; + this.vis = vis; + this.unmount = null; + this.legendRef = React.createRef(); + + // vis mount point + this.container = document.createElement('div'); + this.container.className = 'visLib'; + this.el.appendChild(this.container); + + // chart mount point + this.chartEl = document.createElement('div'); + this.chartEl.className = 'visLib__chart'; + this.container.appendChild(this.chartEl); + + // legend mount point + this.legendEl = document.createElement('div'); + this.legendEl.className = 'visLib__legend'; + this.container.appendChild(this.legendEl); + } + + render(esResponse: any, visParams: VisParams) { + if (this.vislibVis) { + this.destroy(); + } + + return new Promise(async resolve => { + if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { + return resolve(); + } + + await deps.initializeHierarchicalTooltipFormatter(); + await deps.initializePointSeriesTooltipFormatter(); + + this.vislibVis = new Vislib(this.chartEl, visParams, deps); + this.vislibVis.on('brush', this.vis.API.events.brush); + this.vislibVis.on('click', this.vis.API.events.filter); + this.vislibVis.on('renderComplete', resolve); + + this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); + + if (visParams.addLegend) { + $(this.container) + .attr('class', (i, cls) => { + return cls.replace(/visLib--legend-\S+/g, ''); + }) + .addClass((legendClassName as any)[visParams.legendPosition]); + + this.mountLegend(esResponse, visParams.legendPosition); + } + + this.vislibVis.render(esResponse, this.vis.getUiState()); + + // refreshing the legend after the chart is rendered. + // this is necessary because some visualizations + // provide data necessary for the legend only after a render cycle. + if ( + visParams.addLegend && + CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) + ) { + this.unmountLegend(); + this.mountLegend(esResponse, visParams.legendPosition); + this.vislibVis.render(esResponse, this.vis.getUiState()); + } + }); + } + + mountLegend(visData: any, position: Positions) { + this.unmount = mountReactNode( + + )(this.legendEl); + } + + unmountLegend() { + if (this.unmount) { + this.unmount(); + } + } + + destroy() { + if (this.unmount) { + this.unmount(); + } + + if (this.vislibVis) { + this.vislibVis.off('brush', this.vis.API.events.brush); + this.vislibVis.off('click', this.vis.API.events.filter); + this.vislibVis.destroy(); + delete this.vislibVis; + } + } + }; +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/vislib_fn.js b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts similarity index 68% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/vislib_fn.js rename to src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index 85ea5994548d6..0a685cd70e089 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/vislib_fn.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -17,11 +17,36 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; import { i18n } from '@kbn/i18n'; -import { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; -export const vislib = () => ({ +import { + ExpressionFunction, + KibanaDatatable, + Render, +} from '../../../../plugins/expressions/public'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +const name = 'vislib'; + +type Context = KibanaDatatable; + +interface Arguments { + type: string; + visConfig: string; +} + +type VisParams = Required; + +interface RenderValue { + visType: string; + visConfig: VisParams; +} + +type Return = Promise>; + +export const createKbnVislibVisTypesFn = ( + deps: KbnVislibVisTypesDependencies +) => (): ExpressionFunction => ({ name: 'vislib', type: 'render', context: { @@ -34,14 +59,16 @@ export const vislib = () => ({ type: { types: ['string'], default: '""', + help: 'vislib vis type', }, visConfig: { - types: ['string', 'null'], + types: ['string'], default: '"{}"', + help: '', }, }, async fn(context, args) { - const responseHandler = vislibSeriesResponseHandlerProvider().handler; + const responseHandler = deps.vislibSeriesResponseHandlerProvider().handler; const visConfigParams = JSON.parse(args.visConfig); const convertedData = await responseHandler(context, visConfigParams.dimensions); @@ -60,5 +87,3 @@ export const vislib = () => ({ }; }, }); - -functionsRegistry.register(vislib); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts new file mode 100644 index 0000000000000..f44d503895483 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts @@ -0,0 +1,27 @@ +/* + * 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 { createHistogramVisTypeDefinition } from './histogram'; +export { createLineVisTypeDefinition } from './line'; +export { createPieVisTypeDefinition } from './pie'; +export { createAreaVisTypeDefinition } from './area'; +export { createHeatmapVisTypeDefinition } from './heatmap'; +export { createHorizontalBarVisTypeDefinition } from './horizontal_bar'; +export { createGaugeVisTypeDefinition } from './gauge'; +export { createGoalVisTypeDefinition } from './goal'; diff --git a/src/legacy/ui/public/vislib/VISLIB.md b/src/legacy/core_plugins/vis_type_vislib/public/vislib/VISLIB.md similarity index 100% rename from src/legacy/ui/public/vislib/VISLIB.md rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/VISLIB.md diff --git a/src/legacy/ui/public/vislib/__tests__/components/heatmap_color.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js similarity index 97% rename from src/legacy/ui/public/vislib/__tests__/components/heatmap_color.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js index 1cafbadcff655..36c5b60abf5c6 100644 --- a/src/legacy/ui/public/vislib/__tests__/components/heatmap_color.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js @@ -19,7 +19,8 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { getHeatmapColors } from '../../components/color/heatmap_color'; + +import { getHeatmapColors } from '../../../legacy_imports'; describe('Vislib Heatmap Color Module Test Suite', function() { const emptyObject = {}; diff --git a/src/legacy/ui/public/vislib/__tests__/components/labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/components/labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js diff --git a/src/legacy/ui/public/vislib/__tests__/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap diff --git a/src/legacy/ui/public/vislib/__tests__/lib/axis/axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/axis/axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js index 8b32943b64ea8..3081c12415076 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/axis/axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js @@ -20,9 +20,11 @@ import d3 from 'd3'; import _ from 'lodash'; import ngMock from 'ng_mock'; +import 'ui/persisted_state'; + import expect from '@kbn/expect'; import $ from 'jquery'; -import '../../../../persisted_state'; + import { Axis } from '../../../lib/axis'; import { VisConfig } from '../../../lib/vis_config'; @@ -119,7 +121,8 @@ describe('Vislib Axis Class Test Suite', function() { }, data, persistedState, - $('.x-axis-div')[0] + $('.x-axis-div')[0], + () => undefined ); yAxis = new Axis(visConfig, { type: 'value', diff --git a/src/legacy/ui/public/vislib/__tests__/lib/axis_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js similarity index 97% rename from src/legacy/ui/public/vislib/__tests__/lib/axis_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js index d71184fdc1223..cbb294c3b44e4 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/axis_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js @@ -21,12 +21,14 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; import ngMock from 'ng_mock'; + import expect from '@kbn/expect'; +import 'ui/persisted_state'; + import { AxisTitle } from '../../lib/axis/axis_title'; import { AxisConfig } from '../../lib/axis/axis_config'; import { VisConfig } from '../../lib/vis_config'; import { Data } from '../../lib/data'; -import '../../../persisted_state'; describe('Vislib AxisTitle Class Test Suite', function() { let PersistedState; @@ -118,14 +120,15 @@ describe('Vislib AxisTitle Class Test Suite', function() { .style('height', '20px') .style('width', '20px'); - dataObj = new Data(data, new PersistedState()); + dataObj = new Data(data, new PersistedState(), () => undefined); visConfig = new VisConfig( { type: 'histogram', }, data, new PersistedState(), - el.node() + el.node(), + () => undefined ); const xAxisConfig = new AxisConfig(visConfig, { position: 'bottom', diff --git a/src/legacy/ui/public/vislib/__tests__/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/chart_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js index e64999e7bd329..b2086d0749a41 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js @@ -21,9 +21,9 @@ import d3 from 'd3'; import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; + import { ChartTitle } from '../../lib/chart_title'; import { VisConfig } from '../../lib/vis_config'; -import '../../../persisted_state'; describe('Vislib ChartTitle Class Test Suite', function() { let persistedState; @@ -112,7 +112,8 @@ describe('Vislib ChartTitle Class Test Suite', function() { }, data, persistedState, - el.node() + el.node(), + () => undefined ); chartTitle = new ChartTitle(visConfig); }) diff --git a/src/legacy/ui/public/vislib/__tests__/lib/data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/lib/data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js index c12259ee55a5a..5811b1d238163 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js @@ -20,9 +20,9 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import { Data } from '../../lib/data'; -import '../../../persisted_state'; const seriesData = { label: '', @@ -168,7 +168,7 @@ describe('Vislib Data Class Test Suite', function() { }); it('should return an object', function() { - const rowIn = new Data(rowsData, persistedState); + const rowIn = new Data(rowsData, persistedState, () => undefined); expect(_.isObject(rowIn)).to.be(true); }); }); @@ -182,7 +182,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(pieData, persistedState); + data = new Data(pieData, persistedState, () => undefined); }); it('should remove zero values', function() { @@ -196,7 +196,7 @@ describe('Vislib Data Class Test Suite', function() { let serOut; beforeEach(function() { - serIn = new Data(seriesData, persistedState); + serIn = new Data(seriesData, persistedState, () => undefined); serOut = serIn.flatten(); }); @@ -210,7 +210,7 @@ describe('Vislib Data Class Test Suite', function() { function testLength(inputData) { return function() { - const data = new Data(inputData, persistedState); + const data = new Data(inputData, persistedState, () => undefined); const len = _.reduce( data.chartData(), function(sum, chart) { @@ -266,7 +266,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(geohashGridData, persistedState); + data = new Data(geohashGridData, persistedState, () => undefined); }); describe('getVisData', function() { @@ -287,7 +287,7 @@ describe('Vislib Data Class Test Suite', function() { describe('null value check', function() { it('should return false', function() { - const data = new Data(rowsData, persistedState); + const data = new Data(rowsData, persistedState, () => undefined); expect(data.hasNullValues()).to.be(false); }); @@ -307,7 +307,7 @@ describe('Vislib Data Class Test Suite', function() { ], }); - const data = new Data(nullRowData, persistedState); + const data = new Data(nullRowData, persistedState, () => undefined); expect(data.hasNullValues()).to.be(true); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/dispatch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js similarity index 90% rename from src/legacy/ui/public/vislib/__tests__/lib/dispatch.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js index f2d2e7d05a34d..a93db5637c89d 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/dispatch.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js @@ -20,22 +20,23 @@ import _ from 'lodash'; import d3 from 'd3'; import ngMock from 'ng_mock'; + import expect from '@kbn/expect'; +import 'ui/persisted_state'; // Data -import data from 'fixtures/vislib/mock_data/date_histogram/_series'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; -import { SimpleEmitter } from '../../../utils/simple_emitter'; +import data from './fixtures/mock_data/date_histogram/_series'; +import getFixturesVislibVisFixtureProvider from './fixtures/_vis_fixture'; +import { SimpleEmitter } from '../../../legacy_imports'; describe('Vislib Dispatch Class Test Suite', function() { function destroyVis(vis) { vis.destroy(); } - function getEls(el, n, type) { + function getEls(element, n, type) { return d3 - .select(el) + .select(element) .data(new Array(n)) .enter() .append(type); @@ -48,7 +49,8 @@ describe('Vislib Dispatch Class Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); }) @@ -74,8 +76,9 @@ describe('Vislib Dispatch Class Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); persistedState = new ($injector.get('PersistedState'))(); + vis = getVis(); vis.on('brush', _.noop); vis.render(data, persistedState); }) @@ -91,7 +94,7 @@ describe('Vislib Dispatch Class Test Suite', function() { const apply = chart.events.addEvent('event', _.noop); expect(apply).to.be.a('function'); - const els = getEls(vis.el, 3, 'div'); + const els = getEls(vis.element, 3, 'div'); apply(els); els.each(function() { expect(d3.select(this).on('event')).to.be(_.noop); @@ -114,7 +117,7 @@ describe('Vislib Dispatch Class Test Suite', function() { const apply = chart.events[name](chart.series[0].chartEl); expect(apply).to.be.a('function'); - const els = getEls(vis.el, 3, 'div'); + const els = getEls(vis.element, 3, 'div'); apply(els); els.each(function() { expect(d3.select(this).on(event)).to.be.a('function'); @@ -188,7 +191,8 @@ describe('Vislib Dispatch Class Test Suite', function() { let persistedState; ngMock.module('kibana'); ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.on('someEvent', _.noop); vis.render(data, persistedState); @@ -207,7 +211,8 @@ describe('Vislib Dispatch Class Test Suite', function() { let persistedState; ngMock.module('kibana'); ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); vis.on('someEvent', _.noop); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_heatmap.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_heatmap.test.js similarity index 88% rename from src/legacy/ui/public/vislib/__tests__/lib/dispatch_heatmap.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_heatmap.test.js index 19e78ef4f30c2..e22f19ea643fd 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_heatmap.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_heatmap.test.js @@ -16,7 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + import mockDispatchDataD3 from './fixtures/dispatch_heatmap_d3.json'; +import { Dispatch } from '../../lib/dispatch'; +import mockdataPoint from './fixtures/dispatch_heatmap_data_point.json'; +import mockConfigPercentage from './fixtures/dispatch_heatmap_config.json'; + +jest.mock('ui/new_platform'); jest.mock('d3', () => ({ event: { target: { @@ -26,16 +32,14 @@ jest.mock('d3', () => ({ }, }, })); - -import { Dispatch } from '../../lib/dispatch'; -import mockdataPoint from './fixtures/dispatch_heatmap_data_point.json'; -import mockConfigPercentage from './fixtures/dispatch_heatmap_config.json'; - -jest.mock('ui/chrome', () => ({ - getUiSettingsClient: () => ({ - get: () => '', - }), - addBasePath: () => {}, +jest.mock('../../../legacy_imports.ts', () => ({ + ...jest.requireActual('../../../legacy_imports.ts'), + chrome: { + getUiSettingsClient: () => ({ + get: () => '', + }), + addBasePath: () => {}, + }, })); function getHandlerMock(config = {}, data = {}) { diff --git a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js similarity index 90% rename from src/legacy/ui/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js index e196c1bde0737..8fe9ac24db77b 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js @@ -16,7 +16,14 @@ * specific language governing permissions and limitations * under the License. */ + import mockDispatchDataD3 from './fixtures/dispatch_bar_chart_d3.json'; +import { Dispatch } from '../../lib/dispatch'; +import mockdataPoint from './fixtures/dispatch_bar_chart_data_point.json'; +import mockConfigPercentage from './fixtures/dispatch_bar_chart_config_percentage.json'; +import mockConfigNormal from './fixtures/dispatch_bar_chart_config_normal.json'; + +jest.mock('ui/new_platform'); jest.mock('d3', () => ({ event: { target: { @@ -26,17 +33,14 @@ jest.mock('d3', () => ({ }, }, })); - -import { Dispatch } from '../../lib/dispatch'; -import mockdataPoint from './fixtures/dispatch_bar_chart_data_point.json'; -import mockConfigPercentage from './fixtures/dispatch_bar_chart_config_percentage.json'; -import mockConfigNormal from './fixtures/dispatch_bar_chart_config_normal.json'; - -jest.mock('ui/chrome', () => ({ - getUiSettingsClient: () => ({ - get: () => '', - }), - addBasePath: () => {}, +jest.mock('../../../legacy_imports.ts', () => ({ + ...jest.requireActual('../../../legacy_imports.ts'), + chrome: { + getUiSettingsClient: () => ({ + get: () => '', + }), + addBasePath: () => {}, + }, })); function getHandlerMock(config = {}, data = {}) { diff --git a/src/legacy/ui/public/vislib/__tests__/lib/error_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/error_handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js diff --git a/src/fixtures/vislib/_vis_fixture.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js similarity index 61% rename from src/fixtures/vislib/_vis_fixture.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js index d71c76a1fd99e..c49ca732f0915 100644 --- a/src/fixtures/vislib/_vis_fixture.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js @@ -19,7 +19,23 @@ import _ from 'lodash'; import $ from 'jquery'; -import { VislibVisProvider } from 'ui/vislib/vis'; + +import { Vis } from '../../../vis'; + +// TODO: remove legacy imports when/of converting tests to jest +import { + setHierarchicalTooltipFormatter, + getHierarchicalTooltipFormatter, +} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; +import { + setPointSeriesTooltipFormatter, + getPointSeriesTooltipFormatter, +} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; +import { + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, +} from 'ui/vis/response_handlers/vislib'; +import { vislibColor } from 'ui/vis/components/color/color'; const $visCanvas = $('
') .attr('id', 'vislib-vis-fixtures') @@ -52,11 +68,25 @@ afterEach(function() { count = 0; }); -export default function VislibFixtures(Private) { - return function(visLibParams) { - const Vis = Private(VislibVisProvider); +const getDeps = () => { + const uiSettings = new Map(); + return { + uiSettings, + vislibColor, + getHierarchicalTooltipFormatter, + getPointSeriesTooltipFormatter, + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, + }; +}; + +export default function getVislibFixtures(Private) { + setHierarchicalTooltipFormatter(Private); + setPointSeriesTooltipFormatter(Private); + + return function(visLibParams, element) { return new Vis( - $visCanvas.new(), + element || $visCanvas.new(), _.defaults({}, visLibParams || {}, { addTooltip: true, addLegend: true, @@ -64,7 +94,8 @@ export default function VislibFixtures(Private) { setYExtents: false, yAxis: {}, type: 'histogram', - }) + }), + getDeps() ); }; } diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json diff --git a/src/fixtures/vislib/mock_data/date_histogram/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_columns.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_rows_series_with_holes.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows_series_with_holes.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_rows_series_with_holes.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows_series_with_holes.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series_monthly_interval.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_monthly_interval.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series_monthly_interval.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_monthly_interval.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series_neg.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_neg.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series_neg.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_neg.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series_pos_neg.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_pos_neg.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series_pos_neg.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_pos_neg.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_stacked_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_stacked_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_stacked_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_stacked_series.js diff --git a/src/fixtures/vislib/mock_data/filters/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/filters/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_columns.js diff --git a/src/fixtures/vislib/mock_data/filters/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/filters/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_rows.js diff --git a/src/fixtures/vislib/mock_data/filters/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/filters/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_series.js diff --git a/src/fixtures/vislib/mock_data/geohash/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/geohash/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_columns.js diff --git a/src/fixtures/vislib/mock_data/geohash/_geo_json.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_geo_json.js similarity index 100% rename from src/fixtures/vislib/mock_data/geohash/_geo_json.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_geo_json.js diff --git a/src/fixtures/vislib/mock_data/geohash/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/geohash/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_rows.js diff --git a/src/fixtures/vislib/mock_data/histogram/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_columns.js diff --git a/src/fixtures/vislib/mock_data/histogram/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_rows.js diff --git a/src/fixtures/vislib/mock_data/histogram/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_series.js diff --git a/src/fixtures/vislib/mock_data/histogram/_slices.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_slices.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_slices.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_slices.js diff --git a/src/fixtures/vislib/mock_data/not_enough_data/_one_point.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/not_enough_data/_one_point.js similarity index 100% rename from src/fixtures/vislib/mock_data/not_enough_data/_one_point.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/not_enough_data/_one_point.js diff --git a/src/fixtures/vislib/mock_data/range/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/range/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_columns.js diff --git a/src/fixtures/vislib/mock_data/range/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/range/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_rows.js diff --git a/src/fixtures/vislib/mock_data/range/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/range/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_series.js diff --git a/src/fixtures/vislib/mock_data/significant_terms/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/significant_terms/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_columns.js diff --git a/src/fixtures/vislib/mock_data/significant_terms/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/significant_terms/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_rows.js diff --git a/src/fixtures/vislib/mock_data/significant_terms/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/significant_terms/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_series.js diff --git a/src/fixtures/vislib/mock_data/stacked/_stacked.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/stacked/_stacked.js similarity index 100% rename from src/fixtures/vislib/mock_data/stacked/_stacked.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/stacked/_stacked.js diff --git a/src/fixtures/vislib/mock_data/terms/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_columns.js diff --git a/src/fixtures/vislib/mock_data/terms/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_rows.js diff --git a/src/fixtures/vislib/mock_data/terms/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_series.js diff --git a/src/fixtures/vislib/mock_data/terms/_seriesMultiple.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_seriesMultiple.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/handler/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js similarity index 87% rename from src/legacy/ui/public/vislib/__tests__/lib/handler/handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js index d37d67567e8a5..b309c97d24000 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/handler/handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js @@ -21,13 +21,14 @@ import ngMock from 'ng_mock'; import expect from '@kbn/expect'; // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import columns from 'fixtures/vislib/mock_data/date_histogram/_columns'; -import rows from 'fixtures/vislib/mock_data/date_histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import series from '../fixtures/mock_data/date_histogram/_series'; +import columns from '../fixtures/mock_data/date_histogram/_columns'; +import rows from '../fixtures/mock_data/date_histogram/_rows'; +import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../../persisted_state'; +import 'ui/persisted_state'; + +import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; const dateHistogramArray = [series, columns, rows, stackedSeries]; const names = ['series', 'columns', 'rows', 'stackedSeries']; @@ -40,7 +41,8 @@ dateHistogramArray.forEach(function(data, i) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); }) @@ -106,12 +108,12 @@ dateHistogramArray.forEach(function(data, i) { describe('removeAll Method', function() { beforeEach(function() { ngMock.inject(function() { - vis.handler.removeAll(vis.el); + vis.handler.removeAll(vis.element); }); }); it('should remove all DOM elements from the el', function() { - expect($(vis.el).children().length).to.be(0); + expect($(vis.element).children().length).to.be(0); }); }); @@ -121,7 +123,7 @@ dateHistogramArray.forEach(function(data, i) { }); it('should return an error classed DOM element with a text message', function() { - expect($(vis.el).find('.error').length).to.be(1); + expect($(vis.element).find('.error').length).to.be(1); expect($('.error h4').html()).to.be('This is an error!'); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js similarity index 65% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js index 853a00d035a07..c8636f34ce6f8 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/layout/layout.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js @@ -20,16 +20,16 @@ import d3 from 'd3'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import columns from 'fixtures/vislib/mock_data/date_histogram/_columns'; -import rows from 'fixtures/vislib/mock_data/date_histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import series from '../fixtures/mock_data/date_histogram/_series'; +import columns from '../fixtures/mock_data/date_histogram/_columns'; +import rows from '../fixtures/mock_data/date_histogram/_rows'; +import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; import { Layout } from '../../../lib/layout/layout'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../../persisted_state'; +import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; import { VisConfig } from '../../../lib/vis_config'; const dateHistogramArray = [series, columns, rows, stackedSeries]; @@ -46,7 +46,8 @@ dateHistogramArray.forEach(function(data, i) { beforeEach(function() { ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); numberOfCharts = vis.handler.charts.length; @@ -59,17 +60,17 @@ dateHistogramArray.forEach(function(data, i) { describe('createLayout Method', function() { it('should append all the divs', function() { - expect($(vis.el).find('.visWrapper').length).to.be(1); - expect($(vis.el).find('.visAxis--y').length).to.be(2); - expect($(vis.el).find('.visWrapper__column').length).to.be(1); - expect($(vis.el).find('.visAxis__column--y').length).to.be(2); - expect($(vis.el).find('.y-axis-title').length).to.be.above(0); - expect($(vis.el).find('.visAxis__splitAxes--y').length).to.be(2); - expect($(vis.el).find('.visAxis__spacer--y').length).to.be(4); - expect($(vis.el).find('.visWrapper__chart').length).to.be(numberOfCharts); - expect($(vis.el).find('.visAxis--x').length).to.be(2); - expect($(vis.el).find('.visAxis__splitAxes--x').length).to.be(2); - expect($(vis.el).find('.x-axis-title').length).to.be.above(0); + expect($(vis.element).find('.visWrapper').length).to.be(1); + expect($(vis.element).find('.visAxis--y').length).to.be(2); + expect($(vis.element).find('.visWrapper__column').length).to.be(1); + expect($(vis.element).find('.visAxis__column--y').length).to.be(2); + expect($(vis.element).find('.y-axis-title').length).to.be.above(0); + expect($(vis.element).find('.visAxis__splitAxes--y').length).to.be(2); + expect($(vis.element).find('.visAxis__spacer--y').length).to.be(4); + expect($(vis.element).find('.visWrapper__chart').length).to.be(numberOfCharts); + expect($(vis.element).find('.visAxis--x').length).to.be(2); + expect($(vis.element).find('.visAxis__splitAxes--x').length).to.be(2); + expect($(vis.element).find('.x-axis-title').length).to.be.above(0); }); }); @@ -81,35 +82,36 @@ dateHistogramArray.forEach(function(data, i) { }, data, persistedState, - vis.el + vis.element, + () => undefined ); testLayout = new Layout(visConfig); }); it('should append a div with the correct class name', function() { - expect($(vis.el).find('.chart').length).to.be(numberOfCharts); + expect($(vis.element).find('.chart').length).to.be(numberOfCharts); }); it('should bind data to the DOM element', function() { expect( - !!$(vis.el) + !!$(vis.element) .find('.chart') .data() ).to.be(true); }); it('should create children', function() { - expect(typeof $(vis.el).find('.x-axis-div')).to.be('object'); + expect(typeof $(vis.element).find('.x-axis-div')).to.be('object'); }); it('should call split function when provided', function() { - expect(typeof $(vis.el).find('.x-axis-div')).to.be('object'); + expect(typeof $(vis.element).find('.x-axis-div')).to.be('object'); }); it('should throw errors when incorrect arguments provided', function() { expect(function() { testLayout.layout({ - parent: vis.el, + parent: vis.element, type: undefined, class: 'chart', }); @@ -131,7 +133,7 @@ dateHistogramArray.forEach(function(data, i) { expect(function() { testLayout.layout({ - parent: vis.el, + parent: vis.element, type: function(d) { return d; }, @@ -143,26 +145,26 @@ dateHistogramArray.forEach(function(data, i) { describe('appendElem Method', function() { beforeEach(function() { - vis.handler.layout.appendElem(vis.el, 'svg', 'column'); + vis.handler.layout.appendElem(vis.element, 'svg', 'column'); vis.handler.layout.appendElem('.visChart', 'div', 'test'); }); it('should append DOM element to el with a class name', function() { - expect(typeof $(vis.el).find('.column')).to.be('object'); - expect(typeof $(vis.el).find('.test')).to.be('object'); + expect(typeof $(vis.element).find('.column')).to.be('object'); + expect(typeof $(vis.element).find('.test')).to.be('object'); }); }); describe('removeAll Method', function() { beforeEach(function() { - d3.select(vis.el) + d3.select(vis.element) .append('div') .attr('class', 'visualize'); - vis.handler.layout.removeAll(vis.el); + vis.handler.layout.removeAll(vis.element); }); it('should remove all DOM elements from the el', function() { - expect($(vis.el).children().length).to.be(0); + expect($(vis.element).children().length).to.be(0); }); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/layout_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/layout_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/types/column_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/types/column_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/types/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/vis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/vis_config.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js index c9dcd4737b51a..3f0253b4a4670 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/vis_config.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js @@ -20,8 +20,8 @@ import d3 from 'd3'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import { VisConfig } from '../../lib/vis_config'; -import '../../../persisted_state'; describe('Vislib VisConfig Class Test Suite', function() { let el; @@ -101,7 +101,8 @@ describe('Vislib VisConfig Class Test Suite', function() { }, data, new PersistedState(), - el + el, + () => undefined ); }) ); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/x_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js index 2fb7763756975..09fe067537c7f 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js @@ -21,8 +21,8 @@ import d3 from 'd3'; import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import $ from 'jquery'; -import '../../../persisted_state'; import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; @@ -124,7 +124,8 @@ describe('Vislib xAxis Class Test Suite', function() { }, data, persistedState, - $('.x-axis-div')[0] + $('.x-axis-div')[0], + () => undefined ); xAxis = new Axis(visConfig, { type: 'category', diff --git a/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js similarity index 99% rename from src/legacy/ui/public/vislib/__tests__/lib/y_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js index 0189b8b960d32..e857aca3bf3ed 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js @@ -21,8 +21,8 @@ import _ from 'lodash'; import d3 from 'd3'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import $ from 'jquery'; -import '../../../persisted_state'; import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; @@ -97,7 +97,8 @@ function createData(seriesData) { }, data, persistedState, - node + node, + () => undefined ); return new YAxis( visConfig, diff --git a/src/legacy/ui/public/vislib/__tests__/vis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/vis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js index f10f44b7100d8..a6d1c4daf5d2c 100644 --- a/src/legacy/ui/public/vislib/__tests__/vis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js @@ -18,16 +18,17 @@ */ import _ from 'lodash'; -import expect from '@kbn/expect'; +import $ from 'jquery'; import ngMock from 'ng_mock'; -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import columns from 'fixtures/vislib/mock_data/date_histogram/_columns'; -import rows from 'fixtures/vislib/mock_data/date_histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../persisted_state'; +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + +import series from './lib/fixtures/mock_data/date_histogram/_series'; +import columns from './lib/fixtures/mock_data/date_histogram/_columns'; +import rows from './lib/fixtures/mock_data/date_histogram/_rows'; +import stackedSeries from './lib/fixtures/mock_data/date_histogram/_stacked_series'; +import getFixturesVislibVisFixtureProvider from './lib/fixtures/_vis_fixture'; const dataArray = [series, columns, rows, stackedSeries]; @@ -45,9 +46,10 @@ dataArray.forEach(function(data, i) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); - secondVis = Private(FixturesVislibVisFixtureProvider)(); + secondVis = getVis(); }) ); @@ -99,7 +101,7 @@ dataArray.forEach(function(data, i) { }); it('should not remove visualizations that have not been destroyed', function() { - expect($(vis.el).find('.visWrapper').length).to.be(1); + expect($(vis.element).find('.visWrapper').length).to.be(1); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/area_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js similarity index 93% rename from src/legacy/ui/public/vislib/__tests__/visualizations/area_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js index d81fd66f1e111..7fe350bd85e05 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js @@ -18,20 +18,22 @@ */ import d3 from 'd3'; -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; - import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; + const dataTypesArray = { - 'series pos': require('fixtures/vislib/mock_data/date_histogram/_series'), - 'series pos neg': require('fixtures/vislib/mock_data/date_histogram/_series_pos_neg'), - 'series neg': require('fixtures/vislib/mock_data/date_histogram/_series_neg'), - 'term columns': require('fixtures/vislib/mock_data/terms/_columns'), - 'range rows': require('fixtures/vislib/mock_data/range/_rows'), - stackedSeries: require('fixtures/vislib/mock_data/date_histogram/_stacked_series'), + 'series pos': require('../lib/fixtures/mock_data/date_histogram/_series'), + 'series pos neg': require('../lib/fixtures/mock_data/date_histogram/_series_pos_neg'), + 'series neg': require('../lib/fixtures/mock_data/date_histogram/_series_neg'), + 'term columns': require('../lib/fixtures/mock_data/terms/_columns'), + 'range rows': require('../lib/fixtures/mock_data/range/_rows'), + stackedSeries: require('../lib/fixtures/mock_data/date_histogram/_stacked_series'), }; const visLibParams = { @@ -49,7 +51,8 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(dataType, persistedState); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js similarity index 93% rename from src/legacy/ui/public/vislib/__tests__/visualizations/chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js index f876db0aa6b22..088d3377af4dd 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js @@ -18,15 +18,16 @@ */ import d3 from 'd3'; -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { VislibVisProvider } from '../../vis'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + import { Chart } from '../../visualizations/_chart'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; describe('Vislib _chart Test Suite', function() { let persistedState; - let Vis; let vis; let el; let myChart; @@ -111,7 +112,7 @@ describe('Vislib _chart Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - Vis = Private(VislibVisProvider); + const getVis = getFixturesVislibVisFixtureProvider(Private); persistedState = new ($injector.get('PersistedState'))(); el = d3 @@ -126,7 +127,7 @@ describe('Vislib _chart Test Suite', function() { zeroFill: true, }; - vis = new Vis(el[0][0], config); + vis = getVis(config, el[0][0]); vis.render(data, persistedState); myChart = vis.handler.charts[0]; diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/column_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js similarity index 91% rename from src/legacy/ui/public/vislib/__tests__/visualizations/column_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js index 2253783e7e644..d02060ef29bdd 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js @@ -17,24 +17,25 @@ * under the License. */ -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; import d3 from 'd3'; +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg'; -import termsColumns from 'fixtures/vislib/mock_data/terms/_columns'; -import histogramRows from 'fixtures/vislib/mock_data/histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; -import { seriesMonthlyInterval } from 'fixtures/vislib/mock_data/date_histogram/_series_monthly_interval'; -import { rowsSeriesWithHoles } from 'fixtures/vislib/mock_data/date_histogram/_rows_series_with_holes'; -import rowsWithZeros from 'fixtures/vislib/mock_data/date_histogram/_rows'; +import series from '../lib/fixtures/mock_data/date_histogram/_series'; +import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; +import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; +import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; +import histogramRows from '../lib/fixtures/mock_data/histogram/_rows'; +import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; +import { seriesMonthlyInterval } from '../lib/fixtures/mock_data/date_histogram/_series_monthly_interval'; +import { rowsSeriesWithHoles } from '../lib/fixtures/mock_data/date_histogram/_rows_series_with_holes'; +import rowsWithZeros from '../lib/fixtures/mock_data/date_histogram/_rows'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -69,7 +70,8 @@ dataTypesArray.forEach(function(dataType) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(data, persistedState); @@ -257,7 +259,8 @@ describe('stackData method - data set with zeros in percentage mode', function() beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); }) @@ -307,7 +310,8 @@ describe('datumWidth - split chart data set with holes', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(rowsSeriesWithHoles, persistedState); @@ -344,7 +348,8 @@ describe('datumWidth - monthly interval', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(seriesMonthlyInterval, persistedState); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js similarity index 94% rename from src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js index 45d9d4a2e374b..074b34e1c03c4 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js @@ -17,13 +17,15 @@ * under the License. */ -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import $ from 'jquery'; import _ from 'lodash'; -import data from 'fixtures/vislib/mock_data/terms/_seriesMultiple'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + +import data from '../lib/fixtures/mock_data/terms/_seriesMultiple'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; describe('Vislib Gauge Chart Test Suite', function() { let PersistedState; @@ -89,7 +91,7 @@ describe('Vislib Gauge Chart Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vislibVis = Private(FixturesVislibVisFixtureProvider); + vislibVis = getFixturesVislibVisFixtureProvider(Private); PersistedState = $injector.get('PersistedState'); generateVis(); }) diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/heatmap_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/visualizations/heatmap_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js index f1dc4bf07ee31..bf1dbad0b44cf 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/heatmap_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js @@ -17,20 +17,21 @@ * under the License. */ -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; import d3 from 'd3'; +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg'; -import termsColumns from 'fixtures/vislib/mock_data/terms/_columns'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import series from '../lib/fixtures/mock_data/date_histogram/_series'; +import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; +import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; +import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; +import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -74,7 +75,7 @@ describe('Vislib Heatmap Chart Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vislibVis = Private(FixturesVislibVisFixtureProvider); + vislibVis = getFixturesVislibVisFixtureProvider(Private); PersistedState = $injector.get('PersistedState'); generateVis(); }) diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/line_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/visualizations/line_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js index 354be1f0ced0f..d010944a19e47 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js @@ -20,18 +20,19 @@ import d3 from 'd3'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import $ from 'jquery'; import _ from 'lodash'; +import 'ui/persisted_state'; + // Data -import seriesPos from 'fixtures/vislib/mock_data/date_histogram/_series'; -import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg'; -import histogramColumns from 'fixtures/vislib/mock_data/histogram/_columns'; -import rangeRows from 'fixtures/vislib/mock_data/range/_rows'; -import termSeries from 'fixtures/vislib/mock_data/terms/_series'; -import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; +import seriesPos from '../lib/fixtures/mock_data/date_histogram/_series'; +import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; +import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; +import histogramColumns from '../lib/fixtures/mock_data/histogram/_columns'; +import rangeRows from '../lib/fixtures/mock_data/range/_rows'; +import termSeries from '../lib/fixtures/mock_data/terms/_series'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; const dataTypes = [ ['series pos', seriesPos], @@ -54,6 +55,7 @@ describe('Vislib Line Chart', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { + const getVis = getFixturesVislibVisFixtureProvider(Private); const visLibParams = { type: 'line', addLegend: true, @@ -61,10 +63,10 @@ describe('Vislib Line Chart', function() { drawLinesBetweenPoints: true, }; - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); vis.render(data, persistedState); + vis.on('brush', _.noop); }) ); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js similarity index 94% rename from src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js index 9d299c4d3a5df..381dfcd387cc2 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js @@ -18,17 +18,20 @@ */ import d3 from 'd3'; -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; -import fixtures from 'fixtures/fake_hierarchical_data'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import { Vis } from '../../../vis'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +// TODO: Remove ui imports once converting to jest +import 'ui/persisted_state'; +import { vislibSlicesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; + +import fixtures from 'fixtures/fake_hierarchical_data'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { vislibSlicesResponseHandlerProvider } from '../../../vis/response_handlers/vislib'; -import { tabifyAggResponse } from '../../../agg_response/tabify'; + +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { Vis, tabifyAggResponse } from '../../../legacy_imports'; const rowAgg = [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, @@ -129,7 +132,8 @@ describe('No global chart settings', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - chart1 = Private(FixturesVislibVisFixtureProvider)(visLibParams1); + const getVis = getFixturesVislibVisFixtureProvider(Private); + chart1 = getVis(visLibParams1); persistedState = new ($injector.get('PersistedState'))(); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); responseHandler = vislibSlicesResponseHandlerProvider().handler; @@ -163,7 +167,7 @@ describe('No global chart settings', function() { }); it('should render chart titles for all charts', function() { - expect($(chart1.el).find('.visAxis__splitTitles--y').length).to.be(1); + expect($(chart1.element).find('.visAxis__splitTitles--y').length).to.be(1); }); describe('_validatePieData method', function() { @@ -221,7 +225,8 @@ describe('Vislib PieChart Class Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); responseHandler = vislibSlicesResponseHandlerProvider().handler; diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js similarity index 97% rename from src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js index cfe61d6f35198..ec22d43c08cb2 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js @@ -20,8 +20,8 @@ import d3 from 'd3'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import terms from 'fixtures/vislib/mock_data/terms/_columns'; +import series from '../lib/fixtures/mock_data/date_histogram/_series'; +import terms from '../lib/fixtures/mock_data/terms/_columns'; import $ from 'jquery'; import { TimeMarker } from '../../visualizations/time_marker'; diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/vis_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/visualizations/vis_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js diff --git a/src/legacy/ui/public/vislib/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss diff --git a/src/legacy/ui/public/vislib/_variables.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_variables.scss similarity index 100% rename from src/legacy/ui/public/vislib/_variables.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/_variables.scss diff --git a/src/legacy/ui/public/vislib/components/labels/data_array.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/data_array.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/data_array.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/data_array.js diff --git a/src/legacy/ui/public/vislib/components/labels/flatten_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/flatten_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js diff --git a/src/legacy/ui/public/vislib/components/labels/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/index.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/index.js diff --git a/src/legacy/ui/public/vislib/components/labels/labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/labels.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/labels.js diff --git a/src/legacy/ui/public/vislib/components/labels/truncate_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/truncate_labels.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/truncate_labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/truncate_labels.js diff --git a/src/legacy/ui/public/vislib/components/labels/uniq_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/uniq_labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/flatten_data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/flatten_data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/inject_zeros.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/inject_zeros.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/inject_zeros.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/inject_zeros.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/ordered_x_keys.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/ordered_x_keys.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/ordered_x_keys.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/ordered_x_keys.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/uniq_keys.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/uniq_keys.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/uniq_keys.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/uniq_keys.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/zero_fill_data_array.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_fill_data_array.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/zero_fill_data_array.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_fill_data_array.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/zero_filled_array.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_filled_array.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/zero_filled_array.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_filled_array.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/zero_injection.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_injection.test.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/zero_injection.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_injection.test.js diff --git a/src/legacy/ui/public/vislib/errors.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/errors.ts similarity index 95% rename from src/legacy/ui/public/vislib/errors.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/errors.ts index 0c6e720f0ef32..9014349c38d25 100644 --- a/src/legacy/ui/public/vislib/errors.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/errors.ts @@ -19,7 +19,7 @@ /* eslint-disable max-classes-per-file */ -import { KbnError } from '../../../../plugins/kibana_utils/public'; +import { KbnError } from '../../../../../plugins/kibana_utils/public'; export class VislibError extends KbnError { constructor(message: string) { diff --git a/src/legacy/ui/public/vislib/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/index.js similarity index 100% rename from src/legacy/ui/public/vislib/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/index.js diff --git a/src/legacy/ui/public/vislib/lib/_alerts.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_alerts.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/_alerts.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_alerts.scss diff --git a/src/legacy/ui/public/vislib/lib/_data_label.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_data_label.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/_data_label.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_data_label.js diff --git a/src/legacy/ui/public/vislib/lib/_error_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_error_handler.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/_error_handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_error_handler.js diff --git a/src/legacy/ui/public/vislib/lib/_handler.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_handler.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/_handler.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_handler.scss diff --git a/src/legacy/ui/public/vislib/lib/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_index.scss diff --git a/src/legacy/ui/public/vislib/lib/alerts.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/alerts.js similarity index 97% rename from src/legacy/ui/public/vislib/lib/alerts.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/alerts.js index cf79dabf1b07e..086b4e31be1a3 100644 --- a/src/legacy/ui/public/vislib/lib/alerts.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/alerts.js @@ -71,11 +71,11 @@ export class Alerts { const alerts = this.alerts; const vis = this.vis; - $(vis.el) + $(vis.element) .find('.visWrapper__alerts') .append($('
').addClass('visAlerts__tray')); if (!alerts.size()) return; - $(vis.el) + $(vis.element) .find('.visAlerts__tray') .append(alerts.value()); } @@ -89,13 +89,13 @@ export class Alerts { }; if (this.alertDefs.find(alertDef => alertDef.msg === alert.msg)) return; this.alertDefs.push(alert); - $(vis.el) + $(vis.element) .find('.visAlerts__tray') .append(this._addAlert(alert)); } destroy() { - $(this.vis.el) + $(this.vis.element) .find('.visWrapper__alerts') .remove(); } diff --git a/src/legacy/ui/public/vislib/lib/axis/axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_config.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_config.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_config.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_scale.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_scale.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_scale.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_scale.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_title.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_title.js diff --git a/src/legacy/ui/public/vislib/lib/axis/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/index.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/index.js diff --git a/src/legacy/ui/public/vislib/lib/axis/scale_modes.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/scale_modes.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/scale_modes.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/scale_modes.js diff --git a/src/legacy/ui/public/vislib/lib/axis/time_ticks.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/time_ticks.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.js diff --git a/src/legacy/ui/public/vislib/lib/axis/time_ticks.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.test.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/time_ticks.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.test.js diff --git a/src/legacy/ui/public/vislib/lib/chart_grid.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_grid.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/chart_grid.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_grid.js diff --git a/src/legacy/ui/public/vislib/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/chart_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js index f79d14a3deaa6..1c84f98614b05 100644 --- a/src/legacy/ui/public/vislib/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js @@ -19,8 +19,9 @@ import d3 from 'd3'; import _ from 'lodash'; + import { ErrorHandler } from './_error_handler'; -import { Tooltip } from '../../vis/components/tooltip'; +import { Tooltip } from '../../legacy_imports'; export class ChartTitle extends ErrorHandler { constructor(visConfig) { diff --git a/src/legacy/ui/public/vislib/lib/data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js index c10449c681a3c..c7824c43eeec5 100644 --- a/src/legacy/ui/public/vislib/lib/data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js @@ -19,11 +19,11 @@ import d3 from 'd3'; import _ from 'lodash'; + import { injectZeros } from '../components/zero_injection/inject_zeros'; import { orderXValues } from '../components/zero_injection/ordered_x_keys'; import { labels } from '../components/labels/labels'; -import { vislibColor } from '../../vis/components/color/color'; -import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; +import { getFormat } from '../../legacy_imports'; /** * Provides an API for pulling values off the data @@ -35,8 +35,9 @@ import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; * @param attr {Object|*} Visualization options */ export class Data { - constructor(data, uiState) { + constructor(data, uiState, vislibColor) { this.uiState = uiState; + this.vislibColor = vislibColor; this.data = this.copyDataObj(data); this.type = this.getDataType(); this._cleanVisData(); @@ -472,7 +473,7 @@ export class Data { const defaultColors = this.uiState.get('vis.defaultColors'); const overwriteColors = this.uiState.get('vis.colors'); const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors; - return vislibColor(this.getLabels(), colors); + return this.vislibColor(this.getLabels(), colors); } /** @@ -482,7 +483,7 @@ export class Data { * @returns {Function} Performs lookup on string and returns hex color */ getPieColorFunc() { - return vislibColor( + return this.vislibColor( this.pieNames(this.getVisData()).map(function(d) { return d.label; }), diff --git a/src/legacy/ui/public/vislib/lib/dispatch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/dispatch.js similarity index 92% rename from src/legacy/ui/public/vislib/lib/dispatch.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/dispatch.js index fb5cc127c27fd..404f7ef82d97f 100644 --- a/src/legacy/ui/public/vislib/lib/dispatch.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/dispatch.js @@ -20,10 +20,8 @@ import d3 from 'd3'; import { get } from 'lodash'; import $ from 'jquery'; -import { SimpleEmitter } from '../../utils/simple_emitter'; -import chrome from 'ui/chrome'; -const config = chrome.getUiSettingsClient(); +import { SimpleEmitter } from '../../legacy_imports'; /** * Handles event responses @@ -33,9 +31,10 @@ const config = chrome.getUiSettingsClient(); * @param handler {Object} Reference to Handler Class Object */ export class Dispatch extends SimpleEmitter { - constructor(handler) { + constructor(handler, uiSettings) { super(); this.handler = handler; + this.uiSettings = uiSettings; this._listeners = {}; } @@ -196,7 +195,7 @@ export class Dispatch extends SimpleEmitter { const addEvent = this.addEvent; const $el = this.handler.el; if (!this.handler.highlight) { - this.handler.highlight = self.highlight; + this.handler.highlight = self.getHighlighter(self.uiSettings); } function hover(d, i) { @@ -289,21 +288,23 @@ export class Dispatch extends SimpleEmitter { } /** - * Highlight the element that is under the cursor + * return function to Highlight the element that is under the cursor * by reducing the opacity of all the elements on the graph. - * @param element {d3.Selection} - * @method highlight + * @param uiSettings + * @method getHighlighter */ - highlight(element) { - const label = this.getAttribute('data-label'); - if (!label) return; - const dimming = config.get('visualization:dimmingOpacity'); - $(element) - .parent() - .find('[data-label]') - .css('opacity', 1) //Opacity 1 is needed to avoid the css application - .not((els, el) => String($(el).data('label')) === label) - .css('opacity', justifyOpacity(dimming)); + getHighlighter(uiSettings) { + return function highlight(element) { + const label = this.getAttribute('data-label'); + if (!label) return; + const dimming = uiSettings.get('visualization:dimmingOpacity'); + $(element) + .parent() + .find('[data-label]') + .css('opacity', 1) //Opacity 1 is needed to avoid the css application + .not((els, el) => String($(el).data('label')) === label) + .css('opacity', justifyOpacity(dimming)); + }; } /** diff --git a/src/legacy/ui/public/vislib/lib/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js similarity index 96% rename from src/legacy/ui/public/vislib/lib/handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js index 6047faa0c6b15..b887b61578cc4 100644 --- a/src/legacy/ui/public/vislib/lib/handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js @@ -20,15 +20,16 @@ import d3 from 'd3'; import _ from 'lodash'; import MarkdownIt from 'markdown-it'; + import { NoResults } from '../errors'; -import { Binder } from '../../binder'; import { Layout } from './layout/layout'; import { ChartTitle } from './chart_title'; import { Alerts } from './alerts'; import { Axis } from './axis/axis'; import { ChartGrid as Grid } from './chart_grid'; import { visTypes as chartTypes } from '../visualizations/vis_types'; -import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; +import { Binder } from '../../legacy_imports'; +import { dispatchRenderComplete } from '../../../../../../plugins/kibana_utils/public'; const markdownIt = new MarkdownIt({ html: false, @@ -45,9 +46,10 @@ const markdownIt = new MarkdownIt({ * create the visualization */ export class Handler { - constructor(vis, visConfig) { + constructor(vis, visConfig, deps) { this.el = visConfig.get('el'); this.ChartClass = chartTypes[visConfig.get('type')]; + this.deps = deps; this.charts = []; this.vis = vis; @@ -151,7 +153,7 @@ export class Handler { let loadedCount = 0; const chartSelection = selection.selectAll('.chart'); chartSelection.each(function(chartData) { - const chart = new self.ChartClass(self, this, chartData); + const chart = new self.ChartClass(self, this, chartData, self.deps); self.vis.eventNames().forEach(function(event) { self.enable(event, chart); diff --git a/src/legacy/ui/public/vislib/lib/layout/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_index.scss diff --git a/src/legacy/ui/public/vislib/lib/layout/_layout.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/_layout.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss diff --git a/src/legacy/ui/public/vislib/lib/layout/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/index.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/index.js diff --git a/src/legacy/ui/public/vislib/lib/layout/layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout.js diff --git a/src/legacy/ui/public/vislib/lib/layout/layout_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout_types.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/layout_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout_types.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_title_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_title_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/x_axis_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/x_axis_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/y_axis_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/y_axis_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/types/column_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/column_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/types/column_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/column_layout.js diff --git a/src/legacy/ui/public/vislib/lib/layout/types/gauge_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/gauge_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/types/gauge_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/gauge_layout.js diff --git a/src/legacy/ui/public/vislib/lib/layout/types/pie_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/pie_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/types/pie_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/pie_layout.js diff --git a/src/legacy/ui/public/vislib/lib/types/gauge.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/gauge.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/types/gauge.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/gauge.js diff --git a/src/legacy/ui/public/vislib/lib/types/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/index.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/types/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/index.js diff --git a/src/legacy/ui/public/vislib/lib/types/pie.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/pie.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/types/pie.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/pie.js diff --git a/src/legacy/ui/public/vislib/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/types/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js index 332f7408ebc66..eab3bc02f4eec 100644 --- a/src/legacy/ui/public/vislib/lib/types/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js @@ -202,7 +202,7 @@ export const vislibPointSeriesTypes = { 'Positive and negative values are not accurately represented by stacked ' + 'area charts. Either changing the chart mode to "overlap" or using a ' + 'bar chart is recommended.', - test: function(vis, data) { + test: function(_, data) { if (!data.shouldBeStacked() || data.maxNumberOfSeries() < 2) return; const hasPos = data.getYMax(data._getY) > 0; @@ -216,7 +216,7 @@ export const vislibPointSeriesTypes = { 'Parts of or the entire area chart might not be displayed due to null ' + 'values in the data. A line chart is recommended when displaying data ' + 'with null values.', - test: function(vis, data) { + test: function(_, data) { return data.hasNullValues(); }, }, @@ -229,7 +229,7 @@ export const vislibPointSeriesTypes = { const tooManySeries = defaults.charts.length && defaults.charts[0].series.length > cfg.heatmapMaxBuckets; if (hasCharts && tooManySeries) { - defaults.error = i18n.translate('common.ui.vislib.heatmap.maxBucketsText', { + defaults.error = i18n.translate('kbnVislibVisTypes.vislib.heatmap.maxBucketsText', { defaultMessage: 'There are too many series defined ({nr}). The configured maximum is {max}.', values: { diff --git a/src/legacy/ui/public/vislib/lib/types/point_series.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.test.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/types/point_series.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.test.js index cada6127282d8..38a6be548594f 100644 --- a/src/legacy/ui/public/vislib/lib/types/point_series.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.test.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import stackedSeries from '../../../../../../fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import stackedSeries from '../../__tests__/lib/fixtures/mock_data/date_histogram/_stacked_series'; import { vislibPointSeriesTypes } from './point_series'; describe('vislibPointSeriesTypes', () => { diff --git a/src/legacy/ui/public/vislib/lib/vis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/vis_config.js similarity index 93% rename from src/legacy/ui/public/vislib/lib/vis_config.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/vis_config.js index 5f10e89474809..091e6b1752d8d 100644 --- a/src/legacy/ui/public/vislib/lib/vis_config.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/vis_config.js @@ -35,8 +35,8 @@ const DEFAULT_VIS_CONFIG = { }; export class VisConfig { - constructor(visConfigArgs, data, uiState, el) { - this.data = new Data(data, uiState); + constructor(visConfigArgs, data, uiState, el, vislibColor) { + this.data = new Data(data, uiState, vislibColor); const visType = visTypes[visConfigArgs.type]; const typeDefaults = visType(visConfigArgs, this.data); diff --git a/src/legacy/ui/public/vislib/partials/touchdown.tmpl.html b/src/legacy/core_plugins/vis_type_vislib/public/vislib/partials/touchdown.tmpl.html similarity index 100% rename from src/legacy/ui/public/vislib/partials/touchdown.tmpl.html rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/partials/touchdown.tmpl.html diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/vis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vis.js new file mode 100644 index 0000000000000..32afb6a008b61 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vis.js @@ -0,0 +1,184 @@ +/* + * 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 d3 from 'd3'; +import { EventEmitter } from 'events'; + +import { VislibError } from './errors'; +import { VisConfig } from './lib/vis_config'; +import { Handler } from './lib/handler'; + +/** + * Creates the visualizations. + * + * @class Vis + * @constructor + * @param element {HTMLElement} jQuery selected HTML element + * @param config {Object} Parameters that define the chart type and chart options + */ +export class Vis extends EventEmitter { + constructor(element, visConfigArgs, deps) { + super(); + this.element = element.get ? element.get(0) : element; + this.visConfigArgs = _.cloneDeep(visConfigArgs); + this.visConfigArgs.dimmingOpacity = deps.uiSettings.get('visualization:dimmingOpacity'); + this.visConfigArgs.heatmapMaxBuckets = deps.uiSettings.get('visualization:heatmap:maxBuckets'); + this.deps = deps; + } + + hasLegend() { + return this.visConfigArgs.addLegend; + } + + initVisConfig(data, uiState) { + this.data = data; + this.uiState = uiState; + this.visConfig = new VisConfig( + this.visConfigArgs, + this.data, + this.uiState, + this.element, + this.deps.vislibColor + ); + } + + /** + * Renders the visualization + * + * @method render + * @param data {Object} Elasticsearch query results + */ + render(data, uiState) { + if (!data) { + throw new Error('No valid data!'); + } + + if (this.handler) { + this.data = null; + this._runOnHandler('destroy'); + } + + this.initVisConfig(data, uiState); + + this.handler = new Handler(this, this.visConfig, this.deps); + this._runOnHandler('render'); + } + + getLegendLabels() { + return this.visConfig ? this.visConfig.get('legend.labels', null) : null; + } + + getLegendColors() { + return this.visConfig ? this.visConfig.get('legend.colors', null) : null; + } + + _runOnHandler(method) { + try { + this.handler[method](); + } catch (error) { + if (error instanceof VislibError) { + error.displayToScreen(this.handler); + } else { + throw error; + } + } + } + + /** + * Destroys the visualization + * Removes chart and all elements associated with it. + * Removes chart and all elements associated with it. + * Remove event listeners and pass destroy call down to owned objects. + * + * @method destroy + */ + destroy() { + const selection = d3.select(this.element).select('.visWrapper'); + + if (this.handler) this._runOnHandler('destroy'); + + selection.remove(); + } + + /** + * Sets attributes on the visualization + * + * @method set + * @param name {String} An attribute name + * @param val {*} Value to which the attribute name is set + */ + set(name, val) { + this.visConfigArgs[name] = val; + this.render(this.data, this.uiState); + } + + /** + * Gets attributes from the visualization + * + * @method get + * @param name {String} An attribute name + * @returns {*} The value of the attribute name + */ + get(name) { + return this.visConfig.get(name); + } + + /** + * Turns on event listeners. + * + * @param event {String} + * @param listener{Function} + * @returns {*} + */ + on(event, listener) { + const first = this.listenerCount(event) === 0; + const ret = EventEmitter.prototype.on.call(this, event, listener); + const added = this.listenerCount(event) > 0; + + // if this is the first listener added for the event + // enable the event in the handler + if (first && added && this.handler) this.handler.enable(event); + + return ret; + } + + /** + * Turns off event listeners. + * + * @param event {String} + * @param listener{Function} + * @returns {*} + */ + off(event, listener) { + const last = this.listenerCount(event) === 1; + const ret = EventEmitter.prototype.off.call(this, event, listener); + const removed = this.listenerCount(event) === 0; + + // Once all listeners are removed, disable the events in the handler + if (last && removed && this.handler) this.handler.disable(event); + return ret; + } + + removeAllListeners(event) { + const ret = EventEmitter.prototype.removeAllListeners.call(this, event); + this.handler.disable(event); + return ret; + } +} diff --git a/src/legacy/ui/public/vislib/vislib.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vislib.js similarity index 82% rename from src/legacy/ui/public/vislib/vislib.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/vislib.js index 847deb8273a73..024dee60ef2bf 100644 --- a/src/legacy/ui/public/vislib/vislib.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vislib.js @@ -23,8 +23,7 @@ import './lib/types'; import './lib/layout/layout_types'; import './lib/data'; import './visualizations/vis_types'; -import { mappedColors } from '../vis/components/color/mapped_colors'; -import { VislibVisProvider } from './vis'; +import { Vis } from './vis'; // prefetched for faster optimization runs // end prefetching @@ -36,11 +35,9 @@ import { VislibVisProvider } from './vis'; * @main vislib * @return {Object} Contains the version number and the Vis Class for creating visualizations */ -export function VislibProvider(Private, $rootScope) { - $rootScope.$on('$routeChangeStart', () => mappedColors.purge()); - +export function VislibProvider() { return { version: '0.0.0', - Vis: Private(VislibVisProvider), + Vis, }; } diff --git a/src/legacy/ui/public/vislib/visualizations/_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js similarity index 81% rename from src/legacy/ui/public/vislib/visualizations/_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js index 2fbd96d6a070a..ac6e8130a846a 100644 --- a/src/legacy/ui/public/vislib/visualizations/_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js @@ -19,12 +19,10 @@ import d3 from 'd3'; import _ from 'lodash'; + import { dataLabel } from '../lib/_data_label'; import { Dispatch } from '../lib/dispatch'; -import { Tooltip } from '../../vis/components/tooltip'; -import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; -import { getHierarchicalTooltipFormatter } from '../../vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { getPointSeriesTooltipFormatter } from '../../vis/components/tooltip/_pointseries_tooltip_formatter'; +import { Tooltip, getFormat } from '../../legacy_imports'; /** * The Base Class for all visualizations. @@ -36,26 +34,26 @@ import { getPointSeriesTooltipFormatter } from '../../vis/components/tooltip/_po * @param chartData {Object} Elasticsearch query results for this specific chart */ export class Chart { - constructor(handler, el, chartData) { + constructor(handler, element, chartData, deps) { this.handler = handler; - this.chartEl = el; + this.chartEl = element; this.chartData = chartData; this.tooltips = []; - const events = (this.events = new Dispatch(handler)); + const events = (this.events = new Dispatch(handler, deps.uiSettings)); const fieldFormatter = getFormat(this.handler.data.get('tooltipFormatter')); const tooltipFormatterProvider = this.handler.visConfig.get('type') === 'pie' - ? getHierarchicalTooltipFormatter() - : getPointSeriesTooltipFormatter(); + ? deps.getHierarchicalTooltipFormatter() + : deps.getPointSeriesTooltipFormatter(); const tooltipFormatter = tooltipFormatterProvider(fieldFormatter); if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) { - const $el = this.handler.el; + const element = this.handler.el; // Add tooltip - this.tooltip = new Tooltip('chart', $el, tooltipFormatter, events); + this.tooltip = new Tooltip('chart', element, tooltipFormatter, events); this.tooltips.push(this.tooltip); } diff --git a/src/legacy/ui/public/vislib/visualizations/gauge_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js similarity index 97% rename from src/legacy/ui/public/vislib/visualizations/gauge_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js index c67b1b8881d69..729c664032603 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauge_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js @@ -22,8 +22,8 @@ import { Chart } from './_chart'; import { gaugeTypes } from './gauges/gauge_types'; export class GaugeChart extends Chart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); + constructor(handler, chartEl, chartData, deps) { + super(handler, chartEl, chartData, deps); this.gaugeConfig = handler.visConfig.get('gauge', {}); this.gauge = new gaugeTypes[this.gaugeConfig.type](this); } diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/gauges/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_index.scss diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_meter.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_meter.scss diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/gauge_types.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/gauge_types.js diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/meter.js similarity index 99% rename from src/legacy/ui/public/vislib/visualizations/gauges/meter.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/meter.js index 25da3ba957181..f519914662251 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/meter.js @@ -19,7 +19,8 @@ import d3 from 'd3'; import _ from 'lodash'; -import { getHeatmapColors } from '../../components/color/heatmap_color'; + +import { getHeatmapColors } from '../../../legacy_imports'; const arcAngles = { angleFactor: 0.75, diff --git a/src/legacy/ui/public/vislib/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js similarity index 99% rename from src/legacy/ui/public/vislib/visualizations/pie_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js index 6d51c69892bc0..a2cbf0fe295ce 100644 --- a/src/legacy/ui/public/vislib/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js @@ -42,8 +42,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class PieChart extends Chart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); + constructor(handler, chartEl, chartData, deps) { + super(handler, chartEl, chartData, deps); const charts = this.handler.data.getVisData(); this._validatePieData(charts); this._attr = _.defaults(handler.visConfig.get('chart', {}), defaults); diff --git a/src/legacy/ui/public/vislib/visualizations/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js similarity index 97% rename from src/legacy/ui/public/vislib/visualizations/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js index 84dbea2ccc823..c838c51d34bf5 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js @@ -20,7 +20,8 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import { Tooltip } from '../../vis/components/tooltip'; + +import { Tooltip } from '../../legacy_imports'; import { Chart } from './_chart'; import { TimeMarker } from './time_marker'; import { seriesTypes } from './point_series/series_types'; @@ -39,9 +40,10 @@ const touchdownTmpl = _.template(touchdownTmplHtml); * @param chartData {Object} Elasticsearch query results for this specific chart */ export class PointSeries extends Chart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); + constructor(handler, chartEl, chartData, deps) { + super(handler, chartEl, chartData, deps); + this.deps = deps; this.handler = handler; this.chartData = chartData; this.chartEl = chartEl; @@ -255,7 +257,7 @@ export class PointSeries extends Chart { if (!seriArgs.show) return; const SeriClass = seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')] || seriTypes.line; - const series = new SeriClass(self.handler, svg, data.series[i], seriArgs); + const series = new SeriClass(self.handler, svg, data.series[i], seriArgs, this.deps); series.events = self.events; svg.call(series.draw()); self.series.push(series); diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_index.scss diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_labels.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_labels.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/_labels.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_labels.scss diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js index 08147bacdcc98..274ae82271e96 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js @@ -43,8 +43,8 @@ const defaults = { * chart */ export class AreaChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.isOverlapping = this.seriesConfig.mode !== 'stacked'; diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js index 1f18141d86299..4b422d9b1419f 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js @@ -57,8 +57,8 @@ function datumWidth(defaultWidth, datum, nextDatum, scale, gutterWidth, groupCou * @param chartData {Object} Elasticsearch query results for this specific chart */ export class ColumnChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.labelOptions = _.defaults(handler.visConfig.get('labels', {}), defaults.showLabel); } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js index 1bc33bd5bbade..948cf98a64352 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js @@ -19,10 +19,12 @@ import _ from 'lodash'; import moment from 'moment'; -import { PointSeries } from './_point_series'; -import { getHeatmapColors } from '../../components/color/heatmap_color'; + import { isColorDark } from '@elastic/eui'; +import { PointSeries } from './_point_series'; +import { getHeatmapColors } from '../../../legacy_imports'; + const defaults = { color: undefined, // todo fillColor: undefined, // todo @@ -38,8 +40,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class HeatmapChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.handler.visConfig.set('legend', { diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js index 57090b5424f93..0038b4401b302 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js @@ -42,8 +42,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class LineChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/series_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/series_types.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/series_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/series_types.js diff --git a/src/legacy/ui/public/vislib/visualizations/time_marker.d.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.d.ts similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/time_marker.d.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.d.ts diff --git a/src/legacy/ui/public/vislib/visualizations/time_marker.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/time_marker.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.js diff --git a/src/legacy/ui/public/vislib/visualizations/vis_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/vis_types.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/vis_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/vis_types.js diff --git a/src/legacy/core_plugins/visualizations/index.ts b/src/legacy/core_plugins/visualizations/index.ts index cf2d123d8f184..a2779cfe4346d 100644 --- a/src/legacy/core_plugins/visualizations/index.ts +++ b/src/legacy/core_plugins/visualizations/index.ts @@ -26,7 +26,7 @@ export const visualizations: LegacyPluginInitializer = kibana => publicDir: resolve(__dirname, 'public'), require: [], uiExports: { - interpreter: ['plugins/visualizations/expressions/boot'], + styleSheetPaths: resolve(__dirname, 'public/index.scss'), }, }); diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/_embeddables.scss b/src/legacy/core_plugins/visualizations/public/embeddable/_embeddables.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/_embeddables.scss rename to src/legacy/core_plugins/visualizations/public/embeddable/_embeddables.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/_index.scss b/src/legacy/core_plugins/visualizations/public/embeddable/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/_index.scss rename to src/legacy/core_plugins/visualizations/public/embeddable/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/_visualize_lab_disabled.scss b/src/legacy/core_plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/_visualize_lab_disabled.scss rename to src/legacy/core_plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/constants.ts b/src/legacy/core_plugins/visualizations/public/embeddable/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/constants.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_embeddable.tsx rename to src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx b/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx rename to src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx index 52fa937bbe047..3d2af2c591a3c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx +++ b/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx @@ -29,14 +29,14 @@ export function DisabledLabVisualization({ title }: { title: string }) { />
{title} }} />
diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts b/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts similarity index 89% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts index 36efc4b86d0d3..25aa77ec73579 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts @@ -17,10 +17,9 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; - import { VisSavedObject } from './visualize_embeddable'; import { indexPatterns, IIndexPattern } from '../../../../../plugins/data/public'; +import { getUISettings, getSavedObjects } from '../np_ready/public/services'; export async function getIndexPattern( savedVis: VisSavedObject @@ -29,8 +28,8 @@ export async function getIndexPattern( return savedVis.vis.indexPattern; } - const savedObjectsClient = npStart.core.savedObjects.client; - const defaultIndex = npStart.core.uiSettings.get('defaultIndex'); + const savedObjectsClient = getSavedObjects().client; + const defaultIndex = getUISettings().get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/index.ts b/src/legacy/core_plugins/visualizations/public/embeddable/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/index.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/index.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts similarity index 99% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts index b7a3a0f000d72..c1b049ab5e969 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -47,7 +47,7 @@ import { APPLY_FILTER_TRIGGER, } from '../../../../../plugins/embeddable/public'; import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; -import { SavedSearch } from '../discover/np_ready/types'; +import { SavedSearch } from '../../../kibana/public/discover/np_ready/types'; const getKeys = (o: T): Array => Object.keys(o) as Array; diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx similarity index 67% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx rename to src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index ebb9ce2cfdf6b..3f29f97afee48 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -17,26 +17,9 @@ * under the License. */ -import 'uiExports/contextMenuActions'; -import 'uiExports/devTools'; -import 'uiExports/docViews'; -import 'uiExports/embeddableActions'; -import 'uiExports/fieldFormatEditors'; -import 'uiExports/fieldFormats'; -import 'uiExports/indexManagement'; -import 'uiExports/inspectorViews'; -import 'uiExports/savedObjectTypes'; -import 'uiExports/search'; -import 'uiExports/shareContextMenuExtensions'; -import 'uiExports/visTypes'; -import 'uiExports/visualize'; - import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; -import { npSetup, npStart } from 'ui/new_platform'; - -import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; import { @@ -45,9 +28,8 @@ import { Container, EmbeddableOutput, } from '../../../../../plugins/embeddable/public'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; -import { showNewVisModal } from '../visualize'; -import { SavedVisualizations } from '../visualize/np_ready/types'; +import { showNewVisModal } from '../../../kibana/public/visualize/np_ready/wizard/show_new_vis'; +import { SavedVisualizations } from '../../../kibana/public/visualize/np_ready/types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; import { @@ -57,7 +39,15 @@ import { VisSavedObject, } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -import { TypesStart } from '../../../visualizations/public/np_ready/public/types'; + +import { + getUISettings, + getCapabilities, + getHttp, + getTypes, + getSavedObjects, + getUsageCollector, +} from '../np_ready/public/services'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -70,60 +60,48 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< VisualizationAttributes > { public readonly type = VISUALIZE_EMBEDDABLE_TYPE; - private readonly visTypes: TypesStart; static async createVisualizeEmbeddableFactory(): Promise { - return new VisualizeEmbeddableFactory(visualizations.types); + return new VisualizeEmbeddableFactory(); } - constructor(visTypes: TypesStart) { + constructor() { super({ savedObjectMetaData: { - name: i18n.translate('kbn.visualize.savedObjectName', { defaultMessage: 'Visualization' }), + name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }), includeFields: ['visState'], type: 'visualization', getIconForSavedObject: savedObject => { - if (!visTypes) { - return 'visualizeApp'; - } return ( - visTypes.get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp' + getTypes().get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp' ); }, getTooltipForSavedObject: savedObject => { - if (!visTypes) { - return ''; - } return `${savedObject.attributes.title} (${ - visTypes.get(JSON.parse(savedObject.attributes.visState).type).title + getTypes().get(JSON.parse(savedObject.attributes.visState).type).title })`; }, showSavedObject: savedObject => { - if (!visTypes) { - return false; - } const typeName: string = JSON.parse(savedObject.attributes.visState).type; - const visType = visTypes.get(typeName); + const visType = getTypes().get(typeName); if (!visType) { return false; } - if (npStart.core.uiSettings.get('visualize:enableLabs')) { + if (getUISettings().get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; }, }, }); - - this.visTypes = visTypes; } public isEditable() { - return npStart.core.application.capabilities.visualize.save as boolean; + return getCapabilities().visualize.save as boolean; } public getDisplayName() { - return i18n.translate('kbn.embeddable.visualizations.displayName', { + return i18n.translate('visualizations.displayName', { defaultMessage: 'visualization', }); } @@ -134,16 +112,15 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< parent?: Container ): Promise { const $injector = await chrome.dangerouslyGetActiveInjector(); - const config = $injector.get('config'); const savedVisualizations = $injector.get('savedVisualizations'); try { const visId = savedObject.id as string; const editUrl = visId - ? npStart.core.http.basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) + ? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = getUISettings().get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { return new DisabledLabEmbeddable(savedObject.title, input); @@ -191,18 +168,16 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< public async create() { // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. - if (this.visTypes) { - showNewVisModal( - this.visTypes, - { - editorParams: ['addToDashboard'], - }, - npStart.core.http.basePath.prepend, - npStart.core.uiSettings, - npStart.core.savedObjects, - npSetup.plugins.usageCollection - ); - } + showNewVisModal( + getTypes(), + { + editorParams: ['addToDashboard'], + }, + getHttp().basePath.prepend, + getUISettings(), + getSavedObjects(), + getUsageCollector() + ); return undefined; } } diff --git a/src/legacy/core_plugins/visualizations/public/index.scss b/src/legacy/core_plugins/visualizations/public/index.scss new file mode 100644 index 0000000000000..957d06be4daf0 --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/index.scss @@ -0,0 +1,3 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './embeddable/_index'; diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index 92d8ac2c7db3a..b750557c24b94 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -27,4 +27,7 @@ export { } from '../../../ui/public/agg_types/buckets/date_histogram'; export { createFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; export { I18nContext } from '../../../ui/public/i18n'; +export { DefaultEditorController } from '../../../ui/public/vis/editors/default/default_editor_controller'; +import chrome from '../../../ui/public/chrome'; +export { chrome as legacyChrome }; import '../../../ui/public/directives/bind'; diff --git a/src/legacy/ui/public/visualize/loader/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js similarity index 93% rename from src/legacy/ui/public/visualize/loader/vis.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js index ad946aa1212ff..81224b65f7786 100644 --- a/src/legacy/ui/public/visualize/loader/vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js @@ -29,11 +29,9 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; -import { PersistedState } from '../../persisted_state'; +import { PersistedState } from '../../../legacy_imports'; -import { start as visualizations } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; - -const visTypes = visualizations.types; +import { getTypes } from '../services'; export class Vis extends EventEmitter { constructor(visState = { type: 'histogram' }) { @@ -64,7 +62,7 @@ export class Vis extends EventEmitter { this.title = state.title || ''; const type = state.type || this.type; if (_.isString(type)) { - this.type = visTypes.get(type); + this.type = getTypes().get(type); if (!this.type) { throw new Error(`Invalid type "${type}"`); } diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts similarity index 85% rename from src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts index ddf1b11945422..24eb974f6edee 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts @@ -19,13 +19,10 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { PersistedState } from 'ui/persisted_state'; import { VisResponseValue } from 'src/plugins/visualizations/public'; import { ExpressionFunction, Render } from 'src/plugins/expressions/public'; -import { npStart } from 'ui/new_platform'; -import { start as visualizations } from '../np_ready/public/legacy'; +import { PersistedState } from '../../../legacy_imports'; +import { getTypes, getIndexPatterns, getFilterManager } from '../services'; interface Arguments { index?: string | null; @@ -90,15 +87,10 @@ export const visualization = (): ExpressionFunctionVisualization => ({ }, }, async fn(context, args, handlers) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private') as any; - const indexPatterns = npStart.plugins.data.indexPatterns; - const queryFilter = Private(FilterBarQueryFilterProvider); - const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {}; const schemas = args.schemas ? JSON.parse(args.schemas) : {}; - const visType = visualizations.types.get(args.type || 'histogram') as any; - const indexPattern = args.index ? await indexPatterns.get(args.index) : null; + const visType = getTypes().get(args.type || 'histogram') as any; + const indexPattern = args.index ? await getIndexPatterns().get(args.index) : null; const uiStateParams = args.uiState ? JSON.parse(args.uiState) : {}; const uiState = new PersistedState(uiStateParams); @@ -114,7 +106,7 @@ export const visualization = (): ExpressionFunctionVisualization => ({ filters: get(context, 'filters', null), uiState, inspectorAdapters: handlers.inspectorAdapters, - queryFilter, + queryFilter: getFilterManager(), forceFetch: true, }); } diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx similarity index 89% rename from src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx rename to src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx index 40648a137c141..2a12884ecf7c8 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx @@ -17,12 +17,12 @@ * under the License. */ -import chrome from 'ui/chrome'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { legacyChrome } from '../../../legacy_imports'; // @ts-ignore -import { Vis } from '../../../../ui/public/visualize/loader/vis'; -import { Visualization } from '../../../visualizations/public/np_ready/public/components'; +import { Vis } from './vis'; +import { Visualization } from '../components'; export const visualization = () => ({ name: 'visualization', @@ -31,7 +31,7 @@ export const visualization = () => ({ render: async (domNode: HTMLElement, config: any, handlers: any) => { const { visData, visConfig, params } = config; const visType = config.visType || visConfig.type; - const $injector = await chrome.dangerouslyGetActiveInjector(); + const $injector = await legacyChrome.dangerouslyGetActiveInjector(); const $rootScope = $injector.get('$rootScope') as any; if (handlers.vis) { 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 index 1e86aa64d1fa8..41b23b276e88d 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts @@ -27,5 +27,5 @@ import { plugin } from '.'; const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core); -export const start = pluginInstance.start(npStart.core); +export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap index ddd74628e7940..90c1d4472d5ea 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -8,7 +8,7 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentage=true metric={visdimension 0 format='percent' } "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentageMode=true metric={visdimension 0 format='percent' } "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function without buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index ab1664d612b35..a3bf98b5e74a3 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -305,8 +305,8 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { } let expr = `metricvis `; - expr += prepareValue('percentage', percentageMode); - expr += prepareValue('colorScheme', colorSchema); + expr += prepareValue('percentageMode', percentageMode); + expr += prepareValue('colorSchema', colorSchema); expr += prepareValue('colorMode', metricColorMode); expr += prepareValue('useRanges', useRanges); expr += prepareValue('invertColors', invertColors); 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 index 88c5768a0b4e4..2fa85d86a794e 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -28,6 +28,10 @@ import { PluginInitializerContext } from 'src/core/public'; import { VisualizationsSetup, VisualizationsStart } from './'; import { VisualizationsPlugin } from './plugin'; import { coreMock } from '../../../../../../core/public/mocks'; +import { embeddablePluginMock } from '../../../../../../plugins/embeddable/public/mocks'; +import { expressionsPluginMock } from '../../../../../../plugins/expressions/public/mocks'; +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +import { usageCollectionPluginMock } from '../../../../../../plugins/usage_collection/public/mocks'; const createSetupContract = (): VisualizationsSetup => ({ types: { @@ -49,8 +53,15 @@ const createStartContract = (): VisualizationsStart => ({ const createInstance = async () => { const plugin = new VisualizationsPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup()); - const doStart = () => plugin.start(coreMock.createStart()); + const setup = plugin.setup(coreMock.createSetup(), { + expressions: expressionsPluginMock.createSetupContract(), + embeddable: embeddablePluginMock.createStartContract(), + usageCollection: usageCollectionPluginMock.createSetupContract(), + }); + const doStart = () => + plugin.start(coreMock.createStart(), { + data: dataPluginMock.createStartContract(), + }); return { plugin, 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 index ccf6aaf152ea4..cfd22f88167c5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -16,10 +16,28 @@ * specific language governing permissions and limitations * under the License. */ + import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { TypesService, TypesSetup, TypesStart } from './types'; -import { setUISettings, setTypes, setI18n } from './services'; - +import { + setUISettings, + setTypes, + setI18n, + setCapabilities, + setHttp, + setIndexPatterns, + setSavedObjects, + setUsageCollector, + setFilterManager, +} from './services'; +import { VisualizeEmbeddableFactory } from '../../embeddable/visualize_embeddable_factory'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../embeddable'; +import { ExpressionsSetup } from '../../../../../../plugins/expressions/public'; +import { IEmbeddableSetup } from '../../../../../../plugins/embeddable/public'; +import { visualization as visualizationFunction } from './expressions/visualization_function'; +import { visualization as visualizationRenderer } from './expressions/visualization_renderer'; +import { DataPublicPluginStart } from '../../../../../../plugins/data/public'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; /** * Interface for this plugin's returned setup/start contracts. * @@ -34,6 +52,16 @@ export interface VisualizationsStart { types: TypesStart; } +export interface VisualizationsSetupDeps { + expressions: ExpressionsSetup; + embeddable: IEmbeddableSetup; + usageCollection: UsageCollectionSetup; +} + +export interface VisualizationsStartDeps { + data: DataPublicPluginStart; +} + /** * Visualizations Plugin - public * @@ -42,22 +70,46 @@ export interface VisualizationsStart { * * @internal */ -export class VisualizationsPlugin implements Plugin { +export class VisualizationsPlugin + implements + Plugin< + VisualizationsSetup, + VisualizationsStart, + VisualizationsSetupDeps, + VisualizationsStartDeps + > { private readonly types: TypesService = new TypesService(); constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup) { + public setup( + core: CoreSetup, + { expressions, embeddable, usageCollection }: VisualizationsSetupDeps + ) { setUISettings(core.uiSettings); + setUsageCollector(usageCollection); + + expressions.registerFunction(visualizationFunction); + expressions.registerRenderer(visualizationRenderer); + + const embeddableFactory = new VisualizeEmbeddableFactory(); + embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + return { types: this.types.setup(), }; } - public start(core: CoreStart) { - setI18n(core.i18n); + public start(core: CoreStart, { data }: VisualizationsStartDeps) { const types = this.types.start(); + setI18n(core.i18n); setTypes(types); + setCapabilities(core.application.capabilities); + setHttp(core.http); + setSavedObjects(core.savedObjects); + setIndexPatterns(data.indexPatterns); + setFilterManager(data.query.filterManager); + return { types, }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts index 434612d11b28a..433c5c7b6df0d 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts @@ -17,12 +17,40 @@ * under the License. */ -import { I18nStart, IUiSettingsClient } from 'src/core/public'; +import { + Capabilities, + HttpStart, + I18nStart, + IUiSettingsClient, + SavedObjectsStart, +} from 'src/core/public'; import { TypesStart } from './types'; import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; +import { FilterManager, IndexPatternsContract } from '../../../../../../plugins/data/public'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); +export const [getCapabilities, setCapabilities] = createGetterSetter('Capabilities'); + +export const [getHttp, setHttp] = createGetterSetter('Http'); + +export const [getSavedObjects, setSavedObjects] = createGetterSetter( + 'SavedObjects' +); + export const [getTypes, setTypes] = createGetterSetter('Types'); export const [getI18n, setI18n] = createGetterSetter('I18n'); + +export const [getFilterManager, setFilterManager] = createGetterSetter( + 'FilterManager' +); + +export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( + 'IndexPatterns' +); + +export const [getUsageCollector, setUsageCollector] = createGetterSetter( + 'UsageCollection' +); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js index 2f60de3b80614..f849cbfb290ca 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js @@ -18,7 +18,9 @@ */ import _ from 'lodash'; + import { createFiltersFromEvent, onBrushEvent } from '../filters'; +import { DefaultEditorController } from '../../../legacy_imports'; export class BaseVisType { constructor(opts = {}) { @@ -46,7 +48,7 @@ export class BaseVisType { }, requestHandler: 'courier', // select one from registry or pass a function responseHandler: 'none', - editor: 'default', + editor: DefaultEditorController, editorConfig: { collections: {}, // collections used for configuration (list of positions, ...) }, diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js index 115bbd2731840..4f1526c20cb6f 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js @@ -97,6 +97,10 @@ class Vis extends EventEmitter { } } + setVisType(type) { + this.type.type = type; + } + updateState() { this.setState(this.getCurrentState(true)); this.emit('update'); @@ -118,18 +122,6 @@ class Vis extends EventEmitter { }; } - getSerializableState(state) { - return { - title: state.title, - type: state.type, - params: _.cloneDeep(state.params), - aggs: state.aggs.aggs - .map(agg => agg.toJSON()) - .filter(agg => agg.enabled) - .filter(Boolean), - }; - } - copyCurrentState(includeDisabled = false) { const state = this.getCurrentState(includeDisabled); state.aggs = new AggConfigs( diff --git a/src/legacy/ui/public/UI_SYSTEMS.md b/src/legacy/ui/public/UI_SYSTEMS.md index 63fd602075653..37bfbcf92f640 100644 --- a/src/legacy/ui/public/UI_SYSTEMS.md +++ b/src/legacy/ui/public/UI_SYSTEMS.md @@ -6,7 +6,3 @@ In this directory you'll find various UI systems you can use to craft effective * [banners](notify/banners/BANNERS.md) * [toastNotifications](notify/toasts/TOAST_NOTIFICATIONS.md) - -## ui/vislib - -* [VisLib](vislib/VISLIB.md) \ No newline at end of file diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index 747ad025ef691..f5a1d0a7922a7 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -26,5 +26,4 @@ // Can't import vis folder here because of cascading issues, it's imported in core_plugins/kibana // @import './vis/index'; -@import './vislib/index'; @import './visualize/index'; diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts index 07e0d46e4eb70..0edf782318862 100644 --- a/src/legacy/ui/public/agg_types/agg_config.ts +++ b/src/legacy/ui/public/agg_types/agg_config.ts @@ -123,6 +123,7 @@ export class AggConfig { public enabled: boolean; public params: any; public parent?: AggConfigs; + public brandNew?: boolean; private __schema: Schema; private __type: AggType; diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts index 6e811afb1849d..bd2f261c0bf1d 100644 --- a/src/legacy/ui/public/agg_types/agg_configs.ts +++ b/src/legacy/ui/public/agg_types/agg_configs.ts @@ -126,10 +126,10 @@ export class AggConfigs { return aggConfigs; } - createAggConfig( + createAggConfig = ( params: AggConfig | AggConfigOptions, { addToAggConfigs = true } = {} - ) { + ) => { let aggConfig; if (params instanceof AggConfig) { aggConfig = params; @@ -141,7 +141,7 @@ export class AggConfigs { this.aggs.push(aggConfig); } return aggConfig as T; - } + }; /** * Data-by-data comparison of this Aggregation diff --git a/src/legacy/ui/public/vislib/components/color/colormaps.ts b/src/legacy/ui/public/color_maps/color_maps.ts similarity index 100% rename from src/legacy/ui/public/vislib/components/color/colormaps.ts rename to src/legacy/ui/public/color_maps/color_maps.ts diff --git a/src/legacy/ui/public/vislib/components/color/heatmap_color.js b/src/legacy/ui/public/color_maps/heatmap_color.js similarity index 98% rename from src/legacy/ui/public/vislib/components/color/heatmap_color.js rename to src/legacy/ui/public/color_maps/heatmap_color.js index 5e788cd1f3345..06d754235f88b 100644 --- a/src/legacy/ui/public/vislib/components/color/heatmap_color.js +++ b/src/legacy/ui/public/color_maps/heatmap_color.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { vislibColorMaps } from './colormaps'; +import { vislibColorMaps } from './color_maps'; function enforceBounds(x) { if (x < 0) { diff --git a/src/legacy/ui/public/color_maps/index.ts b/src/legacy/ui/public/color_maps/index.ts new file mode 100644 index 0000000000000..50dfe682f4418 --- /dev/null +++ b/src/legacy/ui/public/color_maps/index.ts @@ -0,0 +1,24 @@ +/* + * 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 * from './color_maps'; +// @ts-ignore +export { getHeatmapColors } from './heatmap_color'; +// @ts-ignore +export * from './truncated_color_maps'; diff --git a/src/legacy/ui/public/vislib/components/color/truncated_colormaps.js b/src/legacy/ui/public/color_maps/truncated_color_maps.js similarity index 86% rename from src/legacy/ui/public/vislib/components/color/truncated_colormaps.js rename to src/legacy/ui/public/color_maps/truncated_color_maps.js index ccf005b3726a8..cb7772c875e48 100644 --- a/src/legacy/ui/public/vislib/components/color/truncated_colormaps.js +++ b/src/legacy/ui/public/color_maps/truncated_color_maps.js @@ -17,14 +17,14 @@ * under the License. */ -import { vislibColorMaps } from './colormaps'; +import { vislibColorMaps } from './color_maps'; export const truncatedColorMaps = {}; const colormaps = vislibColorMaps; for (const key in colormaps) { if (colormaps.hasOwnProperty(key)) { - //slice off lightest colors + // slice off lightest colors truncatedColorMaps[key] = { ...colormaps[key], value: colormaps[key].value.slice(Math.floor(colormaps[key].value.length / 4)), @@ -32,7 +32,7 @@ for (const key in colormaps) { } } -export const colorSchemas = Object.values(truncatedColorMaps).map(({ id, label }) => ({ +export const truncatedColorSchemas = Object.values(truncatedColorMaps).map(({ id, label }) => ({ value: id, text: label, })); diff --git a/src/legacy/ui/public/styles/_mixins.scss b/src/legacy/ui/public/styles/_mixins.scss index ae529a4678d5d..2d78768684841 100644 --- a/src/legacy/ui/public/styles/_mixins.scss +++ b/src/legacy/ui/public/styles/_mixins.scss @@ -25,6 +25,7 @@ // Standardizes the look and layout of resizable area handles @mixin kbnResizer($size: ($euiSizeM + 2px), $direction: horizontal) { + position: relative; display: flex; flex: 0 0 $size; background-color: $euiPageBackgroundColor; @@ -34,10 +35,8 @@ user-select: none; @if ($direction == horizontal) { - cursor: ew-resize; width: $size; } @else if ($direction == vertical) { - cursor: ns-resize; height: $size; width: 100%; } @else { @@ -53,6 +52,21 @@ background-color: $euiColorPrimary; color: $euiColorEmptyShade; } + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + @if ($direction == horizontal) { + cursor: ew-resize; + } @else if ($direction == vertical) { + cursor: ns-resize; + } + } } @mixin kibanaFullScreenGraphics($euiZLevel: $euiZLevel9) { diff --git a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js index 26a9c5b008f70..aef7bc3913a49 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js +++ b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js @@ -19,6 +19,9 @@ import _ from 'lodash'; import $ from 'jquery'; + +import chrome from 'ui/chrome'; + import { collectBranch } from './_collect_branch'; import numeral from 'numeral'; import template from './_hierarchical_tooltip.html'; @@ -68,6 +71,12 @@ export const getHierarchicalTooltipFormatter = () => { return _tooltipFormatter; }; +export const initializeHierarchicalTooltipFormatter = async () => { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); +}; + export const setHierarchicalTooltipFormatter = Private => { _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); }; diff --git a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js index fa0b030c736c1..88c9e3d67b4a9 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js +++ b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js @@ -18,6 +18,9 @@ */ import $ from 'jquery'; + +import chrome from 'ui/chrome'; + import template from './_pointseries_tooltip.html'; export function PointSeriesTooltipFormatterProvider($compile, $rootScope) { @@ -75,6 +78,12 @@ export const getPointSeriesTooltipFormatter = () => { return _tooltipFormatter; }; +export const initializePointSeriesTooltipFormatter = async () => { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); +}; + export const setPointSeriesTooltipFormatter = Private => { _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); }; diff --git a/src/legacy/ui/public/vis/editor_size.ts b/src/legacy/ui/public/vis/editor_size.ts new file mode 100644 index 0000000000000..5fdda4c2dad41 --- /dev/null +++ b/src/legacy/ui/public/vis/editor_size.ts @@ -0,0 +1,36 @@ +/* + * 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 DefaultEditorSize { + SMALL = 'small', + MEDIUM = 'medium', + LARGE = 'large', +} + +export const getInitialWidth = (size: DefaultEditorSize) => { + switch (size) { + case DefaultEditorSize.SMALL: + return 15; + case DefaultEditorSize.LARGE: + return 50; + case DefaultEditorSize.MEDIUM: + default: + return 30; + } +}; diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts index fa15f8642c4dc..0a5d0ea748b5e 100644 --- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts +++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts @@ -19,7 +19,6 @@ import { EditorConfigProviderRegistry } from './editor_config_providers'; import { EditorParamConfig, FixedParam, NumericIntervalParam, TimeIntervalParam } from './types'; -import { AggType } from '../../../agg_types'; import { AggConfig } from '../..'; jest.mock('ui/new_platform'); @@ -49,10 +48,9 @@ describe('EditorConfigProvider', () => { const provider = jest.fn(() => ({})); registry.register(provider); expect(provider).not.toHaveBeenCalled(); - const aggType = {} as AggType; const aggConfig = {} as AggConfig; - registry.getConfigForAgg(aggType, indexPattern, aggConfig); - expect(provider).toHaveBeenCalledWith(aggType, indexPattern, aggConfig); + registry.getConfigForAgg(indexPattern, aggConfig); + expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig); }); it('should call all registered providers with given parameters', () => { @@ -62,11 +60,10 @@ describe('EditorConfigProvider', () => { registry.register(provider2); expect(provider).not.toHaveBeenCalled(); expect(provider2).not.toHaveBeenCalled(); - const aggType = {} as AggType; const aggConfig = {} as AggConfig; - registry.getConfigForAgg(aggType, indexPattern, aggConfig); - expect(provider).toHaveBeenCalledWith(aggType, indexPattern, aggConfig); - expect(provider2).toHaveBeenCalledWith(aggType, indexPattern, aggConfig); + registry.getConfigForAgg(indexPattern, aggConfig); + expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig); + expect(provider2).toHaveBeenCalledWith(indexPattern, aggConfig); }); describe('merging configs', () => { @@ -75,7 +72,7 @@ describe('EditorConfigProvider', () => { } function getOutputConfig(reg: EditorConfigProviderRegistry) { - return reg.getConfigForAgg({} as AggType, indexPattern, {} as AggConfig).singleParam; + return reg.getConfigForAgg(indexPattern, {} as AggConfig).singleParam; } it('should have hidden true if at least one config was hidden true', () => { diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts index c7fb937b97424..80dc2bcd68f08 100644 --- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts +++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts @@ -19,18 +19,13 @@ import { TimeIntervalParam } from 'ui/vis/editors/config/types'; import { AggConfig } from '../..'; -import { AggType } from '../../../agg_types'; import { IndexPattern } from '../../../../../../plugins/data/public'; import { leastCommonMultiple } from '../../lib/least_common_multiple'; import { parseEsInterval } from '../../../../../core_plugins/data/public'; import { leastCommonInterval } from '../../lib/least_common_interval'; import { EditorConfig, EditorParamConfig, FixedParam, NumericIntervalParam } from './types'; -type EditorConfigProvider = ( - aggType: AggType, - indexPattern: IndexPattern, - aggConfig: AggConfig -) => EditorConfig; +type EditorConfigProvider = (indexPattern: IndexPattern, aggConfig: AggConfig) => EditorConfig; class EditorConfigProviderRegistry { private providers: Set = new Set(); @@ -39,14 +34,8 @@ class EditorConfigProviderRegistry { this.providers.add(configProvider); } - public getConfigForAgg( - aggType: AggType, - indexPattern: IndexPattern, - aggConfig: AggConfig - ): EditorConfig { - const configs = Array.from(this.providers).map(provider => - provider(aggType, indexPattern, aggConfig) - ); + public getConfigForAgg(indexPattern: IndexPattern, aggConfig: AggConfig): EditorConfig { + const configs = Array.from(this.providers).map(provider => provider(indexPattern, aggConfig)); return this.mergeConfigs(configs); } diff --git a/src/legacy/ui/public/vis/editors/default/_default.scss b/src/legacy/ui/public/vis/editors/default/_default.scss index 2a32eadd55b1e..7562583b037df 100644 --- a/src/legacy/ui/public/vis/editors/default/_default.scss +++ b/src/legacy/ui/public/vis/editors/default/_default.scss @@ -1,8 +1,4 @@ .visEditor--default { - // Prevent the default editor from overflowing. Without that you can cause - // a weird issue where the complete page can be scrolled out of view if - // the editor within the sidebar is too height. - overflow-y: hidden; flex: 1 1 auto; display: flex; @@ -16,12 +12,11 @@ */ .visEditor__collapsibleSidebar { - @include flex-parent(0, 0, auto); - margin-right: $euiSizeL; - flex-direction: row; + background: $euiColorLightestShade; min-width: $vis-editor-sidebar-min-width; - width: $vis-editor-sidebar-min-width; max-width: 100%; + position: relative; + flex-shrink: 0; @include euiBreakpoint('xs', 's', 'm') { // If we are on a small screen we force the editor to take 100% width. @@ -33,35 +28,25 @@ } } -.visEditor__collapsibleSidebar.closed { +// !importants on width are required to override resizable panel inline widths +.visEditor__collapsibleSidebar-isClosed { min-width: 0; -} - -.visEditor__collapsibleSidebar--small { - width: 15%; -} + width: $euiSizeXL !important; // Just enough room for the collapse button -.visEditor__collapsibleSidebar--medium { - width: 30%; -} + .visEditorSidebar { + display: none; + } -.visEditor__collapsibleSidebar--large { - width: 50%; + @include euiBreakpoint('xs', 's', 'm') { + height: $euiSizeXXL; // Just enough room for the collapse button + width: 100% !important; + } } - -/** - * Actual sidebar - */ - -.visEditor__sidebar { - @include flex-parent(1, 0, auto); - - // overridden for tablet and desktop - @include euiBreakpoint('l', 'xl') { - flex-basis: $vis-editor-sidebar-basis; - max-width: calc(100% - #{$vis-editor-resizer-width}); - } +.visEditor__collapsibleSidebarButton { + position: absolute; + right: $euiSizeXS; + top: $euiSizeS; } /** @@ -69,17 +54,30 @@ */ .visEditor__resizer { - @include kbnResizer($vis-editor-resizer-width); - + @include kbnResizer($euiSizeM); @include euiBreakpoint('xs', 's', 'm') { display: none; } } +.visEditor__resizer-isHidden { + display: none; +} + /** * Canvas area */ +.visEditor__visualization { + display: flex; + flex-basis: 100%; + + @include euiBreakpoint('xs', 's', 'm') { + // If we are on a small screen we force the visualization to take 100% width. + width: 100% !important; + } +} + .visEditor__canvas { background-color: $euiColorEmptyShade; display: flex; diff --git a/src/legacy/ui/public/vis/editors/default/_index.scss b/src/legacy/ui/public/vis/editors/default/_index.scss index d5938a0298d36..6abb45dc546a3 100644 --- a/src/legacy/ui/public/vis/editors/default/_index.scss +++ b/src/legacy/ui/public/vis/editors/default/_index.scss @@ -1,6 +1,4 @@ -$vis-editor-sidebar-basis: (100/12) * 2%; // two of twelve columns $vis-editor-sidebar-min-width: 350px; -$vis-editor-resizer-width: $euiSizeM; // Main layout @import './default'; diff --git a/src/legacy/ui/public/vis/editors/default/_sidebar.scss b/src/legacy/ui/public/vis/editors/default/_sidebar.scss index e6b75b1a1f783..cbe7172d62341 100644 --- a/src/legacy/ui/public/vis/editors/default/_sidebar.scss +++ b/src/legacy/ui/public/vis/editors/default/_sidebar.scss @@ -2,18 +2,22 @@ // LAYOUT // -.visEditorSidebar__container { - @include flex-parent(1, 1, auto); - background-color: $euiColorLightestShade; +.visEditorSidebar { + min-width: $vis-editor-sidebar-min-width; } .visEditorSidebar__form { @include flex-parent(1, 1, auto); + max-width: 100%; } .visEditorSidebar__config { padding: $euiSizeS; + > * { + flex-grow: 0; + } + @include euiBreakpoint('l', 'xl') { @include flex-parent(1, 1, 1px); @include euiScrollBar; @@ -21,64 +25,26 @@ } } +.visEditorSidebar__config-isHidden { + display: none; +} + // // NAVIGATION // .visEditorSidebar__indexPattern { - font-weight: $euiFontWeightBold; - padding: $euiSizeXS $euiSizeS; - background-color: shadeOrTint($euiColorPrimary, 60%, 60%); - color: $euiColorEmptyShade; - line-height: $euiSizeL; + @include euiTextTruncate; + padding: $euiSizeS $euiSizeXL $euiSizeS $euiSizeS; // Extra padding on the right for the collapse button } -.visEditorSidebar__nav { - min-height: 0; - - .navbar-right { - // Match correct bootstrap container spacing to pull buttons fully right - margin-right: -15px; - } +.visEditorSidebar__indexPatternPlaceholder { + min-height: $euiSizeXXL; + border-bottom: $euiBorderThin; } -/** - * 1. TODO: Override bootstrap styles. Remove !important once we're rid of bootstrap. - */ -.visEditorSidebar__navLink { - padding: 2px $euiSizeS !important; /* 1 */ - color: $euiColorDarkShade !important; /* 1 */ - - &.visEditorSidebar__navLink-isSelected { - border-bottom: 2px solid $euiColorPrimary; - border-color: $euiColorPrimary !important; - color: $euiColorPrimary !important; - - &:before { - display: none; - } - - &:hover { - background-color: transparent; - } - } -} - -/** - * 1. TODO: Override bootstrap styles. Remove !important once we're rid of bootstrap. - */ -.visEditorSidebar__navLink--danger { - color: $euiColorEmptyShade !important; /* 1 */ - background-color: $euiColorDanger; - - &:hover { - background-color: shadeOrTint($euiColorDanger, 12%, 0%) !important; /* 1 */ - } -} - -.visEditorSidebar__navButtonLink { - // Make the line-height the same size as the icon for better alignment - line-height: $euiSize; +.visEditorSidebar__nav { + flex-grow: 0; } // @@ -104,15 +70,6 @@ } } -.visEditorSidebar__sectionTitle { - @include euiFontSizeL; - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: $euiSizeM; - text-transform: capitalize; -} - // Collapsible section .visEditorSidebar__collapsible { @@ -127,15 +84,6 @@ // FORMS // -.visEditorSidebar__input, -.visEditorSidebar__select { - @include __legacyInputStyles__bad; -} - -.visEditorSidebar__select { - @include __legacySelectStyles__bad; -} - .visEditorSidebar__formRow { display: flex; align-items: center; @@ -155,14 +103,6 @@ flex: 1 1 60%; } -.visEditorSidebar__formRow--expand { - .visEditorSidebar__formLabel, - .visEditorSidebar__formControl { - flex-basis: auto; - flex-grow: 0; - } -} - .visEditorSidebar__aggGroupAccordionButtonContent { font-size: $euiFontSizeS; @@ -170,3 +110,15 @@ color: $euiColorDarkShade; } } + +.visEditorSidebar__controls { + border-top: $euiBorderThin; + padding: $euiSizeS; + display: flex; + justify-content: flex-end; + align-items: center; + + .visEditorSidebar__autoApplyButton { + margin-left: $euiSizeM; + } +} diff --git a/src/legacy/ui/public/vis/editors/default/agg_group.js b/src/legacy/ui/public/vis/editors/default/agg_group.js deleted file mode 100644 index 8fc4934022cf6..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/agg_group.js +++ /dev/null @@ -1,98 +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 'ngreact'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from '../../../modules'; -import { DefaultEditorAggGroup } from './components/agg_group'; - -uiModules - .get('app/visualize') - .directive('visEditorAggGroupWrapper', reactDirective => - reactDirective(wrapInI18nContext(DefaultEditorAggGroup), [ - ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects - ['schemas', { watchDepth: 'collection' }], - ['state', { watchDepth: 'reference' }], - ['addSchema', { watchDepth: 'reference' }], - ['onAggParamsChange', { watchDepth: 'reference' }], - ['onAggTypeChange', { watchDepth: 'reference' }], - ['onToggleEnableAgg', { watchDepth: 'reference' }], - ['removeAgg', { watchDepth: 'reference' }], - ['reorderAggs', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - 'groupName', - 'formIsTouched', - 'lastParentPipelineAggTitle', - 'currentTab', - ]) - ) - .directive('visEditorAggGroup', function() { - return { - restrict: 'E', - scope: true, - require: '?^ngModel', - template: function() { - return ``; - }, - link: function($scope, $el, attr, ngModelCtrl) { - $scope.groupName = attr.groupName; - $scope.$bind('schemas', attr.schemas); - // The model can become touched either onBlur event or when the form is submitted. - // We also watch $touched to identify when the form is submitted. - $scope.$watch( - () => { - return ngModelCtrl.$touched; - }, - value => { - $scope.formIsTouched = value; - } - ); - - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid); - }; - - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - }, - }; - }); diff --git a/src/legacy/ui/public/vis/editors/default/agg_groups.ts b/src/legacy/ui/public/vis/editors/default/agg_groups.ts index f55e6ecd79155..e84306144fa63 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_groups.ts +++ b/src/legacy/ui/public/vis/editors/default/agg_groups.ts @@ -18,11 +18,14 @@ */ import { i18n } from '@kbn/i18n'; +import { $Values } from '@kbn/utility-types'; -export enum AggGroupNames { - Buckets = 'buckets', - Metrics = 'metrics', -} +export const AggGroupNames = Object.freeze({ + Buckets: 'buckets' as 'buckets', + Metrics: 'metrics' as 'metrics', + None: 'none' as 'none', +}); +export type AggGroupNames = $Values; export const aggGroupNamesMap = () => ({ [AggGroupNames.Metrics]: i18n.translate('common.ui.vis.editors.aggGroups.metricsText', { diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap index b78503b298d04..aed0285fd3405 100644 --- a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap @@ -57,9 +57,9 @@ exports[`DefaultEditorAgg component should init with the default set of props 1` groupName="metrics" indexPattern={Object {}} metricAggs={Array []} - onAggParamsChange={[MockFunction]} onAggTypeChange={[Function]} - setTouched={[MockFunction]} + setAggParamValue={[MockFunction]} + setTouched={[Function]} setValidity={[Function]} state={Object {}} /> diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap index 29af0887db2b8..373ff6b4c3ee4 100644 --- a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap @@ -5,6 +5,7 @@ exports[`DefaultEditorAgg component should init with the default set of props 1` onDragEnd={[Function]} > diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx index c19f101fa2c32..6849d00158b06 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx @@ -26,6 +26,7 @@ import { act } from 'react-dom/test-utils'; import { DefaultEditorAggParams } from './agg_params'; import { AggType } from 'ui/agg_types'; import { IndexPattern } from '../../../../../../../plugins/data/public'; +import { AGGS_ACTION_KEYS } from './agg_group_state'; jest.mock('./agg_params', () => ({ DefaultEditorAggParams: () => null, @@ -33,18 +34,18 @@ jest.mock('./agg_params', () => ({ describe('DefaultEditorAgg component', () => { let defaultProps: DefaultEditorAggProps; - let onAggParamsChange: jest.Mock; - let setTouched: jest.Mock; + let setAggParamValue: jest.Mock; + let setStateParamValue: jest.Mock; let onToggleEnableAgg: jest.Mock; let removeAgg: jest.Mock; - let setValidity: jest.Mock; + let setAggsState: jest.Mock; beforeEach(() => { - onAggParamsChange = jest.fn(); - setTouched = jest.fn(); + setAggParamValue = jest.fn(); + setStateParamValue = jest.fn(); onToggleEnableAgg = jest.fn(); removeAgg = jest.fn(); - setValidity = jest.fn(); + setAggsState = jest.fn(); defaultProps = { agg: { @@ -66,10 +67,10 @@ describe('DefaultEditorAgg component', () => { isRemovable: false, metricAggs: [], state: {} as VisState, - onAggParamsChange, + setAggParamValue, + setStateParamValue, onAggTypeChange: () => {}, - setValidity, - setTouched, + setAggsState, onToggleEnableAgg, removeAgg, }; @@ -98,7 +99,11 @@ describe('DefaultEditorAgg component', () => { .setValidity(false); }); comp.update(); - expect(setValidity).toBeCalledWith(false); + expect(setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.VALID, + payload: false, + aggId: defaultProps.agg.id, + }); expect( comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists() @@ -119,7 +124,11 @@ describe('DefaultEditorAgg component', () => { .setValidity(true); }); comp.update(); - expect(setValidity).toBeCalledWith(true); + expect(setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.VALID, + payload: true, + aggId: defaultProps.agg.id, + }); expect(comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').text()).toBe( 'Agg description' @@ -128,16 +137,20 @@ describe('DefaultEditorAgg component', () => { it('should call setTouched when accordion is collapsed', () => { const comp = mount(); - expect(defaultProps.setTouched).toBeCalledTimes(0); + expect(defaultProps.setAggsState).toBeCalledTimes(0); comp.find('.euiAccordion__button').simulate('click'); // make sure that the accordion is collapsed expect(comp.find('.euiAccordion-isOpen').exists()).toBeFalsy(); - expect(defaultProps.setTouched).toBeCalledWith(true); + expect(defaultProps.setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.TOUCHED, + payload: true, + aggId: defaultProps.agg.id, + }); }); - it('should call setValidity inside onSetValidity', () => { + it('should call setAggsState inside setValidity', () => { const comp = mount(); act(() => { @@ -147,7 +160,11 @@ describe('DefaultEditorAgg component', () => { .setValidity(false); }); - expect(setValidity).toBeCalledWith(false); + expect(setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.VALID, + payload: false, + aggId: defaultProps.agg.id, + }); expect( comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists() @@ -197,7 +214,7 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.find('[data-test-subj="toggleDisableAggregationBtn disable"] button').simulate('click'); - expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, false); + expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg.id, false); }); it('should disable the disableAggregation button', () => { @@ -217,7 +234,7 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.find('[data-test-subj="toggleDisableAggregationBtn enable"] button').simulate('click'); - expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, true); + expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg.id, true); }); it('should call removeAgg', () => { @@ -225,7 +242,7 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.find('[data-test-subj="removeDimensionBtn"] button').simulate('click'); - expect(defaultProps.removeAgg).toBeCalledWith(defaultProps.agg); + expect(defaultProps.removeAgg).toBeCalledWith(defaultProps.agg.id); }); }); @@ -269,8 +286,8 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'histogram' } } }); - expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith( - defaultProps.agg.params, + expect(defaultProps.setAggParamValue).toHaveBeenCalledWith( + defaultProps.agg.id, 'min_doc_count', true ); @@ -283,8 +300,8 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'date_histogram' } } }); - expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith( - defaultProps.agg.params, + expect(defaultProps.setAggParamValue).toHaveBeenCalledWith( + defaultProps.agg.id, 'min_doc_count', 0 ); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.tsx index 345c9254ff6c1..5c5905abdb9f0 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { EuiAccordion, EuiToolTip, @@ -31,6 +31,7 @@ import { i18n } from '@kbn/i18n'; import { AggConfig } from '../../..'; import { DefaultEditorAggParams } from './agg_params'; import { DefaultEditorAggCommonProps } from './agg_common_props'; +import { AGGS_ACTION_KEYS, AggsAction } from './agg_group_state'; export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { agg: AggConfig; @@ -41,6 +42,7 @@ export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { isDraggable: boolean; isLastBucket: boolean; isRemovable: boolean; + setAggsState: React.Dispatch; } function DefaultEditorAgg({ @@ -57,12 +59,12 @@ function DefaultEditorAgg({ metricAggs, lastParentPipelineAggTitle, state, - onAggParamsChange, + setAggParamValue, + setStateParamValue, onAggTypeChange, onToggleEnableAgg, removeAgg, - setTouched, - setValidity, + setAggsState, }: DefaultEditorAggProps) { const [isEditorOpen, setIsEditorOpen] = useState((agg as any).brandNew); const [validState, setValidState] = useState(true); @@ -103,8 +105,8 @@ function DefaultEditorAgg({ useEffect(() => { if (isLastBucketAgg && ['date_histogram', 'histogram'].includes(agg.type.name)) { - onAggParamsChange( - agg.params, + setAggParamValue( + agg.id, 'min_doc_count', // "histogram" agg has an editor for "min_doc_count" param, which accepts boolean // "date_histogram" agg doesn't have an editor for "min_doc_count" param, it should be set as a numeric value @@ -113,17 +115,38 @@ function DefaultEditorAgg({ } }, [lastParentPipelineAggTitle, isLastBucket, agg.type]); - const onToggle = (isOpen: boolean) => { - setIsEditorOpen(isOpen); - if (!isOpen) { - setTouched(true); - } - }; + const setTouched = useCallback( + (touched: boolean) => { + setAggsState({ + type: AGGS_ACTION_KEYS.TOUCHED, + payload: touched, + aggId: agg.id, + }); + }, + [setAggsState] + ); - const onSetValidity = (isValid: boolean) => { - setValidity(isValid); - setValidState(isValid); - }; + const setValidity = useCallback( + (isValid: boolean) => { + setAggsState({ + type: AGGS_ACTION_KEYS.VALID, + payload: isValid, + aggId: agg.id, + }); + setValidState(isValid); + }, + [setAggsState] + ); + + const onToggle = useCallback( + (isOpen: boolean) => { + setIsEditorOpen(isOpen); + if (!isOpen) { + setTouched(true); + } + }, + [setTouched] + ); const renderAggButtons = () => { const actionIcons = []; @@ -146,7 +169,7 @@ function DefaultEditorAgg({ color: 'text', disabled: isDisabled, type: 'eye', - onClick: () => onToggleEnableAgg(agg, false), + onClick: () => onToggleEnableAgg(agg.id, false), tooltip: i18n.translate('common.ui.vis.editors.agg.disableAggButtonTooltip', { defaultMessage: 'Disable aggregation', }), @@ -158,7 +181,7 @@ function DefaultEditorAgg({ id: 'enableAggregation', color: 'text', type: 'eyeClosed', - onClick: () => onToggleEnableAgg(agg, true), + onClick: () => onToggleEnableAgg(agg.id, true), tooltip: i18n.translate('common.ui.vis.editors.agg.enableAggButtonTooltip', { defaultMessage: 'Enable aggregation', }), @@ -180,7 +203,7 @@ function DefaultEditorAgg({ id: 'removeDimension', color: 'danger', type: 'cross', - onClick: () => removeAgg(agg), + onClick: () => removeAgg(agg.id), tooltip: i18n.translate('common.ui.vis.editors.agg.removeDimensionButtonTooltip', { defaultMessage: 'Remove dimension', }), @@ -248,9 +271,10 @@ function DefaultEditorAgg({ {SchemaComponent && ( )} diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts index 232eaba76f3a1..a0ddc9a757cc7 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts @@ -18,29 +18,32 @@ */ import { AggType } from 'ui/agg_types'; -import { AggConfig, VisState, VisParams } from '../../..'; -import { AggParams } from '../agg_params'; +import { AggConfig, VisState, VisParams } from 'ui/vis'; import { AggGroupNames } from '../agg_groups'; +import { Schema } from '../schemas'; -export type OnAggParamsChange = < - Params extends AggParams | VisParams, - ParamName extends keyof Params ->( - params: Params, - paramName: ParamName, - value: Params[ParamName] -) => void; +type AggId = AggConfig['id']; +type AggParams = AggConfig['params']; -export interface DefaultEditorAggCommonProps { +export type AddSchema = (schemas: Schema) => void; +export type ReorderAggs = (sourceAgg: AggConfig, destinationAgg: AggConfig) => void; + +export interface DefaultEditorCommonProps { formIsTouched: boolean; groupName: AggGroupNames; - lastParentPipelineAggTitle?: string; metricAggs: AggConfig[]; state: VisState; - onAggParamsChange: OnAggParamsChange; - onAggTypeChange: (agg: AggConfig, aggType: AggType) => void; - onToggleEnableAgg: (agg: AggConfig, isEnable: boolean) => void; - removeAgg: (agg: AggConfig) => void; - setTouched: (isTouched: boolean) => void; - setValidity: (isValid: boolean) => void; + setAggParamValue: ( + aggId: AggId, + paramName: T, + value: AggParams[T] + ) => void; + onAggTypeChange: (aggId: AggId, aggType: AggType) => void; +} + +export interface DefaultEditorAggCommonProps extends DefaultEditorCommonProps { + lastParentPipelineAggTitle?: string; + setStateParamValue: (paramName: T, value: VisParams[T]) => void; + onToggleEnableAgg: (aggId: AggId, isEnable: boolean) => void; + removeAgg: (aggId: AggId) => void; } diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx index 05e8c44b9c720..ae36503c16133 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx @@ -109,8 +109,9 @@ describe('DefaultEditorAgg component', () => { reorderAggs, addSchema: () => {}, removeAgg: () => {}, - onAggParamsChange: () => {}, - onAggTypeChange: () => {}, + setAggParamValue: jest.fn(), + setStateParamValue: jest.fn(), + onAggTypeChange: jest.fn(), onToggleEnableAgg: () => {}, }; }); @@ -127,46 +128,6 @@ describe('DefaultEditorAgg component', () => { expect(setTouched).toBeCalledWith(false); }); - it('should mark group as touched when all invalid aggs are touched', () => { - defaultProps.groupName = AggGroupNames.Buckets; - const comp = mount(); - act(() => { - const aggProps = comp.find(DefaultEditorAgg).props(); - aggProps.setValidity(false); - aggProps.setTouched(true); - }); - - expect(setTouched).toBeCalledWith(true); - }); - - it('should mark group as touched when the form applied', () => { - const comp = mount(); - act(() => { - comp - .find(DefaultEditorAgg) - .first() - .props() - .setValidity(false); - }); - expect(setTouched).toBeCalledWith(false); - comp.setProps({ formIsTouched: true }); - - expect(setTouched).toBeCalledWith(true); - }); - - it('should mark group as invalid when at least one agg is invalid', () => { - const comp = mount(); - act(() => { - comp - .find(DefaultEditorAgg) - .first() - .props() - .setValidity(false); - }); - - expect(setValidity).toBeCalledWith(false); - }); - it('should last bucket has truthy isLastBucket prop', () => { defaultProps.groupName = AggGroupNames.Buckets; const comp = mount(); @@ -182,10 +143,10 @@ describe('DefaultEditorAgg component', () => { comp.props().onDragEnd({ source: { index: 0 }, destination: { index: 1 } }); }); - expect(reorderAggs).toHaveBeenCalledWith([ - defaultProps.state.aggs.aggs[1], + expect(reorderAggs).toHaveBeenCalledWith( defaultProps.state.aggs.aggs[0], - ]); + defaultProps.state.aggs.aggs[1] + ); }); it('should show add button when schemas count is less than max', () => { diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx index 528914f4fd006..7416c36bd5cf1 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect, useReducer, useMemo } from 'react'; +import React, { useEffect, useReducer, useMemo, useCallback } from 'react'; import { EuiTitle, EuiDragDropContext, @@ -34,7 +34,7 @@ import { AggConfig } from '../../../../agg_types/agg_config'; import { aggGroupNamesMap, AggGroupNames } from '../agg_groups'; import { DefaultEditorAgg } from './agg'; import { DefaultEditorAggAdd } from './agg_add'; -import { DefaultEditorAggCommonProps } from './agg_common_props'; +import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from './agg_common_props'; import { isInvalidAggsTouched, isAggRemovable, @@ -46,8 +46,10 @@ import { Schema } from '../schemas'; export interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps { schemas: Schema[]; - addSchema: (schemas: Schema) => void; - reorderAggs: (group: AggConfig[]) => void; + addSchema: AddSchema; + reorderAggs: ReorderAggs; + setValidity(modelName: string, value: boolean): void; + setTouched(isTouched: boolean): void; } function DefaultEditorAggGroup({ @@ -58,7 +60,8 @@ function DefaultEditorAggGroup({ state, schemas = [], addSchema, - onAggParamsChange, + setAggParamValue, + setStateParamValue, onAggTypeChange, onToggleEnableAgg, removeAgg, @@ -66,10 +69,12 @@ function DefaultEditorAggGroup({ setTouched, setValidity, }: DefaultEditorAggGroupProps) { - const groupNameLabel = aggGroupNamesMap()[groupName]; + const groupNameLabel = (aggGroupNamesMap() as any)[groupName]; // e.g. buckets can have no aggs - const group: AggConfig[] = - state.aggs.aggs.filter((agg: AggConfig) => agg.schema.group === groupName) || []; + const group: AggConfig[] = useMemo( + () => state.aggs.aggs.filter((agg: AggConfig) => agg.schema.group === groupName) || [], + [state.aggs.aggs] + ); const stats = { max: 0, @@ -101,7 +106,7 @@ function DefaultEditorAggGroup({ // when isAllAggsTouched is true, it means that all invalid aggs are touched and we will set ngModel's touched to true // which indicates that Apply button can be changed to Error button (when all invalid ngModels are touched) setTouched(isAllAggsTouched); - }, [isAllAggsTouched]); + }, [isAllAggsTouched, setTouched]); useEffect(() => { // when not all invalid aggs are touched and formIsTouched becomes true, it means that Apply button was clicked. @@ -118,38 +123,21 @@ function DefaultEditorAggGroup({ }, [formIsTouched]); useEffect(() => { - setValidity(isGroupValid); - }, [isGroupValid]); - - const onDragEnd: DragDropContextProps['onDragEnd'] = ({ source, destination }) => { - if (source && destination) { - const orderedGroup = Array.from(group); - const [removed] = orderedGroup.splice(source.index, 1); - orderedGroup.splice(destination.index, 0, removed); - - reorderAggs(orderedGroup); - } - }; - - const setTouchedHandler = (aggId: string, touched: boolean) => { - setAggsState({ - type: AGGS_ACTION_KEYS.TOUCHED, - payload: touched, - aggId, - }); - }; - - const setValidityHandler = (aggId: string, valid: boolean) => { - setAggsState({ - type: AGGS_ACTION_KEYS.VALID, - payload: valid, - aggId, - }); - }; + setValidity(`aggGroup__${groupName}`, isGroupValid); + }, [groupName, isGroupValid, setValidity]); + + const onDragEnd: DragDropContextProps['onDragEnd'] = useCallback( + ({ source, destination }) => { + if (source && destination) { + reorderAggs(group[source.index], group[destination.index]); + } + }, + [reorderAggs, group] + ); return ( - +

{groupNameLabel}

@@ -184,12 +172,12 @@ function DefaultEditorAggGroup({ lastParentPipelineAggTitle={lastParentPipelineAggTitle} metricAggs={metricAggs} state={state} - onAggParamsChange={onAggParamsChange} + setAggParamValue={setAggParamValue} + setStateParamValue={setStateParamValue} onAggTypeChange={onAggTypeChange} onToggleEnableAgg={onToggleEnableAgg} removeAgg={removeAgg} - setTouched={isTouched => setTouchedHandler(agg.id, isTouched)} - setValidity={isValid => setValidityHandler(agg.id, isValid)} + setAggsState={setAggsState} /> )} diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx index 980889743c20d..0b787e45a5008 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx @@ -33,7 +33,7 @@ export interface AggsState { [aggId: string]: AggsItem; } -interface AggsAction { +export interface AggsAction { type: AGGS_ACTION_KEYS; payload: boolean; aggId: string; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx index 72c802d7d237e..d3bbf3cc9903a 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx @@ -17,18 +17,59 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { AggParamEditorProps, AggParamCommonProps } from './agg_param_props'; -import { OnAggParamsChange } from './agg_common_props'; +import { DefaultEditorAggCommonProps } from './agg_common_props'; +import { AGG_PARAMS_ACTION_KEYS, AggParamsAction } from './agg_params_state'; interface DefaultEditorAggParamProps extends AggParamCommonProps { paramEditor: React.ComponentType>; - onChange: OnAggParamsChange; + setAggParamValue: DefaultEditorAggCommonProps['setAggParamValue']; + onChangeParamsState: React.Dispatch; } function DefaultEditorAggParam(props: DefaultEditorAggParamProps) { - const { agg, aggParam, paramEditor: ParamEditor, onChange, setValidity, ...rest } = props; + const { + agg, + aggParam, + paramEditor: ParamEditor, + setAggParamValue, + onChangeParamsState, + ...rest + } = props; + + const setValidity = useCallback( + (valid: boolean) => { + onChangeParamsState({ + type: AGG_PARAMS_ACTION_KEYS.VALID, + paramName: aggParam.name, + payload: valid, + }); + }, + [onChangeParamsState, aggParam.name] + ); + + // setTouched can be called from sub-agg which passes a parameter + const setTouched = useCallback( + (isTouched: boolean = true) => { + onChangeParamsState({ + type: AGG_PARAMS_ACTION_KEYS.TOUCHED, + paramName: aggParam.name, + payload: isTouched, + }); + }, + [onChangeParamsState, aggParam.name] + ); + + const setValue = useCallback( + (value: T) => { + if (props.value !== value) { + setAggParamValue(agg.id, aggParam.name, value); + } + }, + [setAggParamValue, agg.id, aggParam.name, props.value] + ); useEffect(() => { if (aggParam.shouldShow && !aggParam.shouldShow(agg)) { @@ -45,7 +86,8 @@ function DefaultEditorAggParam(props: DefaultEditorAggParamProps) { agg={agg} aggParam={aggParam} setValidity={setValidity} - setValue={(value: T) => onChange(agg.params, aggParam.name, value)} + setTouched={setTouched} + setValue={setValue} {...rest} /> ); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts index df77ea9e43eab..953f49d84f5c5 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts @@ -22,28 +22,27 @@ import { AggConfig } from '../../../../agg_types/agg_config'; import { ComboBoxGroupedOptions } from '../utils'; import { EditorConfig } from '../../config/types'; import { VisState } from '../../..'; -import { SubAggParamsProp } from './agg_params'; import { Field } from '../../../../../../../plugins/data/public'; // NOTE: we cannot export the interface with export { InterfaceName } // as there is currently a bug on babel typescript transform plugin for it // https://github.com/babel/babel/issues/7641 // -export interface AggParamCommonProps { +export interface AggParamCommonProps { agg: AggConfig; - aggParam: AggParam; + aggParam: P; disabled?: boolean; editorConfig: EditorConfig; + formIsTouched: boolean; indexedFields?: ComboBoxGroupedOptions; showValidation: boolean; state: VisState; value?: T; metricAggs: AggConfig[]; - subAggParams: SubAggParamsProp; - setValidity(isValid: boolean): void; - setTouched(): void; } -export interface AggParamEditorProps extends AggParamCommonProps { +export interface AggParamEditorProps extends AggParamCommonProps { setValue(value?: T): void; + setValidity(isValid: boolean): void; + setTouched(): void; } diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx index 9f3f5bff3f56e..8c59d69da9478 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { AggConfig, VisState } from '../../..'; import { DefaultEditorAggParams, DefaultEditorAggParamsProps } from './agg_params'; +import { AggGroupNames } from '../agg_groups'; import { IndexPattern } from '../../../../../../../plugins/data/public'; const mockEditorConfig = { @@ -79,7 +80,7 @@ jest.mock('./agg_param', () => ({ })); describe('DefaultEditorAggParams component', () => { - let onAggParamsChange: jest.Mock; + let setAggParamValue: jest.Mock; let onAggTypeChange: jest.Mock; let setTouched: jest.Mock; let setValidity: jest.Mock; @@ -87,7 +88,7 @@ describe('DefaultEditorAggParams component', () => { let defaultProps: DefaultEditorAggParamsProps; beforeEach(() => { - onAggParamsChange = jest.fn(); + setAggParamValue = jest.fn(); onAggTypeChange = jest.fn(); setTouched = jest.fn(); setValidity = jest.fn(); @@ -99,13 +100,16 @@ describe('DefaultEditorAggParams component', () => { params: [{ name: 'interval', deserialize: intervalDeserialize }], }, params: {}, + schema: { + title: '', + }, } as any) as AggConfig, - groupName: 'metrics', + groupName: AggGroupNames.Metrics, formIsTouched: false, indexPattern: {} as IndexPattern, metricAggs: [], state: {} as VisState, - onAggParamsChange, + setAggParamValue, onAggTypeChange, setTouched, setValidity, @@ -131,16 +135,16 @@ describe('DefaultEditorAggParams component', () => { it('should set fixed and default values when editorConfig is defined (works in rollup index)', () => { mount(); - expect(onAggParamsChange).toHaveBeenNthCalledWith( + expect(setAggParamValue).toHaveBeenNthCalledWith( 1, - defaultProps.agg.params, + defaultProps.agg.id, 'useNormalizedEsInterval', false ); expect(intervalDeserialize).toHaveBeenCalledWith('1m'); - expect(onAggParamsChange).toHaveBeenNthCalledWith( + expect(setAggParamValue).toHaveBeenNthCalledWith( 2, - defaultProps.agg.params, + defaultProps.agg.id, 'interval', 'deserialized' ); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx index c62e2837908c7..0f47d9a555d21 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx @@ -17,60 +17,48 @@ * under the License. */ -import React, { useReducer, useEffect, useMemo } from 'react'; +import React, { useCallback, useReducer, useEffect, useMemo } from 'react'; import { EuiForm, EuiAccordion, EuiSpacer, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import useUnmount from 'react-use/lib/useUnmount'; -import { VisState } from 'ui/vis'; -import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/'; +import { AggConfig } from 'ui/agg_types/'; import { IndexPattern } from '../../../../../../../plugins/data/public'; import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; import { getAggParamsToRender, - getError, getAggTypeOptions, - ParamInstance, isInvalidParamsTouched, } from './agg_params_helper'; import { aggTypeReducer, - AGG_TYPE_ACTION_KEYS, aggParamsReducer, AGG_PARAMS_ACTION_KEYS, initAggParamsState, - AggParamsItem, } from './agg_params_state'; import { editorConfigProviders } from '../../config/editor_config_providers'; import { FixedParam, TimeIntervalParam, EditorParamConfig } from '../../config/types'; import { AggGroupNames } from '../agg_groups'; -import { OnAggParamsChange } from './agg_common_props'; +import { DefaultEditorCommonProps } from './agg_common_props'; const FIXED_VALUE_PROP = 'fixedValue'; const DEFAULT_PROP = 'default'; type EditorParamConfigType = EditorParamConfig & { [key: string]: unknown; }; -export interface SubAggParamsProp { - formIsTouched: boolean; - onAggParamsChange: OnAggParamsChange; - onAggTypeChange: (agg: AggConfig, aggType: AggType) => void; -} -export interface DefaultEditorAggParamsProps extends SubAggParamsProp { + +export interface DefaultEditorAggParamsProps extends DefaultEditorCommonProps { agg: AggConfig; aggError?: string; aggIndex?: number; aggIsTooLow?: boolean; className?: string; disabledParams?: string[]; - groupName: string; indexPattern: IndexPattern; - metricAggs: AggConfig[]; - state: VisState; - setTouched: (isTouched: boolean) => void; setValidity: (isValid: boolean) => void; + setTouched: (isTouched: boolean) => void; } function DefaultEditorAggParams({ @@ -84,20 +72,34 @@ function DefaultEditorAggParams({ formIsTouched, indexPattern, metricAggs, - state = {} as VisState, - onAggParamsChange, + state, + setAggParamValue, onAggTypeChange, setTouched, setValidity, }: DefaultEditorAggParamsProps) { - const groupedAggTypeOptions = getAggTypeOptions(agg, indexPattern, groupName); - const errors = getError(agg, aggIsTooLow); - - const editorConfig = useMemo( - () => editorConfigProviders.getConfigForAgg((aggTypes as any)[groupName], indexPattern, agg), - [groupName, agg.type] - ); - const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state }); + const groupedAggTypeOptions = useMemo(() => getAggTypeOptions(agg, indexPattern, groupName), [ + agg, + indexPattern, + groupName, + ]); + const error = aggIsTooLow + ? i18n.translate('common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage', { + defaultMessage: '"{schema}" aggs must run before all other buckets!', + values: { schema: agg.schema.title }, + }) + : ''; + + const editorConfig = useMemo(() => editorConfigProviders.getConfigForAgg(indexPattern, agg), [ + indexPattern, + agg, + ]); + const params = useMemo(() => getAggParamsToRender({ agg, editorConfig, metricAggs, state }), [ + agg, + editorConfig, + metricAggs, + state, + ]); const allParams = [...params.basic, ...params.advanced]; const [paramsState, onChangeParamsState] = useReducer( aggParamsReducer, @@ -107,21 +109,30 @@ function DefaultEditorAggParams({ const [aggType, onChangeAggType] = useReducer(aggTypeReducer, { touched: false, valid: true }); const isFormValid = - !errors.length && + !error && aggType.valid && Object.entries(paramsState).every(([, paramState]) => paramState.valid); const isAllInvalidParamsTouched = - !!errors.length || isInvalidParamsTouched(agg.type, aggType, paramsState); + !!error || isInvalidParamsTouched(agg.type, aggType, paramsState); + + const onAggSelect = useCallback( + value => { + if (agg.type !== value) { + onAggTypeChange(agg.id, value); + // reset touched and valid of params + onChangeParamsState({ type: AGG_PARAMS_ACTION_KEYS.RESET }); + } + }, + [onAggTypeChange, agg] + ); // reset validity before component destroyed useUnmount(() => setValidity(true)); useEffect(() => { Object.entries(editorConfig).forEach(([param, paramConfig]) => { - const paramOptions = agg.type.params.find( - (paramOption: AggParam) => paramOption.name === param - ); + const paramOptions = agg.type.params.find(paramOption => paramOption.name === param); const hasFixedValue = paramConfig.hasOwnProperty(FIXED_VALUE_PROP); const hasDefault = paramConfig.hasOwnProperty(DEFAULT_PROP); @@ -142,7 +153,11 @@ function DefaultEditorAggParams({ } else { newValue = typedParamConfig[property]; } - onAggParamsChange(agg.params, param, newValue); + + // this check is obligatory to avoid infinite render, because setAggParamValue creates a brand new agg object + if (agg.params[param] !== newValue) { + setAggParamValue(agg.id, param, newValue); + } } }); }, [editorConfig]); @@ -160,43 +175,11 @@ function DefaultEditorAggParams({ setTouched(isAllInvalidParamsTouched); }, [isAllInvalidParamsTouched]); - const renderParam = (paramInstance: ParamInstance, model: AggParamsItem) => { - return ( - { - onChangeParamsState({ - type: AGG_PARAMS_ACTION_KEYS.VALID, - paramName: paramInstance.aggParam.name, - payload: valid, - }); - }} - // setTouched can be called from sub-agg which passes a parameter - setTouched={(isTouched: boolean = true) => { - onChangeParamsState({ - type: AGG_PARAMS_ACTION_KEYS.TOUCHED, - paramName: paramInstance.aggParam.name, - payload: isTouched, - }); - }} - subAggParams={{ - onAggParamsChange, - onAggTypeChange, - formIsTouched, - }} - {...paramInstance} - /> - ); - }; - return ( = 1 && groupName === AggGroupNames.Buckets} showValidation={formIsTouched || aggType.touched} - setValue={value => { - onAggTypeChange(agg, value); - // reset touched and valid of params - onChangeParamsState({ type: AGG_PARAMS_ACTION_KEYS.RESET }); - }} - setTouched={() => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.TOUCHED, payload: true })} - setValidity={valid => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.VALID, payload: valid })} + setValue={onAggSelect} + onChangeAggType={onChangeAggType} /> - {params.basic.map((param: ParamInstance) => { + {params.basic.map(param => { const model = paramsState[param.aggParam.name] || { touched: false, valid: true, }; - return renderParam(param, model); + return ( + + ); })} {params.advanced.length ? ( @@ -238,12 +226,23 @@ function DefaultEditorAggParams({ )} > - {params.advanced.map((param: ParamInstance) => { + {params.advanced.map(param => { const model = paramsState[param.aggParam.name] || { touched: false, valid: true, }; - return renderParam(param, model); + + return ( + + ); })} diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts index fb37dd5d94618..c983bb7813de7 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts @@ -22,7 +22,6 @@ import { AggType } from 'ui/agg_types'; import { IndexedArray } from 'ui/indexed_array'; import { getAggParamsToRender, - getError, getAggTypeOptions, isInvalidParamsTouched, } from './agg_params_helper'; @@ -162,20 +161,6 @@ describe('DefaultEditorAggParams helpers', () => { }); }); - describe('getError', () => { - it('should not have any errors', () => { - const errors = getError({ schema: { title: 'Split series' } } as AggConfig, false); - - expect(errors).toEqual([]); - }); - - it('should push an error if an agg is too low', () => { - const errors = getError({ schema: { title: 'Split series' } } as AggConfig, true); - - expect(errors).toEqual(['"Split series" aggs must run before all other buckets!']); - }); - }); - describe('getAggTypeOptions', () => { it('should return agg type options grouped by subtype', () => { const indexPattern = {} as IndexPattern; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts index e0e014f69ef3f..3970238a68435 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts @@ -18,7 +18,6 @@ */ import { get, isEmpty } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { aggTypeFilters } from 'ui/agg_types/filter'; import { aggTypes, AggParam, FieldParamType, AggType } from 'ui/agg_types'; import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter'; @@ -91,27 +90,13 @@ function getAggParamsToRender({ agg, editorConfig, metricAggs, state }: ParamIns metricAggs, state, value: agg.params[param.name], - } as ParamInstance); + }); } }); return params; } -function getError(agg: AggConfig, aggIsTooLow: boolean) { - const errors = []; - if (aggIsTooLow) { - errors.push( - i18n.translate('common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage', { - defaultMessage: '"{schema}" aggs must run before all other buckets!', - values: { schema: agg.schema.title }, - }) - ); - } - - return errors; -} - function getAggTypeOptions( agg: AggConfig, indexPattern: IndexPattern, @@ -148,4 +133,4 @@ function isInvalidParamsTouched( return invalidParams.every(param => param.touched); } -export { getAggParamsToRender, getError, getAggTypeOptions, isInvalidParamsTouched }; +export { getAggParamsToRender, getAggTypeOptions, isInvalidParamsTouched }; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx index 443c655912a55..6b6bb93b29b3e 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx @@ -17,7 +17,7 @@ * under the License. */ import { get, has } from 'lodash'; -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -26,6 +26,7 @@ import { AggType } from 'ui/agg_types'; import { documentationLinks } from '../../../../documentation_links/documentation_links'; import { ComboBoxGroupedOptions } from '../utils'; import { IndexPattern } from '../../../../../../../plugins/data/public'; +import { AGG_TYPE_ACTION_KEYS, AggTypeAction } from './agg_params_state'; interface DefaultEditorAggSelectProps { aggError?: string; @@ -35,9 +36,8 @@ interface DefaultEditorAggSelectProps { showValidation: boolean; isSubAggregation: boolean; value: AggType; - setValidity: (isValid: boolean) => void; + onChangeAggType: React.Dispatch; setValue: (aggType: AggType) => void; - setTouched: () => void; } function DefaultEditorAggSelect({ @@ -49,8 +49,7 @@ function DefaultEditorAggSelect({ aggTypeOptions, showValidation, isSubAggregation, - setTouched, - setValidity, + onChangeAggType, }: DefaultEditorAggSelectProps) { const selectedOptions: ComboBoxGroupedOptions = value ? [{ label: value.title, target: value }] @@ -101,6 +100,25 @@ function DefaultEditorAggSelect({ const isValid = !!value && !errors.length; + const onChange = useCallback( + (options: EuiComboBoxOptionProps[]) => { + const selectedOption = get(options, '0.target'); + if (selectedOption) { + setValue(selectedOption as AggType); + } + }, + [setValue] + ); + + const setTouched = useCallback( + () => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.TOUCHED, payload: true }), + [onChangeAggType] + ); + const setValidity = useCallback( + valid => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.VALID, payload: valid }), + [onChangeAggType] + ); + useEffect(() => { setValidity(isValid); }, [isValid]); @@ -111,13 +129,6 @@ function DefaultEditorAggSelect({ } }, [errors.length]); - const onChange = (options: EuiComboBoxOptionProps[]) => { - const selectedOption = get(options, '0.target'); - if (selectedOption) { - setValue(selectedOption as AggType); - } - }; - return ( ; + vis: Vis; +} + +function DefaultEditorControls({ + applyChanges, + isDirty, + isInvalid, + isTouched, + dispatch, + vis, +}: DefaultEditorControlsProps) { + const { enableAutoApply } = vis.type.editorConfig; + const [autoApplyEnabled, setAutoApplyEnabled] = useState(false); + const toggleAutoApply = useCallback(e => setAutoApplyEnabled(e.target.checked), []); + const onClickDiscard = useCallback(() => dispatch(discardChanges(vis)), [dispatch, vis]); + + useDebounce( + () => { + if (autoApplyEnabled && isDirty) { + applyChanges(); + } + }, + 300, + [isDirty, autoApplyEnabled, applyChanges] + ); + + return ( +
+ {!autoApplyEnabled && ( + + + + + + + + + {isInvalid && isTouched ? ( + + + + + + ) : ( + + + + )} + + + )} + {enableAutoApply && ( + + + + )} +
+ ); +} + +export { DefaultEditorControls }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx new file mode 100644 index 0000000000000..4481ec5cb9b83 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx @@ -0,0 +1,135 @@ +/* + * 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, { useMemo, useCallback } from 'react'; +import { findLast } from 'lodash'; +import { EuiSpacer } from '@elastic/eui'; + +import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; +import { AggConfig } from 'ui/agg_types'; +import { MetricAggType } from 'ui/agg_types/metrics/metric_agg_type'; +import { VisState } from 'ui/vis'; +import { DefaultEditorAggGroup } from '../agg_group'; +import { AggGroupNames } from '../../agg_groups'; +import { + EditorAction, + addNewAgg, + removeAgg, + reorderAggs, + setAggParamValue, + changeAggType, + toggleEnabledAgg, +} from './state'; +import { ISchemas } from '../../schemas'; +import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props'; + +export interface DefaultEditorDataTabProps { + dispatch: React.Dispatch; + formIsTouched: boolean; + isTabSelected: boolean; + metricAggs: AggConfig[]; + schemas: ISchemas; + state: VisState; + setTouched(isTouched: boolean): void; + setValidity(modelName: string, value: boolean): void; + setStateValue: DefaultEditorAggCommonProps['setStateParamValue']; +} + +function DefaultEditorDataTab({ + dispatch, + formIsTouched, + metricAggs, + schemas, + state, + setTouched, + setValidity, + setStateValue, +}: DefaultEditorDataTabProps) { + const lastParentPipelineAgg = useMemo( + () => + findLast( + metricAggs, + ({ type }: { type: MetricAggType }) => type.subtype === parentPipelineAggHelper.subtype + ), + [metricAggs] + ); + const lastParentPipelineAggTitle = lastParentPipelineAgg && lastParentPipelineAgg.type.title; + + const addSchema: AddSchema = useCallback(schema => dispatch(addNewAgg(schema)), [dispatch]); + + const onAggRemove: DefaultEditorAggCommonProps['removeAgg'] = useCallback( + aggId => dispatch(removeAgg(aggId)), + [dispatch] + ); + + const onReorderAggs: ReorderAggs = useCallback((...props) => dispatch(reorderAggs(...props)), [ + dispatch, + ]); + + const onAggParamValueChange: DefaultEditorAggCommonProps['setAggParamValue'] = useCallback( + (...props) => dispatch(setAggParamValue(...props)), + [dispatch] + ); + + const onAggTypeChange: DefaultEditorAggCommonProps['onAggTypeChange'] = useCallback( + (...props) => dispatch(changeAggType(...props)), + [dispatch] + ); + + const onToggleEnableAgg: DefaultEditorAggCommonProps['onToggleEnableAgg'] = useCallback( + (...props) => dispatch(toggleEnabledAgg(...props)), + [dispatch] + ); + + const commonProps = { + addSchema, + formIsTouched, + lastParentPipelineAggTitle, + metricAggs, + state, + reorderAggs: onReorderAggs, + setAggParamValue: onAggParamValueChange, + setStateParamValue: setStateValue, + onAggTypeChange, + onToggleEnableAgg, + setValidity, + setTouched, + removeAgg: onAggRemove, + }; + + return ( + <> + + + + + + + ); +} + +export { DefaultEditorDataTab }; diff --git a/src/legacy/core_plugins/interpreter/public/canvas/consts.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts similarity index 85% rename from src/legacy/core_plugins/interpreter/public/canvas/consts.ts rename to src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts index 2600ada36afdc..31228aad85d1e 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/consts.ts +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts @@ -17,5 +17,6 @@ * under the License. */ -// The server endpoint for retrieiving and running Canvas functions. -export const FUNCTIONS_URL = '/api/interpreter/fns'; +export { DefaultEditorSideBar } from './sidebar'; +export { DefaultEditorDataTab } from './data_tab'; +export { OptionTab } from './navbar'; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx new file mode 100644 index 0000000000000..a1b5003a092f7 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx @@ -0,0 +1,59 @@ +/* + * 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 { EuiTabs, EuiTab } from '@elastic/eui'; + +import { VisOptionsProps } from '../../vis_options_props'; +import { DefaultEditorDataTabProps } from './data_tab'; + +export interface OptionTab { + editor: React.ComponentType; + name: string; + title: string; +} + +interface DefaultEditorNavBarProps { + optionTabs: OptionTab[]; + selectedTab: string; + setSelectedTab(name: string): void; +} + +function DefaultEditorNavBar({ + selectedTab, + setSelectedTab, + optionTabs, +}: DefaultEditorNavBarProps) { + return ( + + {optionTabs.map(({ name, title }) => ( + setSelectedTab(name)} + > + {title} + + ))} + + ); +} + +export { DefaultEditorNavBar }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx new file mode 100644 index 0000000000000..bf35c46dbb7b5 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx @@ -0,0 +1,226 @@ +/* + * 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, { useMemo, useState, useCallback, KeyboardEventHandler, useEffect } from 'react'; +import { get, isEqual } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; + +import { Vis } from 'ui/vis'; +import { PersistedState } from 'ui/persisted_state'; +import { DefaultEditorNavBar, OptionTab } from './navbar'; +import { DefaultEditorControls } from './controls'; +import { setStateParamValue, useEditorReducer, useEditorFormState } from './state'; +import { AggGroupNames } from '../../agg_groups'; +import { DefaultEditorAggCommonProps } from '../agg_common_props'; + +interface DefaultEditorSideBarProps { + isCollapsed: boolean; + onClickCollapse: () => void; + optionTabs: OptionTab[]; + uiState: PersistedState; + vis: Vis; +} + +function DefaultEditorSideBar({ + isCollapsed, + onClickCollapse, + optionTabs, + uiState, + vis, +}: DefaultEditorSideBarProps) { + const [selectedTab, setSelectedTab] = useState(optionTabs[0].name); + const [isDirty, setDirty] = useState(false); + const [state, dispatch] = useEditorReducer(vis); + const { formState, setTouched, setValidity, resetValidity } = useEditorFormState(); + + const responseAggs = useMemo(() => state.aggs.getResponseAggs(), [state.aggs]); + const metricAggs = useMemo( + () => responseAggs.filter(agg => get(agg, 'schema.group') === AggGroupNames.Metrics), + [responseAggs] + ); + const hasHistogramAgg = useMemo(() => responseAggs.some(agg => agg.type.name === 'histogram'), [ + responseAggs, + ]); + + const setStateValidity = useCallback( + (value: boolean) => { + setValidity('visOptions', value); + }, + [setValidity] + ); + + const setStateValue: DefaultEditorAggCommonProps['setStateParamValue'] = useCallback( + (paramName, value) => { + const shouldUpdate = !isEqual(state.params[paramName], value); + + if (shouldUpdate) { + dispatch(setStateParamValue(paramName, value)); + } + }, + [dispatch, state.params] + ); + + const applyChanges = useCallback(() => { + if (formState.invalid || !isDirty) { + setTouched(true); + + return; + } + + vis.setCurrentState(state); + vis.updateState(); + vis.emit('dirtyStateChange', { + isDirty: false, + }); + setTouched(false); + }, [vis, state, formState.invalid, setDirty, setTouched, isDirty]); + + const onSubmit: KeyboardEventHandler = useCallback( + event => { + if (event.ctrlKey && event.keyCode === keyCodes.ENTER) { + event.preventDefault(); + event.stopPropagation(); + + applyChanges(); + } + }, + [applyChanges] + ); + + useEffect(() => { + vis.on('dirtyStateChange', ({ isDirty: dirty }: { isDirty: boolean }) => { + setDirty(dirty); + + if (!dirty) { + resetValidity(); + } + }); + }, [resetValidity, vis]); + + const dataTabProps = { + dispatch, + formIsTouched: formState.touched, + metricAggs, + state, + schemas: vis.type.schemas, + setValidity, + setTouched, + setStateValue, + }; + + const optionTabProps = { + aggs: state.aggs, + hasHistogramAgg, + stateParams: state.params, + vis, + uiState, + setValue: setStateValue, + setValidity: setStateValidity, + setTouched, + }; + + return ( + <> + + +
+ {vis.type.requiresSearch && vis.type.options.showIndexSelection ? ( + +

+ {vis.indexPattern.title} +

+
+ ) : ( +
+ )} + + {optionTabs.length > 1 && ( + + )} + + {optionTabs.map(({ editor: Editor, name }) => { + const isTabSelected = selectedTab === name; + + return ( +
+ +
+ ); + })} + + + + + + + + + + + ); +} + +export { DefaultEditorSideBar }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts new file mode 100644 index 0000000000000..ab1d65c626ae3 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts @@ -0,0 +1,171 @@ +/* + * 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 { AggConfig, Vis, VisParams } from 'ui/vis'; +import { EditorStateActionTypes } from './constants'; +import { Schema } from '../../../schemas'; + +export interface ActionType { + type: T; + payload: P; +} + +type AggId = AggConfig['id']; +type AggParams = AggConfig['params']; + +type AddNewAgg = ActionType; +type DiscardChanges = ActionType; +type ChangeAggType = ActionType< + EditorStateActionTypes.CHANGE_AGG_TYPE, + { aggId: AggId; value: AggConfig['type'] } +>; +type SetAggParamValue = ActionType< + EditorStateActionTypes.SET_AGG_PARAM_VALUE, + { + aggId: AggId; + paramName: T; + value: AggParams[T]; + } +>; +type SetStateParamValue = ActionType< + EditorStateActionTypes.SET_STATE_PARAM_VALUE, + { paramName: T; value: AggParams[T] } +>; +type RemoveAgg = ActionType; +type ReorderAggs = ActionType< + EditorStateActionTypes.REORDER_AGGS, + { sourceAgg: AggConfig; destinationAgg: AggConfig } +>; +type ToggleEnabledAgg = ActionType< + EditorStateActionTypes.TOGGLE_ENABLED_AGG, + { aggId: AggId; enabled: AggConfig['enabled'] } +>; +type UpdateStateParams = ActionType< + EditorStateActionTypes.UPDATE_STATE_PARAMS, + { params: VisParams } +>; + +export type EditorAction = + | AddNewAgg + | DiscardChanges + | ChangeAggType + | SetAggParamValue + | SetStateParamValue + | RemoveAgg + | ReorderAggs + | ToggleEnabledAgg + | UpdateStateParams; + +export interface EditorActions { + addNewAgg(schema: Schema): AddNewAgg; + discardChanges(vis: Vis): DiscardChanges; + changeAggType(aggId: AggId, value: AggConfig['type']): ChangeAggType; + setAggParamValue( + aggId: AggId, + paramName: T, + value: AggParams[T] + ): SetAggParamValue; + setStateParamValue( + paramName: T, + value: AggParams[T] + ): SetStateParamValue; + removeAgg(aggId: AggId): RemoveAgg; + reorderAggs(sourceAgg: AggConfig, destinationAgg: AggConfig): ReorderAggs; + toggleEnabledAgg(aggId: AggId, enabled: AggConfig['enabled']): ToggleEnabledAgg; + updateStateParams(params: VisParams): UpdateStateParams; +} + +const addNewAgg: EditorActions['addNewAgg'] = schema => ({ + type: EditorStateActionTypes.ADD_NEW_AGG, + payload: { + schema, + }, +}); + +const discardChanges: EditorActions['discardChanges'] = vis => ({ + type: EditorStateActionTypes.DISCARD_CHANGES, + payload: vis, +}); + +const changeAggType: EditorActions['changeAggType'] = (aggId, value) => ({ + type: EditorStateActionTypes.CHANGE_AGG_TYPE, + payload: { + aggId, + value, + }, +}); + +const setAggParamValue: EditorActions['setAggParamValue'] = (aggId, paramName, value) => ({ + type: EditorStateActionTypes.SET_AGG_PARAM_VALUE, + payload: { + aggId, + paramName, + value, + }, +}); + +const setStateParamValue: EditorActions['setStateParamValue'] = (paramName, value) => ({ + type: EditorStateActionTypes.SET_STATE_PARAM_VALUE, + payload: { + paramName, + value, + }, +}); + +const removeAgg: EditorActions['removeAgg'] = aggId => ({ + type: EditorStateActionTypes.REMOVE_AGG, + payload: { + aggId, + }, +}); + +const reorderAggs: EditorActions['reorderAggs'] = (sourceAgg, destinationAgg) => ({ + type: EditorStateActionTypes.REORDER_AGGS, + payload: { + sourceAgg, + destinationAgg, + }, +}); + +const toggleEnabledAgg: EditorActions['toggleEnabledAgg'] = (aggId, enabled) => ({ + type: EditorStateActionTypes.TOGGLE_ENABLED_AGG, + payload: { + aggId, + enabled, + }, +}); + +const updateStateParams: EditorActions['updateStateParams'] = params => ({ + type: EditorStateActionTypes.UPDATE_STATE_PARAMS, + payload: { + params, + }, +}); + +export { + addNewAgg, + discardChanges, + changeAggType, + setAggParamValue, + setStateParamValue, + removeAgg, + reorderAggs, + toggleEnabledAgg, + updateStateParams, +}; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts new file mode 100644 index 0000000000000..2c5f5f1384858 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts @@ -0,0 +1,30 @@ +/* + * 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 EditorStateActionTypes { + ADD_NEW_AGG = 'ADD_NEW_AGG', + DISCARD_CHANGES = 'DISCARD_CHANGES', + CHANGE_AGG_TYPE = 'CHANGE_AGG_TYPE', + SET_AGG_PARAM_VALUE = 'SET_AGG_PARAM_VALUE', + SET_STATE_PARAM_VALUE = 'SET_STATE_PARAM_VALUE', + TOGGLE_ENABLED_AGG = 'TOGGLE_ENABLED_AGG', + REMOVE_AGG = 'REMOVE_AGG', + REORDER_AGGS = 'REORDER_AGGS', + UPDATE_STATE_PARAMS = 'UPDATE_STATE_PARAMS', +} diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts new file mode 100644 index 0000000000000..1f98a5f7fa7df --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useState, useCallback } from 'react'; + +export type SetValidity = (modelName: string, value: boolean) => void; +export type SetTouched = (value: boolean) => void; + +const initialFormState = { + validity: {}, + touched: false, + invalid: false, +}; + +function useEditorFormState() { + const [formState, setFormState] = useState(initialFormState); + + const setValidity: SetValidity = useCallback((modelName, value) => { + setFormState(model => { + const validity = { + ...model.validity, + [modelName]: value, + }; + + return { + ...model, + validity, + invalid: Object.values(validity).some(valid => !valid), + }; + }); + }, []); + + const resetValidity = useCallback(() => { + setFormState(initialFormState); + }, []); + + const setTouched = useCallback((touched: boolean) => { + setFormState(model => ({ + ...model, + touched, + })); + }, []); + + return { + formState, + setValidity, + setTouched, + resetValidity, + }; +} + +export { useEditorFormState }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts new file mode 100644 index 0000000000000..6dbd9a69d82c0 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts @@ -0,0 +1,59 @@ +/* + * 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 { useEffect, useReducer, useCallback } from 'react'; +import { isEqual } from 'lodash'; + +import { Vis, VisState, VisParams } from 'ui/vis'; +import { editorStateReducer, initEditorState } from './reducers'; +import { EditorStateActionTypes } from './constants'; +import { EditorAction, updateStateParams } from './actions'; + +export * from './editor_form_state'; +export * from './actions'; + +export function useEditorReducer(vis: Vis): [VisState, React.Dispatch] { + const [state, dispatch] = useReducer(editorStateReducer, vis, initEditorState); + + useEffect(() => { + const handleVisUpdate = (params: VisParams) => { + if (!isEqual(params, state.params)) { + dispatch(updateStateParams(params)); + } + }; + + // fires when visualization state changes, and we need to copy changes to editorState + vis.on('updateEditorStateParams', handleVisUpdate); + + return () => vis.off('updateEditorStateParams', handleVisUpdate); + }, [vis, state.params]); + + const wrappedDispatch = useCallback( + (action: EditorAction) => { + dispatch(action); + + vis.emit('dirtyStateChange', { + isDirty: action.type !== EditorStateActionTypes.DISCARD_CHANGES, + }); + }, + [vis] + ); + + return [state, wrappedDispatch]; +} diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts new file mode 100644 index 0000000000000..db52291c823e7 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts @@ -0,0 +1,182 @@ +/* + * 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 { cloneDeep } from 'lodash'; + +import { AggConfigs, AggConfig } from 'ui/agg_types'; +import { Vis, VisState } from 'ui/vis'; +import { move } from 'ui/utils/collection'; +import { EditorStateActionTypes } from './constants'; +import { AggGroupNames } from '../../../agg_groups'; +import { getEnabledMetricAggsCount } from '../../agg_group_helper'; +import { EditorAction } from './actions'; + +function initEditorState(vis: Vis) { + return vis.copyCurrentState(true); +} + +function editorStateReducer(state: VisState, action: EditorAction): VisState { + switch (action.type) { + case EditorStateActionTypes.ADD_NEW_AGG: { + const aggConfig = state.aggs.createAggConfig(action.payload as AggConfig, { + addToAggConfigs: false, + }); + aggConfig.brandNew = true; + const newAggs = [...state.aggs.aggs, aggConfig]; + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.DISCARD_CHANGES: { + return initEditorState(action.payload); + } + + case EditorStateActionTypes.CHANGE_AGG_TYPE: { + const { aggId, value } = action.payload; + + const newAggs = state.aggs.aggs.map(agg => { + if (agg.id === aggId) { + agg.type = value; + + return agg.toJSON(); + } + + return agg; + }); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.SET_AGG_PARAM_VALUE: { + const { aggId, paramName, value } = action.payload; + + const newAggs = state.aggs.aggs.map(agg => { + if (agg.id === aggId) { + const parsedAgg = agg.toJSON(); + + return { + ...parsedAgg, + params: { + ...parsedAgg.params, + [paramName]: value, + }, + }; + } + + return agg; + }); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.SET_STATE_PARAM_VALUE: { + const { paramName, value } = action.payload; + + return { + ...state, + params: { + ...state.params, + [paramName]: value, + }, + }; + } + + case EditorStateActionTypes.REMOVE_AGG: { + let isMetric = false; + + const newAggs = state.aggs.aggs.filter(({ id, schema }) => { + if (id === action.payload.aggId) { + if (schema.group === AggGroupNames.Metrics) { + isMetric = true; + } + + return false; + } + + return true; + }); + + if (isMetric && getEnabledMetricAggsCount(newAggs) === 0) { + const aggToEnable = newAggs.find(agg => agg.schema.name === 'metric'); + + if (aggToEnable) { + aggToEnable.enabled = true; + } + } + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.REORDER_AGGS: { + const { sourceAgg, destinationAgg } = action.payload; + const destinationIndex = state.aggs.aggs.indexOf(destinationAgg); + const newAggs = move([...state.aggs.aggs], sourceAgg, destinationIndex); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.TOGGLE_ENABLED_AGG: { + const { aggId, enabled } = action.payload; + + const newAggs = state.aggs.aggs.map(agg => { + if (agg.id === aggId) { + const parsedAgg = agg.toJSON(); + + return { + ...parsedAgg, + enabled, + }; + } + + return agg; + }); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.UPDATE_STATE_PARAMS: { + const { params } = action.payload; + + return { + ...state, + params: cloneDeep(params), + }; + } + } +} + +export { editorStateReducer, initEditorState }; diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx index f215cf755886d..55cd237a56689 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx @@ -17,12 +17,12 @@ * under the License. */ -import { VisParams } from '../../..'; -import { AggParams } from '../agg_params'; -import { OnAggParamsChange } from '../components/agg_common_props'; +import { AggConfig, VisParams } from 'ui/vis'; +import { DefaultEditorAggCommonProps } from '../components/agg_common_props'; export interface AggControlProps { - aggParams: AggParams; + agg: AggConfig; editorStateParams: VisParams; - setValue: OnAggParamsChange; + setAggParamValue: DefaultEditorAggCommonProps['setAggParamValue']; + setStateParamValue: DefaultEditorAggCommonProps['setStateParamValue']; } diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts index 6491ef2e46054..98e4931b23ea3 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts @@ -55,7 +55,7 @@ function useFallbackMetric( .filter(isCompatibleAgg) .find(aggregation => aggregation.id === value); - if (!respAgg) { + if (!respAgg && value !== fallbackValue) { setValue(fallbackValue); } } diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx index 4d15ac8e80e63..67ce3ba6d5072 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx @@ -22,7 +22,7 @@ import { act } from 'react-dom/test-utils'; import { mount, shallow, ReactWrapper } from 'enzyme'; import { EuiComboBoxProps, EuiComboBox } from '@elastic/eui'; import { Field } from '../../../../../../../plugins/data/public'; -import { ComboBoxGroupedOptions, SubAggParamsProp } from '..'; +import { ComboBoxGroupedOptions } from '..'; import { FieldParamEditor, FieldParamEditorProps } from './field'; import { AggConfig, VisState } from '../../..'; @@ -69,6 +69,7 @@ describe('FieldParamEditor component', () => { editorComponent: () => null, onChange, } as any, + formIsTouched: false, value: undefined, editorConfig: {}, indexedFields, @@ -78,7 +79,6 @@ describe('FieldParamEditor component', () => { setTouched, state: {} as VisState, metricAggs: [] as AggConfig[], - subAggParams: {} as SubAggParamsProp, }; }); @@ -130,15 +130,6 @@ describe('FieldParamEditor component', () => { expect(setValidity).toHaveBeenCalledWith(false); }); - it('should call setTouched when the control is invalid', () => { - defaultProps.value = field; - const comp = mount(); - expect(setTouched).not.toHaveBeenCalled(); - comp.setProps({ customError: 'customError' }); - - expect(setTouched).toHaveBeenCalled(); - }); - it('should call onChange when a field selected', () => { const comp = mount(); act(() => { diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.tsx index 75a9e24cd0dee..b8cd0d630a019 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/field.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/field.tsx @@ -26,6 +26,7 @@ import { AggConfig } from '../../..'; import { Field } from '../../../../../../../plugins/data/public'; import { formatListAsProse, parseCommaSeparatedList } from '../../../../../../utils'; import { AggParam, FieldParamType } from '../../../../agg_types'; +import { useValidation } from './agg_utils'; import { AggParamEditorProps, ComboBoxGroupedOptions } from '..'; const label = i18n.translate('common.ui.aggTypes.field.fieldLabel', { defaultMessage: 'Field' }); @@ -78,13 +79,7 @@ function FieldParamEditor({ const isValid = !!value && !errors.length; - useEffect(() => { - setValidity(isValid); - - if (!!errors.length) { - setTouched(); - } - }, [isValid]); + useValidation(setValidity, isValid); useEffect(() => { // set field if only one available diff --git a/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx b/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx index 9d337035f8734..efa8366ec550b 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx @@ -17,41 +17,43 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { AggParamType } from '../../../../agg_types/param_types/agg'; import { AggConfig } from '../../..'; +import { useSubAggParamsHandlers } from './utils'; +import { AggGroupNames } from '../agg_groups'; import { AggParamEditorProps, DefaultEditorAggParams } from '..'; function OrderAggParamEditor({ agg, + aggParam, + formIsTouched, value, metricAggs, state, setValue, setValidity, setTouched, - subAggParams, -}: AggParamEditorProps) { +}: AggParamEditorProps) { + const orderBy = agg.params.orderBy; + useEffect(() => { - if (metricAggs) { - const orderBy = agg.params.orderBy; + if (orderBy === 'custom' && !value) { + setValue(aggParam.makeAgg(agg)); + } - // we aren't creating a custom aggConfig - if (!orderBy || orderBy !== 'custom') { - setValue(undefined); - } else if (value) { - setValue(value); - } else { - const paramDef = agg.type.paramByName('orderAgg'); - if (paramDef) { - setValue((paramDef as AggParamType).makeAgg(agg)); - } - } + if (orderBy !== 'custom' && value) { + setValue(undefined); } - }, [agg.params.orderBy, metricAggs]); + }, [orderBy]); - const [innerState, setInnerState] = useState(true); + const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( + agg, + aggParam, + value as AggConfig, + setValue + ); if (!agg.params.orderAgg) { return null; @@ -62,18 +64,14 @@ function OrderAggParamEditor({ { - // to force update when sub-agg params are changed - setInnerState(!innerState); - subAggParams.onAggParamsChange(...rest); - }} - onAggTypeChange={subAggParams.onAggTypeChange} + setAggParamValue={setAggParamValue} + onAggTypeChange={onAggTypeChange} setValidity={setValidity} setTouched={setTouched} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx index d9e0abc0cce59..4d481bd74e8a3 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiFormRow, EuiIconTip, EuiRange, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,7 +26,7 @@ import { AggControlProps } from './agg_control_props'; const DEFAULT_VALUE = 50; const PARAM_NAME = 'radiusRatio'; -function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlProps) { +function RadiusRatioOptionControl({ editorStateParams, setStateParamValue }: AggControlProps) { const label = ( <> { if (!editorStateParams.radiusRatio) { - setValue(editorStateParams, PARAM_NAME, DEFAULT_VALUE); + setStateParamValue(PARAM_NAME, DEFAULT_VALUE); } }, []); + const onChange = useCallback( + (e: React.ChangeEvent | React.MouseEvent) => + setStateParamValue(PARAM_NAME, parseFloat(e.currentTarget.value)), + [setStateParamValue] + ); + return ( <> @@ -58,9 +64,7 @@ function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlPro min={1} max={100} value={editorStateParams.radiusRatio || DEFAULT_VALUE} - onChange={( - e: React.ChangeEvent | React.MouseEvent - ) => setValue(editorStateParams, PARAM_NAME, parseFloat(e.currentTarget.value))} + onChange={onChange} showRange showValue valueAppend="%" diff --git a/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx b/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx index e6e4d34aea836..3ba4279a370dd 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiButtonGroup, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AggControlProps } from './agg_control_props'; @@ -28,8 +28,8 @@ const PARAMS = { COLUMNS: 'visEditorSplitBy__false', }; -function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) { - const idSelected = `visEditorSplitBy__${aggParams.row}`; +function RowsOrColumnsControl({ agg, setAggParamValue }: AggControlProps) { + const idSelected = `visEditorSplitBy__${agg.params.row}`; const options = [ { id: PARAMS.ROWS, @@ -44,6 +44,10 @@ function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) { }), }, ]; + const onChange = useCallback( + optionId => setAggParamValue(agg.id, PARAMS.NAME, optionId === PARAMS.ROWS), + [setAggParamValue] + ); return ( <> @@ -56,7 +60,7 @@ function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) { options={options} isFullWidth={true} idSelected={idSelected} - onChange={optionId => setValue(aggParams, PARAMS.NAME, optionId === PARAMS.ROWS)} + onChange={onChange} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/string.tsx b/src/legacy/ui/public/vis/editors/default/controls/string.tsx index 63827205afdb3..dbfd0a7db33fb 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/string.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/string.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiFieldText, EuiFormRow } from '@elastic/eui'; import { AggParamEditorProps } from '..'; @@ -37,6 +37,8 @@ function StringParamEditor({ setValidity(isValid); }, [isValid]); + const onChange = useCallback(ev => setValue(ev.target.value), [setValue]); + return ( setValue(ev.target.value)} + onChange={onChange} fullWidth={true} compressed onBlur={setTouched} diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx index 559a3f47db563..b233480cb35ba 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx @@ -17,35 +17,40 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { AggParamType } from '../../../../agg_types/param_types/agg'; + +import { AggParamType } from 'ui/agg_types/param_types/agg'; import { AggConfig } from '../../..'; -import { AggParamEditorProps, DefaultEditorAggParams } from '..'; +import { useSubAggParamsHandlers } from './utils'; +import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..'; function SubAggParamEditor({ agg, + aggParam, + formIsTouched, value, metricAggs, state, setValue, setValidity, setTouched, - subAggParams, -}: AggParamEditorProps) { +}: AggParamEditorProps) { useEffect(() => { // we aren't creating a custom aggConfig if (agg.params.metricAgg !== 'custom') { setValue(undefined); } else if (!agg.params.customMetric) { - const customMetric = agg.type.paramByName('customMetric'); - if (customMetric) { - setValue((customMetric as AggParamType).makeAgg(agg)); - } + setValue(aggParam.makeAgg(agg)); } }, [value, metricAggs]); - const [innerState, setInnerState] = useState(true); + const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( + agg, + aggParam, + agg.params.customMetric, + setValue + ); if (agg.params.metricAgg !== 'custom' || !agg.params.customMetric) { return null; @@ -56,18 +61,14 @@ function SubAggParamEditor({ { - // to force update when sub-agg params are changed - setInnerState(!innerState); - subAggParams.onAggParamsChange(...rest); - }} - onAggTypeChange={subAggParams.onAggTypeChange} + setAggParamValue={setAggParamValue} + onAggTypeChange={onAggTypeChange} setValidity={setValidity} setTouched={setTouched} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx index df1640273135e..d0a44d1d35d1c 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx @@ -17,23 +17,25 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiFormLabel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggParamType } from '../../../../agg_types/param_types/agg'; + +import { AggParamType } from 'ui/agg_types/param_types/agg'; import { AggConfig } from '../../../../agg_types/agg_config'; +import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..'; function SubMetricParamEditor({ agg, aggParam, + formIsTouched, metricAggs, state, setValue, setValidity, setTouched, - subAggParams, -}: AggParamEditorProps) { +}: AggParamEditorProps) { const metricTitle = i18n.translate('common.ui.aggTypes.metrics.metricTitle', { defaultMessage: 'Metric', }); @@ -49,14 +51,16 @@ function SubMetricParamEditor({ if (agg.params[type]) { setValue(agg.params[type]); } else { - const param = agg.type.paramByName(type); - if (param) { - setValue((param as AggParamType).makeAgg(agg)); - } + setValue(aggParam.makeAgg(agg)); } }, []); - const [innerState, setInnerState] = useState(true); + const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( + agg, + aggParam, + agg.params[type], + setValue + ); if (!agg.params[type]) { return null; @@ -71,16 +75,12 @@ function SubMetricParamEditor({ agg={agg.params[type]} groupName={aggGroup} className="visEditorAgg__subAgg" - formIsTouched={subAggParams.formIsTouched} + formIsTouched={formIsTouched} indexPattern={agg.getIndexPattern()} metricAggs={metricAggs} state={state} - onAggParamsChange={(...rest) => { - // to force update when sub-agg params are changed - setInnerState(!innerState); - subAggParams.onAggParamsChange(...rest); - }} - onAggTypeChange={subAggParams.onAggTypeChange} + setAggParamValue={setAggParamValue} + onAggTypeChange={onAggTypeChange} setValidity={setValidity} setTouched={setTouched} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts b/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts index 2e00b62d9b7fd..c5abf31a3cd8f 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts @@ -20,14 +20,13 @@ import { AggConfig, VisState } from '../../..'; import { EditorConfig } from '../../config/types'; import { AggParam } from '../../../../agg_types'; -import { SubAggParamsProp } from '..'; export const aggParamCommonPropsMock = { agg: {} as AggConfig, aggParam: {} as AggParam, editorConfig: {} as EditorConfig, + formIsTouched: false, metricAggs: [] as AggConfig[], - subAggParams: {} as SubAggParamsProp, state: {} as VisState, showValidation: false, }; diff --git a/src/legacy/ui/public/vis/editors/default/controls/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/utils.ts new file mode 100644 index 0000000000000..5fd7c284fa23d --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/utils.ts @@ -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 { useCallback } from 'react'; +import { AggConfig } from 'ui/vis'; +import { AggParamType } from 'ui/agg_types/param_types/agg'; + +type SetValue = (value?: AggConfig) => void; + +function useSubAggParamsHandlers( + agg: AggConfig, + aggParam: AggParamType, + subAgg: AggConfig, + setValue: SetValue +) { + const setAggParamValue = useCallback( + (aggId, paramName, val) => { + const parsedParams = subAgg.toJSON(); + const params = { + ...parsedParams, + params: { + ...parsedParams.params, + [paramName]: val, + }, + }; + + setValue(aggParam.makeAgg(agg, params)); + }, + [agg, aggParam, setValue, subAgg] + ); + + const onAggTypeChange = useCallback( + (aggId, aggType) => { + const parsedAgg = subAgg.toJSON(); + + const params = { + ...parsedAgg, + type: aggType, + }; + + setValue(aggParam.makeAgg(agg, params)); + }, + [agg, aggParam, setValue, subAgg] + ); + + return { onAggTypeChange, setAggParamValue }; +} + +export { useSubAggParamsHandlers }; diff --git a/src/legacy/ui/public/vis/editors/default/default.html b/src/legacy/ui/public/vis/editors/default/default.html deleted file mode 100644 index 60fcbafdb88f5..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/default.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
- - -
- -
- -
diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js deleted file mode 100644 index 66f52ea84398f..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ /dev/null @@ -1,213 +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 'ui/angular-bootstrap'; -import './fancy_forms'; -import './sidebar'; -import { i18n } from '@kbn/i18n'; -import './vis_options'; -import './vis_editor_resizer'; -import './vis_type_agg_filter'; -import $ from 'jquery'; - -import _ from 'lodash'; -import angular from 'angular'; -import defaultEditorTemplate from './default.html'; -import { keyCodes } from '@elastic/eui'; -import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; -import { DefaultEditorSize } from '../../editor_size'; - -import { AggGroupNames } from './agg_groups'; - -import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; - -const defaultEditor = function($rootScope, $compile) { - return class DefaultEditor { - static key = 'default'; - - constructor(el, savedObj) { - this.el = $(el); - this.savedObj = savedObj; - this.vis = savedObj.vis; - - if (!this.vis.type.editorConfig.optionTabs && this.vis.type.editorConfig.optionsTemplate) { - this.vis.type.editorConfig.optionTabs = [ - { - name: 'options', - title: i18n.translate('common.ui.vis.editors.sidebar.tabs.optionsLabel', { - defaultMessage: 'Options', - }), - editor: this.vis.type.editorConfig.optionsTemplate, - }, - ]; - } - } - - render({ uiState, timeRange, filters, query, appState }) { - let $scope; - - const updateScope = () => { - $scope.vis = this.vis; - $scope.uiState = uiState; - //$scope.$apply(); - }; - - return new Promise(async resolve => { - if (!this.$scope) { - this.$scope = $scope = $rootScope.$new(); - - updateScope(); - - $scope.state = $scope.vis.copyCurrentState(true); - $scope.oldState = $scope.vis.getSerializableState($scope.state); - - $scope.toggleSidebar = () => { - $scope.$broadcast('render'); - }; - - this.el.one('renderComplete', resolve); - // track state of editable vis vs. "actual" vis - $scope.stageEditableVis = () => { - $scope.oldState = $scope.vis.getSerializableState($scope.state); - $scope.vis.setCurrentState($scope.state); - $scope.vis.updateState(); - $scope.vis.dirty = false; - }; - $scope.resetEditableVis = () => { - $scope.state = $scope.vis.copyCurrentState(true); - $scope.vis.dirty = false; - }; - - $scope.autoApplyEnabled = false; - if ($scope.vis.type.editorConfig.enableAutoApply) { - $scope.toggleAutoApply = () => { - $scope.autoApplyEnabled = !$scope.autoApplyEnabled; - }; - - $scope.$watch( - 'vis.dirty', - _.debounce(() => { - if (!$scope.autoApplyEnabled || !$scope.vis.dirty) return; - $scope.stageEditableVis(); - }, 800) - ); - } - - $scope.submitEditorWithKeyboard = event => { - if (event.ctrlKey && event.keyCode === keyCodes.ENTER) { - event.preventDefault(); - event.stopPropagation(); - $scope.stageEditableVis(); - } - }; - - $scope.getSidebarClass = () => { - if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.SMALL) { - return 'visEditor__collapsibleSidebar--small'; - } else if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.MEDIUM) { - return 'visEditor__collapsibleSidebar--medium'; - } else if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.LARGE) { - return 'visEditor__collapsibleSidebar--large'; - } - }; - - $scope.$watch( - () => { - return $scope.vis.getSerializableState($scope.state); - }, - function(newState) { - $scope.vis.dirty = !angular.equals(newState, $scope.oldState); - const responseAggs = $scope.state.aggs.getResponseAggs(); - $scope.hasHistogramAgg = responseAggs.some(agg => agg.type.name === 'histogram'); - $scope.metricAggs = responseAggs.filter( - agg => _.get(agg, 'schema.group') === AggGroupNames.Metrics - ); - const lastParentPipelineAgg = _.findLast( - $scope.metricAggs, - ({ type }) => type.subtype === parentPipelineAggHelper.subtype - ); - $scope.lastParentPipelineAggTitle = - lastParentPipelineAgg && lastParentPipelineAgg.type.title; - }, - true - ); - - // fires when visualization state changes, and we need to copy changes to editorState - $scope.$watch( - () => { - return $scope.vis.getCurrentState(false); - }, - newState => { - if (!_.isEqual(newState, $scope.oldState)) { - $scope.state = $scope.vis.copyCurrentState(true); - $scope.oldState = newState; - } - }, - true - ); - - // Load the default editor template, attach it to the DOM and compile it. - // It should be added to the DOM before compiling, to prevent some resize - // listener issues. - const template = $(defaultEditorTemplate); - this.el.html(template); - $compile(template)($scope); - } else { - $scope = this.$scope; - updateScope(); - } - - if (!this._handler) { - const visualizationEl = this.el.find('.visEditor__canvas')[0]; - - this._handler = await embeddables - .getEmbeddableFactory('visualization') - .createFromObject(this.savedObj, { - uiState: uiState, - appState, - timeRange: timeRange, - filters: filters || [], - query: query, - }); - this._handler.render(visualizationEl); - } else { - this._handler.updateInput({ - timeRange: timeRange, - filters: filters || [], - query: query, - }); - } - }); - } - - resize() {} - - destroy() { - if (this.$scope) { - this.$scope.$destroy(); - this.$scope = null; - } - if (this._handler) { - this._handler.destroy(); - } - } - }; -}; - -export { defaultEditor }; diff --git a/src/legacy/ui/public/vis/editors/default/default_editor.tsx b/src/legacy/ui/public/vis/editors/default/default_editor.tsx new file mode 100644 index 0000000000000..3e99bb83d224f --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/default_editor.tsx @@ -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 React, { useEffect, useRef, useState, useCallback } from 'react'; + +import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; +import { EditorRenderProps } from '../../../../../core_plugins/kibana/public/visualize/np_ready/types'; +import { VisualizeEmbeddable } from '../../../../../core_plugins/visualizations/public/embeddable'; +import { VisualizeEmbeddableFactory } from '../../../../../core_plugins/visualizations/public/embeddable/visualize_embeddable_factory'; +import { + PanelsContainer, + Panel, +} from '../../../../../core_plugins/console/public/np_ready/application/components/split_panel'; + +import './vis_type_agg_filter'; +import { DefaultEditorSideBar } from './components/sidebar'; +import { DefaultEditorControllerState } from './default_editor_controller'; +import { getInitialWidth } from '../../editor_size'; + +function DefaultEditor({ + savedObj, + uiState, + timeRange, + filters, + appState, + optionTabs, + query, +}: DefaultEditorControllerState & EditorRenderProps) { + const visRef = useRef(null); + const visHandler = useRef(null); + const [isCollapsed, setIsCollapsed] = useState(false); + const [factory, setFactory] = useState(null); + const { vis } = savedObj; + + const onClickCollapse = useCallback(() => { + setIsCollapsed(value => !value); + }, []); + + useEffect(() => { + async function visualize() { + if (!visRef.current || (!visHandler.current && factory)) { + return; + } + + if (!visHandler.current) { + const embeddableFactory = embeddables.getEmbeddableFactory( + 'visualization' + ) as VisualizeEmbeddableFactory; + setFactory(embeddableFactory); + + visHandler.current = (await embeddableFactory.createFromObject(savedObj, { + // should be look through createFromObject interface again because of "id" param + id: '', + uiState, + appState, + timeRange, + filters, + query, + })) as VisualizeEmbeddable; + + visHandler.current.render(visRef.current); + } else { + visHandler.current.updateInput({ + timeRange, + filters, + query, + }); + } + } + + visualize(); + }, [uiState, savedObj, timeRange, filters, appState, query, factory]); + + useEffect(() => { + return () => { + if (visHandler.current) { + visHandler.current.destroy(); + } + }; + }, []); + + const editorInitialWidth = getInitialWidth(vis.type.editorConfig.defaultSize); + + return ( + + +
+ + + + + + + ); +} + +export { DefaultEditor }; diff --git a/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx b/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx new file mode 100644 index 0000000000000..bf843a98deaa5 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx @@ -0,0 +1,89 @@ +/* + * 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 { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { EditorRenderProps } from '../../../../../core_plugins/kibana/public/visualize/np_ready/types'; +import { VisSavedObject } from '../../../../../core_plugins/visualizations/public/embeddable/visualize_embeddable'; +import { DefaultEditor } from './default_editor'; +import { DefaultEditorDataTab, OptionTab } from './components/sidebar'; + +export interface DefaultEditorControllerState { + savedObj: VisSavedObject; + optionTabs: OptionTab[]; +} + +class DefaultEditorController { + private el: HTMLElement; + private state: DefaultEditorControllerState; + + constructor(el: HTMLElement, savedObj: VisSavedObject) { + this.el = el; + const { type: visType } = savedObj.vis; + + const optionTabs = [ + ...(visType.schemas.buckets || visType.schemas.metrics + ? [ + { + name: 'data', + title: i18n.translate('common.ui.vis.editors.sidebar.tabs.dataLabel', { + defaultMessage: 'Data', + }), + editor: DefaultEditorDataTab, + }, + ] + : []), + + ...(!visType.editorConfig.optionTabs && visType.editorConfig.optionsTemplate + ? [ + { + name: 'options', + title: i18n.translate('common.ui.vis.editors.sidebar.tabs.optionsLabel', { + defaultMessage: 'Options', + }), + editor: visType.editorConfig.optionsTemplate, + }, + ] + : visType.editorConfig.optionTabs), + ]; + + this.state = { + savedObj, + optionTabs, + }; + } + + render(props: EditorRenderProps) { + render( + + + , + this.el + ); + } + + destroy() { + unmountComponentAtNode(this.el); + } +} + +export { DefaultEditorController }; diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js deleted file mode 100644 index e9fda6bb9efab..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js +++ /dev/null @@ -1,62 +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 ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import $ from 'jquery'; - -describe('fancy forms', function() { - let $el; - let $scope; - let $compile; - let $rootScope; - let ngForm; - - function generateEl() { - return $('
').html($('')); - } - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - - $scope = $rootScope.$new(); - $el = generateEl(); - - $compile($el)($scope); - $scope.$apply(); - - ngForm = $el.controller('form'); - }) - ); - - describe('ngFormController', function() { - it('counts errors', function() { - expect(ngForm.errorCount()).to.be(1); - }); - - it('clears errors', function() { - $scope.val = 'something'; - $scope.$apply(); - expect(ngForm.errorCount()).to.be(0); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/nested_fancy_forms.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/nested_fancy_forms.js deleted file mode 100644 index 969fa3bfbd888..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/nested_fancy_forms.js +++ /dev/null @@ -1,202 +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 ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import testSubjSelector from '@kbn/test-subj-selector'; -import sinon from 'sinon'; -import $ from 'jquery'; - -const template = ` - - -
    -
  • - - - - -
  • -
- -
-`; - -describe('fancy forms', function() { - let setup; - const trash = []; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject($injector => { - const $rootScope = $injector.get('$rootScope'); - const $compile = $injector.get('$compile'); - - setup = function(options = {}) { - const { name = 'person1', tasks = [], onSubmit = () => {} } = options; - - const $el = $(template).appendTo('body'); - trash.push(() => $el.remove()); - const $scope = $rootScope.$new(); - - $scope.name = name; - $scope.tasks = tasks; - $scope.onSubmit = onSubmit; - - $compile($el)($scope); - $scope.$apply(); - - return { - $el, - $scope, - }; - }; - }) - ); - - afterEach(() => trash.splice(0).forEach(fn => fn())); - - describe('nested forms', function() { - it('treats new fields as "soft" errors', function() { - const { $scope } = setup({ name: '' }); - expect($scope.person.errorCount()).to.be(1); - expect($scope.person.softErrorCount()).to.be(0); - }); - - it('upgrades fields to regular errors on attempted submit', function() { - const { $scope, $el } = setup({ name: '' }); - - expect($scope.person.errorCount()).to.be(1); - expect($scope.person.softErrorCount()).to.be(0); - $el.find(testSubjSelector('submit')).click(); - expect($scope.person.errorCount()).to.be(1); - expect($scope.person.softErrorCount()).to.be(1); - }); - - it('prevents submit when there are errors', function() { - const onSubmit = sinon.stub(); - const { $scope, $el } = setup({ name: '', onSubmit }); - - expect($scope.person.errorCount()).to.be(1); - sinon.assert.notCalled(onSubmit); - $el.find(testSubjSelector('submit')).click(); - expect($scope.person.errorCount()).to.be(1); - sinon.assert.notCalled(onSubmit); - - $scope.$apply(() => { - $scope.name = 'foo'; - }); - - expect($scope.person.errorCount()).to.be(0); - sinon.assert.notCalled(onSubmit); - $el.find(testSubjSelector('submit')).click(); - expect($scope.person.errorCount()).to.be(0); - sinon.assert.calledOnce(onSubmit); - }); - - it('new fields are no longer soft after blur', function() { - const { $scope, $el } = setup({ name: '' }); - expect($scope.person.softErrorCount()).to.be(0); - $el.find(testSubjSelector('name')).blur(); - expect($scope.person.softErrorCount()).to.be(1); - }); - - it('counts errors/softErrors in sub forms', function() { - const { $scope, $el } = setup(); - - expect($scope.person.errorCount()).to.be(0); - - $scope.$apply(() => { - $scope.tasks = [ - { - name: 'foo', - description: '', - }, - { - name: 'foo', - description: '', - }, - ]; - }); - - expect($scope.person.errorCount()).to.be(2); - expect($scope.person.softErrorCount()).to.be(0); - - $el - .find(testSubjSelector('taskDesc')) - .first() - .blur(); - - expect($scope.person.errorCount()).to.be(2); - expect($scope.person.softErrorCount()).to.be(1); - }); - - it('only counts down', function() { - const { $scope, $el } = setup({ - tasks: [ - { - name: 'foo', - description: '', - }, - { - name: 'bar', - description: '', - }, - { - name: 'baz', - description: '', - }, - ], - }); - - // top level form sees 3 errors - expect($scope.person.errorCount()).to.be(3); - expect($scope.person.softErrorCount()).to.be(0); - - $el - .find('ng-form') - .toArray() - .forEach((el, i) => { - const $task = $(el); - const $taskScope = $task.scope(); - const form = $task.controller('form'); - - // sub forms only see one error - expect(form.errorCount()).to.be(1); - expect(form.softErrorCount()).to.be(0); - - // blurs only count locally - $task.find(testSubjSelector('taskDesc')).blur(); - expect(form.softErrorCount()).to.be(1); - - // but parent form see them - expect($scope.person.softErrorCount()).to.be(1); - - $taskScope.$apply(() => { - $taskScope.task.description = 'valid'; - }); - - expect(form.errorCount()).to.be(0); - expect(form.softErrorCount()).to.be(0); - expect($scope.person.errorCount()).to.be(2 - i); - expect($scope.person.softErrorCount()).to.be(0); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js deleted file mode 100644 index 90971140482f7..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js +++ /dev/null @@ -1,87 +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 function decorateFormController($delegate, $injector) { - const [directive] = $delegate; - const FormController = directive.controller; - - class KbnFormController extends FormController { - // prevent inheriting FormController's static $inject property - // which is angular's cache of the DI arguments for a function - static $inject = ['$scope', '$element']; - - constructor($scope, $element, ...superArgs) { - super(...superArgs); - - const onSubmit = event => { - this._markInvalidTouched(event); - }; - - $element.on('submit', onSubmit); - $scope.$on('$destroy', () => { - $element.off('submit', onSubmit); - }); - } - - errorCount() { - return this._getInvalidModels().length; - } - - // same as error count, but filters out untouched and pristine models - softErrorCount() { - return this._getInvalidModels().filter(model => model.$touched || model.$dirty).length; - } - - $setTouched() { - this._getInvalidModels().forEach(model => model.$setTouched()); - } - - _markInvalidTouched(event) { - if (this.errorCount()) { - event.preventDefault(); - event.stopImmediatePropagation(); - this.$setTouched(); - } - } - - _getInvalidModels() { - return this.$$controls.reduce((acc, control) => { - // recurse into sub-form - if (typeof control._getInvalidModels === 'function') { - return [...acc, ...control._getInvalidModels()]; - } - - if (control.$invalid) { - return [...acc, control]; - } - - return acc; - }, []); - } - } - - // replace controller with our wrapper - directive.controller = [ - ...$injector.annotate(KbnFormController), - ...$injector.annotate(FormController), - (...args) => new KbnFormController(...args), - ]; - - return $delegate; -} diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js deleted file mode 100644 index bb4d026aa1810..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js +++ /dev/null @@ -1,54 +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 function decorateModelController($delegate, $injector) { - const [directive] = $delegate; - const ModelController = directive.controller; - - class KbnModelController extends ModelController { - // prevent inheriting ModelController's static $inject property - // which is angular's cache of the DI arguments for a function - static $inject = ['$scope', '$element']; - - constructor($scope, $element, ...superArgs) { - super(...superArgs); - - const onInvalid = () => { - this.$setTouched(); - }; - - // the browser emits an "invalid" event when browser supplied - // validation fails, which implies that the user has indirectly - // interacted with the control and it should be treated as "touched" - $element.on('invalid', onInvalid); - $scope.$on('$destroy', () => { - $element.off('invalid', onInvalid); - }); - } - } - - // replace controller with our wrapper - directive.controller = [ - ...$injector.annotate(KbnModelController), - ...$injector.annotate(ModelController), - (...args) => new KbnModelController(...args), - ]; - - return $delegate; -} diff --git a/src/legacy/ui/public/vis/editors/default/index.ts b/src/legacy/ui/public/vis/editors/default/index.ts index 7079ba23afb5c..fada4e5d2266f 100644 --- a/src/legacy/ui/public/vis/editors/default/index.ts +++ b/src/legacy/ui/public/vis/editors/default/index.ts @@ -18,7 +18,7 @@ */ export { AggParamEditorProps } from './components/agg_param_props'; -export { DefaultEditorAggParams, SubAggParamsProp } from './components/agg_params'; +export { DefaultEditorAggParams } from './components/agg_params'; export { ComboBoxGroupedOptions } from './utils'; export * from './vis_options_props'; export * from './utils'; diff --git a/src/legacy/ui/public/vis/editors/default/schemas.js b/src/legacy/ui/public/vis/editors/default/schemas.ts similarity index 67% rename from src/legacy/ui/public/vis/editors/default/schemas.js rename to src/legacy/ui/public/vis/editors/default/schemas.ts index 69449dc8504a8..3cacd1cfbe68f 100644 --- a/src/legacy/ui/public/vis/editors/default/schemas.js +++ b/src/legacy/ui/public/vis/editors/default/schemas.ts @@ -18,13 +18,49 @@ */ import _ from 'lodash'; + +import { Optional } from '@kbn/utility-types'; + +import { AggParam } from '../../../agg_types'; import { IndexedArray } from '../../../indexed_array'; import { RowsOrColumnsControl } from './controls/rows_or_columns'; import { RadiusRatioOptionControl } from './controls/radius_ratio_option'; import { AggGroupNames } from './agg_groups'; +import { AggControlProps } from './controls/agg_control_props'; + +export interface ISchemas { + [AggGroupNames.Buckets]: Schema[]; + [AggGroupNames.Metrics]: Schema[]; +} + +export interface Schema { + aggFilter: string | string[]; + editor: boolean | string; + group: AggGroupNames; + max: number; + min: number; + name: string; + params: AggParam[]; + title: string; + defaults: unknown; + hideCustomLabel?: boolean; + mustBeFirst?: boolean; + aggSettings?: any; + editorComponent?: React.ComponentType; +} class Schemas { - constructor(schemas) { + // @ts-ignore + all: IndexedArray; + + constructor( + schemas: Array< + Optional< + Schema, + 'min' | 'max' | 'group' | 'title' | 'aggFilter' | 'editor' | 'params' | 'defaults' + > + > + ) { _(schemas || []) .map(schema => { if (!schema.name) throw new Error('all schema must have a unique name'); @@ -35,7 +71,7 @@ class Schemas { name: 'row', default: true, }, - ]; + ] as AggParam[]; schema.editorComponent = RowsOrColumnsControl; } else if (schema.name === 'radius') { schema.editorComponent = RadiusRatioOptionControl; @@ -51,21 +87,23 @@ class Schemas { params: [], }); - return schema; + return schema as Schema; }) - .tap(schemas => { + .tap((fullSchemas: Schema[]) => { this.all = new IndexedArray({ index: ['name'], group: ['group'], immutable: true, - initialSet: schemas, + initialSet: fullSchemas, }); }) .groupBy('group') .forOwn((group, groupName) => { + // @ts-ignore this[groupName] = new IndexedArray({ index: ['name'], immutable: true, + // @ts-ignore initialSet: group, }); }) diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.html b/src/legacy/ui/public/vis/editors/default/sidebar.html deleted file mode 100644 index b0a03e461fc1c..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/sidebar.html +++ /dev/null @@ -1,191 +0,0 @@ -
-
- -

- {{ vis.indexPattern.title }} -

- - - -
- - -
- - -
- -
- -
- -
-
diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.js b/src/legacy/ui/public/vis/editors/default/sidebar.js deleted file mode 100644 index 195ae68c0e959..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/sidebar.js +++ /dev/null @@ -1,105 +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 './agg_group'; -import './vis_options'; -import 'ui/directives/css_truncate'; -import { uiModules } from '../../../modules'; -import sidebarTemplate from './sidebar.html'; -import { move } from '../../../utils/collection'; -import { AggGroupNames } from './agg_groups'; -import { getEnabledMetricAggsCount } from './components/agg_group_helper'; - -uiModules.get('app/visualize').directive('visEditorSidebar', function() { - return { - restrict: 'E', - template: sidebarTemplate, - scope: true, - require: '?^ngModel', - controllerAs: 'sidebar', - controller: function($scope) { - $scope.$watch('vis.type', visType => { - if (visType) { - this.showData = visType.schemas.buckets || visType.schemas.metrics; - if (_.has(visType, 'editorConfig.optionTabs')) { - const activeTabs = visType.editorConfig.optionTabs.filter(tab => { - return _.get(tab, 'active', false); - }); - if (activeTabs.length > 0) { - this.section = activeTabs[0].name; - } - } - this.section = - this.section || - (this.showData ? 'data' : _.get(visType, 'editorConfig.optionTabs[0].name')); - } - }); - - $scope.onAggTypeChange = (agg, value) => { - if (agg.type !== value) { - agg.type = value; - } - }; - - $scope.onAggParamsChange = (params, paramName, value) => { - if (params[paramName] !== value) { - params[paramName] = value; - } - }; - - $scope.addSchema = function(schema) { - const aggConfig = $scope.state.aggs.createAggConfig({ schema }); - aggConfig.brandNew = true; - }; - - $scope.removeAgg = function(agg) { - const aggs = $scope.state.aggs.aggs; - const index = aggs.indexOf(agg); - - if (index === -1) { - return; - } - - aggs.splice(index, 1); - - if (agg.schema.group === AggGroupNames.Metrics) { - const metrics = $scope.state.aggs.bySchemaGroup(AggGroupNames.Metrics); - - if (getEnabledMetricAggsCount(metrics) === 0) { - metrics.find(aggregation => aggregation.schema.name === 'metric').enabled = true; - } - } - }; - - $scope.onToggleEnableAgg = (agg, isEnable) => { - agg.enabled = isEnable; - }; - - $scope.reorderAggs = group => { - //the aggs have been reordered in [group] and we need - //to apply that ordering to [vis.aggs] - const indexOffset = $scope.state.aggs.aggs.indexOf(group[0]); - _.forEach(group, (agg, index) => { - move($scope.state.aggs.aggs, agg, indexOffset + index); - }); - }; - }, - }; -}); diff --git a/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js b/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js deleted file mode 100644 index 3cbc94a029326..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js +++ /dev/null @@ -1,62 +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 'jquery'; -import { uiModules } from '../../../modules'; -import { keyCodes } from '@elastic/eui'; - -uiModules.get('kibana').directive('visEditorResizer', function() { - return { - restrict: 'E', - link: function($scope, $el) { - const $left = $el.parent(); - - $el.on('mousedown', function(event) { - $el.addClass('active'); - const startWidth = $left.width(); - const startX = event.pageX; - - function onMove(event) { - const newWidth = startWidth + event.pageX - startX; - $left.width(newWidth); - } - - $(document.body) - .on('mousemove', onMove) - .one('mouseup', () => { - $el.removeClass('active'); - $(document.body).off('mousemove', onMove); - $scope.$broadcast('render'); - }); - }); - - $el.on('keydown', event => { - const { keyCode } = event; - - if (keyCode === keyCodes.LEFT || keyCode === keyCodes.RIGHT) { - event.preventDefault(); - const startWidth = $left.width(); - const newWidth = startWidth + (keyCode === keyCodes.LEFT ? -15 : 15); - $left.width(newWidth); - $scope.$broadcast('render'); - } - }); - }, - }; -}); diff --git a/src/legacy/ui/public/vis/editors/default/vis_options.js b/src/legacy/ui/public/vis/editors/default/vis_options.js deleted file mode 100644 index 9c9b0353cee27..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/vis_options.js +++ /dev/null @@ -1,121 +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 { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from '../../../modules'; -import { VisOptionsReactWrapper } from './vis_options_react_wrapper'; -import { safeMakeLabel } from './controls/agg_utils'; - -/** - * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute. - * This lets you specify a full-screen UI for editing a vis type, instead of using the regular - * sidebar. - */ - -uiModules - .get('app/visualize') - .directive('visOptionsReactWrapper', reactDirective => - reactDirective(wrapInI18nContext(VisOptionsReactWrapper), [ - ['component', { wrapApply: false }], - ['aggs', { watchDepth: 'collection' }], - ['stateParams', { watchDepth: 'collection' }], - ['vis', { watchDepth: 'collection' }], - ['uiState', { watchDepth: 'collection' }], - ['setValue', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - ['setVisType', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - 'hasHistogramAgg', - 'currentTab', - 'aggsLabels', - ]) - ) - .directive('visEditorVisOptions', function($compile) { - return { - restrict: 'E', - require: '?^ngModel', - scope: { - vis: '=', - visData: '=', - uiState: '=', - editor: '=', - visualizeEditor: '=', - editorState: '=', - onAggParamsChange: '=', - hasHistogramAgg: '=', - currentTab: '=', - }, - link: function($scope, $el, attrs, ngModelCtrl) { - $scope.setValue = (paramName, value) => - $scope.onAggParamsChange($scope.editorState.params, paramName, value); - - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`visOptions`, isValid); - }; - - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - - $scope.setVisType = type => { - $scope.vis.type.type = type; - }; - - // since aggs reference isn't changed when an agg is updated, we need somehow to let React component know about it - $scope.aggsLabels = ''; - - $scope.$watch( - () => { - return $scope.editorState.aggs.aggs - .map(agg => { - return safeMakeLabel(agg); - }) - .join(); - }, - value => { - $scope.aggsLabels = value; - } - ); - - const comp = - typeof $scope.editor === 'string' - ? $scope.editor - : ` - `; - const $editor = $compile(comp)($scope); - $el.append($editor); - }, - }; - }); diff --git a/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx b/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx index 3f9fa9f5f352f..5b4badc103645 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx +++ b/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx @@ -23,13 +23,12 @@ import { Vis } from './../..'; export interface VisOptionsProps { aggs: AggConfigs; - aggsLabels: string; hasHistogramAgg: boolean; + isTabSelected: boolean; stateParams: VisParamType; vis: Vis; uiState: PersistedState; setValue(paramName: T, value: VisParamType[T]): void; setValidity(isValid: boolean): void; - setVisType(type: string): void; setTouched(isTouched: boolean): void; } diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss index 4d7c0e2bdcadb..62050ce4e99fd 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss +++ b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss @@ -1,4 +1,4 @@ -@import '../../vislib/variables'; +@import '../../../../core_plugins/vis_type_vislib/public/vislib/variables'; // NOTE: Some of the styles attempt to align with the TSVB legend diff --git a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js b/src/legacy/ui/public/vis/vis_types/angular_vis_type.js index 88412c76d0d36..c34294d45548c 100644 --- a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js +++ b/src/legacy/ui/public/vis/vis_types/angular_vis_type.js @@ -18,6 +18,7 @@ */ import $ from 'jquery'; +import { isEqual } from 'lodash'; import chrome from 'ui/chrome'; export class AngularVisController { @@ -37,6 +38,11 @@ export class AngularVisController { this.$scope.vis = this.vis; this.$scope.visState = this.vis.getState(); this.$scope.esResponse = esResponse; + + if (!isEqual(this.$scope.visParams, visParams)) { + this.vis.emit('updateEditorStateParams', visParams); + } + this.$scope.visParams = visParams; this.$scope.renderComplete = resolve; this.$scope.renderFailed = reject; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts new file mode 100644 index 0000000000000..d9eea83d40b48 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts @@ -0,0 +1,103 @@ +/* + * 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'; + +/** + * Returns an array of names ordered by appearance in the nested array + * of objects + * + * > Duplicated utilty method from vislib Data class to decouple `vislib_vis_legend` from `vislib` + * + * @see src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js + * + * @returns {Array} Array of unique names (strings) + */ +export function getPieNames(data: any[]): string[] { + const names: string[] = []; + + _.forEach(data, function(obj) { + const columns = obj.raw ? obj.raw.columns : undefined; + _.forEach(getNames(obj, columns), function(name) { + names.push(name); + }); + }); + + return _.uniq(names, 'label'); +} + +/** + * Flattens hierarchical data into an array of objects with a name and index value. + * The indexed value determines the order of nesting in the data. + * Returns an array with names sorted by the index value. + * + * @param data {Object} Chart data object + * @param columns {Object} Contains formatter information + * @returns {Array} Array of names (strings) + */ +function getNames(data: any, columns: any): string[] { + const slices = data.slices; + + if (slices.children) { + const namedObj = returnNames(slices.children, 0, columns); + + return _(namedObj) + .sortBy(function(obj) { + return obj.index; + }) + .unique(function(d) { + return d.label; + }) + .value(); + } + + return []; +} + +/** + * Helper function for getNames + * Returns an array of objects with a name (key) value and an index value. + * The index value allows us to sort the names in the correct nested order. + * + * @param array {Array} Array of data objects + * @param index {Number} Number of times the object is nested + * @param columns {Object} Contains name formatter information + * @returns {Array} Array of labels (strings) + */ +function returnNames(array: any[], index: number, columns: any): any[] { + const names: any[] = []; + + _.forEach(array, function(obj) { + names.push({ + label: obj.name, + values: [obj.rawData], + index, + }); + + if (obj.children) { + const plusIndex = index + 1; + + _.forEach(returnNames(obj.children, plusIndex, columns), function(namedObj) { + names.push(namedObj); + }); + } + }); + + return names; +} diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx index f0100e369f050..d98590f9885b9 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx @@ -23,12 +23,11 @@ import { compact, uniq, map } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; -// @ts-ignore -import { Data } from '../../../vislib/lib/data'; // @ts-ignore import { createFiltersFromEvent } from '../../../../../core_plugins/visualizations/public'; import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; import { VisLegendItem } from './vislib_vis_legend_item'; +import { getPieNames } from './pie_utils'; import { getTableAggs } from '../../../visualize/loader/pipeline_helpers/utilities'; export interface VisLegendProps { @@ -128,7 +127,7 @@ export class VisLegend extends PureComponent { if (!data) return []; data = data.columns || data.rows || [data]; - if (type === 'pie') return Data.prototype.pieNames(data); + if (type === 'pie') return getPieNames(data); return this.getSeriesLabels(data); }; diff --git a/src/legacy/ui/public/vislib/vis.js b/src/legacy/ui/public/vislib/vis.js deleted file mode 100644 index 6ce58c1f5b23b..0000000000000 --- a/src/legacy/ui/public/vislib/vis.js +++ /dev/null @@ -1,190 +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 d3 from 'd3'; -import { EventEmitter } from 'events'; -import chrome from '../chrome'; -import { VislibError } from './errors'; -import { VisConfig } from './lib/vis_config'; -import { Handler } from './lib/handler'; -import { setHierarchicalTooltipFormatter } from '../vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { setPointSeriesTooltipFormatter } from '../vis/components/tooltip/_pointseries_tooltip_formatter'; - -const config = chrome.getUiSettingsClient(); - -export function VislibVisProvider(Private) { - setHierarchicalTooltipFormatter(Private); - setPointSeriesTooltipFormatter(Private); - - /** - * Creates the visualizations. - * - * @class Vis - * @constructor - * @param $el {HTMLElement} jQuery selected HTML element - * @param config {Object} Parameters that define the chart type and chart options - */ - class Vis extends EventEmitter { - constructor($el, visConfigArgs) { - super(); - this.el = $el.get ? $el.get(0) : $el; - this.visConfigArgs = _.cloneDeep(visConfigArgs); - this.visConfigArgs.dimmingOpacity = config.get('visualization:dimmingOpacity'); - this.visConfigArgs.heatmapMaxBuckets = config.get('visualization:heatmap:maxBuckets'); - } - - hasLegend() { - return this.visConfigArgs.addLegend; - } - - initVisConfig(data, uiState) { - this.data = data; - - this.uiState = uiState; - - this.visConfig = new VisConfig(this.visConfigArgs, this.data, this.uiState, this.el); - } - - /** - * Renders the visualization - * - * @method render - * @param data {Object} Elasticsearch query results - */ - render(data, uiState) { - if (!data) { - throw new Error('No valid data!'); - } - - if (this.handler) { - this.data = null; - this._runOnHandler('destroy'); - } - - this.initVisConfig(data, uiState); - - this.handler = new Handler(this, this.visConfig); - this._runOnHandler('render'); - } - - getLegendLabels() { - return this.visConfig ? this.visConfig.get('legend.labels', null) : null; - } - - getLegendColors() { - return this.visConfig ? this.visConfig.get('legend.colors', null) : null; - } - - _runOnHandler(method) { - try { - this.handler[method](); - } catch (error) { - if (error instanceof VislibError) { - error.displayToScreen(this.handler); - } else { - throw error; - } - } - } - - /** - * Destroys the visualization - * Removes chart and all elements associated with it. - * Removes chart and all elements associated with it. - * Remove event listeners and pass destroy call down to owned objects. - * - * @method destroy - */ - destroy() { - const selection = d3.select(this.el).select('.visWrapper'); - - if (this.handler) this._runOnHandler('destroy'); - - selection.remove(); - } - - /** - * Sets attributes on the visualization - * - * @method set - * @param name {String} An attribute name - * @param val {*} Value to which the attribute name is set - */ - set(name, val) { - this.visConfigArgs[name] = val; - this.render(this.data, this.uiState); - } - - /** - * Gets attributes from the visualization - * - * @method get - * @param name {String} An attribute name - * @returns {*} The value of the attribute name - */ - get(name) { - return this.visConfig.get(name); - } - - /** - * Turns on event listeners. - * - * @param event {String} - * @param listener{Function} - * @returns {*} - */ - on(event, listener) { - const first = this.listenerCount(event) === 0; - const ret = EventEmitter.prototype.on.call(this, event, listener); - const added = this.listenerCount(event) > 0; - - // if this is the first listener added for the event - // enable the event in the handler - if (first && added && this.handler) this.handler.enable(event); - - return ret; - } - - /** - * Turns off event listeners. - * - * @param event {String} - * @param listener{Function} - * @returns {*} - */ - off(event, listener) { - const last = this.listenerCount(event) === 1; - const ret = EventEmitter.prototype.off.call(this, event, listener); - const removed = this.listenerCount(event) === 0; - - // Once all listeners are removed, disable the events in the handler - if (last && removed && this.handler) this.handler.disable(event); - return ret; - } - - removeAllListeners(event) { - const ret = EventEmitter.prototype.removeAllListeners.call(this, event); - this.handler.disable(event); - return ret; - } - } - - return Vis; -} diff --git a/src/plugins/bfetch/README.md b/src/plugins/bfetch/README.md index 9c18720e30d96..9ed90a4de306e 100644 --- a/src/plugins/bfetch/README.md +++ b/src/plugins/bfetch/README.md @@ -3,7 +3,54 @@ `bfetch` allows to batch HTTP requests and streams responses back. +# Example + +We will create a batch processing endpoint that receives a number then doubles it +and streams it back. We will also consider the number to be time in milliseconds +and before streaming the number back the server will wait for the specified number of +milliseconds. + +To do that, first create server-side batch processing route using [`addBatchProcessingRoute`](./docs/server/reference.md#addBatchProcessingRoute). + +```ts +plugins.bfetch.addBatchProcessingRoute<{ num: number }, { num: number }>( + '/my-plugin/double', + () => ({ + onBatchItem: async ({ num }) => { + // Validate inputs. + if (num < 0) throw new Error('Invalid number'); + // Wait number of specified milliseconds. + await new Promise(r => setTimeout(r, num)); + // Double the number and send it back. + return { num: 2 * num }; + }, + }) +); +``` + +Now on client-side create `double` function using [`batchedFunction`](./docs/browser/reference.md#batchedFunction). +The newly created `double` function can be called many times and it +will package individual calls into batches and send them to the server. + +```ts +const double = plugins.bfetch.batchedFunction<{ num: number }, { num: number }>({ + url: '/my-plugin/double', +}); +``` + +Note: the created `double` must accept a single object argument (`{ num: number }` in this case) +and it will return a promise that resolves into an object, too (also `{ num: number }` in this case). + +Use the `double` function. + +```ts +double({ num: 1 }).then(console.log, console.error); // { num: 2 } +double({ num: 2 }).then(console.log, console.error); // { num: 4 } +double({ num: 3 }).then(console.log, console.error); // { num: 6 } +``` + + ## Reference - [Browser](./docs/browser/reference.md) -- Server +- [Server](./docs/server/reference.md) diff --git a/src/plugins/bfetch/common/batch.ts b/src/plugins/bfetch/common/batch.ts new file mode 100644 index 0000000000000..6fd2c7e35ed91 --- /dev/null +++ b/src/plugins/bfetch/common/batch.ts @@ -0,0 +1,32 @@ +/* + * 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 interface ErrorLike { + message: string; +} + +export interface BatchRequestData { + batch: Item[]; +} + +export interface BatchResponseItem { + id: number; + result?: Result; + error?: Error; +} diff --git a/src/plugins/bfetch/common/buffer/create_batched_function.ts b/src/plugins/bfetch/common/buffer/create_batched_function.ts new file mode 100644 index 0000000000000..24f28659863a7 --- /dev/null +++ b/src/plugins/bfetch/common/buffer/create_batched_function.ts @@ -0,0 +1,49 @@ +/* + * 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 { ItemBufferParams } from './item_buffer'; +import { TimedItemBufferParams, TimedItemBuffer } from './timed_item_buffer'; + +type Fn = (...args: any) => any; + +export interface BatchedFunctionParams { + onCall: (...args: Parameters) => [ReturnType, BatchEntry]; + onBatch: (items: BatchEntry[]) => void; + flushOnMaxItems?: ItemBufferParams['flushOnMaxItems']; + maxItemAge?: TimedItemBufferParams['maxItemAge']; +} + +export const createBatchedFunction = ( + params: BatchedFunctionParams +): [Func, TimedItemBuffer] => { + const { onCall, onBatch, maxItemAge = 10, flushOnMaxItems = 25 } = params; + const buffer = new TimedItemBuffer({ + onFlush: onBatch, + maxItemAge, + flushOnMaxItems, + }); + + const fn: Func = ((...args) => { + const [result, batchEntry] = onCall(...args); + buffer.write(batchEntry); + return result; + }) as Func; + + return [fn, buffer]; +}; diff --git a/src/plugins/bfetch/common/buffer/index.ts b/src/plugins/bfetch/common/buffer/index.ts new file mode 100644 index 0000000000000..33bc52733289b --- /dev/null +++ b/src/plugins/bfetch/common/buffer/index.ts @@ -0,0 +1,22 @@ +/* + * 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 * from './item_buffer'; +export * from './timed_item_buffer'; +export * from './create_batched_function'; diff --git a/src/plugins/bfetch/common/buffer/item_buffer.ts b/src/plugins/bfetch/common/buffer/item_buffer.ts new file mode 100644 index 0000000000000..663aa5d7b0b7f --- /dev/null +++ b/src/plugins/bfetch/common/buffer/item_buffer.ts @@ -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. + */ + +export interface ItemBufferParams { + /** + * Flushes buffer automatically if number of items in the buffer reaches + * this number. Omit it or set to `Infinity` to never flush on max buffer + * size automatically. + */ + flushOnMaxItems?: number; + + /** + * Callback that is called every time buffer is flushed. It receives a single + * argument which is a list of all buffered items. If `.flush()` is called + * when buffer is empty, `.onflush` is called with empty array. + */ + onFlush: (items: Item[]) => void; +} + +/** + * A simple buffer that collects items. Can be cleared or flushed; and can + * automatically flush when specified number of items is reached. + */ +export class ItemBuffer { + private list: Item[] = []; + + constructor(public readonly params: ItemBufferParams) {} + + /** + * Get current buffer size. + */ + public get length(): number { + return this.list.length; + } + + /** + * Add item to the buffer. + */ + public write(item: Item) { + this.list.push(item); + + const { flushOnMaxItems } = this.params; + if (flushOnMaxItems) { + if (this.list.length >= flushOnMaxItems) { + this.flush(); + } + } + } + + /** + * Remove all items from the buffer. + */ + public clear() { + this.list = []; + } + + /** + * Call `.onflush` method and clear buffer. + */ + public flush() { + let list; + [list, this.list] = [this.list, []]; + this.params.onFlush(list); + } +} diff --git a/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts b/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts new file mode 100644 index 0000000000000..5b145a2523070 --- /dev/null +++ b/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.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 { createBatchedFunction } from '../create_batched_function'; + +describe('createBatchedFunction', () => { + test('calls onCall every time fn is called, calls onBatch once flushOnMaxItems reached', async () => { + const onBatch = jest.fn(); + const onCall = jest.fn(() => [1, 2] as any); + const [fn] = createBatchedFunction({ + onBatch, + onCall, + flushOnMaxItems: 2, + maxItemAge: 10, + }); + + expect(onCall).toHaveBeenCalledTimes(0); + expect(onBatch).toHaveBeenCalledTimes(0); + + fn(123); + + expect(onCall).toHaveBeenCalledTimes(1); + expect(onCall).toHaveBeenCalledWith(123); + expect(onBatch).toHaveBeenCalledTimes(0); + + fn(456); + + expect(onCall).toHaveBeenCalledTimes(2); + expect(onCall).toHaveBeenCalledWith(456); + expect(onBatch).toHaveBeenCalledTimes(1); + expect(onBatch).toHaveBeenCalledWith([2, 2]); + }); + + test('calls onBatch once timeout is reached', async () => { + const onBatch = jest.fn(); + const onCall = jest.fn(() => [4, 3] as any); + const [fn] = createBatchedFunction({ + onBatch, + onCall, + flushOnMaxItems: 2, + maxItemAge: 10, + }); + + expect(onCall).toHaveBeenCalledTimes(0); + expect(onBatch).toHaveBeenCalledTimes(0); + + fn(123); + + expect(onCall).toHaveBeenCalledTimes(1); + expect(onCall).toHaveBeenCalledWith(123); + expect(onBatch).toHaveBeenCalledTimes(0); + + await new Promise(r => setTimeout(r, 15)); + + expect(onCall).toHaveBeenCalledTimes(1); + expect(onBatch).toHaveBeenCalledTimes(1); + expect(onBatch).toHaveBeenCalledWith([3]); + }); +}); diff --git a/src/legacy/core_plugins/interpreter/server/routes/index.ts b/src/plugins/bfetch/common/buffer/tests/item_buffer.test.ts similarity index 85% rename from src/legacy/core_plugins/interpreter/server/routes/index.ts rename to src/plugins/bfetch/common/buffer/tests/item_buffer.test.ts index 50385147dd38e..a921fa8e589a3 100644 --- a/src/legacy/core_plugins/interpreter/server/routes/index.ts +++ b/src/plugins/bfetch/common/buffer/tests/item_buffer.test.ts @@ -17,8 +17,7 @@ * under the License. */ -import { registerServerFunctions } from './server_functions'; +import { ItemBuffer } from '../item_buffer'; +import { runItemBufferTests } from './run_item_buffer_tests'; -export function routes(server: any) { - registerServerFunctions(server); -} +runItemBufferTests(ItemBuffer); diff --git a/src/plugins/bfetch/common/buffer/tests/run_item_buffer_tests.ts b/src/plugins/bfetch/common/buffer/tests/run_item_buffer_tests.ts new file mode 100644 index 0000000000000..b3ba9375448dc --- /dev/null +++ b/src/plugins/bfetch/common/buffer/tests/run_item_buffer_tests.ts @@ -0,0 +1,239 @@ +/* + * 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 { ItemBuffer, ItemBufferParams } from '../item_buffer'; + +export const runItemBufferTests = ( + Buffer: new >(params: Params) => ItemBuffer +) => { + describe('ItemBuffer', () => { + test('can create with or without optional "flushOnMaxItems" param', () => { + new Buffer({ + onFlush: () => {}, + }); + + new Buffer({ + onFlush: () => {}, + flushOnMaxItems: 123, + }); + }); + + test('can add items to the buffer', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write('a'); + buf.write('b'); + buf.write('c'); + }); + + test('returns number of items in the buffer', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + expect(buf.length).toBe(0); + buf.write('a'); + expect(buf.length).toBe(1); + buf.write('b'); + expect(buf.length).toBe(2); + buf.write('c'); + expect(buf.length).toBe(3); + }); + + test('returns correct number of items after .clear() was called', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + expect(buf.length).toBe(0); + buf.write('a'); + expect(buf.length).toBe(1); + buf.clear(); + buf.write('b'); + expect(buf.length).toBe(1); + buf.write('c'); + expect(buf.length).toBe(2); + }); + + test('returns correct number of items after .flush() was called', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + expect(buf.length).toBe(0); + buf.write('a'); + expect(buf.length).toBe(1); + buf.flush(); + buf.write('b'); + expect(buf.length).toBe(1); + buf.write('c'); + expect(buf.length).toBe(2); + }); + + test('can flush buffer and receive items in chronological order', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write('a'); + buf.write('b'); + buf.write('c'); + + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual(['a', 'b', 'c']); + }); + + test('clears buffer after flush', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write('a'); + buf.write('b'); + buf.write('c'); + + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual(['a', 'b', 'c']); + + buf.write('d'); + + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(2); + expect(onFlush.mock.calls[1][0]).toEqual(['d']); + }); + + test('can call .flush() any time as many times as needed', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.flush(); + buf.write(123); + buf.flush(); + buf.flush(); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(4); + expect(onFlush.mock.calls[0][0]).toEqual([]); + expect(onFlush.mock.calls[1][0]).toEqual([123]); + expect(onFlush.mock.calls[2][0]).toEqual([]); + expect(onFlush.mock.calls[3][0]).toEqual([]); + }); + + test('calling .clear() before .flush() cases to return empty list', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write(1); + buf.write(2); + buf.clear(); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual([]); + }); + + test('can call .clear() any time as many times as needed', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.clear(); + buf.flush(); + buf.write(123); + buf.clear(); + buf.flush(); + buf.clear(); + buf.clear(); + buf.flush(); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(4); + expect(onFlush.mock.calls[0][0]).toEqual([]); + expect(onFlush.mock.calls[1][0]).toEqual([]); + expect(onFlush.mock.calls[2][0]).toEqual([]); + expect(onFlush.mock.calls[3][0]).toEqual([]); + }); + + describe('when `flushOnMaxItems` is set', () => { + test('does not flush automatically before `flushOnMaxItems` is reached', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + flushOnMaxItems: 2, + }); + + buf.write(1); + + expect(onFlush).toHaveBeenCalledTimes(0); + }); + + test('automatically flushes buffer when `flushOnMaxItems` is reached', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + flushOnMaxItems: 2, + }); + + buf.write(1); + buf.write(2); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual([1, 2]); + }); + + test('flushes again when `flushOnMaxItems` limit is reached the second time', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + flushOnMaxItems: 2, + }); + + buf.write(1); + buf.write(2); + buf.write(3); + buf.write(4); + buf.write(5); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(3); + expect(onFlush.mock.calls[0][0]).toEqual([1, 2]); + expect(onFlush.mock.calls[1][0]).toEqual([3, 4]); + expect(onFlush.mock.calls[2][0]).toEqual([5]); + }); + }); + }); +}; diff --git a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts new file mode 100644 index 0000000000000..c1c6a8f187a44 --- /dev/null +++ b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts @@ -0,0 +1,104 @@ +/* + * 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 { TimedItemBuffer } from '../timed_item_buffer'; +import { runItemBufferTests } from './run_item_buffer_tests'; + +describe('TimedItemBuffer', () => { + runItemBufferTests(TimedItemBuffer); + + test('does not do unnecessary flushes', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + maxItemAge: 3, + }); + + expect(onFlush).toHaveBeenCalledTimes(0); + buf.write(0); + expect(onFlush).toHaveBeenCalledTimes(0); + buf.flush(); + expect(onFlush).toHaveBeenCalledTimes(1); + }); + + test('does not do extra flush after timeout if buffer was flushed during timeout wait', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + maxItemAge: 10, + }); + + buf.write(0); + await new Promise(r => setTimeout(r, 3)); + buf.flush(); + await new Promise(r => setTimeout(r, 11)); + + expect(onFlush).toHaveBeenCalledTimes(1); + }); + + test('flushes buffer automatically after timeout reached', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + maxItemAge: 2, + }); + + buf.write(1); + buf.write(2); + expect(onFlush).toHaveBeenCalledTimes(0); + + await new Promise(r => setTimeout(r, 3)); + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush).toHaveBeenCalledWith([1, 2]); + }); + + test('does not call flush after timeout if flush was triggered because buffer size reached', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + flushOnMaxItems: 2, + maxItemAge: 2, + }); + + buf.write(1); + buf.write(2); + + expect(onFlush).toHaveBeenCalledTimes(1); + await new Promise(r => setTimeout(r, 3)); + expect(onFlush).toHaveBeenCalledTimes(1); + }); + + test('does not automatically flush if `.clear()` was called', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + flushOnMaxItems: 25, + maxItemAge: 5, + }); + + buf.write(1); + buf.write(2); + await new Promise(r => setImmediate(r)); + buf.clear(); + + expect(onFlush).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 6)); + expect(onFlush).toHaveBeenCalledTimes(0); + }); +}); diff --git a/src/plugins/bfetch/common/buffer/timed_item_buffer.ts b/src/plugins/bfetch/common/buffer/timed_item_buffer.ts new file mode 100644 index 0000000000000..8d0f9e4856f8c --- /dev/null +++ b/src/plugins/bfetch/common/buffer/timed_item_buffer.ts @@ -0,0 +1,58 @@ +/* + * 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 { ItemBuffer, ItemBufferParams } from './item_buffer'; + +export interface TimedItemBufferParams extends ItemBufferParams { + /** + * Flushes buffer when oldest item reaches age specified by this parameter, + * in milliseconds. + */ + maxItemAge?: number; +} + +export class TimedItemBuffer extends ItemBuffer { + private timer: any; + + constructor(public readonly params: TimedItemBufferParams) { + super(params); + } + + public write(item: Item) { + super.write(item); + + if (this.params.maxItemAge && this.length === 1) { + this.timer = setTimeout(this.onTimeout, this.params.maxItemAge); + } + } + + public clear() { + clearTimeout(this.timer); + super.clear(); + } + + public flush() { + clearTimeout(this.timer); + super.flush(); + } + + private onTimeout = () => { + this.flush(); + }; +} diff --git a/src/plugins/bfetch/common/index.ts b/src/plugins/bfetch/common/index.ts index afa73ade80084..085b8e7c58a67 100644 --- a/src/plugins/bfetch/common/index.ts +++ b/src/plugins/bfetch/common/index.ts @@ -19,3 +19,5 @@ export * from './util'; export * from './streaming'; +export * from './buffer'; +export * from './batch'; diff --git a/src/plugins/bfetch/common/streaming/types.ts b/src/plugins/bfetch/common/streaming/types.ts index 1ee92edbc89ff..197ee9a52ff01 100644 --- a/src/plugins/bfetch/common/streaming/types.ts +++ b/src/plugins/bfetch/common/streaming/types.ts @@ -20,5 +20,5 @@ import { Observable } from 'rxjs'; export interface StreamingResponseHandler { - onRequest(payload: Payload): Observable; + getResponseStream(payload: Payload): Observable; } diff --git a/src/plugins/bfetch/common/util/index.ts b/src/plugins/bfetch/common/util/index.ts index 02843af9b4350..b5d1fcabbcd85 100644 --- a/src/plugins/bfetch/common/util/index.ts +++ b/src/plugins/bfetch/common/util/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export * from './normalize_error'; export * from './remove_leading_slash'; diff --git a/src/plugins/bfetch/common/util/normalize_error.ts b/src/plugins/bfetch/common/util/normalize_error.ts new file mode 100644 index 0000000000000..c2ee3d83f5eb5 --- /dev/null +++ b/src/plugins/bfetch/common/util/normalize_error.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ErrorLike } from '../batch'; + +export const normalizeError = (err: any): E => { + if (!err) { + return { + message: 'Unknown error.', + } as E; + } + if (err instanceof Error) { + return { message: err.message } as E; + } + if (typeof err === 'object') { + return { + ...err, + message: err.message || 'Unknown error.', + } as E; + } + return { + message: String(err), + } as E; +}; diff --git a/src/plugins/bfetch/docs/browser/reference.md b/src/plugins/bfetch/docs/browser/reference.md index 47a67c08a4c1f..444b1aa08a98e 100644 --- a/src/plugins/bfetch/docs/browser/reference.md +++ b/src/plugins/bfetch/docs/browser/reference.md @@ -1,8 +1,37 @@ # `bfetch` browser reference +- [`batchedFunction`](#batchedFunction) - [`fetchStreaming`](#fetchStreaming) +## `batchedFunction` + +Creates a function that will buffer its calls (until timeout—10ms default— or capacity reached—25 default) +and send all calls in one batch to the specified endpoint. The endpoint is expected +to stream results back in ND-JSON format using `Transfer-Encoding: chunked`, which is +implemented by `addBatchProcessingRoute` server-side method of `bfetch` plugin. + +The created function is expected to be called with a single object argument and will +return a promise that will resolve to an object. + +```ts +const fn = bfetch.batchedFunction({ url: '/my-plugin/something' }); + +const result = await fn({ foo: 'bar' }); +``` + +Options: + +- `url` — URL endpoint that will receive a batch of requests. This endpoint is expected + to receive batch as a serialized JSON array. It should stream responses back + in ND-JSON format using `Transfer-Encoding: chunked` HTTP/1 streaming. +- `fetchStreaming` — The instance of `fetchStreaming` function that will perform ND-JSON handling. + There should be a version of this function available in setup contract of `bfetch` plugin. +- `flushOnMaxItems` — The maximum size of function call buffer before sending the batch request. +- `maxItemAge` — The maximum timeout in milliseconds of the oldest item in the batch + before sending the batch request. + + ## `fetchStreaming` Executes an HTTP request and expects that server streams back results using @@ -12,4 +41,4 @@ HTTP/1 `Transfer-Encoding: chunked`. const { stream } = bfetch.fetchStreaming({ url: 'http://elastic.co' }); stream.subscribe(value => {}); -``` \ No newline at end of file +``` diff --git a/src/plugins/bfetch/docs/server/reference.md b/src/plugins/bfetch/docs/server/reference.md new file mode 100644 index 0000000000000..424532a50b817 --- /dev/null +++ b/src/plugins/bfetch/docs/server/reference.md @@ -0,0 +1,54 @@ +# `bfetch` server reference + +- [`addBatchProcessingRoute`](#addBatchProcessingRoute) +- [`addStreamingResponseRoute`](#addStreamingResponseRoute) + + +## `addBatchProcessingRoute` + +Sets up a server endpoint that expects to work with [`batchedFunction`](../browser/reference.md#batchedFunction). +The endpoint receives a batch of requests, processes each request and streams results +back immediately as they become available. You only need to implement the +processing of each request (`onBatchItem` function), everything else is handled. + +`onBatchItem` function is called for each individual request in the batch. +`onBatchItem` function receives a single object argument which is the payload +of one request; and it must return a promise that resolves to an object, too. +`onBatchItem` function is allowed to throw, in that case the error will be forwarded +to the browser only to the individual request, the rest of the batch will still continue +executing. + +```ts +plugins.bfetch.addBatchProcessingRoute( + '/my-plugin/double', + request => ({ + onBatchItem: async (payload) => { + // ... + return {}; + }, + }) +); +``` + +`request` is the `KibanaRequest` object. `addBatchProcessingRoute` together with `batchedFunction` +ensure that errors are handled and that all items in the batch get executed. + + +## `addStreamingResponseRoute` + +`addStreamingResponseRoute` is a lower-level interface that receives and `payload` +message returns and observable which results are streamed back as ND-JSON messages +until the observable completes. `addStreamingResponseRoute` does not know about the +type of the messages, it does not handle errors, and it does not have a concept of +batch size—observable can stream any number of messages until it completes. + +```ts +plugins.bfetch.addStreamingResponseRoute('/my-plugin/foo', request => ({ + getResponseStream: (payload) => { + const subject = new Subject(); + setTimeout(() => { subject.next('123'); }, 100); + setTimeout(() => { subject.complete(); }, 200); + return subject; + }, +})); +``` diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts new file mode 100644 index 0000000000000..064b791327e69 --- /dev/null +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -0,0 +1,521 @@ +/* + * 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 { createStreamingBatchedFunction } from './create_streaming_batched_function'; +import { fetchStreaming as fetchStreamingReal } from '../streaming/fetch_streaming'; +import { defer, of } from '../../../kibana_utils/public'; +import { Subject } from 'rxjs'; + +const getPromiseState = (promise: Promise): Promise<'resolved' | 'rejected' | 'pending'> => + Promise.race<'resolved' | 'rejected' | 'pending'>([ + new Promise(resolve => + promise.then( + () => resolve('resolved'), + () => resolve('rejected') + ) + ), + new Promise<'pending'>(resolve => resolve()).then(() => 'pending'), + ]); + +const isPending = (promise: Promise): Promise => + getPromiseState(promise).then(state => state === 'pending'); + +const setup = () => { + const xhr = ({} as unknown) as XMLHttpRequest; + const { promise, resolve, reject } = defer(); + const stream = new Subject(); + + const fetchStreaming = (jest.fn(() => ({ + xhr, + promise, + stream, + })) as unknown) as jest.SpyInstance & typeof fetchStreamingReal; + + return { + fetchStreaming, + xhr, + promise, + resolve, + reject, + stream, + }; +}; + +describe('createStreamingBatchedFunction()', () => { + test('returns a function', () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + }); + expect(typeof fn).toBe('function'); + }); + + test('returned function is async', () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + }); + const res = fn({}); + expect(typeof res.then).toBe('function'); + }); + + describe('when timeout is reached', () => { + test('dispatches batch', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ foo: 'bar' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ baz: 'quix' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + + await new Promise(r => setTimeout(r, 6)); + expect(fetchStreaming).toHaveBeenCalledTimes(1); + }); + + test('does nothing is buffer is empty', async () => { + const { fetchStreaming } = setup(); + createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + expect(fetchStreaming).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 6)); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + }); + + test('sends POST request to correct endpoint', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ foo: 'bar' }); + await new Promise(r => setTimeout(r, 6)); + + expect(fetchStreaming.mock.calls[0][0]).toMatchObject({ + url: '/test', + method: 'POST', + }); + }); + + test('collects calls into an array batch ordered by in same order as calls', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ foo: 'bar' }); + fn({ baz: 'quix' }); + + await new Promise(r => setTimeout(r, 6)); + const { body } = fetchStreaming.mock.calls[0][0]; + expect(JSON.parse(body)).toEqual({ + batch: [{ foo: 'bar' }, { baz: 'quix' }], + }); + }); + }); + + describe('when buffer becomes full', () => { + test('dispatches batch request', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ foo: 'bar' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ baz: 'quix' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ full: 'yep' }); + expect(fetchStreaming).toHaveBeenCalledTimes(1); + }); + + test('sends POST request to correct endpoint with items in array batched sorted in call order', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ a: '1' }); + fn({ b: '2' }); + fn({ c: '3' }); + + expect(fetchStreaming.mock.calls[0][0]).toMatchObject({ + url: '/test', + method: 'POST', + }); + const { body } = fetchStreaming.mock.calls[0][0]; + expect(JSON.parse(body)).toEqual({ + batch: [{ a: '1' }, { b: '2' }, { c: '3' }], + }); + }); + + test('dispatches batch on full buffer and also on timeout', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ a: '1' }); + fn({ b: '2' }); + fn({ c: '3' }); + expect(fetchStreaming).toHaveBeenCalledTimes(1); + fn({ d: '4' }); + await new Promise(r => setTimeout(r, 6)); + expect(fetchStreaming).toHaveBeenCalledTimes(2); + }); + }); + + describe('when receiving results', () => { + test('does not resolve call promises until request finishes', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + await new Promise(r => setTimeout(r, 6)); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(true); + }); + + test('resolves only promise of result that was streamed back', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + const promise3 = fn({ c: '3' }); + await new Promise(r => setTimeout(r, 6)); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(true); + expect(await isPending(promise3)).toBe(true); + + stream.next( + JSON.stringify({ + id: 1, + result: { foo: 'bar' }, + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(true); + + stream.next( + JSON.stringify({ + id: 0, + result: { foo: 'bar 2' }, + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(false); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(true); + }); + + test('resolves each promise with correct data', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + const promise3 = fn({ c: '3' }); + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 1, + result: { foo: 'bar' }, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 2, + result: { foo: 'bar 2' }, + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(false); + expect(await promise2).toEqual({ foo: 'bar' }); + expect(await promise3).toEqual({ foo: 'bar 2' }); + }); + + test('rejects promise on error response', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise = fn({ a: '1' }); + await new Promise(r => setTimeout(r, 6)); + + expect(await isPending(promise)).toBe(true); + + stream.next( + JSON.stringify({ + id: 0, + error: { message: 'oops' }, + }) + '\n' + ); + + expect(await isPending(promise)).toBe(false); + const [, error] = await of(promise); + expect(error).toEqual({ + message: 'oops', + }); + }); + + test('resolves successful requests even after rejected ones', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + const promise3 = of(fn({ a: '3' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 2, + result: { b: '3' }, + }) + '\n' + ); + + await new Promise(r => setTimeout(r, 1)); + + stream.next( + JSON.stringify({ + id: 1, + error: { b: '2' }, + }) + '\n' + ); + + await new Promise(r => setTimeout(r, 1)); + + stream.next( + JSON.stringify({ + id: 0, + result: { b: '1' }, + }) + '\n' + ); + + await new Promise(r => setTimeout(r, 1)); + + const [result1] = await promise1; + const [, error2] = await promise2; + const [result3] = await promise3; + + expect(result1).toEqual({ b: '1' }); + expect(error2).toEqual({ b: '2' }); + expect(result3).toEqual({ b: '3' }); + }); + + describe('when stream closes prematurely', () => { + test('rejects pending promises with CONNECTION error code', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.complete(); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [, error2] = await promise2; + expect(error1).toMatchObject({ + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }); + expect(error2).toMatchObject({ + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }); + }); + + test('rejects with CONNECTION error only pending promises', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 1, + result: { b: '1' }, + }) + '\n' + ); + stream.complete(); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [result1] = await promise2; + expect(error1).toMatchObject({ + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }); + expect(result1).toMatchObject({ + b: '1', + }); + }); + }); + + describe('when stream errors', () => { + test('rejects pending promises with STREAM error code', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.error({ + message: 'something went wrong', + }); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [, error2] = await promise2; + expect(error1).toMatchObject({ + message: 'something went wrong', + code: 'STREAM', + }); + expect(error2).toMatchObject({ + message: 'something went wrong', + code: 'STREAM', + }); + }); + + test('rejects with STREAM error only pending promises', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 1, + result: { b: '1' }, + }) + '\n' + ); + stream.error('oops'); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [result1] = await promise2; + expect(error1).toMatchObject({ + message: 'oops', + code: 'STREAM', + }); + expect(result1).toMatchObject({ + b: '1', + }); + }); + }); + }); +}); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts new file mode 100644 index 0000000000000..07d5724a2520d --- /dev/null +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -0,0 +1,140 @@ +/* + * 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 { defer, Defer } from '../../../kibana_utils/public'; +import { + ItemBufferParams, + TimedItemBufferParams, + createBatchedFunction, + BatchResponseItem, + ErrorLike, +} from '../../common'; +import { fetchStreaming, split } from '../streaming'; +import { normalizeError } from '../../common'; + +export interface BatchItem { + payload: Payload; + future: Defer; +} + +export type BatchedFunc = (payload: Payload) => Promise; + +export interface BatchedFunctionProtocolError extends ErrorLike { + code: string; +} + +export interface StreamingBatchedFunctionParams { + /** + * URL endpoint that will receive a batch of requests. This endpoint is expected + * to receive batch as a serialized JSON array. It should stream responses back + * in ND-JSON format using `Transfer-Encoding: chunked` HTTP/1 streaming. + */ + url: string; + + /** + * The instance of `fetchStreaming` function that will perform ND-JSON handling. + * There should be a version of this function available in setup contract of `bfetch` + * plugin. + */ + fetchStreaming?: typeof fetchStreaming; + + /** + * The maximum size of function call buffer before sending the batch request. + */ + flushOnMaxItems?: ItemBufferParams['flushOnMaxItems']; + + /** + * The maximum timeout in milliseconds of the oldest item in the batch + * before sending the batch request. + */ + maxItemAge?: TimedItemBufferParams['maxItemAge']; +} + +/** + * Returns a function that does not execute immediately but buffers the call internally until + * `params.flushOnMaxItems` is reached or after `params.maxItemAge` timeout in milliseconds is reached. Once + * one of those thresholds is reached all buffered calls are sent in one batch to the + * server using `params.fetchStreaming` in a POST request. Responses are streamed back + * and each batch item is resolved once corresponding response is received. + */ +export const createStreamingBatchedFunction = ( + params: StreamingBatchedFunctionParams +): BatchedFunc => { + const { + url, + fetchStreaming: fetchStreamingInjected = fetchStreaming, + flushOnMaxItems = 25, + maxItemAge = 10, + } = params; + const [fn] = createBatchedFunction, BatchItem>({ + onCall: (payload: Payload) => { + const future = defer(); + const entry: BatchItem = { + payload, + future, + }; + return [future.promise, entry]; + }, + onBatch: async items => { + try { + let responsesReceived = 0; + const batch = items.map(({ payload }) => payload); + const { stream } = fetchStreamingInjected({ + url, + body: JSON.stringify({ batch }), + method: 'POST', + }); + stream.pipe(split('\n')).subscribe({ + next: (json: string) => { + const response = JSON.parse(json) as BatchResponseItem; + if (response.error) { + responsesReceived++; + items[response.id].future.reject(response.error); + } else if (response.result) { + responsesReceived++; + items[response.id].future.resolve(response.result); + } + }, + error: error => { + const normalizedError = normalizeError(error); + normalizedError.code = 'STREAM'; + for (const { future } of items) future.reject(normalizedError); + }, + complete: () => { + const streamTerminatedPrematurely = responsesReceived !== items.length; + if (streamTerminatedPrematurely) { + const error: BatchedFunctionProtocolError = { + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }; + for (const { future } of items) future.reject(error); + } + }, + }); + await stream.toPromise(); + } catch (error) { + for (const item of items) item.future.reject(error); + } + }, + flushOnMaxItems, + maxItemAge, + }); + + return fn; +}; diff --git a/src/plugins/bfetch/public/index.ts b/src/plugins/bfetch/public/index.ts index a57dd77fe7e67..8707e5a438159 100644 --- a/src/plugins/bfetch/public/index.ts +++ b/src/plugins/bfetch/public/index.ts @@ -20,7 +20,8 @@ import { PluginInitializerContext } from '../../../core/public'; import { BfetchPublicPlugin } from './plugin'; -export { BfetchPublicSetup, BfetchPublicStart, BfetchPublicApi } from './plugin'; +export { BfetchPublicSetup, BfetchPublicStart, BfetchPublicContract } from './plugin'; +export { split } from './streaming'; export function plugin(initializerContext: PluginInitializerContext) { return new BfetchPublicPlugin(initializerContext); diff --git a/src/plugins/bfetch/public/mocks.ts b/src/plugins/bfetch/public/mocks.ts index e8caf5c9cb739..f457b9ae5d671 100644 --- a/src/plugins/bfetch/public/mocks.ts +++ b/src/plugins/bfetch/public/mocks.ts @@ -27,6 +27,7 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { fetchStreaming: jest.fn(), + batchedFunction: jest.fn(), }; return setupContract; }; @@ -34,6 +35,7 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { fetchStreaming: jest.fn(), + batchedFunction: jest.fn(), }; return startContract; @@ -56,7 +58,7 @@ const createPlugin = async () => { }; }; -export const uiActionsPluginMock = { +export const bfetchPluginMock = { createSetupContract, createStartContract, createPlugin, diff --git a/src/plugins/bfetch/public/plugin.ts b/src/plugins/bfetch/public/plugin.ts index db18a15afa1e7..783c448c567e5 100644 --- a/src/plugins/bfetch/public/plugin.ts +++ b/src/plugins/bfetch/public/plugin.ts @@ -20,6 +20,11 @@ import { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/public'; import { fetchStreaming as fetchStreamingStatic, FetchStreamingParams } from './streaming'; import { removeLeadingSlash } from '../common'; +import { + createStreamingBatchedFunction, + BatchedFunc, + StreamingBatchedFunctionParams, +} from './batching/create_streaming_batched_function'; // eslint-disable-next-line export interface BfetchPublicSetupDependencies {} @@ -27,12 +32,15 @@ export interface BfetchPublicSetupDependencies {} // eslint-disable-next-line export interface BfetchPublicStartDependencies {} -export interface BfetchPublicApi { +export interface BfetchPublicContract { fetchStreaming: (params: FetchStreamingParams) => ReturnType; + batchedFunction: ( + params: StreamingBatchedFunctionParams + ) => BatchedFunc; } -export type BfetchPublicSetup = BfetchPublicApi; -export type BfetchPublicStart = BfetchPublicApi; +export type BfetchPublicSetup = BfetchPublicContract; +export type BfetchPublicStart = BfetchPublicContract; export class BfetchPublicPlugin implements @@ -42,7 +50,7 @@ export class BfetchPublicPlugin BfetchPublicSetupDependencies, BfetchPublicStartDependencies > { - private api!: BfetchPublicApi; + private contract!: BfetchPublicContract; constructor(private readonly initializerContext: PluginInitializerContext) {} @@ -51,16 +59,18 @@ export class BfetchPublicPlugin const basePath = core.http.basePath.get(); const fetchStreaming = this.fetchStreaming(version, basePath); + const batchedFunction = this.batchedFunction(fetchStreaming); - this.api = { + this.contract = { fetchStreaming, + batchedFunction, }; - return this.api; + return this.contract; } public start(core: CoreStart, plugins: BfetchPublicStartDependencies): BfetchPublicStart { - return this.api; + return this.contract; } public stop() {} @@ -78,4 +88,12 @@ export class BfetchPublicPlugin ...(params.headers || {}), }, }); + + private batchedFunction = ( + fetchStreaming: BfetchPublicContract['fetchStreaming'] + ): BfetchPublicContract['batchedFunction'] => params => + createStreamingBatchedFunction({ + ...params, + fetchStreaming: params.fetchStreaming || fetchStreaming, + }); } diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts index e59af71cb76bc..7845616026ea1 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts @@ -36,14 +36,6 @@ test('returns XHR request', () => { expect(typeof xhr.readyState).toBe('number'); }); -test('returns promise', () => { - setup(); - const { promise } = fetchStreaming({ - url: 'http://example.com', - }); - expect(typeof promise.then).toBe('function'); -}); - test('returns stream', () => { setup(); const { stream } = fetchStreaming({ @@ -54,12 +46,12 @@ test('returns stream', () => { test('promise resolves when request completes', async () => { const env = setup(); - const { promise } = fetchStreaming({ + const { stream } = fetchStreaming({ url: 'http://example.com', }); let resolved = false; - promise.then(() => (resolved = true)); + stream.toPromise().then(() => (resolved = true)); await tick(); expect(resolved).toBe(false); @@ -142,12 +134,12 @@ test('completes stream observable when request finishes', async () => { test('promise throws when request errors', async () => { const env = setup(); - const { promise } = fetchStreaming({ + const { stream } = fetchStreaming({ url: 'http://example.com', }); const spy = jest.fn(); - promise.catch(spy); + stream.toPromise().catch(spy); await tick(); expect(spy).toHaveBeenCalledTimes(0); @@ -168,12 +160,11 @@ test('promise throws when request errors', async () => { test('stream observable errors when request errors', async () => { const env = setup(); - const { promise, stream } = fetchStreaming({ + const { stream } = fetchStreaming({ url: 'http://example.com', }); const spy = jest.fn(); - promise.catch(() => {}); stream.subscribe({ error: spy, }); diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.ts index 44a3693e7010b..899e8a1824a41 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.ts @@ -17,7 +17,6 @@ * under the License. */ -import { defer } from '../../../kibana_utils/common'; import { fromStreamingXhr } from './from_streaming_xhr'; export interface FetchStreamingParams { @@ -38,7 +37,6 @@ export function fetchStreaming({ body = '', }: FetchStreamingParams) { const xhr = new window.XMLHttpRequest(); - const { promise, resolve, reject } = defer(); // Begin the request xhr.open(method, url); @@ -49,17 +47,11 @@ export function fetchStreaming({ const stream = fromStreamingXhr(xhr); - stream.subscribe({ - complete: () => resolve(), - error: error => reject(error), - }); - // Send the payload to the server xhr.send(body); return { xhr, - promise, stream, }; } diff --git a/src/plugins/bfetch/server/index.ts b/src/plugins/bfetch/server/index.ts index f1a3f7fd44cf6..06b7c793c537e 100644 --- a/src/plugins/bfetch/server/index.ts +++ b/src/plugins/bfetch/server/index.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext } from '../../../core/server'; import { BfetchServerPlugin } from './plugin'; -export { BfetchServerSetup, BfetchServerStart } from './plugin'; +export { BfetchServerSetup, BfetchServerStart, BatchProcessingRouteParams } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new BfetchServerPlugin(initializerContext); diff --git a/src/plugins/bfetch/server/mocks.ts b/src/plugins/bfetch/server/mocks.ts index 8ec68650a60dc..e0a76ba8da325 100644 --- a/src/plugins/bfetch/server/mocks.ts +++ b/src/plugins/bfetch/server/mocks.ts @@ -26,6 +26,7 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { + addBatchProcessingRoute: jest.fn(), addStreamingResponseRoute: jest.fn(), }; return setupContract; @@ -54,7 +55,7 @@ const createPlugin = async () => { }; }; -export const uiActionsPluginMock = { +export const bfetchPluginMock = { createSetupContract, createStartContract, createPlugin, diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 75baeafc17669..fd1fe009e93ae 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -17,9 +17,24 @@ * under the License. */ -import { CoreStart, PluginInitializerContext, CoreSetup, Plugin, Logger } from 'src/core/server'; +import { + CoreStart, + PluginInitializerContext, + CoreSetup, + Plugin, + Logger, + KibanaRequest, +} from 'src/core/server'; import { schema } from '@kbn/config-schema'; -import { StreamingResponseHandler, removeLeadingSlash } from '../common'; +import { Subject } from 'rxjs'; +import { + StreamingResponseHandler, + BatchRequestData, + BatchResponseItem, + ErrorLike, + removeLeadingSlash, + normalizeError, +} from '../common'; import { createNDJSONStream } from './streaming'; // eslint-disable-next-line @@ -28,8 +43,19 @@ export interface BfetchServerSetupDependencies {} // eslint-disable-next-line export interface BfetchServerStartDependencies {} +export interface BatchProcessingRouteParams { + onBatchItem: (data: BatchItemData) => Promise; +} + export interface BfetchServerSetup { - addStreamingResponseRoute: (path: string, handler: StreamingResponseHandler) => void; + addBatchProcessingRoute: ( + path: string, + handler: (request: KibanaRequest) => BatchProcessingRouteParams + ) => void; + addStreamingResponseRoute: ( + path: string, + params: (request: KibanaRequest) => StreamingResponseHandler + ) => void; } // eslint-disable-next-line @@ -49,8 +75,10 @@ export class BfetchServerPlugin const logger = this.initializerContext.logger.get(); const router = core.http.createRouter(); const addStreamingResponseRoute = this.addStreamingResponseRoute({ router, logger }); + const addBatchProcessingRoute = this.addBatchProcessingRoute(addStreamingResponseRoute); return { + addBatchProcessingRoute, addStreamingResponseRoute, }; } @@ -76,17 +104,56 @@ export class BfetchServerPlugin }, }, async (context, request, response) => { + const handlerInstance = handler(request); const data = request.body; + const headers = { + 'Content-Type': 'application/x-ndjson', + Connection: 'keep-alive', + 'Transfer-Encoding': 'chunked', + 'Cache-Control': 'no-cache', + }; return response.ok({ - headers: { - 'Content-Type': 'application/x-ndjson', - Connection: 'keep-alive', - 'Transfer-Encoding': 'chunked', - 'Cache-Control': 'no-cache', - }, - body: createNDJSONStream(data, handler, logger), + headers, + body: createNDJSONStream(data, handlerInstance, logger), }); } ); }; + + private addBatchProcessingRoute = ( + addStreamingResponseRoute: BfetchServerSetup['addStreamingResponseRoute'] + ): BfetchServerSetup['addBatchProcessingRoute'] => < + BatchItemData extends object, + BatchItemResult extends object, + E extends ErrorLike = ErrorLike + >( + path: string, + handler: (request: KibanaRequest) => BatchProcessingRouteParams + ) => { + addStreamingResponseRoute< + BatchRequestData, + BatchResponseItem + >(path, request => { + const handlerInstance = handler(request); + return { + getResponseStream: ({ batch }) => { + const subject = new Subject>(); + let cnt = batch.length; + batch.forEach(async (batchItem, id) => { + try { + const result = await handlerInstance.onBatchItem(batchItem); + subject.next({ id, result }); + } catch (err) { + const error = normalizeError(err); + subject.next({ id, error }); + } finally { + cnt--; + if (!cnt) subject.complete(); + } + }); + return subject; + }, + }; + }); + }; } diff --git a/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts index b1f39f4acbcb5..82fe31906e8bf 100644 --- a/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts +++ b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts @@ -29,7 +29,7 @@ export const createNDJSONStream = ( logger: Logger ): Stream => { const stream = new PassThrough(); - const results = handler.onRequest(payload); + const results = handler.getResponseStream(payload); results.subscribe({ next: (message: Response) => { diff --git a/src/plugins/data/public/autocomplete/autocomplete_service.ts b/src/plugins/data/public/autocomplete/autocomplete_service.ts new file mode 100644 index 0000000000000..0527f833b0f8c --- /dev/null +++ b/src/plugins/data/public/autocomplete/autocomplete_service.ts @@ -0,0 +1,77 @@ +/* + * 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 { CoreSetup } from 'src/core/public'; +import { QuerySuggestionsGetFn } from './providers/query_suggestion_provider'; +import { + setupValueSuggestionProvider, + ValueSuggestionsGetFn, +} from './providers/value_suggestion_provider'; + +export class AutocompleteService { + private readonly querySuggestionProviders: Map = new Map(); + private getValueSuggestions?: ValueSuggestionsGetFn; + + private addQuerySuggestionProvider = ( + language: string, + provider: QuerySuggestionsGetFn + ): void => { + if (language && provider) { + this.querySuggestionProviders.set(language, provider); + } + }; + + private getQuerySuggestions: QuerySuggestionsGetFn = args => { + const { language } = args; + const provider = this.querySuggestionProviders.get(language); + + if (provider) { + return provider(args); + } + }; + + private hasQuerySuggestions = (language: string) => this.querySuggestionProviders.has(language); + + /** @public **/ + public setup(core: CoreSetup) { + this.getValueSuggestions = setupValueSuggestionProvider(core); + + return { + addQuerySuggestionProvider: this.addQuerySuggestionProvider, + + /** @obsolete **/ + /** please use "getProvider" only from the start contract **/ + getQuerySuggestions: this.getQuerySuggestions, + }; + } + + /** @public **/ + public start() { + return { + getQuerySuggestions: this.getQuerySuggestions, + hasQuerySuggestions: this.hasQuerySuggestions, + getValueSuggestions: this.getValueSuggestions!, + }; + } + + /** @internal **/ + public clearProviders(): void { + this.querySuggestionProviders.clear(); + } +} diff --git a/src/plugins/data/public/autocomplete/index.ts b/src/plugins/data/public/autocomplete/index.ts new file mode 100644 index 0000000000000..5b8f3ae510bfd --- /dev/null +++ b/src/plugins/data/public/autocomplete/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { AutocompleteService } from './autocomplete_service'; +export { QuerySuggestion, QuerySuggestionType, QuerySuggestionsGetFn } from './types'; diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts similarity index 59% rename from src/plugins/data/public/autocomplete_provider/types.ts rename to src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts index 389057f94144d..53abdd44c0c3f 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts @@ -17,56 +17,40 @@ * under the License. */ -import { AutocompleteProviderRegister } from '.'; -import { IIndexPattern, IFieldType } from '../../common'; +import { IFieldType, IIndexPattern } from '../../../common/index_patterns'; -export type AutocompletePublicPluginSetup = Pick< - AutocompleteProviderRegister, - 'addProvider' | 'getProvider' ->; -export type AutocompletePublicPluginStart = Pick; +export type QuerySuggestionType = 'field' | 'value' | 'operator' | 'conjunction' | 'recentSearch'; -/** @public **/ -export type AutocompleteProvider = (args: { - config: { - get(configKey: string): any; - }; - indexPatterns: IIndexPattern[]; - boolFilter?: any; -}) => GetSuggestions; +export type QuerySuggestionsGetFn = ( + args: QuerySuggestionsGetFnArgs +) => Promise | undefined; -/** @public **/ -export type GetSuggestions = (args: { +interface QuerySuggestionsGetFnArgs { + language: string; + indexPatterns: IIndexPattern[]; query: string; selectionStart: number; selectionEnd: number; signal?: AbortSignal; -}) => Promise; - -/** @public **/ -export type AutocompleteSuggestionType = - | 'field' - | 'value' - | 'operator' - | 'conjunction' - | 'recentSearch'; - -// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm -// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the -// TypeScript compiler will narrow the type to the parts of the union that have a field prop. -/** @public **/ -export type AutocompleteSuggestion = BasicAutocompleteSuggestion | FieldAutocompleteSuggestion; + boolFilter?: any; +} -interface BasicAutocompleteSuggestion { +interface BasicQuerySuggestion { + type: QuerySuggestionType; description?: string; end: number; start: number; text: string; - type: AutocompleteSuggestionType; cursorIndex?: number; } -export type FieldAutocompleteSuggestion = BasicAutocompleteSuggestion & { +interface FieldQuerySuggestion extends BasicQuerySuggestion { type: 'field'; field: IFieldType; -}; +} + +// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm +// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the +// TypeScript compiler will narrow the type to the parts of the union that have a field prop. +/** @public **/ +export type QuerySuggestion = BasicQuerySuggestion | FieldQuerySuggestion; diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts similarity index 51% rename from src/plugins/data/public/suggestions_provider/value_suggestions.test.ts rename to src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts index 9089105b4e3a8..6b0c0f07cf6c9 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts @@ -17,98 +17,121 @@ * under the License. */ -import { stubIndexPattern, stubFields } from '../stubs'; -import { getSuggestionsProvider } from './value_suggestions'; -import { IUiSettingsClient } from 'kibana/public'; +import { stubIndexPattern, stubFields } from '../../stubs'; +import { setupValueSuggestionProvider, ValueSuggestionsGetFn } from './value_suggestion_provider'; +import { IUiSettingsClient, CoreSetup } from 'kibana/public'; -describe('getSuggestions', () => { - let getSuggestions: any; +describe('FieldSuggestions', () => { + let getValueSuggestions: ValueSuggestionsGetFn; let http: any; + let shouldSuggestValues: boolean; - describe('with value suggestions disabled', () => { - beforeEach(() => { - const config = { get: (key: string) => false } as IUiSettingsClient; - http = { fetch: jest.fn() }; - getSuggestions = getSuggestionsProvider(config, http); - }); + beforeEach(() => { + const uiSettings = { get: (key: string) => shouldSuggestValues } as IUiSettingsClient; + http = { fetch: jest.fn() }; + getValueSuggestions = setupValueSuggestionProvider({ http, uiSettings } as CoreSetup); + }); + + describe('with value suggestions disabled', () => { it('should return an empty array', async () => { - const index = stubIndexPattern.id; - const [field] = stubFields; - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: stubFields[0], + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); }); describe('with value suggestions enabled', () => { - beforeEach(() => { - const config = { get: (key: string) => true } as IUiSettingsClient; - http = { fetch: jest.fn() }; - getSuggestions = getSuggestionsProvider(config, http); - }); + shouldSuggestValues = true; it('should return true/false for boolean fields', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ type }) => type === 'boolean'); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([true, false]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should return an empty array if the field type is not a string or boolean', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ type }) => type !== 'string' && type !== 'boolean'); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should return an empty array if the field is not aggregatable', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ aggregatable }) => !aggregatable); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should otherwise request suggestions', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; - await getSuggestions(index, field, query); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(http.fetch).toHaveBeenCalled(); }); it('should cache results if using the same index/field/query/filter', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; - await getSuggestions(index, field, query); - await getSuggestions(index, field, query); + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + }; + + await getValueSuggestions(args); + await getValueSuggestions(args); + expect(http.fetch).toHaveBeenCalledTimes(1); }); it('should cache results for only one minute', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + }; const { now } = Date; Date.now = jest.fn(() => 0); - await getSuggestions(index, field, query); + + await getValueSuggestions(args); + Date.now = jest.fn(() => 60 * 1000); - await getSuggestions(index, field, query); + await getValueSuggestions(args); Date.now = now; expect(http.fetch).toHaveBeenCalledTimes(2); @@ -118,14 +141,54 @@ describe('getSuggestions', () => { const fields = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - await getSuggestions('index', fields[0], ''); - await getSuggestions('index', fields[0], 'query'); - await getSuggestions('index', fields[1], ''); - await getSuggestions('index', fields[1], 'query'); - await getSuggestions('logstash-*', fields[0], ''); - await getSuggestions('logstash-*', fields[0], 'query'); - await getSuggestions('logstash-*', fields[1], ''); - await getSuggestions('logstash-*', fields[1], 'query'); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: '', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: 'query', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: '', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: 'query', + }); + + const customIndexPattern = { + ...stubIndexPattern, + title: 'customIndexPattern', + }; + + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[0], + query: '', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[0], + query: 'query', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: '', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: 'query', + }); + expect(http.fetch).toHaveBeenCalledTimes(8); }); }); diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts similarity index 55% rename from src/plugins/data/public/suggestions_provider/value_suggestions.ts rename to src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index e64156c290db1..5df88000edbd5 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -18,51 +18,53 @@ */ import { memoize } from 'lodash'; +import { CoreSetup } from 'src/core/public'; +import { IIndexPattern, IFieldType } from '../../../common'; -import { IUiSettingsClient, HttpSetup } from 'src/core/public'; -import { IGetSuggestions } from './types'; -import { IFieldType } from '../../common'; +function resolver(title: string, field: IFieldType, query: string, boolFilter: any) { + // Only cache results for a minute + const ttl = Math.floor(Date.now() / 1000 / 60); + + return [ttl, query, title, field.name, JSON.stringify(boolFilter)].join('|'); +} + +export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; + +interface ValueSuggestionsGetFnArgs { + indexPattern: IIndexPattern; + field: IFieldType; + query: string; + boolFilter?: any[]; + signal?: AbortSignal; +} -export function getSuggestionsProvider( - uiSettings: IUiSettingsClient, - http: HttpSetup -): IGetSuggestions { +export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsGetFn => { const requestSuggestions = memoize( - ( - index: string, - field: IFieldType, - query: string, - boolFilter: any = [], - signal?: AbortSignal - ) => { - return http.fetch(`/api/kibana/suggestions/values/${index}`, { + (index: string, field: IFieldType, query: string, boolFilter: any = [], signal?: AbortSignal) => + core.http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', body: JSON.stringify({ query, field: field.name, boolFilter }), signal, - }); - }, + }), resolver ); - return async ( - index: string, - field: IFieldType, - query: string, - boolFilter?: any, - signal?: AbortSignal - ) => { - const shouldSuggestValues = uiSettings.get('filterEditor:suggestValues'); + return async ({ + indexPattern, + field, + query, + boolFilter, + signal, + }: ValueSuggestionsGetFnArgs): Promise => { + const shouldSuggestValues = core!.uiSettings.get('filterEditor:suggestValues'); + const { title } = indexPattern; + if (field.type === 'boolean') { return [true, false]; } else if (!shouldSuggestValues || !field.aggregatable || field.type !== 'string') { return []; } - return await requestSuggestions(index, field, query, boolFilter, signal); - }; -} -function resolver(index: string, field: IFieldType, query: string, boolFilter: any) { - // Only cache results for a minute - const ttl = Math.floor(Date.now() / 1000 / 60); - return [ttl, query, index, field.name, JSON.stringify(boolFilter)].join('|'); -} + return await requestSuggestions(title, field, query, boolFilter, signal); + }; +}; diff --git a/src/legacy/core_plugins/visualizations/public/expressions/boot.ts b/src/plugins/data/public/autocomplete/types.ts similarity index 67% rename from src/legacy/core_plugins/visualizations/public/expressions/boot.ts rename to src/plugins/data/public/autocomplete/types.ts index c30493e7d4c36..759e2dd25a5bc 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/boot.ts +++ b/src/plugins/data/public/autocomplete/types.ts @@ -17,9 +17,17 @@ * under the License. */ -import { npSetup } from 'ui/new_platform'; -import { visualization as visualizationFunction } from './visualization_function'; -import { visualization as visualizationRenderer } from './visualization_renderer'; +import { AutocompleteService } from './autocomplete_service'; -npSetup.plugins.expressions.registerFunction(visualizationFunction); -npSetup.plugins.expressions.registerRenderer(visualizationRenderer); +/** @public **/ +export type AutocompleteSetup = ReturnType; + +/** @public **/ +export type AutocompleteStart = ReturnType; + +/** @public **/ +export { + QuerySuggestion, + QuerySuggestionsGetFn, + QuerySuggestionType, +} from './providers/query_suggestion_provider'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 4b330600417e7..19ba246ce02dd 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -18,6 +18,8 @@ */ import { PluginInitializerContext } from '../../../core/public'; +import * as autocomplete from './autocomplete'; + export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); } @@ -49,11 +51,6 @@ export { TimeRange, } from '../common'; -/** - * Static code to be shared externally - * @public - */ -export * from './autocomplete_provider'; export * from './field_formats_provider'; export * from './index_patterns'; export * from './search'; @@ -97,3 +94,5 @@ export { // Export plugin after all other imports import { DataPublicPlugin } from './plugin'; export { DataPublicPlugin as Plugin }; + +export { autocomplete }; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 03d3dad61ed05..08a7a3ef11537 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -30,9 +30,9 @@ export type Setup = jest.Mocked>; export type Start = jest.Mocked>; const autocompleteMock: any = { - addProvider: jest.fn(), - getProvider: jest.fn(), - clearProviders: jest.fn(), + getValueSuggestions: jest.fn(), + getQuerySuggestions: jest.fn(), + hasQuerySuggestions: jest.fn(), }; const fieldFormatsMock: PublicMethodsOf = { diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index cd55048ca527f..78abcbcfec467 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -25,8 +25,7 @@ import { DataSetupDependencies, DataStartDependencies, } from './types'; -import { AutocompleteProviderRegister } from './autocomplete_provider'; -import { getSuggestionsProvider } from './suggestions_provider'; +import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; @@ -38,7 +37,7 @@ import { APPLY_FILTER_TRIGGER } from '../../embeddable/public'; import { createSearchBar } from './ui/search_bar/create_search_bar'; export class DataPublicPlugin implements Plugin { - private readonly autocomplete = new AutocompleteProviderRegister(); + private readonly autocomplete = new AutocompleteService(); private readonly searchService: SearchService; private readonly fieldFormatsService: FieldFormatsService; private readonly queryService: QueryService; @@ -62,7 +61,7 @@ export class DataPublicPlugin implements Plugin any; diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 4fd8bdbaae7b8..d2af256302248 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -20,10 +20,9 @@ import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IUiActionsSetup, IUiActionsStart } from 'src/plugins/ui_actions/public'; -import { AutocompletePublicPluginSetup, AutocompletePublicPluginStart } from '.'; +import { AutocompleteSetup, AutocompleteStart } from './autocomplete/types'; import { FieldFormatsSetup, FieldFormatsStart } from './field_formats_provider'; import { ISearchSetup, ISearchStart } from './search'; -import { IGetSuggestions } from './suggestions_provider/types'; import { QuerySetup, QueryStart } from './query'; import { IndexPatternSelectProps } from './ui/index_pattern_select'; import { IndexPatternsContract } from './index_patterns'; @@ -38,15 +37,14 @@ export interface DataStartDependencies { } export interface DataPublicPluginSetup { - autocomplete: AutocompletePublicPluginSetup; + autocomplete: AutocompleteSetup; search: ISearchSetup; fieldFormats: FieldFormatsSetup; query: QuerySetup; } export interface DataPublicPluginStart { - autocomplete: AutocompletePublicPluginStart; - getSuggestions: IGetSuggestions; + autocomplete: AutocompleteStart; indexPatterns: IndexPatternsContract; search: ISearchStart; fieldFormats: FieldFormatsStart; @@ -57,9 +55,6 @@ export interface DataPublicPluginStart { }; } -export * from './autocomplete_provider/types'; -export { IGetSuggestions } from './suggestions_provider/types'; - export interface IDataPluginServices extends Partial { appName: string; uiSettings: CoreStart['uiSettings']; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts index bb15cffa67b59..13542710eac08 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts @@ -18,11 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { esFilters } from '../../../..'; +import { FILTERS } from '../../../../../common/es_query/filters'; export interface Operator { message: string; - type: esFilters.FILTERS; + type: FILTERS; negate: boolean; fieldTypes?: string[]; } @@ -31,7 +31,7 @@ export const isOperator = { message: i18n.translate('data.filter.filterEditor.isOperatorOptionLabel', { defaultMessage: 'is', }), - type: esFilters.FILTERS.PHRASE, + type: FILTERS.PHRASE, negate: false, }; @@ -39,7 +39,7 @@ export const isNotOperator = { message: i18n.translate('data.filter.filterEditor.isNotOperatorOptionLabel', { defaultMessage: 'is not', }), - type: esFilters.FILTERS.PHRASE, + type: FILTERS.PHRASE, negate: true, }; @@ -47,7 +47,7 @@ export const isOneOfOperator = { message: i18n.translate('data.filter.filterEditor.isOneOfOperatorOptionLabel', { defaultMessage: 'is one of', }), - type: esFilters.FILTERS.PHRASES, + type: FILTERS.PHRASES, negate: false, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], }; @@ -56,7 +56,7 @@ export const isNotOneOfOperator = { message: i18n.translate('data.filter.filterEditor.isNotOneOfOperatorOptionLabel', { defaultMessage: 'is not one of', }), - type: esFilters.FILTERS.PHRASES, + type: FILTERS.PHRASES, negate: true, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], }; @@ -65,7 +65,7 @@ export const isBetweenOperator = { message: i18n.translate('data.filter.filterEditor.isBetweenOperatorOptionLabel', { defaultMessage: 'is between', }), - type: esFilters.FILTERS.RANGE, + type: FILTERS.RANGE, negate: false, fieldTypes: ['number', 'date', 'ip'], }; @@ -74,7 +74,7 @@ export const isNotBetweenOperator = { message: i18n.translate('data.filter.filterEditor.isNotBetweenOperatorOptionLabel', { defaultMessage: 'is not between', }), - type: esFilters.FILTERS.RANGE, + type: FILTERS.RANGE, negate: true, fieldTypes: ['number', 'date', 'ip'], }; @@ -83,7 +83,7 @@ export const existsOperator = { message: i18n.translate('data.filter.filterEditor.existsOperatorOptionLabel', { defaultMessage: 'exists', }), - type: esFilters.FILTERS.EXISTS, + type: FILTERS.EXISTS, negate: false, }; @@ -91,7 +91,7 @@ export const doesNotExistOperator = { message: i18n.translate('data.filter.filterEditor.doesNotExistOperatorOptionLabel', { defaultMessage: 'does not exist', }), - type: esFilters.FILTERS.EXISTS, + type: FILTERS.EXISTS, negate: true, }; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx index 61290cc16b8a8..2b2d83c9f5a8b 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx @@ -63,13 +63,19 @@ export class PhraseSuggestorUI extends Component this.updateSuggestions(`${value}`); }; - protected updateSuggestions = debounce(async (value: string = '') => { + protected updateSuggestions = debounce(async (query: string = '') => { const { indexPattern, field } = this.props as PhraseSuggestorProps; if (!field || !this.isSuggestingValues()) { return; } this.setState({ isLoading: true }); - const suggestions = await this.services.data.getSuggestions(indexPattern.title, field, value); + + const suggestions = await this.services.data.autocomplete.getValueSuggestions({ + indexPattern, + field, + query, + }); + this.setState({ suggestions, isLoading: false }); }, 500); } diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 1fb39710f6754..bc08c87304fca 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -151,9 +151,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -777,9 +777,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -1385,9 +1385,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -2008,9 +2008,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -2616,9 +2616,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -3239,9 +3239,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 960a843f98ab9..cf219c35bcced 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -35,8 +35,7 @@ import { InjectedIntl, injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { debounce, compact, isEqual } from 'lodash'; import { Toast } from 'src/core/public'; import { - AutocompleteSuggestion, - AutocompleteSuggestionType, + autocomplete, IDataPluginServices, IIndexPattern, PersistedLog, @@ -71,7 +70,7 @@ interface Props { interface State { isSuggestionsVisible: boolean; index: number | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; suggestionLimit: number; selectionStart: number | null; selectionEnd: number | null; @@ -90,7 +89,7 @@ const KEY_CODES = { END: 35, }; -const recentSearchType: AutocompleteSuggestionType = 'recentSearch'; +const recentSearchType: autocomplete.QuerySuggestionType = 'recentSearch'; export class QueryStringInputUI extends Component { public state: State = { @@ -138,15 +137,14 @@ export class QueryStringInputUI extends Component { return; } - const uiSettings = this.services.uiSettings; const language = this.props.query.language; const queryString = this.getQueryString(); const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); - const autocompleteProvider = this.services.data.autocomplete.getProvider(language); + const hasQuerySuggestions = this.services.data.autocomplete.hasQuerySuggestions(language); if ( - !autocompleteProvider || + !hasQuerySuggestions || !Array.isArray(this.state.indexPatterns) || compact(this.state.indexPatterns).length === 0 ) { @@ -154,10 +152,6 @@ export class QueryStringInputUI extends Component { } const indexPatterns = this.state.indexPatterns; - const getAutocompleteSuggestions = autocompleteProvider({ - config: uiSettings, - indexPatterns, - }); const { selectionStart, selectionEnd } = this.inputRef; if (selectionStart === null || selectionEnd === null) { @@ -167,12 +161,16 @@ export class QueryStringInputUI extends Component { try { if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({ - query: queryString, - selectionStart, - selectionEnd, - signal: this.abortController.signal, - }); + const suggestions = + (await this.services.data.autocomplete.getQuerySuggestions({ + language, + indexPatterns, + query: queryString, + selectionStart, + selectionEnd, + signal: this.abortController.signal, + })) || []; + return [...suggestions, ...recentSearchSuggestions]; } catch (e) { // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error @@ -321,7 +319,7 @@ export class QueryStringInputUI extends Component { } }; - private selectSuggestion = (suggestion: AutocompleteSuggestion) => { + private selectSuggestion = (suggestion: autocomplete.QuerySuggestion) => { if (!this.inputRef) { return; } @@ -351,7 +349,7 @@ export class QueryStringInputUI extends Component { } }; - private handleNestedFieldSyntaxNotification = (suggestion: AutocompleteSuggestion) => { + private handleNestedFieldSyntaxNotification = (suggestion: autocomplete.QuerySuggestion) => { if ( 'field' in suggestion && suggestion.field.subType && @@ -453,7 +451,7 @@ export class QueryStringInputUI extends Component { } }; - private onClickSuggestion = (suggestion: AutocompleteSuggestion) => { + private onClickSuggestion = (suggestion: autocomplete.QuerySuggestion) => { if (!this.inputRef) { return; } diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx index 591176bf133fa..0c5c701642757 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx @@ -19,14 +19,14 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; const noop = () => { return; }; -const mockSuggestion: AutocompleteSuggestion = { +const mockSuggestion: autocomplete.QuerySuggestion = { description: 'This is not a helpful suggestion', end: 0, start: 42, diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx index fd29de4573ff0..1d2ac8dee1a8a 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx @@ -20,7 +20,7 @@ import { EuiIcon } from '@elastic/eui'; import classNames from 'classnames'; import React, { FunctionComponent } from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; function getEuiIconType(type: string) { switch (type) { @@ -40,10 +40,10 @@ function getEuiIconType(type: string) { } interface Props { - onClick: (suggestion: AutocompleteSuggestion) => void; + onClick: (suggestion: autocomplete.QuerySuggestion) => void; onMouseEnter: () => void; selected: boolean; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; innerRef: (node: HTMLDivElement) => void; ariaId: string; } diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx index 7fb2fdf25104a..b84f612b6d13a 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx @@ -19,7 +19,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; import { SuggestionsComponent } from './suggestions_component'; @@ -27,7 +27,7 @@ const noop = () => { return; }; -const mockSuggestions: AutocompleteSuggestion[] = [ +const mockSuggestions: autocomplete.QuerySuggestion[] = [ { description: 'This is not a helpful suggestion', end: 0, diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index e4cccbcde4fb8..b37a2e479e874 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -19,15 +19,15 @@ import { isEmpty } from 'lodash'; import React, { Component } from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; interface Props { index: number | null; - onClick: (suggestion: AutocompleteSuggestion) => void; + onClick: (suggestion: autocomplete.QuerySuggestion) => void; onMouseEnter: (index: number) => void; show: boolean; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; loadMore: () => void; } diff --git a/src/plugins/embeddable/public/components/embeddable_panel/__examples__/embeddable_panel.examples.tsx b/src/plugins/embeddable/public/components/embeddable_panel/__examples__/embeddable_panel.examples.tsx new file mode 100644 index 0000000000000..7ec8848b8cebd --- /dev/null +++ b/src/plugins/embeddable/public/components/embeddable_panel/__examples__/embeddable_panel.examples.tsx @@ -0,0 +1,24 @@ +/* + * 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 * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { EmbeddablePanel } from '..'; + +storiesOf('components/EmbeddablePanel', module).add('default', () => ); diff --git a/src/plugins/embeddable/public/components/embeddable_panel/index.tsx b/src/plugins/embeddable/public/components/embeddable_panel/index.tsx new file mode 100644 index 0000000000000..7089efa4bca88 --- /dev/null +++ b/src/plugins/embeddable/public/components/embeddable_panel/index.tsx @@ -0,0 +1,29 @@ +/* + * 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 { EuiPanel } from '@elastic/eui'; +import * as React from 'react'; + +export const EmbeddablePanel = () => { + return ( + + Hello world + + ); +}; diff --git a/src/plugins/embeddable/scripts/storybook.js b/src/plugins/embeddable/scripts/storybook.js new file mode 100644 index 0000000000000..0d7712fe973f4 --- /dev/null +++ b/src/plugins/embeddable/scripts/storybook.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'embeddable', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.examples.tsx')], +}); diff --git a/src/plugins/expressions/common/type.ts b/src/plugins/expressions/common/type.ts index de9c43d01a0aa..c9daed9b6785a 100644 --- a/src/plugins/expressions/common/type.ts +++ b/src/plugins/expressions/common/type.ts @@ -30,11 +30,6 @@ export function getType(node: any) { } export function serializeProvider(types: any) { - return { - serialize: provider('serialize'), - deserialize: provider('deserialize'), - }; - function provider(key: any) { return (context: any) => { const type = getType(context); @@ -43,6 +38,11 @@ export function serializeProvider(types: any) { return fn(context); }; } + + return { + serialize: provider('serialize'), + deserialize: provider('deserialize'), + }; } export class Type { diff --git a/src/plugins/expressions/kibana.json b/src/plugins/expressions/kibana.json index ec87b56f3745e..cba693dd4bc20 100644 --- a/src/plugins/expressions/kibana.json +++ b/src/plugins/expressions/kibana.json @@ -1,9 +1,10 @@ { "id": "expressions", "version": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": [ + "bfetch", "inspector" ] } diff --git a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts b/src/plugins/expressions/public/batched_fetch.test.ts similarity index 97% rename from src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts rename to src/plugins/expressions/public/batched_fetch.test.ts index 3da15cf54cda0..7273be872a725 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts +++ b/src/plugins/expressions/public/batched_fetch.test.ts @@ -18,7 +18,7 @@ */ import { batchedFetch, Request } from './batched_fetch'; -import { defer } from '../../../../../plugins/kibana_utils/public'; +import { defer } from '../../kibana_utils/public'; import { Subject } from 'rxjs'; const serialize = (o: any) => JSON.stringify(o); diff --git a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts b/src/plugins/expressions/public/batched_fetch.ts similarity index 87% rename from src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts rename to src/plugins/expressions/public/batched_fetch.ts index 717a87fc90f9f..6a155b7d42b72 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts +++ b/src/plugins/expressions/public/batched_fetch.ts @@ -20,13 +20,11 @@ import _ from 'lodash'; import { filter, map } from 'rxjs/operators'; // eslint-disable-next-line -import { split } from '../../../../../plugins/bfetch/public/streaming'; -import { BfetchPublicApi } from '../../../../../plugins/bfetch/public'; -import { defer } from '../../../../../plugins/kibana_utils/public'; -import { FUNCTIONS_URL } from './consts'; +import { split, BfetchPublicContract } from '../../bfetch/public'; +import { defer } from '../../kibana_utils/public'; export interface Options { - fetchStreaming: BfetchPublicApi['fetchStreaming']; + fetchStreaming: BfetchPublicContract['fetchStreaming']; serialize: any; ms?: number; } @@ -111,9 +109,9 @@ export function batchedFetch({ fetchStreaming, serialize, ms = 10 }: Options) { * Runs the specified batch of functions on the server, then resolves * the related promises. */ -async function processBatch(fetchStreaming: BfetchPublicApi['fetchStreaming'], batch: Batch) { - const { stream, promise } = fetchStreaming({ - url: FUNCTIONS_URL, +async function processBatch(fetchStreaming: BfetchPublicContract['fetchStreaming'], batch: Batch) { + const { stream } = fetchStreaming({ + url: `/api/interpreter/fns`, body: JSON.stringify({ functions: Object.values(batch).map(({ request }) => request), }), @@ -137,7 +135,7 @@ async function processBatch(fetchStreaming: BfetchPublicApi['fetchStreaming'], b }); try { - await promise; + await stream.toPromise(); } catch (error) { Object.values(batch).forEach(({ future }) => { future.reject(error); diff --git a/src/plugins/expressions/public/execute.ts b/src/plugins/expressions/public/execute.ts index 12e84f677ce3e..89ef272a0d023 100644 --- a/src/plugins/expressions/public/execute.ts +++ b/src/plugins/expressions/public/execute.ts @@ -65,6 +65,7 @@ export class ExpressionDataHandler { getInitialContext, inspectorAdapters: this.inspectorAdapters, abortSignal: this.abortController.signal, + variables: params.variables, }) .then( (v: IInterpreterResult) => { diff --git a/src/plugins/expressions/public/functions/tests/var.test.ts b/src/plugins/expressions/public/functions/tests/var.test.ts new file mode 100644 index 0000000000000..fe5963ec8c509 --- /dev/null +++ b/src/plugins/expressions/public/functions/tests/var.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { functionWrapper } from './utils'; +import { variable } from '../var'; +import { FunctionHandlers } from '../../../common/types'; +import { KibanaContext } from '../../../common/expression_types/kibana_context'; + +describe('interpreter/functions#var', () => { + const fn = functionWrapper(variable); + let context: Partial; + let initialContext: KibanaContext; + let handlers: FunctionHandlers; + + beforeEach(() => { + context = { timeRange: { from: '0', to: '1' } }; + initialContext = { + type: 'kibana_context', + query: { language: 'lucene', query: 'geo.src:US' }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + }, + query: { match: {} }, + }, + ], + timeRange: { from: '2', to: '3' }, + }; + handlers = { + getInitialContext: () => initialContext, + variables: { test: 1 } as any, + }; + }); + + it('returns the selected variable', () => { + const actual = fn(context, { name: 'test' }, handlers); + expect(actual).toEqual(1); + }); + + it('returns undefined if variable does not exist', () => { + const actual = fn(context, { name: 'unknown' }, handlers); + expect(actual).toEqual(undefined); + }); +}); diff --git a/src/plugins/expressions/public/functions/tests/var_set.test.ts b/src/plugins/expressions/public/functions/tests/var_set.test.ts new file mode 100644 index 0000000000000..7efa8ebc0dd3f --- /dev/null +++ b/src/plugins/expressions/public/functions/tests/var_set.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { functionWrapper } from './utils'; +import { variableSet } from '../var_set'; +import { FunctionHandlers } from '../../../common/types'; +import { KibanaContext } from '../../../common/expression_types/kibana_context'; + +describe('interpreter/functions#varset', () => { + const fn = functionWrapper(variableSet); + let context: Partial; + let initialContext: KibanaContext; + let handlers: FunctionHandlers; + let variables: Record; + + beforeEach(() => { + context = { timeRange: { from: '0', to: '1' } }; + initialContext = { + type: 'kibana_context', + query: { language: 'lucene', query: 'geo.src:US' }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + }, + query: { match: {} }, + }, + ], + timeRange: { from: '2', to: '3' }, + }; + handlers = { + getInitialContext: () => initialContext, + variables: { test: 1 } as any, + }; + + variables = handlers.variables; + }); + + it('updates a variable', () => { + const actual = fn(context, { name: 'test', value: 2 }, handlers); + expect(variables.test).toEqual(2); + expect(actual).toEqual(context); + }); + + it('sets a new variable', () => { + const actual = fn(context, { name: 'new', value: 3 }, handlers); + expect(variables.new).toEqual(3); + expect(actual).toEqual(context); + }); + + it('stores context if value is not set', () => { + const actual = fn(context, { name: 'test' }, handlers); + expect(variables.test).toEqual(context); + expect(actual).toEqual(context); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/schemas.d.ts b/src/plugins/expressions/public/functions/var.ts similarity index 51% rename from src/legacy/ui/public/vis/editors/default/schemas.d.ts rename to src/plugins/expressions/public/functions/var.ts index 236421f30fb09..9410149060216 100644 --- a/src/legacy/ui/public/vis/editors/default/schemas.d.ts +++ b/src/plugins/expressions/public/functions/var.ts @@ -17,21 +17,33 @@ * under the License. */ -import { AggParam } from '../../../agg_types'; -import { AggGroupNames } from './agg_groups'; -import { AggControlProps } from './controls/agg_control_props'; +import { i18n } from '@kbn/i18n'; +import { ExpressionFunction } from '../../common/types'; -export interface Schema { - aggFilter: string | string[]; - editor: boolean | string; - group: AggGroupNames; - max: number; - min: number; +interface Arguments { name: string; - params: AggParam[]; - title: string; - defaults: unknown; - hideCustomLabel?: boolean; - mustBeFirst?: boolean; - editorComponent?: React.ComponentType; } + +type Context = any; +type ExpressionFunctionVar = ExpressionFunction<'var', Context, Arguments, any>; + +export const variable = (): ExpressionFunctionVar => ({ + name: 'var', + help: i18n.translate('expressions.functions.var.help', { + defaultMessage: 'Updates kibana global context', + }), + args: { + name: { + types: ['string'], + aliases: ['_'], + required: true, + help: i18n.translate('expressions.functions.var.name.help', { + defaultMessage: 'Specify name of the variable', + }), + }, + }, + fn(context, args, handlers) { + const variables: Record = handlers.variables; + return variables[args.name]; + }, +}); diff --git a/src/plugins/expressions/public/functions/var_set.ts b/src/plugins/expressions/public/functions/var_set.ts new file mode 100644 index 0000000000000..a10ee7a00814f --- /dev/null +++ b/src/plugins/expressions/public/functions/var_set.ts @@ -0,0 +1,58 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { ExpressionFunction } from '../../common/types'; + +interface Arguments { + name: string; + value?: any; +} + +type Context = any; +type ExpressionFunctionVarSet = ExpressionFunction<'var_set', Context, Arguments, Context>; + +export const variableSet = (): ExpressionFunctionVarSet => ({ + name: 'var_set', + help: i18n.translate('expressions.functions.varset.help', { + defaultMessage: 'Updates kibana global context', + }), + args: { + name: { + types: ['string'], + aliases: ['_'], + required: true, + help: i18n.translate('expressions.functions.varset.name.help', { + defaultMessage: 'Specify name of the variable', + }), + }, + value: { + aliases: ['val'], + help: i18n.translate('expressions.functions.varset.val.help', { + defaultMessage: + 'Specify value for the variable. If not provided input context will be used', + }), + }, + }, + fn(context, args, handlers) { + const variables: Record = handlers.variables; + variables[args.name] = args.value === undefined ? context : args.value; + return context; + }, +}); diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 0342713f7627b..d714282360f71 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -178,6 +178,9 @@ export class ExpressionLoader { if (params.extraHandlers && this.params) { this.params.extraHandlers = params.extraHandlers; } + if (params.variables && this.params) { + this.params.variables = params.variables; + } } } diff --git a/src/plugins/expressions/public/mocks.tsx b/src/plugins/expressions/public/mocks.tsx index 089c324677712..a3476a24dd7ed 100644 --- a/src/plugins/expressions/public/mocks.tsx +++ b/src/plugins/expressions/public/mocks.tsx @@ -23,6 +23,7 @@ import { ExpressionsSetup, ExpressionsStart, plugin as pluginInitializer } from /* eslint-disable */ import { coreMock } from '../../../core/public/mocks'; import { inspectorPluginMock } from '../../inspector/public/mocks'; +import { bfetchPluginMock } from '../../bfetch/public/mocks'; /* eslint-enable */ export type Setup = jest.Mocked; @@ -48,6 +49,7 @@ const createSetupContract = (): Setup => { interpretAst: () => {}, }, }), + loadLegacyServerFunctionWrappers: () => Promise.resolve(), }, }; return setupContract; @@ -71,6 +73,7 @@ const createPlugin = async () => { const coreStart = coreMock.createStart(); const plugin = pluginInitializer(pluginInitializerContext); const setup = await plugin.setup(coreSetup, { + bfetch: bfetchPluginMock.createSetupContract(), inspector: inspectorPluginMock.createSetupContract(), }); @@ -82,6 +85,7 @@ const createPlugin = async () => { setup, doStart: async () => await plugin.start(coreStart, { + bfetch: bfetchPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), }), }; diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index 7471326cdd749..2ba10be76cd92 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -20,6 +20,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ExpressionInterpretWithHandlers, ExpressionExecutor } from './types'; import { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './registries'; +import { BfetchPublicSetup, BfetchPublicStart } from '../../bfetch/public'; import { Setup as InspectorSetup, Start as InspectorStart } from '../../inspector/public'; import { setCoreStart, @@ -32,6 +33,8 @@ import { clog as clogFunction } from './functions/clog'; import { font as fontFunction } from './functions/font'; import { kibana as kibanaFunction } from './functions/kibana'; import { kibanaContext as kibanaContextFunction } from './functions/kibana_context'; +import { variable } from './functions/var'; +import { variableSet } from './functions/var_set'; import { boolean as booleanType, datatable as datatableType, @@ -56,12 +59,15 @@ import { ExpressionLoader, loader } from './loader'; import { ExpressionDataHandler, execute } from './execute'; import { render, ExpressionRenderHandler } from './render'; import { AnyExpressionFunction, AnyExpressionType } from '../common/types'; +import { serializeProvider } from '../common'; export interface ExpressionsSetupDeps { + bfetch: BfetchPublicSetup; inspector: InspectorSetup; } export interface ExpressionsStartDeps { + bfetch: BfetchPublicStart; inspector: InspectorStart; } @@ -74,6 +80,7 @@ export interface ExpressionsSetup { renderers: RenderFunctionsRegistry; types: TypesRegistry; getExecutor: () => ExpressionExecutor; + loadLegacyServerFunctionWrappers: () => Promise; }; } @@ -96,7 +103,7 @@ export class ExpressionsPublicPlugin constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { inspector }: ExpressionsSetupDeps): ExpressionsSetup { + public setup(core: CoreSetup, { inspector, bfetch }: ExpressionsSetupDeps): ExpressionsSetup { const { functions, renderers, types } = this; setRenderersRegistry(renderers); @@ -109,6 +116,8 @@ export class ExpressionsPublicPlugin registerFunction(fontFunction); registerFunction(kibanaFunction); registerFunction(kibanaContextFunction); + registerFunction(variable); + registerFunction(variableSet); types.register(booleanType); types.register(datatableType); @@ -142,6 +151,31 @@ export class ExpressionsPublicPlugin setInterpreter(getExecutor().interpreter); + let cached: Promise | null = null; + const loadLegacyServerFunctionWrappers = async () => { + if (!cached) { + cached = (async () => { + const serverFunctionList = await core.http.get(`/api/interpreter/fns`); + const batchedFunction = bfetch.batchedFunction({ url: `/api/interpreter/fns` }); + const { serialize } = serializeProvider(types.toJS()); + + // For every sever-side function, register a client-side + // function that matches its definition, but which simply + // calls the server-side function endpoint. + Object.keys(serverFunctionList).forEach(functionName => { + const fn = () => ({ + ...serverFunctionList[functionName], + fn: (context: any, args: any) => { + return batchedFunction({ functionName, args, context: serialize(context) }); + }, + }); + registerFunction(fn); + }); + })(); + } + return cached; + }; + const setup: ExpressionsSetup = { registerFunction, registerRenderer: (renderer: any) => { @@ -155,6 +189,7 @@ export class ExpressionsPublicPlugin renderers, types, getExecutor, + loadLegacyServerFunctionWrappers, }, }; diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 66a3da48dbee9..e094e5e91d006 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -65,6 +65,7 @@ export interface IExpressionLoaderParams { export interface IInterpreterHandlers { getInitialContext: IGetInitialContext; inspectorAdapters?: Adapters; + variables?: Record; abortSignal?: AbortSignal; } diff --git a/src/plugins/expressions/server/index.ts b/src/plugins/expressions/server/index.ts new file mode 100644 index 0000000000000..6718602ccdef5 --- /dev/null +++ b/src/plugins/expressions/server/index.ts @@ -0,0 +1,27 @@ +/* + * 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 } from '../../../core/server'; +import { ExpressionsServerPlugin } from './plugin'; + +export { ExpressionsServerSetup, ExpressionsServerStart } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ExpressionsServerPlugin(initializerContext); +} diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts new file mode 100644 index 0000000000000..54e2a5a387342 --- /dev/null +++ b/src/plugins/expressions/server/legacy.ts @@ -0,0 +1,135 @@ +/* + * 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 max-classes-per-file */ + +// TODO: Remove this file once https://github.com/elastic/kibana/issues/46906 is complete. + +// @ts-ignore +import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common'; + +import Boom from 'boom'; +import { schema } from '@kbn/config-schema'; +import { CoreSetup, Logger } from 'src/core/server'; +import { ExpressionsServerSetupDependencies } from './plugin'; +import { typeSpecs as types, Type } from '../common'; +import { serializeProvider } from '../common'; + +export class TypesRegistry extends Registry { + wrapper(obj: any) { + return new (Type as any)(obj); + } +} + +export class FunctionsRegistry extends Registry { + wrapper(obj: any) { + return new Fn(obj); + } +} + +export const registries = { + types: new TypesRegistry(), + serverFunctions: new FunctionsRegistry(), +}; + +export interface LegacyInterpreterServerApi { + registries(): typeof registries; + register(specs: Record): typeof registries; +} + +export const createLegacyServerInterpreterApi = (): LegacyInterpreterServerApi => { + const api = registryFactory(registries); + + register(registries, { + types, + }); + + return api; +}; + +export const createLegacyServerEndpoints = ( + api: LegacyInterpreterServerApi, + logger: Logger, + core: CoreSetup, + plugins: ExpressionsServerSetupDependencies +) => { + const router = core.http.createRouter(); + + /** + * Register the endpoint that returns the list of server-only functions. + */ + router.get( + { + path: `/api/interpreter/fns`, + validate: { + body: schema.any(), + }, + }, + async (context, request, response) => { + const functions = api.registries().serverFunctions.toJS(); + const body = JSON.stringify(functions); + return response.ok({ + body, + }); + } + ); + + /** + * Run a single Canvas function. + * + * @param {*} server - The Kibana server object + * @param {*} handlers - The Canvas handlers + * @param {*} fnCall - Describes the function being run `{ functionName, args, context }` + */ + async function runFunction(handlers: any, fnCall: any) { + const { functionName, args, context } = fnCall; + const { deserialize } = serializeProvider(registries.types.toJS()); + const fnDef = registries.serverFunctions.toJS()[functionName]; + if (!fnDef) throw Boom.notFound(`Function "${functionName}" could not be found.`); + const deserialized = deserialize(context); + const result = fnDef.fn(deserialized, args, handlers); + return result; + } + + /** + * Register an endpoint that executes a batch of functions, and streams the + * results back using ND-JSON. + */ + plugins.bfetch.addBatchProcessingRoute(`/api/interpreter/fns`, request => { + const scopedClient = core.elasticsearch.dataClient.asScoped(request); + const handlers = { + environment: 'server', + elasticsearchClient: async ( + endpoint: string, + clientParams: Record = {}, + options?: any + ) => scopedClient.callAsCurrentUser(endpoint, clientParams, options), + }; + + return { + onBatchItem: async (fnCall: any) => { + const result = await runFunction(handlers, fnCall); + if (typeof result === 'undefined') { + throw new Error(`Function ${fnCall.functionName} did not return anything.`); + } + return result; + }, + }; + }); +}; diff --git a/src/plugins/expressions/server/mocks.ts b/src/plugins/expressions/server/mocks.ts new file mode 100644 index 0000000000000..4510ae6dc0b4a --- /dev/null +++ b/src/plugins/expressions/server/mocks.ts @@ -0,0 +1,73 @@ +/* + * 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 { ExpressionsServerSetup, ExpressionsServerStart } from '.'; +import { plugin as pluginInitializer } from '.'; +import { coreMock } from '../../../core/server/mocks'; + +/* eslint-disable */ +import { bfetchPluginMock } from '../../bfetch/server/mocks'; +/* eslint-enable */ + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = { + __LEGACY: { + register: jest.fn(), + registries: jest.fn(), + }, + }; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = {}; + + return startContract; +}; + +const createPlugin = async () => { + const pluginInitializerContext = coreMock.createPluginInitializerContext(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + const plugin = pluginInitializer(pluginInitializerContext); + const setup = await plugin.setup(coreSetup, { + bfetch: bfetchPluginMock.createSetupContract(), + }); + + return { + pluginInitializerContext, + coreSetup, + coreStart, + plugin, + setup, + doStart: async () => + await plugin.start(coreStart, { + bfetch: bfetchPluginMock.createStartContract(), + }), + }; +}; + +export const expressionsPluginMock = { + createSetupContract, + createStartContract, + createPlugin, +}; diff --git a/src/plugins/expressions/server/plugin.ts b/src/plugins/expressions/server/plugin.ts new file mode 100644 index 0000000000000..84c780b5ca226 --- /dev/null +++ b/src/plugins/expressions/server/plugin.ts @@ -0,0 +1,77 @@ +/* + * 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 { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server'; +import { BfetchServerSetup, BfetchServerStart } from '../../bfetch/server'; +import { + LegacyInterpreterServerApi, + createLegacyServerInterpreterApi, + createLegacyServerEndpoints, +} from './legacy'; + +// eslint-disable-next-line +export interface ExpressionsServerSetupDependencies { + bfetch: BfetchServerSetup; +} + +// eslint-disable-next-line +export interface ExpressionsServerStartDependencies { + bfetch: BfetchServerStart; +} + +export interface ExpressionsServerSetup { + __LEGACY: LegacyInterpreterServerApi; +} + +// eslint-disable-next-line +export interface ExpressionsServerStart {} + +export class ExpressionsServerPlugin + implements + Plugin< + ExpressionsServerSetup, + ExpressionsServerStart, + ExpressionsServerSetupDependencies, + ExpressionsServerStartDependencies + > { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public setup( + core: CoreSetup, + plugins: ExpressionsServerSetupDependencies + ): ExpressionsServerSetup { + const logger = this.initializerContext.logger.get(); + + const legacyApi = createLegacyServerInterpreterApi(); + createLegacyServerEndpoints(legacyApi, logger, core, plugins); + + return { + __LEGACY: legacyApi, + }; + } + + public start( + core: CoreStart, + plugins: ExpressionsServerStartDependencies + ): ExpressionsServerStart { + return {}; + } + + public stop() {} +} 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 33d4dcfd6606a..cdbc2bb9b5473 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 @@ -39,7 +39,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/kibana/public/visualize_embeddable/constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/visualizations/public/embeddable/constants'; export interface OnSaveProps { newTitle: string; diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index eb3bb96c8e874..bfb45b88964d8 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -18,4 +18,5 @@ */ export * from './defer'; +export * from './of'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; diff --git a/src/plugins/kibana_utils/common/of.test.ts b/src/plugins/kibana_utils/common/of.test.ts new file mode 100644 index 0000000000000..6c3f0ec1592bd --- /dev/null +++ b/src/plugins/kibana_utils/common/of.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { of } from './of'; + +describe('of()', () => { + describe('when promise resolves', () => { + const promise = new Promise(resolve => resolve()).then(() => 123); + + test('first member of 3-tuple is the promise value', async () => { + const [result] = await of(promise); + expect(result).toBe(123); + }); + + test('second member of 3-tuple is undefined', async () => { + const [, error] = await of(promise); + expect(error).toBe(undefined); + }); + + test('third, flag member, of 3-tuple is true', async () => { + const [, , resolved] = await of(promise); + expect(resolved).toBe(true); + }); + }); + + describe('when promise rejects', () => { + const promise = new Promise(resolve => resolve()).then(() => { + // eslint-disable-next-line no-throw-literal + throw 123; + }); + + test('first member of 3-tuple is undefined', async () => { + const [result] = await of(promise); + expect(result).toBe(undefined); + }); + + test('second member of 3-tuple is thrown error', async () => { + const [, error] = await of(promise); + expect(error).toBe(123); + }); + + test('third, flag member, of 3-tuple is false', async () => { + const [, , resolved] = await of(promise); + expect(resolved).toBe(false); + }); + }); +}); diff --git a/src/legacy/core_plugins/interpreter/server/lib/create_handlers.ts b/src/plugins/kibana_utils/common/of.ts similarity index 56% rename from src/legacy/core_plugins/interpreter/server/lib/create_handlers.ts rename to src/plugins/kibana_utils/common/of.ts index 6e295d0aecaa5..fa0ec8b0ce306 100644 --- a/src/legacy/core_plugins/interpreter/server/lib/create_handlers.ts +++ b/src/plugins/kibana_utils/common/of.ts @@ -17,16 +17,21 @@ * under the License. */ -export const createHandlers = (request: any, server: any) => { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - const config = server.config(); - - return { - environment: 'server', - serverUri: - config.has('server.rewriteBasePath') && config.get('server.rewriteBasePath') - ? `${server.info.uri}${config.get('server.basePath')}` - : server.info.uri, - elasticsearchClient: async (...args: any) => callWithRequest(request, ...args), - }; +/** + * Given a promise awaits it and returns a 3-tuple, with the following members: + * + * - First entry is either the resolved value of the promise or `undefined`. + * - Second entry is either the error thrown by promise or `undefined`. + * - Third entry is a boolean, truthy if promise was resolved and falsy if rejected. + * + * @param promise Promise to convert to 3-tuple. + */ +export const of = async ( + promise: Promise +): Promise<[T | undefined, E | undefined, boolean]> => { + try { + return [await promise, undefined, true]; + } catch (error) { + return [undefined, error, false]; + } }; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 0ba444c4e9395..fa58a61e51232 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -17,7 +17,7 @@ * under the License. */ -export { defer } from '../common'; +export { defer, Defer, of } from '../common'; export * from './core'; export * from './errors'; export * from './field_mapping'; diff --git a/src/plugins/testbed/server/index.ts b/src/plugins/testbed/server/index.ts index 4873fe0926472..96d5612508a96 100644 --- a/src/plugins/testbed/server/index.ts +++ b/src/plugins/testbed/server/index.ts @@ -23,7 +23,6 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { CoreSetup, CoreStart, - LegacyRenderOptions, Logger, PluginInitializerContext, PluginConfigDescriptor, @@ -78,29 +77,6 @@ class Plugin { } ); - router.get( - { - path: '/requestcontext/render/{id}', - validate: { - params: schema.object({ - id: schema.maybe(schema.string()), - }), - }, - }, - async (context, req, res) => { - const { id } = req.params; - const options: Partial = { app: { getId: () => id! } }; - const body = await context.core.rendering.render(options); - - return res.ok({ - body, - headers: { - 'content-securty-policy': core.http.csp.header, - }, - }); - } - ); - return { data$: this.initializerContext.config.create().pipe( map(configValue => { diff --git a/test/api_integration/apis/core/index.js b/test/api_integration/apis/core/index.js index 766faf067e4ac..be1ecf9b9c497 100644 --- a/test/api_integration/apis/core/index.js +++ b/test/api_integration/apis/core/index.js @@ -33,11 +33,6 @@ export default function({ getService }) { 200, 'SavedObjects client: {"page":1,"per_page":20,"total":0,"saved_objects":[]}' )); - - it('provides access to application rendering client', async () => { - await supertest.get('/requestcontext/render/core').expect(200, /app:core/); - await supertest.get('/requestcontext/render/testbed').expect(200, /app:testbed/); - }); }); describe('compression', () => { diff --git a/test/functional/apps/home/_navigation.ts b/test/functional/apps/home/_navigation.ts index 96285901289b6..6fd631baa27d7 100644 --- a/test/functional/apps/home/_navigation.ts +++ b/test/functional/apps/home/_navigation.ts @@ -64,8 +64,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { } }); - // FLAKY: https://github.com/elastic/kibana/issues/33468 - it.skip('detect navigate back issues', async () => { + it('detect navigate back issues', async () => { let currUrl; // Detects bug described in issue #31238 - where back navigation would get stuck to URL encoding handling in Angular. // Navigate to home app diff --git a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts new file mode 100644 index 0000000000000..eb83e80e2bbe1 --- /dev/null +++ b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts @@ -0,0 +1,90 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const filterBar = getService('filterBar'); + const renderable = getService('renderable'); + const dashboardAddPanel = getService('dashboardAddPanel'); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'header', + 'dashboard', + 'timePicker', + 'visEditor', + 'visChart', + ]); + + describe('data table with index without time filter filters', function indexPatternCreation() { + const vizName1 = 'Visualization DataTable w/o time filter'; + + before(async function() { + log.debug('navigateToApp visualize'); + await PageObjects.visualize.navigateToNewVisualization(); + log.debug('clickDataTable'); + await PageObjects.visualize.clickDataTable(); + log.debug('clickNewSearch'); + await PageObjects.visualize.clickNewSearch( + PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED + ); + log.debug('Bucket = Split Rows'); + await PageObjects.visEditor.clickBucket('Split rows'); + log.debug('Aggregation = Histogram'); + await PageObjects.visEditor.selectAggregation('Histogram'); + log.debug('Field = bytes'); + await PageObjects.visEditor.selectField('bytes'); + log.debug('Interval = 2000'); + await PageObjects.visEditor.setInterval('2000', { type: 'numeric' }); + await PageObjects.visEditor.clickGo(); + }); + + it('should be able to save and load', async function() { + await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); + + await PageObjects.visualize.loadSavedVisualization(vizName1); + await PageObjects.visChart.waitForVisualization(); + }); + + it('timefilter should be disabled', async () => { + const isOff = await PageObjects.timePicker.isOff(); + expect(isOff).to.be(true); + }); + + // test to cover bug #54548 - add this visualization to a dashboard and filter + it('should add to dashboard and allow filtering', async function() { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.addVisualization(vizName1); + + // hover and click on cell to filter + await PageObjects.visChart.filterOnTableCell('1', '2'); + + await PageObjects.header.waitUntilLoadingHasFinished(); + await renderable.waitForRender(); + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.be(1); + + await filterBar.removeAllFilters(); + }); + }); +} diff --git a/test/functional/apps/visualize/_inspector.js b/test/functional/apps/visualize/_inspector.js index 84f955d9c7879..d989f8e2539a0 100644 --- a/test/functional/apps/visualize/_inspector.js +++ b/test/functional/apps/visualize/_inspector.js @@ -37,6 +37,7 @@ export default function({ getService, getPageObjects }) { it('should update table header when columns change', async function() { await inspector.open(); await inspector.expectTableHeaders(['Count']); + await inspector.close(); log.debug('Add Average Metric on machine.ram field'); await PageObjects.visEditor.clickBucket('Y-axis', 'metrics'); @@ -45,6 +46,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.clickGo(); await inspector.open(); await inspector.expectTableHeaders(['Count', 'Average machine.ram']); + await inspector.close(); }); describe('filtering on inspector table values', function() { diff --git a/test/functional/apps/visualize/_markdown_vis.js b/test/functional/apps/visualize/_markdown_vis.js index 51c03c90f507b..fee6c074af5d2 100644 --- a/test/functional/apps/visualize/_markdown_vis.js +++ b/test/functional/apps/visualize/_markdown_vis.js @@ -63,7 +63,7 @@ export default function({ getPageObjects, getService }) { }); it('should resize the editor', async function() { - const editorSidebar = await find.byCssSelector('.visEditor__sidebar'); + const editorSidebar = await find.byCssSelector('.visEditor__collapsibleSidebar'); const initialSize = await editorSidebar.getSize(); await PageObjects.visEditor.sizeUpEditor(); const afterSize = await editorSidebar.getSize(); diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index e7ce5808554b4..d0f7810b6f8bb 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -57,7 +57,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.selectField('machine.ram', 'metrics'); // go to options page log.debug('Going to axis options'); - await pointSeriesVis.clickAxisOptions(); + await PageObjects.visEditor.clickMetricsAndAxes(); // add another value axis log.debug('adding axis'); await pointSeriesVis.clickAddAxis(); diff --git a/test/functional/apps/visualize/_region_map.js b/test/functional/apps/visualize/_region_map.js index 10cbd9913c70c..2467a54061643 100644 --- a/test/functional/apps/visualize/_region_map.js +++ b/test/functional/apps/visualize/_region_map.js @@ -57,6 +57,7 @@ export default function({ getService, getPageObjects }) { ]; await inspector.open(); await inspector.expectTableData(expectedData); + await inspector.close(); }); it('should change results after changing layer to world', async function() { @@ -94,6 +95,8 @@ export default function({ getService, getPageObjects }) { ['BR', '415'], ]; expect(actualData).to.eql(expectedData); + + await inspector.close(); }); it('should contain a dropdown with the default road_map base layer as an option', async () => { diff --git a/test/functional/apps/visualize/_tag_cloud.js b/test/functional/apps/visualize/_tag_cloud.js index 4f921cec1fdf1..a527e9bcad42f 100644 --- a/test/functional/apps/visualize/_tag_cloud.js +++ b/test/functional/apps/visualize/_tag_cloud.js @@ -77,12 +77,12 @@ export default function({ getService, getPageObjects }) { }); it('should collapse the sidebar', async function() { - const editorSidebar = await find.byCssSelector('.collapsible-sidebar'); + const editorSidebar = await find.byCssSelector('.visEditorSidebar'); await PageObjects.visEditor.clickEditorSidebarCollapse(); // Give d3 tag cloud some time to rearrange tags await PageObjects.common.sleep(1000); - const afterSize = await editorSidebar.getSize(); - expect(afterSize.width).to.be(0); + const isDisplayed = await editorSidebar.isDisplayed(); + expect(isDisplayed).to.be(false); await PageObjects.visEditor.clickEditorSidebarCollapse(); }); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 2a13b6fea9158..68285971e5c4a 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -47,6 +47,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_area_chart')); loadTestFile(require.resolve('./_data_table')); loadTestFile(require.resolve('./_data_table_nontimeindex')); + loadTestFile(require.resolve('./_data_table_notimeindex_filters')); }); describe('', function() { diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 75a15cc16db2e..347923670c413 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -371,6 +371,12 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo await browser.pressKeys(browser.keys.ENTER); } + // Pause the browser at a certain place for debugging + // Not meant for usage in CI, only for dev-usage + async pause() { + return browser.pause(); + } + /** * Clicks cancel button on modal * @param overlayWillStay pass in true if your test will show multiple modals in succession diff --git a/test/functional/page_objects/point_series_page.js b/test/functional/page_objects/point_series_page.js index 74bf07b59bc38..594facb8b74b5 100644 --- a/test/functional/page_objects/point_series_page.js +++ b/test/functional/page_objects/point_series_page.js @@ -23,10 +23,6 @@ export function PointSeriesPageProvider({ getService }) { const find = getService('find'); class PointSeriesVis { - async clickAxisOptions() { - return await testSubjects.click('visEditorTabadvanced'); - } - async clickAddAxis() { return await testSubjects.click('visualizeAddYAxisButton'); } diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js index 7c67678429478..2394abc9c2185 100644 --- a/test/functional/page_objects/time_picker.js +++ b/test/functional/page_objects/time_picker.js @@ -124,6 +124,11 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { await this.setAbsoluteRange(this.defaultStartTime, this.defaultEndTime); } + async isOff() { + const element = await find.byClassName('euiDatePickerRange--readOnly'); + return !!element; + } + async isQuickSelectMenuOpen() { return await testSubjects.exists('superDatePickerQuickMenu'); } diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 30e13d551fa28..1e098e86216e3 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -37,19 +37,19 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP class VisualizeEditorPage { public async clickDataTab() { - await testSubjects.click('visualizeEditDataLink'); + await testSubjects.click('visEditorTab__data'); } public async clickOptionsTab() { - await testSubjects.click('visEditorTaboptions'); + await testSubjects.click('visEditorTab__options'); } public async clickMetricsAndAxes() { - await testSubjects.click('visEditorTabadvanced'); + await testSubjects.click('visEditorTab__advanced'); } public async clickVisEditorTab(tabName: string) { - await testSubjects.click('visEditorTab' + tabName); + await testSubjects.click(`visEditorTab__${tabName}`); await header.waitUntilLoadingHasFinished(); } @@ -134,7 +134,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP public async getBucketErrorMessage() { const error = await find.byCssSelector( - '[group-name="buckets"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' + '[data-test-subj="bucketsAggGroup"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' ); const errorMessage = await error.getAttribute('innerText'); log.debug(errorMessage); @@ -152,7 +152,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP ) { log.debug(`selectField ${fieldValue}`); const selector = ` - [group-name="${groupName}"] + [data-test-subj="${groupName}AggGroup"] [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen [data-test-subj="visAggEditorParams"] ${childAggregationType ? '.visEditorAgg__subAgg' : ''} @@ -180,7 +180,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP childAggregationType = false ) { const comboBoxElement = await find.byCssSelector(` - [group-name="${groupName}"] + [data-test-subj="${groupName}AggGroup"] [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen ${childAggregationType ? '.visEditorAgg__subAgg' : ''} [data-test-subj="defaultEditorAggSelect"] @@ -291,8 +291,9 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async sizeUpEditor() { - await testSubjects.click('visualizeEditorResizer'); - await browser.pressKeys(browser.keys.ARROW_RIGHT); + const resizerPanel = await testSubjects.find('splitPanelResizer'); + // Drag panel 100 px left + await browser.dragAndDrop({ location: resizerPanel }, { location: { x: -100, y: 0 } }); } public async toggleDisabledAgg(agg: string) { @@ -320,7 +321,10 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async toggleAutoMode() { - await testSubjects.click('visualizeEditorAutoButton'); + // this is a temporary solution, should be replaced with initial after fixing the EuiToggleButton + // passing the data-test-subj attribute to a checkbox + await find.clickByCssSelector('.visEditorSidebar__controls input[type="checkbox"]'); + // await testSubjects.click('visualizeEditorAutoButton'); } public async isApplyEnabled() { @@ -428,7 +432,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async clickMetricEditor() { - await find.clickByCssSelector('[group-name="metrics"] .euiAccordion__button'); + await find.clickByCssSelector('[data-test-subj="metricsAggGroup"] .euiAccordion__button'); } public async clickMetricByIndex(index: number) { diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 0071b8d993f70..e54e3d1d01154 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -200,7 +200,7 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide } public async getSideEditorExists() { - return await find.existsByCssSelector('.collapsible-sidebar'); + return await find.existsByCssSelector('.visEditor__collapsibleSidebar'); } public async clickSavedSearch(savedSearchName: string) { diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 72bcc07ab98cf..2d799b7daca73 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -199,6 +199,14 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { return await driver.get(url); } + /** + * Pauses the execution in the browser, similar to setting a breakpoint for debugging. + * @return {Promise} + */ + public async pause() { + await driver.executeAsyncScript(`(async () => { debugger; return Promise.resolve(); })()`); + } + /** * Moves the remote environment’s mouse cursor to the specified point {x, y} which is * offset to browser page top left corner. @@ -232,8 +240,8 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { * @return {Promise} */ public async dragAndDrop( - from: { offset: { x: any; y: any }; location: any }, - to: { offset: { x: any; y: any }; location: any } + from: { offset?: { x: any; y: any }; location: any }, + to: { offset?: { x: any; y: any }; location: any } ) { if (this.isW3CEnabled) { // The offset should be specified in pixels relative to the center of the element's bounding box diff --git a/test/functional/services/dashboard/add_panel.js b/test/functional/services/dashboard/add_panel.js index 7a26f8d447981..e390fd918eaee 100644 --- a/test/functional/services/dashboard/add_panel.js +++ b/test/functional/services/dashboard/add_panel.js @@ -28,6 +28,8 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { async clickOpenAddPanel() { log.debug('DashboardAddPanel.clickOpenAddPanel'); await testSubjects.click('dashboardAddPanelButton'); + // Give some time for the animation to complete + await PageObjects.common.sleep(500); } async clickAddNewEmbeddableLink(type) { @@ -96,8 +98,8 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { if (!isOpen) { await retry.try(async () => { await this.clickOpenAddPanel(); - const isOpen = await this.isAddPanelOpen(); - if (!isOpen) { + const isNowOpen = await this.isAddPanelOpen(); + if (!isNowOpen) { throw new Error('Add panel still not open, trying again.'); } }); diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 1a7abc6317075..1bd6358749e11 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -43,6 +43,7 @@ import { Browsers } from './browsers'; const throttleOption: string = process.env.TEST_THROTTLE_NETWORK as string; const headlessBrowser: string = process.env.TEST_BROWSER_HEADLESS as string; +const remoteDebug: string = process.env.TEST_REMOTE_DEBUG as string; const SECOND = 1000; const MINUTE = 60 * SECOND; const NO_QUEUE_COMMANDS = ['getLog', 'getStatus', 'newSession', 'quit']; @@ -97,6 +98,10 @@ async function attemptToCreateCommand( // See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md chromeOptions.push('headless', 'disable-gpu'); } + if (remoteDebug === '1') { + // Visit chrome://inspect in chrome to remotely view/debug + chromeOptions.push('headless', 'disable-gpu', 'remote-debugging-port=9222'); + } chromeCapabilities.set('goog:chromeOptions', { w3c: false, args: chromeOptions, diff --git a/test/functional/services/test_subjects.ts b/test/functional/services/test_subjects.ts index 8ef008d5dee50..d47b838c8d72a 100644 --- a/test/functional/services/test_subjects.ts +++ b/test/functional/services/test_subjects.ts @@ -169,10 +169,14 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { }); } - public async getAttribute(selector: string, attribute: string): Promise { + public async getAttribute( + selector: string, + attribute: string, + timeout?: number + ): Promise { return await retry.try(async () => { log.debug(`TestSubjects.getAttribute(${selector}, ${attribute})`); - const element = await this.find(selector); + const element = await this.find(selector, timeout); return await element.getAttribute(attribute); }); } diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 96efd952e6ba2..d96febee7b06d 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.0", + "@elastic/eui": "18.2.1", "react": "^16.12.0", "react-dom": "^16.12.0" } diff --git a/test/interpreter_functional/screenshots/baseline/metric_percentage.png b/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png similarity index 100% rename from test/interpreter_functional/screenshots/baseline/metric_percentage.png rename to test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json similarity index 100% rename from test/interpreter_functional/snapshots/baseline/metric_percentage.json rename to test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json diff --git a/test/interpreter_functional/snapshots/session/metric_percentage.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json similarity index 100% rename from test/interpreter_functional/snapshots/session/metric_percentage.json rename to test/interpreter_functional/snapshots/session/metric_percentage_mode.json diff --git a/test/interpreter_functional/test_suites/run_pipeline/metric.ts b/test/interpreter_functional/test_suites/run_pipeline/metric.ts index c238bedfa28ce..5f685037d4fad 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/metric.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/metric.ts @@ -81,11 +81,15 @@ export default function({ ).toMatchScreenshot(); }); - it('with percentage option', async () => { + it('with percentageMode option', async () => { const expression = - 'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}'; + 'metricVis metric={visdimension 0} percentageMode=true colorRange={range from=0 to=1000}'; await ( - await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot() + await expectExpression( + 'metric_percentage_mode', + expression, + dataContext + ).toMatchSnapshot() ).toMatchScreenshot(); }); }); diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index e9a4f3bcc4b1a..e63054f1b6912 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -38,6 +38,7 @@ export default async function({ readConfigFile }) { require.resolve('./test_suites/embeddable_explorer'), require.resolve('./test_suites/core_plugins'), require.resolve('./test_suites/management'), + require.resolve('./test_suites/bfetch_explorer'), ], services: { ...functionalConfig.get('services'), diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/kibana.json b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/kibana.json new file mode 100644 index 0000000000000..1acc7df871c94 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "kbn_tp_bfetch_explorer", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["kbn_tp_bfetch_explorer"], + "server": true, + "ui": true, + "requiredPlugins": ["bfetch"], + "optionalPlugins": [] +} diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/package.json new file mode 100644 index 0000000000000..e396489a1ffc4 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/package.json @@ -0,0 +1,17 @@ +{ + "name": "kbn_tp_bfetch_explorer", + "version": "1.0.0", + "main": "target/examples/kbn_tp_bfetch_explorer", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/src/plugins/data/public/suggestions_provider/index.ts b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/public/index.ts similarity index 92% rename from src/plugins/data/public/suggestions_provider/index.ts rename to test/plugin_functional/plugins/kbn_tp_bfetch_explorer/public/index.ts index c83662043b822..547dfe2aa38d2 100644 --- a/src/plugins/data/public/suggestions_provider/index.ts +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/public/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { getSuggestionsProvider } from './value_suggestions'; +export * from '../../../../../examples/bfetch_explorer/public'; diff --git a/src/legacy/core_plugins/timelion/public/__tests__/index.js b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/server/index.ts similarity index 92% rename from src/legacy/core_plugins/timelion/public/__tests__/index.js rename to test/plugin_functional/plugins/kbn_tp_bfetch_explorer/server/index.ts index 9cd61c3665a1a..b4370eb53311e 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/index.js +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/server/index.ts @@ -17,5 +17,4 @@ * under the License. */ -import './_tick_generator.js'; -describe('Timelion', function() {}); +export * from '../../../../../examples/bfetch_explorer/server'; diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/tsconfig.json new file mode 100644 index 0000000000000..994f81e396763 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true, + "types": [ + "node", + "jest", + "react" + ] + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "server/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 7693d6f9c07bc..170cc77ca37cc 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.0", + "@elastic/eui": "18.2.1", "react": "^16.12.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index bf58535e57994..85c76071d1e94 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.0", + "@elastic/eui": "18.2.1", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts index 57b5ad086e6a7..6d125bc3002e0 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts @@ -22,6 +22,20 @@ import 'ui/autoload/all'; import 'uiExports/interpreter'; import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; +import 'uiExports/contextMenuActions'; +import 'uiExports/devTools'; +import 'uiExports/docViews'; +import 'uiExports/embeddableActions'; +import 'uiExports/fieldFormatEditors'; +import 'uiExports/fieldFormats'; +import 'uiExports/home'; +import 'uiExports/indexManagement'; +import 'uiExports/inspectorViews'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/search'; +import 'uiExports/shareContextMenuExtensions'; +import 'uiExports/visTypes'; +import 'uiExports/visualize'; import { npSetup, npStart } from 'ui/new_platform'; import { ExitFullScreenButton } from 'ui/exit_full_screen'; diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index 98dd9ab51da96..ade93c9f50099 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.0", + "@elastic/eui": "18.2.1", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/rendering_plugin/kibana.json b/test/plugin_functional/plugins/rendering_plugin/kibana.json new file mode 100644 index 0000000000000..886eca2bdde1d --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "rendering_plugin", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["rendering_plugin"], + "server": true, + "ui": true +} diff --git a/test/plugin_functional/plugins/rendering_plugin/package.json b/test/plugin_functional/plugins/rendering_plugin/package.json new file mode 100644 index 0000000000000..9ab6eccbe31be --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/package.json @@ -0,0 +1,17 @@ +{ + "name": "rendering_plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/rendering_plugin", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/rendering_plugin/public/index.ts b/test/plugin_functional/plugins/rendering_plugin/public/index.ts new file mode 100644 index 0000000000000..c32f3c03e8c63 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/public/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { PluginInitializer } from 'kibana/public'; +import { RenderingPlugin } from './plugin'; + +export const plugin: PluginInitializer = () => new RenderingPlugin(); diff --git a/test/plugin_functional/plugins/rendering_plugin/public/plugin.tsx b/test/plugin_functional/plugins/rendering_plugin/public/plugin.tsx new file mode 100644 index 0000000000000..6e80b56953ca0 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/public/plugin.tsx @@ -0,0 +1,41 @@ +/* + * 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 { render, unmountComponentAtNode } from 'react-dom'; +import { Plugin, CoreSetup } from 'kibana/public'; + +export class RenderingPlugin implements Plugin { + public setup(core: CoreSetup) { + core.application.register({ + id: 'rendering', + title: 'Rendering', + appRoute: '/render', + async mount(context, { element }) { + render(

rendering service

, element); + + return () => unmountComponentAtNode(element); + }, + }); + } + + public start() {} + + public stop() {} +} diff --git a/src/legacy/ui/public/vis/editor_size.js b/test/plugin_functional/plugins/rendering_plugin/server/index.ts similarity index 89% rename from src/legacy/ui/public/vis/editor_size.js rename to test/plugin_functional/plugins/rendering_plugin/server/index.ts index 5383772b3ad00..90ffdebb29f29 100644 --- a/src/legacy/ui/public/vis/editor_size.js +++ b/test/plugin_functional/plugins/rendering_plugin/server/index.ts @@ -17,8 +17,6 @@ * under the License. */ -export const DefaultEditorSize = { - SMALL: 'small', - MEDIUM: 'medium', - LARGE: 'large', -}; +import { RenderingPlugin } from './plugin'; + +export const plugin = () => new RenderingPlugin(); diff --git a/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts b/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts new file mode 100644 index 0000000000000..fad19728b7514 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts @@ -0,0 +1,63 @@ +/* + * 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 { Plugin, CoreSetup, IRenderOptions } from 'kibana/server'; + +import { schema } from '@kbn/config-schema'; + +export class RenderingPlugin implements Plugin { + public setup(core: CoreSetup) { + const router = core.http.createRouter(); + + router.get( + { + path: '/render/{id}', + validate: { + query: schema.object( + { + includeUserSettings: schema.boolean({ defaultValue: true }), + }, + { allowUnknowns: true } + ), + params: schema.object({ + id: schema.maybe(schema.string()), + }), + }, + }, + async (context, req, res) => { + const { id } = req.params; + const { includeUserSettings } = req.query; + const app = { getId: () => id! }; + const options: Partial = { app, includeUserSettings }; + const body = await context.core.rendering.render(options); + + return res.ok({ + body, + headers: { + 'content-security-policy': core.http.csp.header, + }, + }); + } + ); + } + + public start() {} + + public stop() {} +} diff --git a/test/plugin_functional/plugins/rendering_plugin/tsconfig.json b/test/plugin_functional/plugins/rendering_plugin/tsconfig.json new file mode 100644 index 0000000000000..1ba21f11b7de2 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/test_suites/bfetch_explorer/batched_function.ts b/test/plugin_functional/test_suites/bfetch_explorer/batched_function.ts new file mode 100644 index 0000000000000..cb2a0b41694c2 --- /dev/null +++ b/test/plugin_functional/test_suites/bfetch_explorer/batched_function.ts @@ -0,0 +1,93 @@ +/* + * 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 { FtrProviderContext } from '../../../functional/ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('batchedFunction', () => { + beforeEach(async () => { + await appsMenu.clickLink('bfetch explorer'); + await testSubjects.click('count-until'); + await testSubjects.click('double-integers'); + }); + + it('executes all requests in a batch', async () => { + const form = await testSubjects.find('DoubleIntegers'); + const btn = await form.findByCssSelector('button'); + await btn.click(); + await new Promise(r => setTimeout(r, 4000)); + const pre = await form.findByCssSelector('pre'); + const text = await pre.getVisibleText(); + const json = JSON.parse(text); + + expect(json).to.eql([ + { + num: -1, + error: { + message: 'Invalid number', + }, + }, + { + num: 300, + result: { + num: 600, + }, + }, + { + num: 1000, + result: { + num: 2000, + }, + }, + { + num: 2000, + result: { + num: 4000, + }, + }, + ]); + }); + + it('streams results back', async () => { + const form = await testSubjects.find('DoubleIntegers'); + const btn = await form.findByCssSelector('button'); + await btn.click(); + + await new Promise(r => setTimeout(r, 500)); + const pre = await form.findByCssSelector('pre'); + + const text1 = await pre.getVisibleText(); + const json1 = JSON.parse(text1); + + expect(json1.length > 0).to.be(true); + expect(json1.length < 4).to.be(true); + + await new Promise(r => setTimeout(r, 3500)); + + const text2 = await pre.getVisibleText(); + const json2 = JSON.parse(text2); + + expect(json2.length).to.be(4); + }); + }); +} diff --git a/test/plugin_functional/test_suites/bfetch_explorer/index.ts b/test/plugin_functional/test_suites/bfetch_explorer/index.ts new file mode 100644 index 0000000000000..54f127d6de89a --- /dev/null +++ b/test/plugin_functional/test_suites/bfetch_explorer/index.ts @@ -0,0 +1,36 @@ +/* + * 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 { FtrProviderContext } from '../../../functional/ftr_provider_context'; + +export default function({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { + const browser = getService('browser'); + const appsMenu = getService('appsMenu'); + const PageObjects = getPageObjects(['common', 'header']); + + describe('bfetch explorer', function() { + before(async () => { + await browser.setWindowSize(1300, 900); + await PageObjects.common.navigateToApp('settings'); + await appsMenu.clickLink('bfetch explorer'); + }); + + loadTestFile(require.resolve('./batched_function')); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index 231458fad155b..f50d460532556 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -121,13 +121,13 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider expect(wrapperWidth).to.be.below(windowWidth); }); - it.skip('can navigate from NP apps to legacy apps', async () => { + it('can navigate from NP apps to legacy apps', async () => { await appsMenu.clickLink('Management'); await loadingScreenShown(); await testSubjects.existOrFail('managementNav'); }); - it.skip('can navigate from legacy apps to NP apps', async () => { + it('can navigate from legacy apps to NP apps', async () => { await appsMenu.clickLink('Foo'); await loadingScreenShown(); await testSubjects.existOrFail('fooAppHome'); diff --git a/test/plugin_functional/test_suites/core_plugins/index.ts b/test/plugin_functional/test_suites/core_plugins/index.ts index d66e2e7dc5da7..d57ad11ea004e 100644 --- a/test/plugin_functional/test_suites/core_plugins/index.ts +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -29,5 +29,6 @@ export default function({ loadTestFile }: PluginFunctionalProviderContext) { loadTestFile(require.resolve('./top_nav')); loadTestFile(require.resolve('./application_leave_confirm')); loadTestFile(require.resolve('./application_status')); + loadTestFile(require.resolve('./rendering')); }); } diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts new file mode 100644 index 0000000000000..d0025c82a7ba5 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -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 expect from '@kbn/expect'; + +import '../../plugins/core_provider_plugin/types'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + + function navigate(path: string) { + return browser.get(`${PageObjects.common.getHostPort()}${path}`); + } + + function getLegacyMode() { + return browser.execute(() => { + return JSON.parse(document.querySelector('kbn-injected-metadata')!.getAttribute('data')!) + .legacyMode; + }); + } + + function getUserSettings() { + return browser.execute(() => { + return JSON.parse(document.querySelector('kbn-injected-metadata')!.getAttribute('data')!) + .legacyMetadata.uiSettings.user; + }); + } + + async function init() { + const loading = await testSubjects.find('kbnLoadingMessage', 5000); + + return () => find.waitForElementStale(loading); + } + + describe('rendering service', () => { + it('renders "core" application', async () => { + await navigate('/render/core'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(false); + expect(userSettings).to.not.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('renderingHeader')).to.be(true); + }); + + it('renders "core" application without user settings', async () => { + await navigate('/render/core?includeUserSettings=false'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(false); + expect(userSettings).to.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('renderingHeader')).to.be(true); + }); + + it('renders "legacy" application', async () => { + await navigate('/render/core_plugin_legacy'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(true); + expect(userSettings).to.not.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); + expect(await testSubjects.exists('renderingHeader')).to.be(false); + }); + + it('renders "legacy" application without user settings', async () => { + await navigate('/render/core_plugin_legacy?includeUserSettings=false'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(true); + expect(userSettings).to.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); + expect(await testSubjects.exists('renderingHeader')).to.be(false); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/server_plugins.ts b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts index 3f89036fc2b65..eb232b1458991 100644 --- a/test/plugin_functional/test_suites/core_plugins/server_plugins.ts +++ b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts @@ -54,15 +54,5 @@ export default function({ getService }: PluginFunctionalProviderContext) { statusCode: 400, }); }); - - it('renders core application explicitly', async () => { - await supertest.get('/requestcontext/render/core').expect(200, /app:core/); - }); - - it('renders legacy application', async () => { - await supertest - .get('/requestcontext/render/core_plugin_legacy') - .expect(200, /app:core_plugin_legacy/); - }); }); } diff --git a/tsconfig.json b/tsconfig.json index a2da9c127e7ba..811b05abeb648 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ ], "test_utils/*": [ "src/test_utils/public/*" - ] + ], + "fixtures/*": ["src/fixtures/*"] }, // Support .tsx files and transform JSX into calls to React.createElement "jsx": "react", @@ -51,7 +52,8 @@ "types": [ "node", "jest", - "react" + "react", + "flot" ] }, "include": [ diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index f38181ce56a2f..b746f0ae258cd 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -17,6 +17,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], moduleNameMapper: { '^ui/(.*)': `${kibanaDirectory}/src/legacy/ui/public/$1`, + '^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`, 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts index e0568a36f8743..33f2b21799814 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts @@ -8,26 +8,28 @@ import { ActionTypeRegistry } from '../action_type_registry'; import { ActionsConfigurationUtilities } from '../actions_config'; import { Logger } from '../../../../../../src/core/server'; -import { getActionType as getServerLogActionType } from './server_log'; -import { getActionType as getSlackActionType } from './slack'; import { getActionType as getEmailActionType } from './email'; import { getActionType as getIndexActionType } from './es_index'; import { getActionType as getPagerDutyActionType } from './pagerduty'; +import { getActionType as getServerLogActionType } from './server_log'; +import { getActionType as getServiceNowActionType } from './servicenow'; +import { getActionType as getSlackActionType } from './slack'; import { getActionType as getWebhookActionType } from './webhook'; export function registerBuiltInActionTypes({ - logger, - actionTypeRegistry, actionsConfigUtils: configurationUtilities, + actionTypeRegistry, + logger, }: { - logger: Logger; - actionTypeRegistry: ActionTypeRegistry; actionsConfigUtils: ActionsConfigurationUtilities; + actionTypeRegistry: ActionTypeRegistry; + logger: Logger; }) { - actionTypeRegistry.register(getServerLogActionType({ logger })); - actionTypeRegistry.register(getSlackActionType({ configurationUtilities })); actionTypeRegistry.register(getEmailActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getIndexActionType({ logger })); actionTypeRegistry.register(getPagerDutyActionType({ logger, configurationUtilities })); + actionTypeRegistry.register(getServerLogActionType({ logger })); + actionTypeRegistry.register(getServiceNowActionType({ configurationUtilities })); + actionTypeRegistry.register(getSlackActionType({ configurationUtilities })); actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities })); } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts new file mode 100644 index 0000000000000..cfd3a9d70dc93 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import axios, { AxiosResponse } from 'axios'; +import { Services } from '../../types'; +import { ParamsType, SecretsType } from '../servicenow'; + +interface PostServiceNowOptions { + apiUrl: string; + data: ParamsType; + headers: Record; + services?: Services; + secrets: SecretsType; +} + +// post an event to serviceNow +export async function postServiceNow(options: PostServiceNowOptions): Promise { + const { apiUrl, data, headers, secrets } = options; + const axiosOptions = { + headers, + validateStatus: () => true, + auth: secrets, + }; + return axios.post(`${apiUrl}/api/now/v1/table/incident`, data, axiosOptions); +} diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts new file mode 100644 index 0000000000000..a445c6afde4d5 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts @@ -0,0 +1,279 @@ +/* + * 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. + */ + +jest.mock('./lib/post_servicenow', () => ({ + postServiceNow: jest.fn(), +})); + +import { getActionType } from './servicenow'; +import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; +import { validateConfig, validateSecrets, validateParams } from '../lib'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { postServiceNow } from './lib/post_servicenow'; +import { createActionTypeRegistry } from './index.test'; +import { configUtilsMock } from '../actions_config.mock'; + +const postServiceNowMock = postServiceNow as jest.Mock; + +const ACTION_TYPE_ID = '.servicenow'; + +const services: Services = { + callCluster: async (path: string, opts: any) => {}, + savedObjectsClient: savedObjectsClientMock.create(), +}; + +let actionType: ActionType; + +const mockServiceNow = { + config: { + apiUrl: 'www.servicenowisinkibanaactions.com', + }, + secrets: { + password: 'secret-password', + username: 'secret-username', + }, + params: { + comments: 'hello cool service now incident', + short_description: 'this is a cool service now incident', + }, +}; + +beforeAll(() => { + const { actionTypeRegistry } = createActionTypeRegistry(); + actionType = actionTypeRegistry.get(ACTION_TYPE_ID); +}); + +describe('get()', () => { + test('should return correct action type', () => { + expect(actionType.id).toEqual(ACTION_TYPE_ID); + expect(actionType.name).toEqual('servicenow'); + }); +}); + +describe('validateConfig()', () => { + test('should validate and pass when config is valid', () => { + const { config } = mockServiceNow; + expect(validateConfig(actionType, config)).toEqual(config); + }); + + test('should validate and throw error when config is invalid', () => { + expect(() => { + validateConfig(actionType, { shouldNotBeHere: true }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type config: [apiUrl]: expected value of type [string] but got [undefined]"` + ); + }); + + test('should validate and pass when the servicenow url is whitelisted', () => { + actionType = getActionType({ + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: url => { + expect(url).toEqual('https://events.servicenow.com/v2/enqueue'); + }, + }, + }); + + expect( + validateConfig(actionType, { apiUrl: 'https://events.servicenow.com/v2/enqueue' }) + ).toEqual({ apiUrl: 'https://events.servicenow.com/v2/enqueue' }); + }); + + test('config validation returns an error if the specified URL isnt whitelisted', () => { + actionType = getActionType({ + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: _ => { + throw new Error(`target url is not whitelisted`); + }, + }, + }); + + expect(() => { + validateConfig(actionType, { apiUrl: 'https://events.servicenow.com/v2/enqueue' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type config: error configuring servicenow action: target url is not whitelisted"` + ); + }); +}); + +describe('validateSecrets()', () => { + test('should validate and pass when secrets is valid', () => { + const { secrets } = mockServiceNow; + expect(validateSecrets(actionType, secrets)).toEqual(secrets); + }); + + test('should validate and throw error when secrets is invalid', () => { + expect(() => { + validateSecrets(actionType, { username: false }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: [password]: expected value of type [string] but got [undefined]"` + ); + + expect(() => { + validateSecrets(actionType, { username: false, password: 'hello' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: [username]: expected value of type [string] but got [boolean]"` + ); + }); +}); + +describe('validateParams()', () => { + test('should validate and pass when params is valid', () => { + const { params } = mockServiceNow; + expect(validateParams(actionType, params)).toEqual(params); + }); + + test('should validate and throw error when params is invalid', () => { + expect(() => { + validateParams(actionType, { eventAction: 'ackynollage' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action params: [short_description]: expected value of type [string] but got [undefined]"` + ); + }); +}); + +describe('execute()', () => { + beforeEach(() => { + postServiceNowMock.mockReset(); + }); + const { config, params, secrets } = mockServiceNow; + test('should succeed with valid params', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 201, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + const { apiUrl, data, headers } = postServiceNowMock.mock.calls[0][0]; + expect({ apiUrl, data, headers, secrets }).toMatchInlineSnapshot(` + Object { + "apiUrl": "www.servicenowisinkibanaactions.com", + "data": Object { + "comments": "hello cool service now incident", + "short_description": "this is a cool service now incident", + }, + "headers": Object { + "Accept": "application/json", + "Content-Type": "application/json", + }, + "secrets": Object { + "password": "secret-password", + "username": "secret-username", + }, + } + `); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); + }); + + test('should fail when postServiceNow throws', async () => { + postServiceNowMock.mockImplementation(() => { + throw new Error('doing some testing'); + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event", + "serviceMessage": "doing some testing", + "status": "error", + } + `); + }); + + test('should fail when postServiceNow returns 429', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 429, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event: http status 429, retry later", + "retry": true, + "status": "error", + } + `); + }); + + test('should fail when postServiceNow returns 501', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 501, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event: http status 501, retry later", + "retry": true, + "status": "error", + } + `); + }); + + test('should fail when postServiceNow returns 418', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 418, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event: unexpected status 418", + "status": "error", + } + `); + }); +}); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts new file mode 100644 index 0000000000000..2d5c18207def3 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts @@ -0,0 +1,169 @@ +/* + * 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 { curry } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { + ActionType, + ActionTypeExecutorOptions, + ActionTypeExecutorResult, + ExecutorType, +} from '../types'; +import { ActionsConfigurationUtilities } from '../actions_config'; +import { postServiceNow } from './lib/post_servicenow'; + +// config definition +export type ConfigType = TypeOf; + +const ConfigSchemaProps = { + apiUrl: schema.string(), +}; + +const ConfigSchema = schema.object(ConfigSchemaProps); + +function validateConfig( + configurationUtilities: ActionsConfigurationUtilities, + configObject: ConfigType +) { + if (configObject.apiUrl == null) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiNullError', { + defaultMessage: 'ServiceNow [apiUrl] is required', + }); + } + try { + configurationUtilities.ensureWhitelistedUri(configObject.apiUrl); + } catch (whitelistError) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiWhitelistError', { + defaultMessage: 'error configuring servicenow action: {message}', + values: { + message: whitelistError.message, + }, + }); + } +} +// secrets definition +export type SecretsType = TypeOf; +const SecretsSchemaProps = { + password: schema.string(), + username: schema.string(), +}; + +const SecretsSchema = schema.object(SecretsSchemaProps); + +function validateSecrets( + configurationUtilities: ActionsConfigurationUtilities, + secrets: SecretsType +) { + if (secrets.username == null) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiUserError', { + defaultMessage: 'error configuring servicenow action: no secrets [username] provided', + }); + } + if (secrets.password == null) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiPasswordError', { + defaultMessage: 'error configuring servicenow action: no secrets [password] provided', + }); + } +} + +// params definition + +export type ParamsType = TypeOf; + +const ParamsSchema = schema.object({ + comments: schema.maybe(schema.string()), + short_description: schema.string(), +}); + +// action type definition +export function getActionType({ + configurationUtilities, + executor = serviceNowExecutor, +}: { + configurationUtilities: ActionsConfigurationUtilities; + executor?: ExecutorType; +}): ActionType { + return { + id: '.servicenow', + name: 'servicenow', + validate: { + config: schema.object(ConfigSchemaProps, { + validate: curry(validateConfig)(configurationUtilities), + }), + secrets: schema.object(SecretsSchemaProps, { + validate: curry(validateSecrets)(configurationUtilities), + }), + params: ParamsSchema, + }, + executor, + }; +} + +// action executor + +async function serviceNowExecutor( + execOptions: ActionTypeExecutorOptions +): Promise { + const actionId = execOptions.actionId; + const config = execOptions.config as ConfigType; + const secrets = execOptions.secrets as SecretsType; + const params = execOptions.params as ParamsType; + const headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + let response; + try { + response = await postServiceNow({ apiUrl: config.apiUrl, data: params, headers, secrets }); + } catch (err) { + const message = i18n.translate('xpack.actions.builtin.servicenow.postingErrorMessage', { + defaultMessage: 'error posting servicenow event', + }); + return { + status: 'error', + actionId, + message, + serviceMessage: err.message, + }; + } + if (response.status === 200 || response.status === 201 || response.status === 204) { + return { + status: 'ok', + actionId, + data: response.data, + }; + } + + if (response.status === 429 || response.status >= 500) { + const message = i18n.translate('xpack.actions.builtin.servicenow.postingRetryErrorMessage', { + defaultMessage: 'error posting servicenow event: http status {status}, retry later', + values: { + status: response.status, + }, + }); + + return { + status: 'error', + actionId, + message, + retry: true, + }; + } + + const message = i18n.translate('xpack.actions.builtin.servicenow.postingUnexpectedErrorMessage', { + defaultMessage: 'error posting servicenow event: unexpected status {status}', + values: { + status: response.status, + }, + }); + + return { + status: 'error', + actionId, + message, + }; +} diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 2af66059d9fed..9b8cfe9b69ed0 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -883,7 +883,6 @@ describe('enable()', () => { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - scheduledTaskId: 'task-123', updatedBy: 'elastic', apiKey: null, apiKeyOwner: null, @@ -892,6 +891,9 @@ describe('enable()', () => { version: '123', } ); + expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: 'task-123', + }); expect(taskManager.schedule).toHaveBeenCalledWith({ taskType: `alerting:2`, params: { @@ -964,7 +966,6 @@ describe('enable()', () => { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - scheduledTaskId: 'task-123', apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', @@ -973,6 +974,9 @@ describe('enable()', () => { version: '123', } ); + expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: 'task-123', + }); expect(taskManager.schedule).toHaveBeenCalledWith({ taskType: `alerting:2`, params: { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index fe96a233b8663..7801e8f478712 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -362,7 +362,6 @@ export class AlertsClient { }); if (attributes.enabled === false) { - const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); const username = await this.getUserName(); await this.savedObjectsClient.update( 'alert', @@ -372,11 +371,11 @@ export class AlertsClient { enabled: true, ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), updatedBy: username, - - scheduledTaskId: scheduledTask.id, }, { version } ); + const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); + await this.savedObjectsClient.update('alert', id, { scheduledTaskId: scheduledTask.id }); await this.invalidateApiKey({ apiKey: attributes.apiKey }); } } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx new file mode 100644 index 0000000000000..80281c1a0a8fc --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; +import { + ApmPluginContext, + ApmPluginContextValue +} from '../../../context/ApmPluginContext'; + +storiesOf('app/ServiceMap/PlatinumLicensePrompt', module).add( + 'example', + () => { + const contextMock = ({ + core: { http: { basePath: { prepend: () => {} } } } + } as unknown) as ApmPluginContextValue; + + return ( + + + + ); + }, + { + info: { + source: false + } + } +); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx index 1ecf72f6fa3fc..cf701d02aa5cb 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx @@ -49,7 +49,7 @@ export function TraceOverview() { return ( - + diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 67bff86c8ccdf..32432b7b85ef6 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -18,8 +18,7 @@ import { history } from '../../../utils/history'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; import { - AutocompleteProvider, - AutocompleteSuggestion, + autocomplete, esKuery, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; @@ -29,7 +28,7 @@ const Container = styled.div` `; interface State { - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; isLoadingSuggestions: boolean; } @@ -38,32 +37,6 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { return esKuery.toElasticsearchQuery(ast, indexPattern); } -function getSuggestions( - query: string, - selectionStart: number, - indexPattern: IIndexPattern, - boolFilter: unknown, - autocompleteProvider?: AutocompleteProvider -) { - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true - }; - - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [indexPattern], - boolFilter - }); - return getAutocompleteSuggestions({ - query, - selectionStart, - selectionEnd: selectionStart - }); -} - export function KueryBar() { const [state, setState] = useState({ suggestions: [], @@ -72,7 +45,6 @@ export function KueryBar() { const { urlParams } = useUrlParams(); const location = useLocation(); const { data } = useApmPluginContext().plugins; - const autocompleteProvider = data.autocomplete.getProvider('kuery'); let currentRequestCheck; @@ -100,16 +72,16 @@ export function KueryBar() { const currentRequest = uniqueId(); currentRequestCheck = currentRequest; - const boolFilter = getBoolFilter(urlParams); try { const suggestions = ( - await getSuggestions( - inputValue, + (await data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: getBoolFilter(urlParams), + query: inputValue, selectionStart, - indexPattern, - boolFilter, - autocompleteProvider - ) + selectionEnd: selectionStart + })) || [] ) .filter(suggestion => !startsWith(suggestion.text, 'span.')) .slice(0, 15); diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md index 6b21f08b7695e..2106243d12aea 100644 --- a/x-pack/legacy/plugins/apm/readme.md +++ b/x-pack/legacy/plugins/apm/readme.md @@ -40,7 +40,6 @@ For testing purposes APM uses 3 custom users: **kibana_write_user** Apps: read/write. Indices: None - To create the users with the correct roles run the following script: ```sh @@ -88,6 +87,12 @@ yarn prettier "./x-pack/legacy/plugins/apm/**/*.{tsx,ts,js}" --write yarn eslint ./x-pack/legacy/plugins/apm --fix ``` +#### Storybook + +Start the [Storybook](https://storybook.js.org/) development environment with +`yarn storybook apm`. All files with a .stories.tsx extension will be loaded. +You can access the development environment at http://localhost:9001. + #### Further resources - [Cypress integration tests](cypress/README.md) diff --git a/x-pack/legacy/plugins/apm/scripts/storybook.js b/x-pack/legacy/plugins/apm/scripts/storybook.js new file mode 100644 index 0000000000000..846ee1f301e30 --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/storybook.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. + */ + +import { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'apm', + storyGlobs: [ + join(__dirname, '..', 'public', 'components', '**', '*.stories.tsx') + ] +}); diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap index 18ce29982b616..580cafff95e0c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap @@ -6,7 +6,7 @@ Array [ Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -42,12 +42,24 @@ Array [ }, }, }, - "terms": Object { - "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": 100, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], }, }, }, @@ -104,7 +116,7 @@ Array [ Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -140,12 +152,22 @@ Array [ }, }, }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, + }, + "transactions": Object { "terms": Object { "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": 100, }, }, }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index e33255b5baa55..58fbe664cccab 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -4,7 +4,7 @@ exports[`transaction group queries fetches top traces 1`] = ` Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -40,12 +40,24 @@ Object { }, }, }, - "terms": Object { - "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": "myIndex", + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], }, }, }, @@ -98,7 +110,7 @@ exports[`transaction group queries fetches top transactions 1`] = ` Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -134,12 +146,22 @@ Object { }, }, }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, + }, + "transactions": Object { "terms": Object { "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": "myIndex", }, }, }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap index 2d8b16e95f769..66b805ab2efc1 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap @@ -3,12 +3,12 @@ exports[`transactionGroupsTransformer should match snapshot 1`] = ` Array [ Object { - "averageResponseTime": 255966.30555555556, - "impact": 4.3693406535517445, - "name": "POST /api/orders", - "p95": 320238.5, + "averageResponseTime": 48021.972616494, + "impact": 100, + "name": "GET /api", + "p95": 67138.18364917398, "sample": Object { - "@timestamp": "2018-11-18T20:43:32.010Z", + "@timestamp": "2018-11-18T20:53:44.070Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -16,52 +16,50 @@ Array [ }, "context": Object { "custom": Object { - "containerId": 4669, + "containerId": 5176, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 2413, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { - "body": "[REDACTED]", "headers": Object { - "accept": "application/json", - "connection": "close", - "content-length": "129", - "content-type": "application/json", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-86c68779d8a65b06fb78e770ffc436a5-4aaea53dc1791183-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", - "method": "POST", + "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders", + "full": "http://opbeans-node:3000/api/types/3", "hostname": "opbeans-node", - "pathname": "/api/orders", + "pathname": "/api/types/3", "port": "3000", "protocol": "http:", - "raw": "/api/orders", + "raw": "/api/types/3", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "13", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:43:32 GMT", - "etag": "W/\\"d-g9K2iK4ordyN88lGL4LmPlYNfhc\\"", + "content-type": "application/json;charset=UTF-8", + "date": "Sun, 18 Nov 2018 20:53:43 GMT", + "transfer-encoding": "chunked", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 404, }, "service": Object { "agent": Object { @@ -101,48 +99,48 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "4aaea53dc1791183", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542573812010006, + "us": 1542574424070007, }, "trace": Object { - "id": "2b1252a338249daeecf6afb0c236e31b", + "id": "86c68779d8a65b06fb78e770ffc436a5", }, "transaction": Object { "duration": Object { - "us": 291572, + "us": 8684, }, - "id": "2c9f39e9ec4a0111", - "name": "POST /api/orders", - "result": "HTTP 2xx", + "id": "a78bca581dcd8ff8", + "name": "GET /api", + "result": "HTTP 4xx", "sampled": true, "span_count": Object { - "started": 16, + "started": 1, }, "type": "request", }, }, - "transactionsPerMinute": 5684.210526315789, + "transactionsPerMinute": 691926.3157894736, }, Object { - "averageResponseTime": 48021.972616494, - "impact": 100, - "name": "GET /api", - "p95": 67138.18364917398, + "averageResponseTime": 2651.8784461553205, + "impact": 15.770246496477105, + "name": "GET static file", + "p95": 6140.579335038363, "sample": Object { - "@timestamp": "2018-11-18T20:53:44.070Z", + "@timestamp": "2018-11-18T20:53:43.304Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { - "custom": Object { - "containerId": 5176, - }, "process": Object { "argv": Array [ "/usr/local/bin/node", @@ -155,36 +153,37 @@ baz", "request": Object { "headers": Object { "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-86c68779d8a65b06fb78e770ffc436a5-4aaea53dc1791183-01", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "curl/7.38.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/types/3", + "full": "http://opbeans-node:3000/", "hostname": "opbeans-node", - "pathname": "/api/types/3", + "pathname": "/", "port": "3000", "protocol": "http:", - "raw": "/api/types/3", + "raw": "/", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-type": "application/json;charset=UTF-8", + "accept-ranges": "bytes", + "cache-control": "public, max-age=0", + "connection": "keep-alive", + "content-length": "640", + "content-type": "text/html; charset=UTF-8", "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "transfer-encoding": "chunked", + "etag": "W/\\"280-1670775e878\\"", + "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", "x-powered-by": "Express", }, - "status_code": 404, + "status_code": 200, }, "service": Object { "agent": Object { @@ -207,59 +206,43 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, }, "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "4aaea53dc1791183", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574424070007, + "us": 1542574423304006, }, "trace": Object { - "id": "86c68779d8a65b06fb78e770ffc436a5", + "id": "b303d2a4a007946b63b9db7fafe639a0", }, "transaction": Object { "duration": Object { - "us": 8684, + "us": 1801, }, - "id": "a78bca581dcd8ff8", - "name": "GET /api", - "result": "HTTP 4xx", + "id": "2869c13633534be5", + "name": "GET static file", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 1, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 691926.3157894736, + "transactionsPerMinute": 1977031.5789473683, }, Object { - "averageResponseTime": 33265.03326147213, - "impact": 10.256357027376065, - "name": "GET /api/orders", - "p95": 58827.489999999976, + "averageResponseTime": 32554.36257814184, + "impact": 14.344171563678346, + "name": "GET /api/stats", + "p95": 59356.73611111111, "sample": Object { - "@timestamp": "2018-11-18T20:53:40.973Z", + "@timestamp": "2018-11-18T20:53:42.560Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -267,7 +250,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 408, + "containerId": 207, }, "process": Object { "argv": Array [ @@ -280,35 +263,38 @@ baz", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-63ccc3b0929dafb7f2fbcabdc7f7af25-821a787e73ab1563-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "if-none-match": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders", + "full": "http://opbeans-node:3000/api/stats", "hostname": "opbeans-node", - "pathname": "/api/orders", + "pathname": "/api/stats", "port": "3000", "protocol": "http:", - "raw": "/api/orders", + "raw": "/api/stats", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "103612", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:40 GMT", - "etag": "W/\\"194bc-cOw6+iRf7XCeqMXHrle3IOig7tY\\"", + "connection": "keep-alive", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "etag": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 304, }, "service": Object { "agent": Object { @@ -348,39 +334,42 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "821a787e73ab1563", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574420973006, + "us": 1542574422560002, }, "trace": Object { - "id": "0afce85f593cbbdd09949936fe964f0f", + "id": "63ccc3b0929dafb7f2fbcabdc7f7af25", }, "transaction": Object { "duration": Object { - "us": 23040, + "us": 28753, }, - "id": "89f200353eb50539", - "name": "GET /api/orders", - "result": "HTTP 2xx", + "id": "fb754e7628da2fb5", + "name": "GET /api/stats", + "result": "HTTP 3xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 7, }, "type": "request", }, }, - "transactionsPerMinute": 102536.84210526315, + "transactionsPerMinute": 146494.73684210525, }, Object { - "averageResponseTime": 32900.72714285714, - "impact": 2.1791207411745854, - "name": "GET /log-message", - "p95": 40444, + "averageResponseTime": 32159.926322043968, + "impact": 10.27904952170656, + "name": "GET /api/customers", + "p95": 59845.85714285714, "sample": Object { - "@timestamp": "2018-11-18T20:49:09.225Z", + "@timestamp": "2018-11-18T20:53:21.180Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -388,48 +377,51 @@ baz", }, "context": Object { "custom": Object { - "containerId": 321, + "containerId": 2531, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3142, + "pid": 3710, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-541025da8ecc2f51f21c1a4ad6992b77-ca18d9d4c3879519-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/log-message", + "full": "http://opbeans-node:3000/api/customers", "hostname": "opbeans-node", - "pathname": "/log-message", + "pathname": "/api/customers", "port": "3000", "protocol": "http:", - "raw": "/log-message", + "raw": "/api/customers", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "24", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:49:09 GMT", - "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", + "connection": "keep-alive", + "content-length": "186769", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:21 GMT", + "etag": "W/\\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\\"", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 200, }, "service": Object { "agent": Object { @@ -469,39 +461,42 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "ca18d9d4c3879519", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574149225004, + "us": 1542574401180002, }, "trace": Object { - "id": "ba18b741cdd3ac83eca89a5fede47577", + "id": "541025da8ecc2f51f21c1a4ad6992b77", }, "transaction": Object { "duration": Object { - "us": 32381, + "us": 18077, }, - "id": "b9a8f96d7554d09f", - "name": "GET /log-message", - "result": "HTTP 5xx", + "id": "94852b9dd1075982", + "name": "GET /api/customers", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 22105.263157894737, + "transactionsPerMinute": 106294.73684210525, }, Object { - "averageResponseTime": 32554.36257814184, - "impact": 14.344171563678346, - "name": "GET /api/stats", - "p95": 59356.73611111111, + "averageResponseTime": 33265.03326147213, + "impact": 10.256357027376065, + "name": "GET /api/orders", + "p95": 58827.489999999976, "sample": Object { - "@timestamp": "2018-11-18T20:53:42.560Z", + "@timestamp": "2018-11-18T20:53:40.973Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -509,7 +504,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 207, + "containerId": 408, }, "process": Object { "argv": Array [ @@ -522,38 +517,35 @@ baz", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-63ccc3b0929dafb7f2fbcabdc7f7af25-821a787e73ab1563-01", + "connection": "close", "host": "opbeans-node:3000", - "if-none-match": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/stats", + "full": "http://opbeans-node:3000/api/orders", "hostname": "opbeans-node", - "pathname": "/api/stats", + "pathname": "/api/orders", "port": "3000", "protocol": "http:", - "raw": "/api/stats", + "raw": "/api/orders", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", - "etag": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", + "connection": "close", + "content-length": "103612", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:40 GMT", + "etag": "W/\\"194bc-cOw6+iRf7XCeqMXHrle3IOig7tY\\"", "x-powered-by": "Express", }, - "status_code": 304, + "status_code": 200, }, "service": Object { "agent": Object { @@ -593,42 +585,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "821a787e73ab1563", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574422560002, + "us": 1542574420973006, }, "trace": Object { - "id": "63ccc3b0929dafb7f2fbcabdc7f7af25", + "id": "0afce85f593cbbdd09949936fe964f0f", }, "transaction": Object { "duration": Object { - "us": 28753, - }, - "id": "fb754e7628da2fb5", - "name": "GET /api/stats", - "result": "HTTP 3xx", + "us": 23040, + }, + "id": "89f200353eb50539", + "name": "GET /api/orders", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 7, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 146494.73684210525, + "transactionsPerMinute": 102536.84210526315, }, Object { - "averageResponseTime": 32387.73641304348, - "impact": 2.2558112380477584, - "name": "GET /log-error", - "p95": 40061.1, + "averageResponseTime": 27516.89144558744, + "impact": 9.651458992731666, + "name": "GET /api/products/top", + "p95": 56064.679999999986, "sample": Object { - "@timestamp": "2018-11-18T20:52:51.462Z", + "@timestamp": "2018-11-18T20:52:57.316Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -636,48 +625,52 @@ baz", }, "context": Object { "custom": Object { - "containerId": 4877, + "containerId": 5113, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3659, + "pid": 3686, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-74f12e705936d66350f4741ebeb55189-fcebe94cd2136215-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/log-error", + "full": "http://opbeans-node:3000/api/products/top", "hostname": "opbeans-node", - "pathname": "/log-error", + "pathname": "/api/products/top", "port": "3000", "protocol": "http:", - "raw": "/log-error", + "raw": "/api/products/top", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "24", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:51 GMT", - "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", + "connection": "keep-alive", + "content-length": "282", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:57 GMT", + "etag": "W/\\"11a-lcI9zuMZYYsDRpEZgYqDYr96cKM\\"", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 200, }, "service": Object { "agent": Object { @@ -717,39 +710,42 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "fcebe94cd2136215", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574371462005, + "us": 1542574377316005, }, "trace": Object { - "id": "15366d65659b5fc8f67ff127391b3aff", + "id": "74f12e705936d66350f4741ebeb55189", }, "transaction": Object { "duration": Object { - "us": 33367, + "us": 48781, }, - "id": "ec9c465c5042ded8", - "name": "GET /log-error", - "result": "HTTP 5xx", + "id": "be4bd5475d5d9e6f", + "name": "GET /api/products/top", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 4, }, "type": "request", }, }, - "transactionsPerMinute": 23242.105263157893, + "transactionsPerMinute": 116652.63157894736, }, Object { - "averageResponseTime": 32159.926322043968, - "impact": 10.27904952170656, - "name": "GET /api/customers", - "p95": 59845.85714285714, + "averageResponseTime": 12683.190864600327, + "impact": 4.4239778504968, + "name": "GET /api/products", + "p95": 35009.67999999999, "sample": Object { - "@timestamp": "2018-11-18T20:53:21.180Z", + "@timestamp": "2018-11-18T20:53:43.477Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -757,48 +753,45 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2531, + "containerId": 2857, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3710, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-541025da8ecc2f51f21c1a4ad6992b77-ca18d9d4c3879519-01", + "connection": "close", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/customers", + "full": "http://opbeans-node:3000/api/products", "hostname": "opbeans-node", - "pathname": "/api/customers", + "pathname": "/api/products", "port": "3000", "protocol": "http:", - "raw": "/api/customers", + "raw": "/api/products", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "186769", + "connection": "close", + "content-length": "1023", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:21 GMT", - "etag": "W/\\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\\"", + "date": "Sun, 18 Nov 2018 20:53:43 GMT", + "etag": "W/\\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -841,25 +834,22 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "ca18d9d4c3879519", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574401180002, + "us": 1542574423477006, }, "trace": Object { - "id": "541025da8ecc2f51f21c1a4ad6992b77", + "id": "bee00a8efb523ca4b72adad57f7caba3", }, "transaction": Object { "duration": Object { - "us": 18077, + "us": 6915, }, - "id": "94852b9dd1075982", - "name": "GET /api/customers", + "id": "d8fc6d3b8707b64c", + "name": "GET /api/products", "result": "HTTP 2xx", "sampled": true, "span_count": Object { @@ -868,15 +858,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 106294.73684210525, + "transactionsPerMinute": 116147.36842105263, }, Object { - "averageResponseTime": 27516.89144558744, - "impact": 9.651458992731666, - "name": "GET /api/products/top", - "p95": 56064.679999999986, + "averageResponseTime": 255966.30555555556, + "impact": 4.3693406535517445, + "name": "POST /api/orders", + "p95": 320238.5, "sample": Object { - "@timestamp": "2018-11-18T20:52:57.316Z", + "@timestamp": "2018-11-18T20:43:32.010Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -884,49 +874,49 @@ baz", }, "context": Object { "custom": Object { - "containerId": 5113, + "containerId": 4669, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3686, + "pid": 2413, "ppid": 1, "title": "node /app/server.js", }, "request": Object { + "body": "[REDACTED]", "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-74f12e705936d66350f4741ebeb55189-fcebe94cd2136215-01", + "accept": "application/json", + "connection": "close", + "content-length": "129", + "content-type": "application/json", "host": "opbeans-node:3000", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "GET", + "method": "POST", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/products/top", + "full": "http://opbeans-node:3000/api/orders", "hostname": "opbeans-node", - "pathname": "/api/products/top", + "pathname": "/api/orders", "port": "3000", "protocol": "http:", - "raw": "/api/products/top", + "raw": "/api/orders", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "282", + "connection": "close", + "content-length": "13", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:57 GMT", - "etag": "W/\\"11a-lcI9zuMZYYsDRpEZgYqDYr96cKM\\"", + "date": "Sun, 18 Nov 2018 20:43:32 GMT", + "etag": "W/\\"d-g9K2iK4ordyN88lGL4LmPlYNfhc\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -969,42 +959,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "fcebe94cd2136215", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574377316005, + "us": 1542573812010006, }, "trace": Object { - "id": "74f12e705936d66350f4741ebeb55189", + "id": "2b1252a338249daeecf6afb0c236e31b", }, "transaction": Object { "duration": Object { - "us": 48781, + "us": 291572, }, - "id": "be4bd5475d5d9e6f", - "name": "GET /api/products/top", + "id": "2c9f39e9ec4a0111", + "name": "POST /api/orders", "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 4, + "started": 16, }, "type": "request", }, }, - "transactionsPerMinute": 116652.63157894736, + "transactionsPerMinute": 5684.210526315789, }, Object { - "averageResponseTime": 21331.714285714286, - "impact": 0.28817487960409877, - "name": "POST /api", - "p95": 30938, + "averageResponseTime": 17189.329210275926, + "impact": 3.424381787142002, + "name": "GET /api/products/:id/customers", + "p95": 39284.79999999999, "sample": Object { - "@timestamp": "2018-11-18T20:29:42.751Z", + "@timestamp": "2018-11-18T20:48:24.769Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1012,50 +999,51 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2927, + "containerId": 1735, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 546, + "pid": 3100, "ppid": 1, "title": "node /app/server.js", }, "request": Object { - "body": "[REDACTED]", "headers": Object { - "accept": "application/json", - "connection": "close", - "content-length": "129", - "content-type": "application/json", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-28f178c354d17f400dea04bc4a7b3c57-68f5d1607cac7779-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", - "method": "POST", + "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders", + "full": "http://opbeans-node:3000/api/products/2/customers", "hostname": "opbeans-node", - "pathname": "/api/orders", + "pathname": "/api/products/2/customers", "port": "3000", "protocol": "http:", - "raw": "/api/orders", + "raw": "/api/products/2/customers", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "0", - "date": "Sun, 18 Nov 2018 20:29:42 GMT", + "connection": "keep-alive", + "content-length": "186570", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:48:24 GMT", + "etag": "W/\\"2d8ca-Z9NzuHyGyxwtzpOkcIxBvzm24iw\\"", "x-powered-by": "Express", }, - "status_code": 400, + "status_code": 200, }, "service": Object { "agent": Object { @@ -1095,23 +1083,26 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "68f5d1607cac7779", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542572982751005, + "us": 1542574104769029, }, "trace": Object { - "id": "8ed4d94ec8fc11b1ea1b0aa59c2320ff", + "id": "28f178c354d17f400dea04bc4a7b3c57", }, "transaction": Object { "duration": Object { - "us": 21083, + "us": 49338, }, - "id": "d67c2f7aa897110c", - "name": "POST /api", - "result": "HTTP 4xx", + "id": "2a87ae20ad04ee0c", + "name": "GET /api/products/:id/customers", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { "started": 1, @@ -1119,15 +1110,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 4642.105263157894, + "transactionsPerMinute": 66378.94736842105, }, Object { - "averageResponseTime": 17189.329210275926, - "impact": 3.424381787142002, - "name": "GET /api/products/:id/customers", - "p95": 39284.79999999999, + "averageResponseTime": 11257.757916666667, + "impact": 2.558180605569336, + "name": "GET /api/types", + "p95": 35222.944444444445, "sample": Object { - "@timestamp": "2018-11-18T20:48:24.769Z", + "@timestamp": "2018-11-18T20:53:44.978Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1135,48 +1126,45 @@ baz", }, "context": Object { "custom": Object { - "containerId": 1735, + "containerId": 2193, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3100, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-28f178c354d17f400dea04bc4a7b3c57-68f5d1607cac7779-01", + "connection": "close", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/products/2/customers", + "full": "http://opbeans-node:3000/api/types", "hostname": "opbeans-node", - "pathname": "/api/products/2/customers", + "pathname": "/api/types", "port": "3000", "protocol": "http:", - "raw": "/api/products/2/customers", + "raw": "/api/types", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "186570", + "connection": "close", + "content-length": "112", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:48:24 GMT", - "etag": "W/\\"2d8ca-Z9NzuHyGyxwtzpOkcIxBvzm24iw\\"", + "date": "Sun, 18 Nov 2018 20:53:44 GMT", + "etag": "W/\\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -1219,42 +1207,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "68f5d1607cac7779", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574104769029, + "us": 1542574424978005, }, "trace": Object { - "id": "28f178c354d17f400dea04bc4a7b3c57", + "id": "0d84126973411c19b470f2d9eea958d3", }, "transaction": Object { "duration": Object { - "us": 49338, + "us": 7891, }, - "id": "2a87ae20ad04ee0c", - "name": "GET /api/products/:id/customers", + "id": "0f10668e4fb3adc7", + "name": "GET /api/types", "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 1, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 66378.94736842105, + "transactionsPerMinute": 75789.47368421052, }, Object { - "averageResponseTime": 12763.68806073154, - "impact": 1.7479924334286208, - "name": "GET /api/types/:id", - "p95": 30576.749999999996, + "averageResponseTime": 3504.5108924806746, + "impact": 2.3600993453143766, + "name": "GET *", + "p95": 11431.738095238095, "sample": Object { - "@timestamp": "2018-11-18T20:53:35.967Z", + "@timestamp": "2018-11-18T20:53:42.493Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1262,7 +1247,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 5345, + "containerId": 6446, }, "process": Object { "argv": Array [ @@ -1275,35 +1260,41 @@ baz", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "if-modified-since": "Mon, 12 Nov 2018 10:27:07 GMT", + "if-none-match": "W/\\"280-1670775e878\\"", + "upgrade-insecure-requests": "1", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/api/types/1", + "full": "http://opbeans-node:3000/dashboard", "hostname": "opbeans-node", - "pathname": "/api/types/1", + "pathname": "/dashboard", "port": "3000", "protocol": "http:", - "raw": "/api/types/1", + "raw": "/dashboard", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "217", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:35 GMT", - "etag": "W/\\"d9-cebOOHODBQMZd1wt+ZZBaSPgQLQ\\"", + "accept-ranges": "bytes", + "cache-control": "public, max-age=0", + "connection": "keep-alive", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "etag": "W/\\"280-1670775e878\\"", + "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 304, }, "service": Object { "agent": Object { @@ -1348,34 +1339,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574415967005, + "us": 1542574422493006, }, "trace": Object { - "id": "2223b30b5cbaf2e221fcf70ac6d9abbe", + "id": "7efb6ade88cdea20cd96ca482681cde7", }, "transaction": Object { "duration": Object { - "us": 13064, + "us": 1901, }, - "id": "053436abacdec0a4", - "name": "GET /api/types/:id", - "result": "HTTP 2xx", + "id": "f5fc4621949b63fb", + "name": "GET *", + "result": "HTTP 3xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 45757.8947368421, + "transactionsPerMinute": 224684.21052631576, }, Object { - "averageResponseTime": 12683.190864600327, - "impact": 4.4239778504968, - "name": "GET /api/products", - "p95": 35009.67999999999, + "averageResponseTime": 32387.73641304348, + "impact": 2.2558112380477584, + "name": "GET /log-error", + "p95": 40061.1, "sample": Object { - "@timestamp": "2018-11-18T20:53:43.477Z", + "@timestamp": "2018-11-18T20:52:51.462Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1383,14 +1374,14 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2857, + "containerId": 4877, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 3659, "ppid": 1, "title": "node /app/server.js", }, @@ -1407,24 +1398,24 @@ baz", "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/products", + "full": "http://opbeans-node:3000/log-error", "hostname": "opbeans-node", - "pathname": "/api/products", + "pathname": "/log-error", "port": "3000", "protocol": "http:", - "raw": "/api/products", + "raw": "/log-error", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "1023", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "etag": "W/\\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\\"", + "content-length": "24", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:51 GMT", + "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 500, }, "service": Object { "agent": Object { @@ -1469,34 +1460,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574423477006, + "us": 1542574371462005, }, "trace": Object { - "id": "bee00a8efb523ca4b72adad57f7caba3", + "id": "15366d65659b5fc8f67ff127391b3aff", }, "transaction": Object { "duration": Object { - "us": 6915, + "us": 33367, }, - "id": "d8fc6d3b8707b64c", - "name": "GET /api/products", - "result": "HTTP 2xx", + "id": "ec9c465c5042ded8", + "name": "GET /log-error", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 116147.36842105263, + "transactionsPerMinute": 23242.105263157893, }, Object { - "averageResponseTime": 11257.757916666667, - "impact": 2.558180605569336, - "name": "GET /api/types", - "p95": 35222.944444444445, + "averageResponseTime": 32900.72714285714, + "impact": 2.1791207411745854, + "name": "GET /log-message", + "p95": 40444, "sample": Object { - "@timestamp": "2018-11-18T20:53:44.978Z", + "@timestamp": "2018-11-18T20:49:09.225Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1504,14 +1495,14 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2193, + "containerId": 321, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 3142, "ppid": 1, "title": "node /app/server.js", }, @@ -1528,24 +1519,24 @@ baz", "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/types", + "full": "http://opbeans-node:3000/log-message", "hostname": "opbeans-node", - "pathname": "/api/types", + "pathname": "/log-message", "port": "3000", "protocol": "http:", - "raw": "/api/types", + "raw": "/log-message", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "112", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:44 GMT", - "etag": "W/\\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\\"", + "content-length": "24", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:49:09 GMT", + "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 500, }, "service": Object { "agent": Object { @@ -1590,34 +1581,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574424978005, + "us": 1542574149225004, }, "trace": Object { - "id": "0d84126973411c19b470f2d9eea958d3", + "id": "ba18b741cdd3ac83eca89a5fede47577", }, "transaction": Object { "duration": Object { - "us": 7891, + "us": 32381, }, - "id": "0f10668e4fb3adc7", - "name": "GET /api/types", - "result": "HTTP 2xx", + "id": "b9a8f96d7554d09f", + "name": "GET /log-message", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 75789.47368421052, + "transactionsPerMinute": 22105.263157894737, }, Object { - "averageResponseTime": 10584.05144193297, - "impact": 1.280810614916383, - "name": "GET /api/orders/:id", - "p95": 26555.399999999998, + "averageResponseTime": 10548.218597063622, + "impact": 1.8338763992340905, + "name": "GET /api/products/:id", + "p95": 28413.383333333328, "sample": Object { - "@timestamp": "2018-11-18T20:51:36.949Z", + "@timestamp": "2018-11-18T20:52:57.963Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1625,14 +1616,14 @@ baz", }, "context": Object { "custom": Object { - "containerId": 5999, + "containerId": 7184, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3475, + "pid": 3686, "ppid": 1, "title": "node /app/server.js", }, @@ -1649,22 +1640,24 @@ baz", "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders/183", + "full": "http://opbeans-node:3000/api/products/3", "hostname": "opbeans-node", - "pathname": "/api/orders/183", + "pathname": "/api/products/3", "port": "3000", "protocol": "http:", - "raw": "/api/orders/183", + "raw": "/api/products/3", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "0", - "date": "Sun, 18 Nov 2018 20:51:36 GMT", + "content-length": "231", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:57 GMT", + "etag": "W/\\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\\"", "x-powered-by": "Express", }, - "status_code": 404, + "status_code": 200, }, "service": Object { "agent": Object { @@ -1709,18 +1702,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574296949004, + "us": 1542574377963005, }, "trace": Object { - "id": "dab6421fa44a6869887e0edf32e1ad6f", + "id": "ca86ec845e412e4b4506a715d51548ec", }, "transaction": Object { "duration": Object { - "us": 5906, + "us": 6959, }, - "id": "937ef5588454f74a", - "name": "GET /api/orders/:id", - "result": "HTTP 4xx", + "id": "d324897ffb7ebcdc", + "name": "GET /api/products/:id", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { "started": 1, @@ -1728,15 +1721,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 40515.789473684206, + "transactionsPerMinute": 58073.68421052631, }, Object { - "averageResponseTime": 10548.218597063622, - "impact": 1.8338763992340905, - "name": "GET /api/products/:id", - "p95": 28413.383333333328, + "averageResponseTime": 9868.217894736843, + "impact": 1.7722323960215767, + "name": "GET /api/customers/:id", + "p95": 27486.5, "sample": Object { - "@timestamp": "2018-11-18T20:52:57.963Z", + "@timestamp": "2018-11-18T20:52:56.797Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1744,7 +1737,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 7184, + "containerId": 8225, }, "process": Object { "argv": Array [ @@ -1757,32 +1750,35 @@ baz", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-e6140d30363f18b585f5d3b753f4d025-aa82e2c847265626-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/api/products/3", + "full": "http://opbeans-node:3000/api/customers/700", "hostname": "opbeans-node", - "pathname": "/api/products/3", + "pathname": "/api/customers/700", "port": "3000", "protocol": "http:", - "raw": "/api/products/3", + "raw": "/api/customers/700", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "231", + "connection": "keep-alive", + "content-length": "193", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:57 GMT", - "etag": "W/\\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\\"", + "date": "Sun, 18 Nov 2018 20:52:56 GMT", + "etag": "W/\\"c1-LbuhkuLzFyZ0H+7+JQGA5b0kvNs\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -1825,22 +1821,25 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "aa82e2c847265626", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574377963005, + "us": 1542574376797031, }, "trace": Object { - "id": "ca86ec845e412e4b4506a715d51548ec", + "id": "e6140d30363f18b585f5d3b753f4d025", }, "transaction": Object { "duration": Object { - "us": 6959, + "us": 9735, }, - "id": "d324897ffb7ebcdc", - "name": "GET /api/products/:id", + "id": "60e230d12f3f0960", + "name": "GET /api/customers/:id", "result": "HTTP 2xx", "sampled": true, "span_count": Object { @@ -1849,15 +1848,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 58073.68421052631, + "transactionsPerMinute": 59999.99999999999, }, Object { - "averageResponseTime": 9868.217894736843, - "impact": 1.7722323960215767, - "name": "GET /api/customers/:id", - "p95": 27486.5, + "averageResponseTime": 12763.68806073154, + "impact": 1.7479924334286208, + "name": "GET /api/types/:id", + "p95": 30576.749999999996, "sample": Object { - "@timestamp": "2018-11-18T20:52:56.797Z", + "@timestamp": "2018-11-18T20:53:35.967Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1865,48 +1864,45 @@ baz", }, "context": Object { "custom": Object { - "containerId": 8225, + "containerId": 5345, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3686, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-e6140d30363f18b585f5d3b753f4d025-aa82e2c847265626-01", + "connection": "close", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/customers/700", + "full": "http://opbeans-node:3000/api/types/1", "hostname": "opbeans-node", - "pathname": "/api/customers/700", + "pathname": "/api/types/1", "port": "3000", "protocol": "http:", - "raw": "/api/customers/700", + "raw": "/api/types/1", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "193", + "connection": "close", + "content-length": "217", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:56 GMT", - "etag": "W/\\"c1-LbuhkuLzFyZ0H+7+JQGA5b0kvNs\\"", + "date": "Sun, 18 Nov 2018 20:53:35 GMT", + "etag": "W/\\"d9-cebOOHODBQMZd1wt+ZZBaSPgQLQ\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -1949,42 +1945,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "aa82e2c847265626", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574376797031, + "us": 1542574415967005, }, "trace": Object { - "id": "e6140d30363f18b585f5d3b753f4d025", + "id": "2223b30b5cbaf2e221fcf70ac6d9abbe", }, "transaction": Object { "duration": Object { - "us": 9735, + "us": 13064, }, - "id": "60e230d12f3f0960", - "name": "GET /api/customers/:id", + "id": "053436abacdec0a4", + "name": "GET /api/types/:id", "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 1, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 59999.99999999999, + "transactionsPerMinute": 45757.8947368421, }, Object { - "averageResponseTime": 5192.9, - "impact": 0, - "name": "POST unknown route", - "p95": 13230.5, + "averageResponseTime": 10584.05144193297, + "impact": 1.280810614916383, + "name": "GET /api/orders/:id", + "p95": 26555.399999999998, "sample": Object { - "@timestamp": "2018-11-18T18:43:50.994Z", + "@timestamp": "2018-11-18T20:51:36.949Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1992,52 +1985,43 @@ baz", }, "context": Object { "custom": Object { - "containerId": 6102, + "containerId": 5999, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 19196, + "pid": 3475, "ppid": 1, "title": "node /app/server.js", }, "request": Object { - "body": "[REDACTED]", "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "content-length": "380", - "content-type": "multipart/form-data; boundary=2b2e40be188a4cb5a56c05a0c182f6c9", - "elastic-apm-traceparent": "00-19688959ea6cbccda8013c11566ea329-1fc3665eef2dcdfc-01", - "host": "172.18.0.9:3000", - "user-agent": "Python/3.7 aiohttp/3.3.2", - "x-forwarded-for": "172.18.0.11", + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "POST", + "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.9", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://172.18.0.9:3000/api/orders/csv", - "hostname": "172.18.0.9", - "pathname": "/api/orders/csv", + "full": "http://opbeans-node:3000/api/orders/183", + "hostname": "opbeans-node", + "pathname": "/api/orders/183", "port": "3000", "protocol": "http:", - "raw": "/api/orders/csv", + "raw": "/api/orders/183", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "154", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 18:43:50 GMT", - "x-content-type-options": "nosniff", + "connection": "close", + "content-length": "0", + "date": "Sun, 18 Nov 2018 20:51:36 GMT", "x-powered-by": "Express", }, "status_code": 404, @@ -2080,92 +2064,87 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "1fc3665eef2dcdfc", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542566630994005, + "us": 1542574296949004, }, "trace": Object { - "id": "19688959ea6cbccda8013c11566ea329", + "id": "dab6421fa44a6869887e0edf32e1ad6f", }, "transaction": Object { "duration": Object { - "us": 3467, + "us": 5906, }, - "id": "92c3ceea57899061", - "name": "POST unknown route", + "id": "937ef5588454f74a", + "name": "GET /api/orders/:id", "result": "HTTP 4xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 1, }, "type": "request", }, }, - "transactionsPerMinute": 631.578947368421, + "transactionsPerMinute": 40515.789473684206, }, Object { - "averageResponseTime": 4694.005586592179, - "impact": 0.1498515000753004, - "name": "GET /is-it-coffee-time", - "p95": 11022.99999999992, + "averageResponseTime": 1422.926672899693, + "impact": 1.0027124806135428, + "name": "GET unknown route", + "p95": 2311.885238095238, "sample": Object { - "@timestamp": "2018-11-18T20:46:19.317Z", + "@timestamp": "2018-11-18T20:53:42.504Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { - "custom": Object { - "containerId": 8593, - }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 2760, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/is-it-coffee-time", + "full": "http://opbeans-node:3000/rum-config.js", "hostname": "opbeans-node", - "pathname": "/is-it-coffee-time", + "pathname": "/rum-config.js", "port": "3000", "protocol": "http:", - "raw": "/is-it-coffee-time", + "raw": "/rum-config.js", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "148", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:46:19 GMT", - "x-content-type-options": "nosniff", + "connection": "keep-alive", + "content-length": "172", + "content-type": "text/javascript", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 200, }, "service": Object { "agent": Object { @@ -2188,19 +2167,6 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, }, "host": Object { "name": "b359e3afece8", @@ -2210,18 +2176,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542573979317007, + "us": 1542574422504004, }, "trace": Object { - "id": "821812b416de4c73ced87f8777fa46a6", + "id": "4399e7233e6e7b77e70c2fff111b8f28", }, "transaction": Object { "duration": Object { - "us": 4253, + "us": 911, }, - "id": "319a5c555a1ab207", - "name": "GET /is-it-coffee-time", - "result": "HTTP 5xx", + "id": "107881ae2be1b56d", + "name": "GET unknown route", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { "started": 0, @@ -2229,15 +2195,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 11305.263157894737, + "transactionsPerMinute": 236431.5789473684, }, Object { - "averageResponseTime": 4549.889880952381, - "impact": 0.13543365054509587, - "name": "GET /throw-error", - "p95": 7719.700000000001, + "averageResponseTime": 21331.714285714286, + "impact": 0.28817487960409877, + "name": "POST /api", + "p95": 30938, "sample": Object { - "@timestamp": "2018-11-18T20:47:10.714Z", + "@timestamp": "2018-11-18T20:29:42.751Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -2245,49 +2211,50 @@ baz", }, "context": Object { "custom": Object { - "containerId": 7220, + "containerId": 2927, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 2895, + "pid": 546, "ppid": 1, "title": "node /app/server.js", }, "request": Object { + "body": "[REDACTED]", "headers": Object { + "accept": "application/json", "connection": "close", + "content-length": "129", + "content-type": "application/json", "host": "opbeans-node:3000", "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "GET", + "method": "POST", "socket": Object { "encrypted": false, "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/throw-error", + "full": "http://opbeans-node:3000/api/orders", "hostname": "opbeans-node", - "pathname": "/throw-error", + "pathname": "/api/orders", "port": "3000", "protocol": "http:", - "raw": "/throw-error", + "raw": "/api/orders", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "148", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:47:10 GMT", - "x-content-type-options": "nosniff", + "content-length": "0", + "date": "Sun, 18 Nov 2018 20:29:42 GMT", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 400, }, "service": Object { "agent": Object { @@ -2332,34 +2299,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574030714012, + "us": 1542572982751005, }, "trace": Object { - "id": "6c0ef23e1f963f304ce440a909914d35", + "id": "8ed4d94ec8fc11b1ea1b0aa59c2320ff", }, "transaction": Object { "duration": Object { - "us": 4458, + "us": 21083, }, - "id": "ecd187dc53f09fbd", - "name": "GET /throw-error", - "result": "HTTP 5xx", + "id": "d67c2f7aa897110c", + "name": "POST /api", + "result": "HTTP 4xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 1, }, "type": "request", }, }, - "transactionsPerMinute": 10610.526315789473, + "transactionsPerMinute": 4642.105263157894, }, Object { - "averageResponseTime": 3504.5108924806746, - "impact": 2.3600993453143766, - "name": "GET *", - "p95": 11431.738095238095, + "averageResponseTime": 4694.005586592179, + "impact": 0.1498515000753004, + "name": "GET /is-it-coffee-time", + "p95": 11022.99999999992, "sample": Object { - "@timestamp": "2018-11-18T20:53:42.493Z", + "@timestamp": "2018-11-18T20:46:19.317Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -2367,54 +2334,49 @@ baz", }, "context": Object { "custom": Object { - "containerId": 6446, + "containerId": 8593, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 2760, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", + "connection": "close", "host": "opbeans-node:3000", - "if-modified-since": "Mon, 12 Nov 2018 10:27:07 GMT", - "if-none-match": "W/\\"280-1670775e878\\"", - "upgrade-insecure-requests": "1", - "user-agent": "Chromeless 1.4.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/dashboard", + "full": "http://opbeans-node:3000/is-it-coffee-time", "hostname": "opbeans-node", - "pathname": "/dashboard", + "pathname": "/is-it-coffee-time", "port": "3000", "protocol": "http:", - "raw": "/dashboard", + "raw": "/is-it-coffee-time", }, }, "response": Object { "headers": Object { - "accept-ranges": "bytes", - "cache-control": "public, max-age=0", - "connection": "keep-alive", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", - "etag": "W/\\"280-1670775e878\\"", - "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", + "connection": "close", + "content-length": "148", + "content-security-policy": "default-src 'self'", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:46:19 GMT", + "x-content-type-options": "nosniff", "x-powered-by": "Express", }, - "status_code": 304, + "status_code": 500, }, "service": Object { "agent": Object { @@ -2459,18 +2421,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574422493006, + "us": 1542573979317007, }, "trace": Object { - "id": "7efb6ade88cdea20cd96ca482681cde7", + "id": "821812b416de4c73ced87f8777fa46a6", }, "transaction": Object { "duration": Object { - "us": 1901, + "us": 4253, }, - "id": "f5fc4621949b63fb", - "name": "GET *", - "result": "HTTP 3xx", + "id": "319a5c555a1ab207", + "name": "GET /is-it-coffee-time", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { "started": 0, @@ -2478,15 +2440,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 224684.21052631576, + "transactionsPerMinute": 11305.263157894737, }, Object { - "averageResponseTime": 2742.4615384615386, - "impact": 0.08501028923348058, - "name": "OPTIONS unknown route", - "p95": 4370.000000000002, + "averageResponseTime": 4549.889880952381, + "impact": 0.13543365054509587, + "name": "GET /throw-error", + "p95": 7719.700000000001, "sample": Object { - "@timestamp": "2018-11-18T20:49:00.707Z", + "@timestamp": "2018-11-18T20:47:10.714Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -2494,50 +2456,49 @@ baz", }, "context": Object { "custom": Object { - "containerId": 3775, + "containerId": 7220, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3142, + "pid": 2895, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { "connection": "close", - "content-length": "0", "host": "opbeans-node:3000", "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "OPTIONS", + "method": "GET", "socket": Object { "encrypted": false, "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/", + "full": "http://opbeans-node:3000/throw-error", "hostname": "opbeans-node", - "pathname": "/", + "pathname": "/throw-error", "port": "3000", "protocol": "http:", - "raw": "/", + "raw": "/throw-error", }, }, "response": Object { "headers": Object { - "allow": "GET,HEAD", "connection": "close", - "content-length": "8", + "content-length": "148", + "content-security-policy": "default-src 'self'", "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:49:00 GMT", - "etag": "W/\\"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg\\"", + "date": "Sun, 18 Nov 2018 20:47:10 GMT", + "x-content-type-options": "nosniff", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 500, }, "service": Object { "agent": Object { @@ -2582,18 +2543,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574140707006, + "us": 1542574030714012, }, "trace": Object { - "id": "469e3e5f91ffe3195a8e58cdd1cdefa8", + "id": "6c0ef23e1f963f304ce440a909914d35", }, "transaction": Object { "duration": Object { - "us": 2371, + "us": 4458, }, - "id": "a8c87ebc7ec68bc0", - "name": "OPTIONS unknown route", - "result": "HTTP 2xx", + "id": "ecd187dc53f09fbd", + "name": "GET /throw-error", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { "started": 0, @@ -2601,38 +2562,42 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 11494.736842105262, + "transactionsPerMinute": 10610.526315789473, }, Object { - "averageResponseTime": 2651.8784461553205, - "impact": 15.770246496477105, - "name": "GET static file", - "p95": 6140.579335038363, + "averageResponseTime": 2742.4615384615386, + "impact": 0.08501028923348058, + "name": "OPTIONS unknown route", + "p95": 4370.000000000002, "sample": Object { - "@timestamp": "2018-11-18T20:53:43.304Z", + "@timestamp": "2018-11-18T20:49:00.707Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { + "custom": Object { + "containerId": 3775, + }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 3142, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", + "connection": "close", + "content-length": "0", "host": "opbeans-node:3000", - "user-agent": "curl/7.38.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "GET", + "method": "OPTIONS", "socket": Object { "encrypted": false, "remote_address": "::ffff:172.18.0.10", @@ -2648,14 +2613,12 @@ baz", }, "response": Object { "headers": Object { - "accept-ranges": "bytes", - "cache-control": "public, max-age=0", - "connection": "keep-alive", - "content-length": "640", - "content-type": "text/html; charset=UTF-8", - "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "etag": "W/\\"280-1670775e878\\"", - "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", + "allow": "GET,HEAD", + "connection": "close", + "content-length": "8", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:49:00 GMT", + "etag": "W/\\"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -2681,6 +2644,19 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, }, "host": Object { "name": "b359e3afece8", @@ -2690,17 +2666,17 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574423304006, + "us": 1542574140707006, }, "trace": Object { - "id": "b303d2a4a007946b63b9db7fafe639a0", + "id": "469e3e5f91ffe3195a8e58cdd1cdefa8", }, "transaction": Object { "duration": Object { - "us": 1801, + "us": 2371, }, - "id": "2869c13633534be5", - "name": "GET static file", + "id": "a8c87ebc7ec68bc0", + "name": "OPTIONS unknown route", "result": "HTTP 2xx", "sampled": true, "span_count": Object { @@ -2709,63 +2685,71 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 1977031.5789473683, + "transactionsPerMinute": 11494.736842105262, }, Object { - "averageResponseTime": 1422.926672899693, - "impact": 1.0027124806135428, - "name": "GET unknown route", - "p95": 2311.885238095238, + "averageResponseTime": 5192.9, + "impact": 0, + "name": "POST unknown route", + "p95": 13230.5, "sample": Object { - "@timestamp": "2018-11-18T20:53:42.504Z", + "@timestamp": "2018-11-18T18:43:50.994Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { + "custom": Object { + "containerId": 6102, + }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 19196, "ppid": 1, "title": "node /app/server.js", }, "request": Object { + "body": "[REDACTED]", "headers": Object { "accept": "*/*", "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "host": "opbeans-node:3000", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", + "content-length": "380", + "content-type": "multipart/form-data; boundary=2b2e40be188a4cb5a56c05a0c182f6c9", + "elastic-apm-traceparent": "00-19688959ea6cbccda8013c11566ea329-1fc3665eef2dcdfc-01", + "host": "172.18.0.9:3000", + "user-agent": "Python/3.7 aiohttp/3.3.2", + "x-forwarded-for": "172.18.0.11", }, "http_version": "1.1", - "method": "GET", + "method": "POST", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.9", }, "url": Object { - "full": "http://opbeans-node:3000/rum-config.js", - "hostname": "opbeans-node", - "pathname": "/rum-config.js", + "full": "http://172.18.0.9:3000/api/orders/csv", + "hostname": "172.18.0.9", + "pathname": "/api/orders/csv", "port": "3000", "protocol": "http:", - "raw": "/rum-config.js", + "raw": "/api/orders/csv", }, }, "response": Object { "headers": Object { "connection": "keep-alive", - "content-length": "172", - "content-type": "text/javascript", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "content-length": "154", + "content-security-policy": "default-src 'self'", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 18:43:50 GMT", + "x-content-type-options": "nosniff", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 404, }, "service": Object { "agent": Object { @@ -2788,27 +2772,43 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, }, "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "1fc3665eef2dcdfc", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574422504004, + "us": 1542566630994005, }, "trace": Object { - "id": "4399e7233e6e7b77e70c2fff111b8f28", + "id": "19688959ea6cbccda8013c11566ea329", }, "transaction": Object { "duration": Object { - "us": 911, + "us": 3467, }, - "id": "107881ae2be1b56d", - "name": "GET unknown route", - "result": "HTTP 2xx", + "id": "92c3ceea57899061", + "name": "POST unknown route", + "result": "HTTP 4xx", "sampled": true, "span_count": Object { "started": 0, @@ -2816,7 +2816,7 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 236431.5789473684, + "transactionsPerMinute": 631.578947368421, }, ] `; diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts index b08bdc334fc87..a4885f2884976 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -5,19 +5,21 @@ */ import { + SERVICE_NAME, TRANSACTION_DURATION, - TRANSACTION_SAMPLED + TRANSACTION_SAMPLED, + TRANSACTION_NAME } from '../../../common/elasticsearch_fieldnames'; +import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; +import { mergeProjection } from '../../../common/projections/util/merge_projection'; import { PromiseReturnType } from '../../../typings/common'; +import { SortOptions } from '../../../typings/elasticsearch/aggregations'; +import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { Setup, SetupTimeRange, SetupUIFilters } from '../helpers/setup_request'; -import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; -import { mergeProjection } from '../../../common/projections/util/merge_projection'; -import { SortOptions } from '../../../typings/elasticsearch/aggregations'; -import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; interface TopTransactionOptions { type: 'top_transactions'; @@ -38,7 +40,7 @@ export function transactionGroupsFetcher( options: Options, setup: Setup & SetupTimeRange & SetupUIFilters ) { - const { client, config } = setup; + const { client } = setup; const projection = getTransactionGroupsProjection({ setup, @@ -50,6 +52,13 @@ export function transactionGroupsFetcher( { '@timestamp': { order: 'desc' as const } } ]; + const isTopTraces = options.type === 'top_traces'; + + if (isTopTraces) { + // Delete the projection aggregation when searching for traces, as it should use the combined aggregation instead + delete projection.body.aggs; + } + const params = mergeProjection(projection, { body: { size: 0, @@ -60,19 +69,18 @@ export function transactionGroupsFetcher( } }, aggs: { - transactions: { - terms: { - ...projection.body.aggs.transactions.terms, - order: { sum: 'desc' as const }, - size: config['xpack.apm.ui.transactionGroupBucketSize'] + transaction_groups: { + composite: { + size: 10000, + sources: [ + ...(isTopTraces + ? [{ service: { terms: { field: SERVICE_NAME } } }] + : []), + { transaction: { terms: { field: TRANSACTION_NAME } } } + ] }, aggs: { - sample: { - top_hits: { - size: 1, - sort - } - }, + sample: { top_hits: { size: 1, sort } }, avg: { avg: { field: TRANSACTION_DURATION } }, p95: { percentiles: { field: TRANSACTION_DURATION, percents: [95] } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts index 2632cc6e94b93..bc61f1cab149e 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts @@ -12,12 +12,12 @@ export const transactionGroupsResponse = ({ _shards: { total: 44, successful: 44, skipped: 0, failed: 0 }, hits: { total: 131557, max_score: null, hits: [] }, aggregations: { - transactions: { + transaction_groups: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { - key: 'POST /api/orders', + key: { transaction: 'POST /api/orders' }, doc_count: 180, avg: { value: 255966.30555555556 }, p95: { values: { '95.0': 320238.5 } }, @@ -137,7 +137,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api', + key: { transaction: 'GET /api' }, doc_count: 21911, avg: { value: 48021.972616494 }, p95: { values: { '95.0': 67138.18364917398 } }, @@ -257,7 +257,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/orders', + key: { transaction: 'GET /api/orders' }, doc_count: 3247, avg: { value: 33265.03326147213 }, p95: { values: { '95.0': 58827.489999999976 } }, @@ -373,7 +373,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /log-message', + key: { transaction: 'GET /log-message' }, doc_count: 700, avg: { value: 32900.72714285714 }, p95: { values: { '95.0': 40444 } }, @@ -489,7 +489,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/stats', + key: { transaction: 'GET /api/stats' }, doc_count: 4639, avg: { value: 32554.36257814184 }, p95: { values: { '95.0': 59356.73611111111 } }, @@ -610,7 +610,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /log-error', + key: { transaction: 'GET /log-error' }, doc_count: 736, avg: { value: 32387.73641304348 }, p95: { values: { '95.0': 40061.1 } }, @@ -726,7 +726,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/customers', + key: { transaction: 'GET /api/customers' }, doc_count: 3366, avg: { value: 32159.926322043968 }, p95: { values: { '95.0': 59845.85714285714 } }, @@ -847,7 +847,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products/top', + key: { transaction: 'GET /api/products/top' }, doc_count: 3694, avg: { value: 27516.89144558744 }, p95: { values: { '95.0': 56064.679999999986 } }, @@ -969,7 +969,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'POST /api', + key: { transaction: 'POST /api' }, doc_count: 147, avg: { value: 21331.714285714286 }, p95: { values: { '95.0': 30938 } }, @@ -1087,7 +1087,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products/:id/customers', + key: { transaction: 'GET /api/products/:id/customers' }, doc_count: 2102, avg: { value: 17189.329210275926 }, p95: { values: { '95.0': 39284.79999999999 } }, @@ -1209,7 +1209,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/types/:id', + key: { transaction: 'GET /api/types/:id' }, doc_count: 1449, avg: { value: 12763.68806073154 }, p95: { values: { '95.0': 30576.749999999996 } }, @@ -1325,7 +1325,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products', + key: { transaction: 'GET /api/products' }, doc_count: 3678, avg: { value: 12683.190864600327 }, p95: { values: { '95.0': 35009.67999999999 } }, @@ -1441,7 +1441,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/types', + key: { transaction: 'GET /api/types' }, doc_count: 2400, avg: { value: 11257.757916666667 }, p95: { values: { '95.0': 35222.944444444445 } }, @@ -1557,7 +1557,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/orders/:id', + key: { transaction: 'GET /api/orders/:id' }, doc_count: 1283, avg: { value: 10584.05144193297 }, p95: { values: { '95.0': 26555.399999999998 } }, @@ -1671,7 +1671,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products/:id', + key: { transaction: 'GET /api/products/:id' }, doc_count: 1839, avg: { value: 10548.218597063622 }, p95: { values: { '95.0': 28413.383333333328 } }, @@ -1787,7 +1787,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/customers/:id', + key: { transaction: 'GET /api/customers/:id' }, doc_count: 1900, avg: { value: 9868.217894736843 }, p95: { values: { '95.0': 27486.5 } }, @@ -1908,7 +1908,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'POST unknown route', + key: { transaction: 'POST unknown route' }, doc_count: 20, avg: { value: 5192.9 }, p95: { values: { '95.0': 13230.5 } }, @@ -2034,7 +2034,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /is-it-coffee-time', + key: { transaction: 'GET /is-it-coffee-time' }, doc_count: 358, avg: { value: 4694.005586592179 }, p95: { values: { '95.0': 11022.99999999992 } }, @@ -2151,7 +2151,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /throw-error', + key: { transaction: 'GET /throw-error' }, doc_count: 336, avg: { value: 4549.889880952381 }, p95: { values: { '95.0': 7719.700000000001 } }, @@ -2268,7 +2268,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET *', + key: { transaction: 'GET *' }, doc_count: 7115, avg: { value: 3504.5108924806746 }, p95: { values: { '95.0': 11431.738095238095 } }, @@ -2391,7 +2391,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'OPTIONS unknown route', + key: { transaction: 'OPTIONS unknown route' }, doc_count: 364, avg: { value: 2742.4615384615386 }, p95: { values: { '95.0': 4370.000000000002 } }, @@ -2509,7 +2509,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET static file', + key: { transaction: 'GET static file' }, doc_count: 62606, avg: { value: 2651.8784461553205 }, p95: { values: { '95.0': 6140.579335038363 } }, @@ -2614,7 +2614,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET unknown route', + key: { transaction: 'GET unknown route' }, doc_count: 7487, avg: { value: 1422.926672899693 }, p95: { values: { '95.0': 2311.885238095238 } }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts index 6acd34af24353..709fa3afdc128 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts @@ -21,7 +21,7 @@ describe('transactionGroupsTransformer', () => { it('should transform response correctly', () => { const bucket = { - key: 'POST /api/orders', + key: { transaction: 'POST /api/orders' }, doc_count: 180, avg: { value: 255966.30555555556 }, p95: { values: { '95.0': 320238.5 } }, @@ -36,7 +36,7 @@ describe('transactionGroupsTransformer', () => { const response = ({ aggregations: { - transactions: { + transaction_groups: { buckets: [bucket] } } @@ -58,7 +58,7 @@ describe('transactionGroupsTransformer', () => { it('should calculate impact from sum', () => { const getBucket = (sum: number) => ({ - key: 'POST /api/orders', + key: { transaction: 'POST /api/orders' }, doc_count: 180, avg: { value: 300000 }, p95: { values: { '95.0': 320000 } }, @@ -68,7 +68,9 @@ describe('transactionGroupsTransformer', () => { const response = ({ aggregations: { - transactions: { buckets: [getBucket(10), getBucket(20), getBucket(50)] } + transaction_groups: { + buckets: [getBucket(10), getBucket(20), getBucket(50)] + } } } as unknown) as ESResponse; @@ -76,6 +78,6 @@ describe('transactionGroupsTransformer', () => { transactionGroupsTransformer({ response, start: 100, end: 20000 }).map( bucket => bucket.impact ) - ).toEqual([0, 25, 100]); + ).toEqual([100, 25, 0]); }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts index 58a952baa8233..0a03a88cbf4a2 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts @@ -5,6 +5,7 @@ */ import moment from 'moment'; +import { sortByOrder } from 'lodash'; import { ESResponse } from './fetcher'; function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { @@ -24,9 +25,20 @@ function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { })); } +const getBuckets = (response: ESResponse) => { + if (response.aggregations) { + return sortByOrder( + response.aggregations.transaction_groups.buckets, + ['sum.value'], + ['desc'] + ); + } + return []; +}; + export type ITransactionGroup = ReturnType; function getTransactionGroup( - bucket: Required['aggregations']['transactions']['buckets'][0], + bucket: ReturnType[0], minutes: number ) { const averageResponseTime = bucket.avg.value; @@ -35,7 +47,7 @@ function getTransactionGroup( const sample = bucket.sample.hits.hits[0]._source; return { - name: bucket.key as string, + name: bucket.key.transaction, sample, p95: bucket.p95.values['95.0'], averageResponseTime, @@ -53,7 +65,7 @@ export function transactionGroupsTransformer({ start: number; end: number; }): ITransactionGroup[] { - const buckets = response.aggregations?.transactions.buckets || []; + const buckets = getBuckets(response); const duration = moment.duration(end - start); const minutes = duration.asMinutes(); const transactionGroups = buckets.map(bucket => diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx index 1f49711ae5e4a..f3e0f3dfbdae7 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx @@ -13,7 +13,7 @@ import { import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { composeStateUpdaters } from '../../utils/typed_react'; import { SuggestionItem } from './suggestion_item'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx index a753a944a2ecb..0132667b9e510 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx @@ -9,13 +9,13 @@ import { tint } from 'polished'; import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; interface SuggestionItemProps { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; } export const SuggestionItem: React.FC = props => { diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx index 26ddd682405cb..d1cbc0888dca8 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eu import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { TABLE_CONFIG } from '../../../common/constants'; import { AutocompleteField } from '../autocomplete_field/index'; import { ControlSchema } from './action_schema'; @@ -31,7 +31,7 @@ export interface KueryBarProps { loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; onChange?: (value: string) => void; onSubmit?: (value: string) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx index 2ac20438536c8..db73a7cb38c11 100644 --- a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../src/plugins/data/public'; import { FrontendLibs } from '../lib/types'; import { RendererFunction } from '../utils/typed_react'; @@ -17,7 +17,7 @@ interface WithKueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; }>; } @@ -28,7 +28,7 @@ interface WithKueryAutocompletionLifecycleState { expression: string; cursorPosition: number; } | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; } export class WithKueryAutocompletion extends React.Component< diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts index 4f4ce70e817c6..12898027d5fb5 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; export interface ElasticsearchAdapter { convertKueryToEsQuery: (kuery: string) => Promise; - getSuggestions: (kuery: string, selectionStart: any) => Promise; + getSuggestions: (kuery: string, selectionStart: any) => Promise; isKueryValid(kuery: string): boolean; } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts index e001bf6c6e844..111255b55c99b 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapter_types'; export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { constructor( private readonly mockIsKueryValid: (kuery: string) => boolean, private readonly mockKueryToEsQuery: (kuery: string) => string, - private readonly suggestions: AutocompleteSuggestion[] + private readonly suggestions: autocomplete.QuerySuggestion[] ) {} public isKueryValid(kuery: string): boolean { @@ -23,7 +23,7 @@ export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { public async getSuggestions( kuery: string, selectionStart: any - ): Promise { + ): Promise { return this.suggestions; } } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index 8771181639f4d..fc400c600e575 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -7,10 +7,7 @@ import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; import { ElasticsearchAdapter } from './adapter_types'; -import { AutocompleteSuggestion, esKuery } from '../../../../../../../../src/plugins/data/public'; - -const getAutocompleteProvider = (language: string) => - npStart.plugins.data.autocomplete.getProvider(language); +import { autocomplete, esKuery } from '../../../../../../../../src/plugins/data/public'; export class RestElasticsearchAdapter implements ElasticsearchAdapter { private cachedIndexPattern: any = null; @@ -33,30 +30,23 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { const indexPattern = await this.getIndexPattern(); return JSON.stringify(esKuery.toElasticsearchQuery(ast, indexPattern)); } + public async getSuggestions( kuery: string, selectionStart: any - ): Promise { - const autocompleteProvider = getAutocompleteProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; + ): Promise { const indexPattern = await this.getIndexPattern(); - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: null, - }); - const results = getAutocompleteSuggestions({ - query: kuery || '', - selectionStart, - selectionEnd: selectionStart, - }); - return results; + return ( + (await npStart.plugins.data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: [], + query: kuery || '', + selectionStart, + selectionEnd: selectionStart, + })) || [] + ); } private async getIndexPattern() { diff --git a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts index f357e698afc3a..47df51dea8620 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts @@ -24,14 +24,14 @@ import { TagsLib } from '../tags'; import { FrontendLibs } from '../types'; import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; import { ElasticsearchLib } from './../elasticsearch'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; const onKibanaReady = uiModules.get('kibana').run; export function compose( mockIsKueryValid: (kuery: string) => boolean, mockKueryToEsQuery: (kuery: string) => string, - suggestions: AutocompleteSuggestion[] + suggestions: autocomplete.QuerySuggestion[] ): FrontendLibs { const esAdapter = new MemoryElasticsearchAdapter( mockIsKueryValid, diff --git a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts index 0897dfd9c1392..d71512e80d3d5 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; interface HiddenFields { @@ -35,7 +35,7 @@ export class ElasticsearchLib { kuery: string, selectionStart: any, fieldPrefix?: string - ): Promise { + ): Promise { const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); const filteredSuggestions = suggestions.filter(suggestion => { diff --git a/x-pack/legacy/plugins/canvas/.storybook/config.js b/x-pack/legacy/plugins/canvas/.storybook/config.js index d3cd8e8057ae8..725a3b12666d1 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/config.js @@ -10,7 +10,6 @@ import { withKnobs } from '@storybook/addon-knobs/react'; import { withInfo } from '@storybook/addon-info'; import { create } from '@storybook/theming'; -import { coreMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; // If we're running Storyshots, be sure to register the require context hook. @@ -38,9 +37,12 @@ if (process.env.NODE_ENV === 'test') { } // Add New Platform Context for any stories that need it -addDecorator(fn => ( - {fn()} -)); +const settings = new Map(); +settings.set('darkMode', true); +const platform = { + uiSettings: settings, +}; +addDecorator(fn => {fn()}); function loadStories() { require('./dll_contexts'); @@ -55,7 +57,7 @@ function loadStories() { css.keys().forEach(filename => css(filename)); // Find all files ending in *.examples.ts - const req = require.context('./..', true, /.examples.tsx$/); + const req = require.context('./..', true, /.(stories|examples).tsx$/); req.keys().forEach(filename => req(filename)); } diff --git a/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js b/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js index 66ef1a192d403..273323d065ecf 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js +++ b/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js @@ -26,3 +26,10 @@ const uiStyles = require.context( /[\/\\](?!mixins|variables|_|\.|bootstrap_(light|dark))[^\/\\]+\.less/ ); uiStyles.keys().forEach(key => uiStyles(key)); + +const json = require.context( + '../shareable_runtime/test/workpads', + false, + /\.json$/ +); +json.keys().forEach(key => json(key)); diff --git a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js index b7924486c34cb..76240d212da15 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js @@ -52,7 +52,7 @@ jest.mock('plugins/interpreter/registries', () => ({})); // Disabling this test due to https://github.com/elastic/eui/issues/2242 jest.mock( - '../public/components/workpad_header/workpad_export/__examples__/disabled_panel.examples', + '../public/components/workpad_header/workpad_export/__examples__/disabled_panel.stories', () => { return 'Disabled Panel'; } @@ -60,7 +60,7 @@ jest.mock( // Disabling this test due to https://github.com/elastic/eui/issues/2242 jest.mock( - '../public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.examples', + '../public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories', () => { return 'Disabled Panel'; } diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js index 662078585422f..019194716b230 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js @@ -47,15 +47,18 @@ module.exports = async ({ config }) => { }); // Parse props data for .tsx files - config.module.rules.push({ - test: /\.tsx$/, - // Exclude example files, as we don't display props info for them - exclude: /\.examples.tsx$/, - use: [ - // Parse TS comments to create Props tables in the UI - require.resolve('react-docgen-typescript-loader'), - ], - }); + // This is notoriously slow, and is making Storybook unusable. Disabling for now. + // See: https://github.com/storybookjs/storybook/issues/7998 + // + // config.module.rules.push({ + // test: /\.tsx$/, + // // Exclude example files, as we don't display props info for them + // exclude: /\.examples.tsx$/, + // use: [ + // // Parse TS comments to create Props tables in the UI + // require.resolve('react-docgen-typescript-loader'), + // ], + // }); // Enable SASS, but exclude CSS Modules in Storybook config.module.rules.push({ diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js index 12f2195c067ca..0a648e861b386 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js @@ -24,9 +24,7 @@ module.exports = { entry: [ '@elastic/eui/dist/eui_theme_light.css', '@kbn/ui-framework/dist/kui_light.css', - '@storybook/addon-actions', '@storybook/addon-actions/register', - '@storybook/addon-info', '@storybook/addon-knobs', '@storybook/addon-knobs/react', '@storybook/addon-knobs/register', diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts index 8f5ad859d28ba..cc92d864282e7 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts @@ -6,7 +6,7 @@ // @ts-ignore import { MAP_SAVED_OBJECT_TYPE } from '../../../maps/common/constants'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize_embeddable/constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/visualizations/public/embeddable/constants'; import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants'; export const EmbeddableTypes: { map: string; search: string; visualization: string } = { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts index f6eb377e2698b..d3b1bbe31c715 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts @@ -5,7 +5,7 @@ */ import { ExpressionFunction } from 'src/plugins/expressions/common/types'; -import { VisualizeInput } from 'src/legacy/core_plugins/kibana/public/visualize_embeddable'; +import { VisualizeInput } from 'src/legacy/core_plugins/visualizations/public/embeddable'; import { EmbeddableTypes, EmbeddableExpressionType, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx index fc4603b5be6e5..9d5a3893dbcb8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx @@ -8,7 +8,7 @@ import React, { ReactNode, FunctionComponent } from 'react'; import PropTypes from 'prop-types'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; import 'react-datetime/css/react-datetime.css'; -import { UnitStrings } from '../../../../../i18n'; +import { UnitStrings } from '../../../../../i18n/units'; const { quickRanges: strings } = UnitStrings; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts index f52465fd3da70..1c436d3630b53 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UnitStrings } from '../../../../../../i18n'; +import { UnitStrings } from '../../../../../../i18n/units'; export interface QuickRange { /** Start date string of range */ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx index a03e64ce3a02e..599b15524ddda 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx @@ -11,7 +11,7 @@ import { EuiButton } from '@elastic/eui'; import moment from 'moment'; import { DatetimeQuickList } from '../datetime_quick_list'; import { DatetimeRangeAbsolute } from '../datetime_range_absolute'; -import { ComponentStrings } from '../../../../../i18n'; +import { ComponentStrings } from '../../../../../i18n/components'; const { TimePicker: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx index 1dafd7ba648c3..8f6061b688319 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx @@ -42,7 +42,16 @@ export const TimePickerPopover: FunctionComponent = ({ from, to, onSelect anchorClassName="canvasTimePickerPopover__anchor" button={button} > - {() => } + {({ closePopover }) => ( + { + onSelect(...args); + closePopover(); + }} + /> + )} ); }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx index 0f01860520b50..806a61042494f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx @@ -10,7 +10,7 @@ import { EuiSelect, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; import immutable from 'object-path-immutable'; import { get } from 'lodash'; import { ExpressionAST } from '../../../../types'; -import { ArgumentStrings } from '../../../../i18n'; +import { ArgumentStrings } from '../../../../i18n/ui'; const { AxisConfig: strings } = ArgumentStrings; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/common/lib/constants.ts b/x-pack/legacy/plugins/canvas/common/lib/constants.ts index 7494ea13e6c08..40e143b9ec589 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/constants.ts +++ b/x-pack/legacy/plugins/canvas/common/lib/constants.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SHAREABLE_RUNTIME_NAME } from '../../shareable_runtime/constants'; +import { SHAREABLE_RUNTIME_NAME } from '../../shareable_runtime/constants_static'; export const CANVAS_TYPE = 'canvas-workpad'; export const CUSTOM_ELEMENT_TYPE = 'canvas-element'; diff --git a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx index 9cc6f870b9bde..9c66043454585 100644 --- a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { MouseEvent } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { EuiButtonIcon } from '@elastic/eui'; // @ts-ignore untyped local @@ -28,7 +28,7 @@ interface Props { } export const ArgAddPopover = ({ options }: Props) => { - const button = (handleClick: (ev: MouseEvent) => void) => ( + const button = (handleClick: React.MouseEventHandler) => ( = (props: Props) => { const { value, anchorPosition, ariaLabel, ...rest } = props; - const button = (handleClick: (ev: MouseEvent) => void) => ( + const button = (handleClick: React.MouseEventHandler) => ( -
- -
-
- - - -
-
- - - -
-
-
-
- -
-
- - - -
-
- - - -
-
-
-
- -
-
- - - -
-
- - - -
-
-
-
-`; - -exports[`Storyshots components/ElementTypes/ElementGrid with controls and filter 1`] = ` -
-
- -
-
- - - -
-
- - - -
-
-
-
-`; - -exports[`Storyshots components/ElementTypes/ElementGrid with filter 1`] = ` -
-
- -
-
-`; - -exports[`Storyshots components/ElementTypes/ElementGrid without controls 1`] = ` -
-
- -
-
- -
-
- -
-
-`; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx b/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx index fd8836f5ac120..a23274296f64f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent, MouseEvent } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { ElementControls: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js index 1934eab85d034..60c7e731691fa 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js @@ -58,7 +58,9 @@ function selectorFactory(dispatch) { export const ElementWrapper = compose( connectAdvanced(selectorFactory), withPropsOnChange( - (props, nextProps) => !isEqual(props.element, nextProps.element), + (props, nextProps) => + !isEqual(props.element, nextProps.element) || + !isEqual(props.selectedPage, nextProps.selectedPage), props => { const { element, createHandlers } = props; const handlers = createHandlers(element, props.selectedPage); diff --git a/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx index 27c951d1757f8..1c94969292b59 100644 --- a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx @@ -17,11 +17,11 @@ import { EuiTitle, } from '@elastic/eui'; import { keymap } from '../../lib/keymap'; -import { ShortcutMap, ShortcutNameSpace } from '../../../types'; +import { ShortcutMap, ShortcutNameSpace } from '../../../types/shortcuts'; import { getClientPlatform } from '../../lib/get_client_platform'; import { getId } from '../../lib/get_id'; import { getPrettyShortcut } from '../../lib/get_pretty_shortcut'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { KeyboardShortcutsDoc: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx b/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx index a5d7ce1109447..25b2e6587c869 100644 --- a/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx @@ -5,25 +5,23 @@ */ /* eslint react/no-did-mount-set-state: 0, react/forbid-elements: 0 */ -import React, { ReactNode, ReactElement, Component, MouseEvent } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { EuiToolTip, ToolTipPositions, EuiPopover, PropsOf } from '@elastic/eui'; +import { EuiPopover, EuiToolTip } from '@elastic/eui'; -export interface PopoverChildrenProps { - closePopover: () => void; -} - -type EuiPopoverProps = PropsOf; -interface Props extends Omit { +interface Props { + button: (handleClick: React.MouseEventHandler) => React.ReactElement; + tooltipPosition?: 'top' | 'bottom' | 'left' | 'right'; + children: (arg: { closePopover: () => void }) => React.ReactNode; isOpen?: boolean; ownFocus?: boolean; - - button: (handleClick: (ev: MouseEvent) => void) => ReactElement; - - children: (props: PopoverChildrenProps) => ReactNode; - - tooltip: string; - tooltipPosition: ToolTipPositions; + tooltip?: string; + panelClassName?: string; + anchorClassName?: string; + anchorPosition?: string; + panelPaddingSize?: 'none' | 's' | 'm' | 'l'; + id?: string; + className?: string; } interface State { @@ -47,8 +45,8 @@ export class Popover extends Component { tooltipPosition: 'top', }; - state = { - isPopoverOpen: false, + state: State = { + isPopoverOpen: !!this.props.isOpen, }; componentDidMount() { @@ -58,7 +56,7 @@ export class Popover extends Component { } handleClick = () => { - this.setState(state => ({ + this.setState((state: any) => ({ isPopoverOpen: !state.isPopoverOpen, })); }; @@ -72,9 +70,9 @@ export class Popover extends Component { render() { const { button, children, tooltip, tooltipPosition, ...rest } = this.props; - const wrappedButton = (handleClick: (ev: MouseEvent) => void) => { + const wrappedButton = (handleClick: any) => { // wrap button in tooltip, if tooltip text is provided - if (!this.state.isPopoverOpen && tooltip.length) { + if (!this.state.isPopoverOpen && tooltip && tooltip.length) { return ( {button(handleClick)} @@ -86,17 +84,18 @@ export class Popover extends Component { }; const appWrapper = document.querySelector('.app-wrapper'); + const EuiPopoverAny = (EuiPopover as any) as React.FC; return ( - {children({ closePopover: this.closePopover })} - + ); } } diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx index 717ec6d0faecc..d42e08d2bc852 100644 --- a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { MouseEvent } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { EuiLink, EuiPanel } from '@elastic/eui'; import { Popover } from '../popover'; @@ -21,7 +21,7 @@ interface Props { } export const ShapePickerPopover = ({ shapes, onChange, value, ariaLabel }: Props) => { - const button = (handleClick: (ev: MouseEvent) => void) => ( + const button = (handleClick: React.MouseEventHandler) => ( diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx index 95d9035774a6a..e1ce539256daa 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx @@ -6,7 +6,7 @@ import React, { FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { GroupSettings: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx index 999c1c2daaf5b..cae5671bf901b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx @@ -6,7 +6,7 @@ import React, { FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { MultiElementSettings: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx index 3a0e191b9f4b5..d4dec6cca064b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment, MouseEvent } from 'react'; +import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, @@ -20,7 +20,8 @@ import { import { Popover } from '../popover'; import { CustomElementModal } from '../custom_element_modal'; import { ToolTipShortcut } from '../tool_tip_shortcut/'; -import { ComponentStrings, ShortcutStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; +import { ShortcutStrings } from '../../../i18n/shortcuts'; const topBorderClassName = 'canvasContextMenu--topBorder'; @@ -138,7 +139,7 @@ interface MenuTuple { panel: EuiContextMenuPanelDescriptor; } -const contextMenuButton = (handleClick: (event: MouseEvent) => void) => ( +const contextMenuButton = (handleClick: React.MouseEventHandler) => ( ( enabled, getExportUrl: type => { if (type === 'pdf') { - const { createPdfUri } = getPdfUrl( - workpad, - { pageCount }, - kibana.services.http.basePath.prepend - ); - return getAbsoluteUrl(createPdfUri); + const pdfUrl = getPdfUrl(workpad, { pageCount }, kibana.services.http.basePath.prepend); + return getAbsoluteUrl(pdfUrl); } throw new Error(strings.getUnknownExportErrorMessage(type)); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx index ae85a6d89ca67..ef70079cf697b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; import { Clipboard } from '../../clipboard'; -import { ComponentStrings } from '../../../../i18n'; +import { ComponentStrings } from '../../../../i18n/components'; const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings; interface Props { diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts new file mode 100644 index 0000000000000..ceaf82c1c07d6 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +jest.mock('../../../../common/lib/fetch'); + +import { getPdfUrl, createPdf } from './utils'; +import { workpads } from '../../../../__tests__/fixtures/workpads'; +import { fetch } from '../../../../common/lib/fetch'; + +const addBasePath = jest.fn().mockImplementation(s => `basepath/${s}`); +const workpad = workpads[0]; + +test('getPdfUrl returns the correct url', () => { + const url = getPdfUrl(workpad, { pageCount: 2 }, addBasePath); + + expect(url).toMatchInlineSnapshot( + `"basepath//api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FPhoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas%20workpad',relativeUrls:!(%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F1,%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F2),title:'base%20workpad')"` + ); +}); + +test('createPdf posts to create the pdf', () => { + createPdf(workpad, { pageCount: 2 }, addBasePath); + + expect(fetch.post).toBeCalled(); + + const args = (fetch.post as jest.MockedFunction).mock.calls[0]; + + expect(args[0]).toMatchInlineSnapshot(`"basepath//api/reporting/generate/printablePdf"`); + expect(args[1]).toMatchInlineSnapshot(` + Object { + "jobParams": "(browserTimezone:America/Phoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas workpad',relativeUrls:!(/app/canvas#/export/workpad/pdf/base-workpad/page/1,/app/canvas#/export/workpad/pdf/base-workpad/page/2),title:'base workpad')", + } + `); +}); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts index f0ca5fac1d271..f7f191a48de82 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts @@ -7,6 +7,7 @@ import rison from 'rison-node'; // @ts-ignore Untyped local. import { fetch } from '../../../../common/lib/fetch'; +import { getStartPlugins } from '../../../legacy'; import { CanvasWorkpad } from '../../../../types'; // type of the desired pdf output (print or preserve_layout) @@ -25,7 +26,7 @@ interface PdfUrlData { createPdfPayload: { jobParams: string }; } -export function getPdfUrl( +function getPdfUrlParts( { id, name: title, width, height }: CanvasWorkpad, { pageCount }: PageCount, addBasePath: (path: string) => string @@ -68,7 +69,16 @@ export function getPdfUrl( }; } +export function getPdfUrl(...args: Arguments): string { + const urlParts = getPdfUrlParts(...args); + + return `${urlParts.createPdfUri}?${getStartPlugins().__LEGACY.QueryString.param( + 'jobParams', + urlParts.createPdfPayload.jobParams + )}`; +} + export function createPdf(...args: Arguments) { - const { createPdfUri, createPdfPayload } = getPdfUrl(...args); + const { createPdfUri, createPdfPayload } = getPdfUrlParts(...args); return fetch.post(createPdfUri, createPdfPayload); } diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx index 20f48c2092766..0558652fb6029 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FunctionComponent, useState, MouseEvent } from 'react'; +import React, { FunctionComponent, useState } from 'react'; import PropTypes from 'prop-types'; import { EuiButtonIcon, EuiContextMenu, EuiIcon } from '@elastic/eui'; // @ts-ignore Untyped local @@ -13,7 +13,7 @@ import { DisabledPanel } from './disabled_panel'; import { PDFPanel } from './pdf_panel'; import { ShareWebsiteFlyout } from './flyout'; -import { ComponentStrings } from '../../../../i18n'; +import { ComponentStrings } from '../../../../i18n/components'; const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings; type ClosePopoverFn = () => void; @@ -129,7 +129,7 @@ export const WorkpadExport: FunctionComponent = ({ ], }); - const exportControl = (togglePopover: (ev: MouseEvent) => void) => ( + const exportControl = (togglePopover: React.MouseEventHandler) => ( any; formatMsg: any; + QueryString: any; setRootController: Chrome['setRootController']; storage: typeof Storage; trackSubUrlForApp: Chrome['trackSubUrlForApp']; diff --git a/x-pack/legacy/plugins/canvas/scripts/storybook_new.js b/x-pack/legacy/plugins/canvas/scripts/storybook_new.js new file mode 100644 index 0000000000000..4871898b73a45 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/scripts/storybook_new.js @@ -0,0 +1,13 @@ +/* + * 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 { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'canvas', + storyGlobs: [join(__dirname, '..', '**', '*.stories.tsx')], +}); diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js b/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js index 605591167c499..2ac92f57a90ed 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js @@ -5,9 +5,7 @@ */ const path = require('path'); - -const LIBRARY_NAME = 'KbnCanvas'; -const SHAREABLE_RUNTIME_NAME = 'kbn_canvas'; +const { LIBRARY_NAME, SHAREABLE_RUNTIME_NAME } = require('./constants_static'); const KIBANA_ROOT_PATH = '../../../../..'; const CANVAS_ROOT_PATH = 'x-pack/legacy/plugins/canvas'; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.d.ts b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.d.ts new file mode 100644 index 0000000000000..fec4da996ba8f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.d.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const LIBRARY_NAME: string; +export const SHAREABLE_RUNTIME_NAME: string; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.js b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.js new file mode 100644 index 0000000000000..914a7070920e4 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const LIBRARY_NAME = 'KbnCanvas'; +const SHAREABLE_RUNTIME_NAME = 'kbn_canvas'; + +module.exports = { + LIBRARY_NAME, + SHAREABLE_RUNTIME_NAME, +}; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 470fa00734d27..391973f6d909b 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -30,7 +30,7 @@ import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; import 'ui/autoload/all'; import 'ui/kbn_top_nav'; -import 'ui/vislib'; +import 'ui/color_maps'; import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index 360561df71957..95b7dd22e9fcf 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -21,6 +21,7 @@ import { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; +jest.mock('ui/new_platform'); jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); const waitForIndexPatternFetch = () => new Promise(r => setTimeout(r)); @@ -51,7 +52,7 @@ function wrapSearchBarInContext(testProps: OuterSearchBarProps) { savedQueries: {}, }, autocomplete: { - getProvider: () => undefined, + hasQuerySuggestions: () => false, }, }, }; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx index d1b0ee3a3e83e..f79741d9a1a9f 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.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 React, { useState } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -14,9 +14,6 @@ import { UseField, FormDataProvider, FormRow, ToggleField } from '../../../share import { ComboBoxOption } from '../../../types'; export const SourceFieldSection = () => { - const [includeComboBoxOptions, setIncludeComboBoxOptions] = useState([]); - const [excludeComboBoxOptions, setExcludeComboBoxOptions] = useState([]); - const renderWarning = () => ( { {({ label, helpText, value, setValue }) => ( { setValue(newValue); @@ -90,7 +87,6 @@ export const SourceFieldSection = () => { }; setValue([...(value as ComboBoxOption[]), newOption]); - setIncludeComboBoxOptions([...includeComboBoxOptions, newOption]); }} fullWidth /> @@ -104,13 +100,13 @@ export const SourceFieldSection = () => { {({ label, helpText, value, setValue }) => ( { setValue(newValue); @@ -121,7 +117,6 @@ export const SourceFieldSection = () => { }; setValue([...(value as ComboBoxOption[]), newOption]); - setExcludeComboBoxOptions([...excludeComboBoxOptions, newOption]); }} fullWidth /> diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/document_fields_header.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/document_fields_header.tsx index a97e54afbf067..a4e746aa4037d 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/document_fields_header.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/document_fields_header.tsx @@ -46,7 +46,15 @@ export const DocumentFieldsHeader = React.memo(({ searchValue, onSearchChange }: } )} value={searchValue} - onChange={e => onSearchChange(e.target.value)} + onChange={e => { + // Temporary fix until EUI fixes the contract + // See my comment https://github.com/elastic/eui/pull/2723/files#r366725059 + if (typeof e === 'string') { + onSearchChange(e); + } else { + onSearchChange(e.target.value); + } + }} aria-label={i18n.translate( 'xpack.idxMgmt.mappingsEditor.documentFields.searchFieldsAriaLabel', { diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx index 9402a64b22dde..a55bd96dce3d0 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx @@ -234,7 +234,7 @@ export const LoadMappingsProvider = ({ onJson, children }: Props) => { mappings, }} diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/templates_form/templates_form.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/templates_form/templates_form.tsx index 0aa6a90039a86..471217108ba6f 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/templates_form/templates_form.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/templates_form/templates_form.tsx @@ -62,8 +62,11 @@ export const TemplatesForm = React.memo(({ defaultValue }: Props) => { const dispatch = useDispatch(); useEffect(() => { - const subscription = form.subscribe(updatedTemplates => { - dispatch({ type: 'templates.update', value: { ...updatedTemplates, form } }); + const subscription = form.subscribe(({ data, isValid, validate }) => { + dispatch({ + type: 'templates.update', + value: { data, isValid, validate, submitForm: form.submit }, + }); }); return subscription.unsubscribe; }, [form]); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx index 39da6dcf336b5..64888253ecab4 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import Joi from 'joi'; +import * as t from 'io-ts'; import { EuiLink, EuiCode } from '@elastic/eui'; import { @@ -100,11 +100,10 @@ const fielddataFrequencyFilterParam = { }, }, }, - schema: Joi.object().keys({ - min: Joi.number(), - max: Joi.number(), - min_segment_size: Joi.number(), - }), + schema: t.record( + t.union([t.literal('min'), t.literal('max'), t.literal('min_segment_size')]), + t.number + ), }; const analyzerValidations = [ @@ -178,40 +177,40 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.string(), + schema: t.string, }, store: { fieldConfig: { type: FIELD_TYPES.CHECKBOX, defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, index: { fieldConfig: { type: FIELD_TYPES.CHECKBOX, defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, doc_values: { fieldConfig: { defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, doc_values_binary: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, fielddata: { fieldConfig: { type: FIELD_TYPES.CHECKBOX, defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, fielddata_frequency_filter: fielddataFrequencyFilterParam, fielddata_frequency_filter_percentage: { @@ -280,19 +279,19 @@ export const PARAMETERS_DEFINITION = { fieldConfig: { defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, coerce_shape: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, ignore_malformed: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, null_value: { fieldConfig: { @@ -300,7 +299,6 @@ export const PARAMETERS_DEFINITION = { type: FIELD_TYPES.TEXT, label: nullValueLabel, }, - schema: Joi.string(), }, null_value_ip: { fieldConfig: { @@ -323,7 +321,7 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.number(), + schema: t.number, }, null_value_boolean: { fieldConfig: { @@ -332,7 +330,7 @@ export const PARAMETERS_DEFINITION = { deserializer: (value: string | boolean) => mapIndexToValue.indexOf(value), serializer: (value: number) => mapIndexToValue[value], }, - schema: Joi.any().valid([true, false, 'true', 'false']), + schema: t.union([t.literal(true), t.literal(false), t.literal('true'), t.literal('false')]), }, null_value_geo_point: { fieldConfig: { @@ -376,7 +374,7 @@ export const PARAMETERS_DEFINITION = { } }, }, - schema: Joi.any(), + schema: t.any, }, copy_to: { fieldConfig: { @@ -398,7 +396,7 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.string(), + schema: t.string, }, max_input_length: { fieldConfig: { @@ -421,7 +419,7 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.number(), + schema: t.number, }, locale: { fieldConfig: { @@ -454,7 +452,7 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.string(), + schema: t.string, }, orientation: { fieldConfig: { @@ -464,7 +462,7 @@ export const PARAMETERS_DEFINITION = { defaultMessage: 'Orientation', }), }, - schema: Joi.string(), + schema: t.string, }, boost: { fieldConfig: { @@ -484,7 +482,7 @@ export const PARAMETERS_DEFINITION = { }, ], } as FieldConfig, - schema: Joi.number(), + schema: t.number, }, scaling_factor: { title: i18n.translate('xpack.idxMgmt.mappingsEditor.parameters.scalingFactorFieldTitle', { @@ -535,7 +533,7 @@ export const PARAMETERS_DEFINITION = { defaultMessage: 'Value must be greater than 0.', }), } as FieldConfig, - schema: Joi.number(), + schema: t.number, }, dynamic: { fieldConfig: { @@ -545,7 +543,7 @@ export const PARAMETERS_DEFINITION = { type: FIELD_TYPES.CHECKBOX, defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, enabled: { fieldConfig: { @@ -555,7 +553,7 @@ export const PARAMETERS_DEFINITION = { type: FIELD_TYPES.CHECKBOX, defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, format: { fieldConfig: { @@ -577,7 +575,7 @@ export const PARAMETERS_DEFINITION = { /> ), }, - schema: Joi.string(), + schema: t.string, }, analyzer: { fieldConfig: { @@ -587,7 +585,7 @@ export const PARAMETERS_DEFINITION = { defaultValue: INDEX_DEFAULT, validations: analyzerValidations, }, - schema: Joi.string(), + schema: t.string, }, search_analyzer: { fieldConfig: { @@ -597,7 +595,7 @@ export const PARAMETERS_DEFINITION = { defaultValue: INDEX_DEFAULT, validations: analyzerValidations, }, - schema: Joi.string(), + schema: t.string, }, search_quote_analyzer: { fieldConfig: { @@ -607,7 +605,7 @@ export const PARAMETERS_DEFINITION = { defaultValue: INDEX_DEFAULT, validations: analyzerValidations, }, - schema: Joi.string(), + schema: t.string, }, normalizer: { fieldConfig: { @@ -636,76 +634,76 @@ export const PARAMETERS_DEFINITION = { defaultMessage: `The name of a normalizer defined in the index's settings.`, }), }, - schema: Joi.string(), + schema: t.string, }, index_options: { fieldConfig: { ...indexOptionsConfig, defaultValue: 'positions', }, - schema: Joi.string(), + schema: t.string, }, index_options_keyword: { fieldConfig: { ...indexOptionsConfig, defaultValue: 'docs', }, - schema: Joi.string(), + schema: t.string, }, index_options_flattened: { fieldConfig: { ...indexOptionsConfig, defaultValue: 'docs', }, - schema: Joi.string(), + schema: t.string, }, eager_global_ordinals: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, index_phrases: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, preserve_separators: { fieldConfig: { defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, preserve_position_increments: { fieldConfig: { defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, ignore_z_value: { fieldConfig: { defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, points_only: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, norms: { fieldConfig: { defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, norms_keyword: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, term_vector: { fieldConfig: { @@ -715,7 +713,7 @@ export const PARAMETERS_DEFINITION = { }), defaultValue: 'no', }, - schema: Joi.string(), + schema: t.string, }, path: { fieldConfig: { @@ -741,7 +739,7 @@ export const PARAMETERS_DEFINITION = { serializer: (value: AliasOption[]) => (value.length === 0 ? '' : value[0].id), } as FieldConfig, targetTypesNotAllowed: ['object', 'nested', 'alias'] as DataType[], - schema: Joi.string(), + schema: t.string, }, position_increment_gap: { fieldConfig: { @@ -771,7 +769,7 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.number(), + schema: t.number, }, index_prefixes: { fieldConfig: { defaultValue: {} }, // Needed for FieldParams typing @@ -791,9 +789,9 @@ export const PARAMETERS_DEFINITION = { } as FieldConfig, }, }, - schema: Joi.object().keys({ - min_chars: Joi.number(), - max_chars: Joi.number(), + schema: t.partial({ + min_chars: t.number, + max_chars: t.number, }), }, similarity: { @@ -804,13 +802,13 @@ export const PARAMETERS_DEFINITION = { defaultMessage: 'Similarity algorithm', }), }, - schema: Joi.string(), + schema: t.string, }, split_queries_on_whitespace: { fieldConfig: { defaultValue: false, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, ignore_above: { fieldConfig: { @@ -842,13 +840,13 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.number(), + schema: t.number, }, enable_position_increments: { fieldConfig: { defaultValue: true, }, - schema: Joi.boolean().strict(), + schema: t.boolean, }, depth_limit: { fieldConfig: { @@ -868,7 +866,7 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.number(), + schema: t.number, }, dims: { fieldConfig: { @@ -894,6 +892,6 @@ export const PARAMETERS_DEFINITION = { }, ], }, - schema: Joi.string(), + schema: t.string, }, }; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/error_reporter.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/error_reporter.ts new file mode 100644 index 0000000000000..e9beee1071597 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/error_reporter.ts @@ -0,0 +1,42 @@ +/* + * 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 { ValidationError } from 'io-ts'; +import { fold } from 'fp-ts/lib/Either'; +import { Reporter } from 'io-ts/lib/Reporter'; + +export type ReporterResult = Array<{ path: string[]; message: string }>; + +const failure = (validation: ValidationError[]): ReporterResult => { + return validation.map(e => { + const path: string[] = []; + let validationName = ''; + + e.context.forEach((ctx, idx) => { + if (ctx.key) { + path.push(ctx.key); + } + + if (idx === e.context.length - 1) { + validationName = ctx.type.name; + } + }); + const lastItemName = path[path.length - 1]; + return { + path, + message: + 'Invalid value ' + + JSON.stringify(e.value) + + ` supplied to ${lastItemName}(${validationName})`, + }; + }); +}; + +const empty: never[] = []; +const success = () => empty; + +export const errorReporter: Reporter = { + report: fold(failure, success), +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts index e9af16af2afa0..bb86c6277ffe5 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts @@ -63,9 +63,9 @@ describe('Mappings configuration validator', () => { expect(errors).not.toBe(undefined); expect(errors!.length).toBe(3); expect(errors!).toEqual([ - { code: 'ERR_CONFIG', configName: 'numeric_detection' }, - { code: 'ERR_CONFIG', configName: 'dynamic_date_formats' }, { code: 'ERR_CONFIG', configName: '_source' }, + { code: 'ERR_CONFIG', configName: 'dynamic_date_formats' }, + { code: 'ERR_CONFIG', configName: 'numeric_detection' }, ]); }); }); @@ -220,7 +220,6 @@ describe('Properties validator', () => { coerce: 1234, coerce_shape: '', ignore_malformed: 0, - null_value: {}, null_value_numeric: 'abc', null_value_boolean: [], copy_to: [], diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts index cd7fc57d1dbc8..fff735da2e758 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts @@ -3,7 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; +import { pick } from 'lodash'; +import * as t from 'io-ts'; +import { ordString } from 'fp-ts/lib/Ord'; +import { toArray } from 'fp-ts/lib/Set'; +import { isLeft, isRight } from 'fp-ts/lib/Either'; +import { errorReporter } from './error_reporter'; import { ALL_DATA_TYPES, PARAMETERS_DEFINITION } from '../constants'; import { FieldMeta } from '../types'; import { getFieldMeta } from '../lib'; @@ -72,7 +77,7 @@ const validateParameter = (parameter: string, value: any): boolean => { const parameterSchema = (PARAMETERS_DEFINITION as any)[parameter]!.schema; if (parameterSchema) { - return Boolean(Joi.validate(value, parameterSchema).error) === false; + return isRight(parameterSchema.decode(value)); } // Fallback, if no schema defined for the parameter (this should not happen in theory) @@ -192,54 +197,55 @@ export const validateProperties = (properties = {}): PropertiesValidatorResponse * Single source of truth to validate the *configuration* of the mappings. * Whenever a user loads a JSON object it will be validate against this Joi schema. */ -export const mappingsConfigurationSchema = Joi.object().keys({ - dynamic: Joi.any().valid([true, false, 'strict']), - date_detection: Joi.boolean().strict(), - numeric_detection: Joi.boolean().strict(), - dynamic_date_formats: Joi.array().items(Joi.string()), - _source: Joi.object().keys({ - enabled: Joi.boolean().strict(), - includes: Joi.array().items(Joi.string()), - excludes: Joi.array().items(Joi.string()), +export const mappingsConfigurationSchema = t.partial({ + dynamic: t.union([t.literal(true), t.literal(false), t.literal('strict')]), + date_detection: t.boolean, + numeric_detection: t.boolean, + dynamic_date_formats: t.array(t.string), + _source: t.partial({ + enabled: t.boolean, + includes: t.array(t.string), + excludes: t.array(t.string), }), - _meta: Joi.object(), - _routing: Joi.object().keys({ - required: Joi.boolean().strict(), + _meta: t.UnknownRecord, + _routing: t.partial({ + required: t.boolean, }), }); +const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.props); + const validateMappingsConfiguration = ( mappingsConfiguration: any ): { value: any; errors: MappingsValidationError[] } => { - // Array to keep track of invalid configuration parameters. - const configurationRemoved: string[] = []; + // Set to keep track of invalid configuration parameters. + const configurationRemoved: Set = new Set(); - const { value: parsedConfiguration, error: configurationError } = Joi.validate( - mappingsConfiguration, - mappingsConfigurationSchema, - { - stripUnknown: true, - abortEarly: false, - } - ); + let copyOfMappingsConfig = { ...mappingsConfiguration }; + const result = mappingsConfigurationSchema.decode(mappingsConfiguration); - if (configurationError) { + if (isLeft(result)) { /** * To keep the logic simple we will strip out the parameters that contain errors */ - configurationError.details.forEach(error => { + const errors = errorReporter.report(result); + errors.forEach(error => { const configurationName = error.path[0]; - configurationRemoved.push(configurationName); - delete parsedConfiguration[configurationName]; + configurationRemoved.add(configurationName); + delete copyOfMappingsConfig[configurationName]; }); } - const errors: MappingsValidationError[] = configurationRemoved.map(configName => ({ - code: 'ERR_CONFIG', - configName, - })); + copyOfMappingsConfig = pick(copyOfMappingsConfig, mappingsConfigurationSchemaKeys); + + const errors: MappingsValidationError[] = toArray(ordString)(configurationRemoved) + .map(configName => ({ + code: 'ERR_CONFIG', + configName, + })) + .sort((a, b) => a.configName.localeCompare(b.configName)) as MappingsValidationError[]; - return { value: parsedConfiguration, errors }; + return { value: copyOfMappingsConfig, errors }; }; export const validateMappings = (mappings: any = {}): MappingsValidatorResponse => { diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_editor.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_editor.tsx index d1fee4c0af745..e3fdf42d889e9 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_editor.tsx @@ -68,7 +68,7 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting return; } } else if (selectedTab === 'templates') { - const { isValid: isTemplatesFormValid } = await state.templates.form!.submit(); + const { isValid: isTemplatesFormValid } = await state.templates.submitForm!(); if (!isTemplatesFormValid) { return; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_state.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_state.tsx index 54cdea9ff8a42..65a1aa2858d14 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_state.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/mappings_state.tsx @@ -149,8 +149,11 @@ export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: P : Promise.resolve(true); const templatesFormValidator = - state.templates.form !== undefined - ? (await state.templates.form!.submit()).isValid + state.templates.submitForm !== undefined + ? new Promise(async resolve => { + const { isValid } = await state.templates.submitForm!(); + resolve(isValid); + }) : Promise.resolve(true); const promisesToValidate = [configurationFormValidator, templatesFormValidator]; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts index e843f4e841631..26d5b8e1edfa5 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts @@ -58,6 +58,11 @@ interface ConfigurationFormState extends OnFormUpdateArg submitForm?: FormHook['submit']; } +interface TemplatesFormState extends OnFormUpdateArg { + defaultValue: MappingsTemplates; + submitForm?: FormHook['submit']; +} + export interface State { isValid: boolean | undefined; configuration: ConfigurationFormState; @@ -72,12 +77,7 @@ export interface State { term: string; result: SearchResult[]; }; - templates: { - defaultValue: { - dynamic_templates: MappingsTemplates['dynamic_templates']; - }; - form?: FormHook; - } & OnFormUpdateArg; + templates: TemplatesFormState; } export type Action = diff --git a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx index 4c215835ca240..dc6eabb325d16 100644 --- a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx +++ b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx @@ -12,7 +12,7 @@ import { } from '@elastic/eui'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; import { composeStateUpdaters } from '../../utils/typed_react'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; autoFocus?: boolean; 'aria-label'?: string; diff --git a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx index 0c29b1f51b07e..79b18f5888bd5 100644 --- a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx @@ -8,14 +8,14 @@ import { EuiIcon } from '@elastic/eui'; import { transparentize } from 'polished'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; interface Props { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; } export const SuggestionItem: React.FC = props => { diff --git a/x-pack/legacy/plugins/infra/public/components/loading/__examples__/index.stories.tsx b/x-pack/legacy/plugins/infra/public/components/loading/__examples__/index.stories.tsx new file mode 100644 index 0000000000000..04aa1a344d1a2 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/loading/__examples__/index.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { InfraLoadingPanel } from '..'; + +storiesOf('infra/InfraLoadingPanel', module).add('example', () => ( + +)); diff --git a/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx index ba072b754b957..c92e2ecec9261 100644 --- a/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/with_kuery_autocompletion.tsx @@ -6,17 +6,14 @@ import React from 'react'; import { npStart } from 'ui/new_platform'; -import { AutocompleteSuggestion, IIndexPattern } from 'src/plugins/data/public'; +import { autocomplete, IIndexPattern } from 'src/plugins/data/public'; import { RendererFunction } from '../utils/typed_react'; -const getAutocompleteProvider = (language: string) => - npStart.plugins.data.autocomplete.getProvider(language); - interface WithKueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; }>; indexPattern: IIndexPattern; } @@ -28,7 +25,7 @@ interface WithKueryAutocompletionLifecycleState { expression: string; cursorPosition: number; } | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; } export class WithKueryAutocompletion extends React.Component< @@ -56,21 +53,13 @@ export class WithKueryAutocompletion extends React.Component< maxSuggestions?: number ) => { const { indexPattern } = this.props; - const autocompletionProvider = getAutocompleteProvider('kuery'); - const config = { - get: () => true, - }; + const language = 'kuery'; + const hasQuerySuggestions = npStart.plugins.data.autocomplete.hasQuerySuggestions(language); - if (!autocompletionProvider) { + if (!hasQuerySuggestions) { return; } - const getSuggestions = autocompletionProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: [], - }); - this.setState({ currentRequest: { expression, @@ -79,11 +68,15 @@ export class WithKueryAutocompletion extends React.Component< suggestions: [], }); - const suggestions = await getSuggestions({ - query: expression, - selectionStart: cursorPosition, - selectionEnd: cursorPosition, - }); + const suggestions = + (await npStart.plugins.data.autocomplete.getQuerySuggestions({ + language, + query: expression, + selectionStart: cursorPosition, + selectionEnd: cursorPosition, + indexPatterns: [indexPattern], + boolFilter: [], + })) || []; this.setState(state => state.currentRequest && diff --git a/x-pack/legacy/plugins/infra/scripts/storybook.js b/x-pack/legacy/plugins/infra/scripts/storybook.js new file mode 100644 index 0000000000000..05d5daedf58f2 --- /dev/null +++ b/x-pack/legacy/plugins/infra/scripts/storybook.js @@ -0,0 +1,13 @@ +/* + * 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 { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'infra', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.stories.tsx')], +}); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js deleted file mode 100644 index a5fb4eff388f7..0000000000000 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js +++ /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 { flatten, mapValues, uniq } from 'lodash'; -import { getSuggestionsProvider as field } from './field'; -import { getSuggestionsProvider as value } from './value'; -import { getSuggestionsProvider as operator } from './operator'; -import { getSuggestionsProvider as conjunction } from './conjunction'; -import { esKuery } from '../../../../../../src/plugins/data/public'; - -const cursorSymbol = '@kuery-cursor@'; - -function dedup(suggestions) { - return uniq(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); -} - -export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { - const getSuggestionsByType = mapValues({ field, value, operator, conjunction }, provider => { - return provider({ config, indexPatterns, boolFilter }); - }); - - return function getSuggestions({ query, selectionStart, selectionEnd, signal }) { - const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr( - selectionEnd - )}`; - - let cursorNode; - try { - cursorNode = esKuery.fromKueryExpression(cursoredQuery, { cursorSymbol, parseCursor: true }); - } catch (e) { - cursorNode = {}; - } - - const { suggestionTypes = [] } = cursorNode; - const suggestionsByType = suggestionTypes.map(type => { - return getSuggestionsByType[type](cursorNode, signal); - }); - return Promise.all(suggestionsByType).then(suggestionsByType => - dedup(flatten(suggestionsByType)) - ); - }; -}; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__fixtures__/index_pattern_response.json similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__fixtures__/index_pattern_response.json diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/conjunction.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/conjunction.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/conjunction.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/conjunction.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/escape_kuery.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/escape_kuery.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/escape_kuery.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/escape_kuery.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/field.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/field.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/operator.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__tests__/operator.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/conjunction.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/conjunction.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/escape_kuery.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/escape_kuery.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/escape_kuery.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/escape_kuery.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.js new file mode 100644 index 0000000000000..b877f9eb852d5 --- /dev/null +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.js @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten, uniq } from 'lodash'; +import { getSuggestionsProvider as field } from './field'; +import { getSuggestionsProvider as value } from './value'; +import { getSuggestionsProvider as operator } from './operator'; +import { getSuggestionsProvider as conjunction } from './conjunction'; +import { esKuery } from '../../../../../../src/plugins/data/public'; + +const cursorSymbol = '@kuery-cursor@'; +const providers = { + field, + value, + operator, + conjunction, +}; + +function dedup(suggestions) { + return uniq(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); +} + +const getProviderByType = (type, args) => providers[type](args); + +export const setupKqlQuerySuggestionProvider = ({ uiSettings }) => ({ + indexPatterns, + boolFilter, + query, + selectionStart, + selectionEnd, + signal, +}) => { + const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr( + selectionEnd + )}`; + + let cursorNode; + try { + cursorNode = esKuery.fromKueryExpression(cursoredQuery, { cursorSymbol, parseCursor: true }); + } catch (e) { + cursorNode = {}; + } + + const { suggestionTypes = [] } = cursorNode; + const suggestionsByType = suggestionTypes.map(type => + getProviderByType(type, { + config: uiSettings, + indexPatterns, + boolFilter, + })(cursorNode, signal) + ); + return Promise.all(suggestionsByType).then(suggestionsByType => + dedup(flatten(suggestionsByType)) + ); +}; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.js similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.js diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.test.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.test.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.js similarity index 71% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.js index f44a3d9d658f3..9d0d70fd95747 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.js @@ -15,7 +15,7 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { indexPatterns.map(indexPattern => { return indexPattern.fields.map(field => ({ ...field, - indexPatternTitle: indexPattern.title, + indexPattern, })); }) ); @@ -27,18 +27,22 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; const fields = allFields.filter(field => field.name === fullFieldName); const query = `${prefix}${suffix}`.trim(); - const { getSuggestions } = npStart.plugins.data; + const { getValueSuggestions } = npStart.plugins.data.autocomplete; - const suggestionsByField = fields.map(field => { - return getSuggestions(field.indexPatternTitle, field, query, boolFilter, signal).then( - data => { - const quotedValues = data.map(value => - typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` - ); - return wrapAsSuggestions(start, end, query, quotedValues); - } - ); - }); + const suggestionsByField = fields.map(field => + getValueSuggestions({ + indexPattern: field.indexPattern, + field, + query, + boolFilter, + signal, + }).then(data => { + const quotedValues = data.map(value => + typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` + ); + return wrapAsSuggestions(start, end, query, quotedValues); + }) + ); return Promise.all(suggestionsByField).then(suggestions => flatten(suggestions)); }; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.js similarity index 55% rename from x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js rename to x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.js index d989fd9046a4d..f5b652d2e2164 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.js @@ -6,25 +6,26 @@ import { getSuggestionsProvider } from './value'; import indexPatternResponse from './__fixtures__/index_pattern_response.json'; - import { npStart } from 'ui/new_platform'; jest.mock('ui/new_platform', () => ({ npStart: { plugins: { data: { - getSuggestions: (_, field) => { - let res; - if (field.type === 'boolean') { - res = [true, false]; - } else if (field.name === 'machine.os') { - res = ['Windo"ws', "Mac'", 'Linux']; - } else if (field.name === 'nestedField.child') { - res = ['foo']; - } else { - res = []; - } - return Promise.resolve(res); + autocomplete: { + getValueSuggestions: jest.fn(({ field }) => { + let res; + if (field.type === 'boolean') { + res = [true, false]; + } else if (field.name === 'machine.os') { + res = ['Windo"ws', "Mac'", 'Linux']; + } else if (field.name === 'nestedField.child') { + res = ['foo']; + } else { + res = []; + } + return Promise.resolve(res); + }), }, }, }, @@ -49,19 +50,24 @@ describe('Kuery value suggestions', function() { const fieldName = 'i_dont_exist'; const prefix = ''; const suffix = ''; - const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); + const suggestions = await getSuggestions({ fieldName, prefix, suffix }); expect(suggestions.map(({ text }) => text)).toEqual([]); - expect(spy).toHaveBeenCalledTimes(0); + + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toHaveBeenCalledTimes(0); }); test('should format suggestions', async () => { - const fieldName = 'ssl'; // Has results with quotes in mock - const prefix = ''; - const suffix = ''; const start = 1; const end = 5; - const suggestions = await getSuggestions({ fieldName, prefix, suffix, start, end }); + const suggestions = await getSuggestions({ + fieldName: 'ssl', + prefix: '', + suffix: '', + start, + end, + }); + expect(suggestions[0].type).toEqual('value'); expect(suggestions[0].start).toEqual(start); expect(suggestions[0].end).toEqual(end); @@ -80,64 +86,60 @@ describe('Kuery value suggestions', function() { describe('Boolean suggestions', function() { test('should stringify boolean fields', async () => { - const fieldName = 'ssl'; - const prefix = ''; - const suffix = ''; - const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ fieldName: 'ssl', prefix: '', suffix: '' }); + expect(suggestions.map(({ text }) => text)).toEqual(['true ', 'false ']); - expect(spy).toHaveBeenCalledTimes(1); + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toHaveBeenCalledTimes(1); }); test('should filter out boolean suggestions', async () => { - const fieldName = 'ssl'; // Has results with quotes in mock - const prefix = 'fa'; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ fieldName: 'ssl', prefix: 'fa', suffix: '' }); + expect(suggestions.length).toEqual(1); }); }); describe('String suggestions', function() { test('should merge prefix and suffix', async () => { - const fieldName = 'machine.os.raw'; const prefix = 'he'; const suffix = 'llo'; - const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); - await getSuggestions({ fieldName, prefix, suffix }); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toBeCalledWith( - expect.any(String), - expect.any(Object), - prefix + suffix, - undefined, - undefined + + await getSuggestions({ fieldName: 'machine.os.raw', prefix, suffix }); + + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toHaveBeenCalledTimes(1); + expect(npStart.plugins.data.autocomplete.getValueSuggestions).toBeCalledWith( + expect.objectContaining({ + field: expect.any(Object), + query: prefix + suffix, + }) ); }); test('should escape quotes in suggestions', async () => { - const fieldName = 'machine.os'; // Has results with quotes in mock - const prefix = ''; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ fieldName: 'machine.os', prefix: '', suffix: '' }); + expect(suggestions[0].text).toEqual('"Windo\\"ws" '); expect(suggestions[1].text).toEqual('"Mac\'" '); expect(suggestions[2].text).toEqual('"Linux" '); }); test('should filter out string suggestions', async () => { - const fieldName = 'machine.os'; // Has results with quotes in mock - const prefix = 'banana'; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ + fieldName: 'machine.os', + prefix: 'banana', + suffix: '', + }); + expect(suggestions.length).toEqual(0); }); test('should partially filter out string suggestions - case insensitive', async () => { - const fieldName = 'machine.os'; // Has results with quotes in mock - const prefix = 'ma'; - const suffix = ''; - const suggestions = await getSuggestions({ fieldName, prefix, suffix }); + const suggestions = await getSuggestions({ + fieldName: 'machine.os', + prefix: 'ma', + suffix: '', + }); + expect(suggestions.length).toEqual(1); }); }); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts index ded66a7c6e8f0..216e0f49ccd34 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core import { Plugin as DataPublicPlugin } from '../../../../../src/plugins/data/public'; // @ts-ignore -import { kueryProvider } from './autocomplete_providers'; +import { setupKqlQuerySuggestionProvider } from './kql_query_suggestion'; /** @internal */ export interface KueryAutocompletePluginSetupDependencies { @@ -25,8 +25,10 @@ export class KueryAutocompletePlugin implements Plugin, void> { this.initializerContext = initializerContext; } - public async setup(core: CoreSetup, { data }: KueryAutocompletePluginSetupDependencies) { - data.autocomplete.addProvider(KUERY_LANGUAGE_NAME, kueryProvider); + public async setup(core: CoreSetup, plugins: KueryAutocompletePluginSetupDependencies) { + const kueryProvider = setupKqlQuerySuggestionProvider(core, plugins); + + plugins.data.autocomplete.addQuerySuggestionProvider(KUERY_LANGUAGE_NAME, kueryProvider); } public start(core: CoreStart) { diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 7465de2dba7f1..f7d9ae5741afb 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -103,13 +103,13 @@ export class AppPlugin { }) ); const updateUrlTime = (urlVars: Record): void => { - const decoded: RisonObject = rison.decode(urlVars._g) as RisonObject; - if (!decoded) { + const decoded = rison.decode(urlVars._g); + if (!isRisonObject(decoded)) { return; } // @ts-ignore decoded.time = data.query.timefilter.timefilter.getTime(); - urlVars._g = rison.encode((decoded as unknown) as RisonObject); + urlVars._g = rison.encode(decoded); }; const redirectTo = ( routeProps: RouteComponentProps<{ id?: string }>, diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index d28f483c9b987..d38a23560fa9f 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -16,8 +16,7 @@ import { APP_ID, APP_ICON, createMapPath, MAP_SAVED_OBJECT_TYPE } from './common export function maps(kibana) { return new kibana.Plugin({ - // task_manager could be required, but is only used for telemetry - require: ['kibana', 'elasticsearch', 'xpack_main', 'tile_map'], + require: ['kibana', 'elasticsearch'], id: APP_ID, configPrefix: 'xpack.maps', publicDir: resolve(__dirname, 'public'), diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js index b9346516eeea8..ec3a588d3627f 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js @@ -23,7 +23,6 @@ import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; import { getInitialLayers } from '../angular/get_initial_layers'; import { mergeInputWithSavedMap } from './merge_input_with_saved_map'; import '../angular/services/gis_map_saved_object_loader'; -import 'ui/vis/map/service_settings'; export class MapEmbeddableFactory extends EmbeddableFactory { type = MAP_SAVED_OBJECT_TYPE; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js index fd3ae8f0ab7e3..967a3c41aec26 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js @@ -15,7 +15,7 @@ import { FIELD_ORIGIN, } from '../../../common/constants'; -const AGG_DELIMITER = '_of_'; +export const AGG_DELIMITER = '_of_'; export class AbstractESAggSource extends AbstractESSource { static METRIC_SCHEMA_CONFIG = { @@ -53,33 +53,12 @@ export class AbstractESAggSource extends AbstractESSource { : []; } - createField({ fieldName, label }) { - //if there is a corresponding field with a custom label, use that one. - if (!label) { - const matchField = this._metricFields.find(field => field.getName() === fieldName); - if (matchField) { - label = matchField.getLabel(); - } - } + getFieldByName(name) { + return this.getMetricFieldForName(name); + } - if (fieldName === COUNT_PROP_NAME) { - return new ESAggMetricField({ - aggType: COUNT_AGG_TYPE, - label: label, - source: this, - origin: this.getOriginForField(), - }); - } - //this only works because aggType is a fixed set and does not include the `_of_` string - const [aggType, docField] = fieldName.split(AGG_DELIMITER); - const esDocField = new ESDocField({ fieldName: docField, source: this }); - return new ESAggMetricField({ - label: label, - esDocField, - aggType, - source: this, - origin: this.getOriginForField(), - }); + createField() { + throw new Error('Cannot create a new field from just a fieldname for an es_agg_source.'); } hasMatchingMetricField(fieldName) { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js index 484d9853b88cd..bf679e766470c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js @@ -39,7 +39,7 @@ const requestTypeOptions = [ }, { label: i18n.translate('xpack.maps.source.esGeoGrid.pointsDropdownOption', { - defaultMessage: 'points', + defaultMessage: 'clusters', }), value: RENDER_AS.POINT, }, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index 6e8b2e1e7c6f5..443668984b0b4 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -9,9 +9,14 @@ import _ from 'lodash'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggConfigs } from 'ui/agg_types'; import { i18n } from '@kbn/i18n'; -import { DEFAULT_MAX_BUCKETS_LIMIT, FIELD_ORIGIN, METRIC_TYPE } from '../../../common/constants'; +import { + COUNT_PROP_LABEL, + DEFAULT_MAX_BUCKETS_LIMIT, + FIELD_ORIGIN, + METRIC_TYPE, +} from '../../../common/constants'; import { ESDocField } from '../fields/es_doc_field'; -import { AbstractESAggSource } from './es_agg_source'; +import { AbstractESAggSource, AGG_DELIMITER } from './es_agg_source'; const TERMS_AGG_NAME = 'join'; @@ -85,14 +90,15 @@ export class ESTermSource extends AbstractESAggSource { } formatMetricKey(aggType, fieldName) { - const metricKey = aggType !== METRIC_TYPE.COUNT ? `${aggType}_of_${fieldName}` : aggType; + const metricKey = + aggType !== METRIC_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : aggType; return `${FIELD_NAME_PREFIX}${metricKey}${GROUP_BY_DELIMITER}${ this._descriptor.indexPatternTitle }.${this._termField.getName()}`; } formatMetricLabel(type, fieldName) { - const metricLabel = type !== METRIC_TYPE.COUNT ? `${type} ${fieldName}` : 'count'; + const metricLabel = type !== METRIC_TYPE.COUNT ? `${type} ${fieldName}` : COUNT_PROP_LABEL; return `${metricLabel} of ${this._descriptor.indexPatternTitle}:${this._termField.getName()}`; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js index b9d8ae86c5850..3952aacf03b33 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js @@ -50,10 +50,26 @@ export class AbstractVectorSource extends AbstractSource { ); } + /** + * factory function creating a new field-instance + * @param fieldName + * @param label + * @returns {ESAggMetricField} + */ createField() { throw new Error(`Should implemement ${this.constructor.type} ${this}`); } + /** + * Retrieves a field. This may be an existing instance. + * @param fieldName + * @param label + * @returns {ESAggMetricField} + */ + getFieldByName(name) { + return this.createField({ fieldName: name }); + } + _createDefaultLayerDescriptor(options, mapColors) { return VectorLayer.createDescriptor( { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js index cc840d552e659..df212f23cd894 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js @@ -6,7 +6,7 @@ import React from 'react'; -import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; +import { vislibColorMaps } from 'ui/color_maps'; import { getLegendColors, getColor } from 'ui/vis/map/color_util'; import { ColorGradient } from './components/color_gradient'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; @@ -97,36 +97,6 @@ const COLOR_PALETTES_CONFIGS = [ id: 'palette_0', colors: DEFAULT_FILL_COLORS.slice(0, COLOR_PALETTE_MAX_SIZE), }, - { - id: 'palette_1', - colors: [ - '#a6cee3', - '#1f78b4', - '#b2df8a', - '#33a02c', - '#fb9a99', - '#e31a1c', - '#fdbf6f', - '#ff7f00', - '#cab2d6', - '#6a3d9a', - ], - }, - { - id: 'palette_2', - colors: [ - '#8dd3c7', - '#ffffb3', - '#bebada', - '#fb8072', - '#80b1d3', - '#fdb462', - '#b3de69', - '#fccde5', - '#d9d9d9', - '#bc80bd', - ], - }, ]; export function getColorPalette(paletteId) { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js index 42e88220bd1d9..e80ccb9e144b9 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js @@ -227,7 +227,7 @@ export class DynamicColorProperty extends DynamicStyleProperty { } mbStops.push(defaultColor); //last color is default color - return ['match', ['get', this._options.field.name], ...mbStops]; + return ['match', ['to-string', ['get', this._options.field.name]], ...mbStops]; } _getMbOrdinalColorStops() { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 30d1c5726ba48..ec0afb6a6a37e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -579,9 +579,7 @@ export class VectorStyle extends AbstractStyle { //fieldDescriptor.label is ignored. This is essentially cruft duplicating label-info from the metric-selection //Ignore this custom label if (fieldDescriptor.origin === FIELD_ORIGIN.SOURCE) { - return this._source.createField({ - fieldName: fieldDescriptor.name, - }); + return this._source.getFieldByName(fieldDescriptor.name); } else if (fieldDescriptor.origin === FIELD_ORIGIN.JOIN) { const join = this._layer.getValidJoins().find(join => { return join.getRightJoinSource().hasMatchingMetricField(fieldDescriptor.name); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js index c250d83720580..b8f5c74139a63 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js @@ -30,6 +30,9 @@ class MockSource { getSupportedShapeTypes() { return this._supportedShapeTypes; } + getFieldByName(fieldName) { + return new MockField({ fieldName }); + } createField({ fieldName }) { return new MockField({ fieldName }); } diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap index 48cf53cf1ac01..3b93213da4033 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap @@ -9,6 +9,7 @@ exports[`AnnotationsTable Initialization with annotations prop. 1`] = ` Object { "field": "annotation", "name": "Annotation", + "scope": "row", "sortable": true, "width": "50%", }, diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index 6c4e8925f369f..3329bf1aab64a 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -323,6 +323,7 @@ const AnnotationsTable = injectI18n( }), sortable: true, width: '50%', + scope: 'row', }, { field: 'timestamp', diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.test.js b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.test.js index b40808d51dd66..206b9e01bab8c 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.test.js @@ -63,9 +63,6 @@ describe('AnomaliesTable', () => { expect(columns).toEqual( expect.arrayContaining([ - expect.objectContaining({ - name: '', - }), expect.objectContaining({ name: 'time', }), diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js index 5454911673fe2..23a40d9ecf295 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonIcon, EuiLink } from '@elastic/eui'; +import { EuiButtonIcon, EuiLink, EuiScreenReaderOnly } from '@elastic/eui'; import React from 'react'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { formatHumanReadableDate, @@ -65,7 +66,16 @@ export function getColumns( ) { const columns = [ { - name: '', + name: ( + +

+ +

+
+ ), render: item => ( toggleRow(item)} @@ -91,6 +101,7 @@ export function getColumns( defaultMessage: 'time', }), dataType: 'date', + scope: 'row', render: date => renderTime(date, interval), textOnly: true, sortable: true, diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js index 8cbee27bdd9a8..074a584f3a136 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -479,7 +479,6 @@ export const LinksMenu = injectI18n( return ( npStart.plugins.data.autocomplete.getProvider(language); - -export async function getSuggestions(query, selectionStart, indexPattern, boolFilter) { - const autocompleteProvider = getAutocompleteProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; - - const getAutocompleteSuggestions = autocompleteProvider({ - config, +export function getSuggestions(query, selectionStart, indexPattern, boolFilter) { + return npStart.plugins.data.autocomplete.getQuerySuggestions({ + language: 'kuery', indexPatterns: [indexPattern], boolFilter, - }); - return getAutocompleteSuggestions({ query, selectionStart, selectionEnd: selectionStart, diff --git a/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts b/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts index ee0540f6d5825..b85fb634891e5 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts +++ b/x-pack/legacy/plugins/ml/public/application/components/ml_in_memory_table/types.ts @@ -28,6 +28,7 @@ export interface FieldDataColumnType { render?: RenderFunc; footer?: string | ReactElement | FooterFunc; textOnly?: boolean; + scope?: 'col' | 'row' | 'colgroup' | 'rowgroup'; 'data-test-subj'?: string; } diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx index ac83d598f2382..774fe2d742834 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx @@ -81,6 +81,8 @@ export const Tabs: FC = ({ tabId, mainTabId, disableLinks }) => { const tabs = getTabs(mainTabId, disableLinks); + if (tabs.length === 0) return null; + return ( {tabs.map((tab: Tab) => { diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx index ca6146f3e23b5..eb068f40716bc 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx @@ -23,12 +23,14 @@ interface Duration { function getRecentlyUsedRangesFactory(timeHistory: TimeHistory) { return function(): Duration[] { - return timeHistory.get().map(({ from, to }: TimeRange) => { - return { - start: from, - end: to, - }; - }); + return ( + timeHistory.get()?.map(({ from, to }: TimeRange) => { + return { + start: from, + end: to, + }; + }) ?? [] + ); }; } @@ -54,9 +56,18 @@ export const TopNav: FC = () => { useEffect(() => { const subscriptions = new Subscription(); - subscriptions.add(timefilter.getRefreshIntervalUpdate$().subscribe(timefilterUpdateListener)); - subscriptions.add(timefilter.getTimeUpdate$().subscribe(timefilterUpdateListener)); - subscriptions.add(timefilter.getEnabledUpdated$().subscribe(timefilterUpdateListener)); + const refreshIntervalUpdate$ = timefilter.getRefreshIntervalUpdate$(); + if (refreshIntervalUpdate$ !== undefined) { + subscriptions.add(refreshIntervalUpdate$.subscribe(timefilterUpdateListener)); + } + const timeUpdate$ = timefilter.getTimeUpdate$(); + if (timeUpdate$ !== undefined) { + subscriptions.add(timeUpdate$.subscribe(timefilterUpdateListener)); + } + const enabledUpdated$ = timefilter.getEnabledUpdated$(); + if (enabledUpdated$ !== undefined) { + subscriptions.add(enabledUpdated$.subscribe(timefilterUpdateListener)); + } return function cleanup() { subscriptions.unsubscribe(); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/fields.ts index e7d5afeba371f..e309013e585ad 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -244,7 +244,7 @@ export function getFlattenedFields(obj: EsDocSource, resultsField: string): EsFi export const getDefaultFieldsFromJobCaps = ( fields: Field[], jobConfig: DataFrameAnalyticsConfig -): { selectedFields: Field[]; docFields: Field[] } => { +): { selectedFields: Field[]; docFields: Field[]; depVarType?: ES_FIELD_TYPES } => { const fieldsObj = { selectedFields: [], docFields: [] }; if (fields.length === 0) { return fieldsObj; @@ -282,6 +282,7 @@ export const getDefaultFieldsFromJobCaps = ( return { selectedFields, docFields: allFields, + depVarType: type, }; }; @@ -388,13 +389,40 @@ export const toggleSelectedFieldSimple = ( } return selectedFields; }; - -export const toggleSelectedField = (selectedFields: Field[], column: EsFieldName): Field[] => { +// Fields starting with 'ml' or custom result name not included in newJobCapsService fields so +// need to recreate the field with correct type and add to selected fields +export const toggleSelectedField = ( + selectedFields: Field[], + column: EsFieldName, + resultsField: string, + depVarType?: ES_FIELD_TYPES +): Field[] => { const index = selectedFields.map(field => field.name).indexOf(column); if (index === -1) { const columnField = newJobCapsService.getFieldById(column); if (columnField !== null) { selectedFields.push(columnField); + } else { + const resultFieldPattern = `^${resultsField}\.`; + const regex = new RegExp(resultFieldPattern); + const isResultField = column.match(regex) !== null; + let newField; + + if (isResultField && column.includes('is_training')) { + newField = { + id: column, + name: column, + type: ES_FIELD_TYPES.BOOLEAN, + }; + } else if (isResultField && depVarType !== undefined) { + newField = { + id: column, + name: column, + type: depVarType, + }; + } + + if (newField) selectedFields.push(newField); } } else { selectedFields.splice(index, 1); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx index 57c8b9ad37c65..2fd7693bcf233 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx @@ -67,6 +67,22 @@ import { ExplorationTitle } from './classification_exploration'; const PAGE_SIZE_OPTIONS = [5, 10, 25, 50]; const MlInMemoryTableBasic = mlInMemoryTableBasicFactory(); + +const showingDocs = i18n.translate( + 'xpack.ml.dataframe.analytics.classificationExploration.documentsShownHelpText', + { + defaultMessage: 'Showing documents for which predictions exist', + } +); + +const showingFirstDocs = i18n.translate( + 'xpack.ml.dataframe.analytics.classificationExploration.firstDocumentsShownHelpText', + { + defaultMessage: 'Showing first {searchSize} documents for which predictions exist', + values: { searchSize: SEARCH_SIZE }, + } +); + interface Props { jobConfig: DataFrameAnalyticsConfig; jobStatus: DATA_FRAME_TASK_STATE; @@ -79,6 +95,7 @@ export const ResultsTable: FC = React.memo( const [pageSize, setPageSize] = useState(25); const [selectedFields, setSelectedFields] = useState([] as Field[]); const [docFields, setDocFields] = useState([] as Field[]); + const [depVarType, setDepVarType] = useState(undefined); const [isColumnsPopoverVisible, setColumnsPopoverVisible] = useState(false); const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); const [searchError, setSearchError] = useState(undefined); @@ -102,7 +119,9 @@ export const ResultsTable: FC = React.memo( function toggleColumn(column: EsFieldName) { if (tableItems.length > 0 && jobConfig !== undefined) { // spread to a new array otherwise the component wouldn't re-render - setSelectedFields([...toggleSelectedField(selectedFields, column)]); + setSelectedFields([ + ...toggleSelectedField(selectedFields, column, jobConfig.dest.results_field, depVarType), + ]); } } @@ -113,7 +132,7 @@ export const ResultsTable: FC = React.memo( sortDirection, status, tableItems, - } = useExploreData(jobConfig, selectedFields, setSelectedFields, setDocFields); + } = useExploreData(jobConfig, selectedFields, setSelectedFields, setDocFields, setDepVarType); const columns: Array> = selectedFields.map(field => { const { type } = field; @@ -465,19 +484,11 @@ export const ResultsTable: FC = React.memo( )} {(columns.length > 0 || searchQuery !== defaultSearchQuery) && ( - {tableItems.length === SEARCH_SIZE && ( - - - - )} + + + >, - setDocFields: React.Dispatch> + setDocFields: React.Dispatch>, + setDepVarType: React.Dispatch> ): UseExploreDataReturnType => { const [errorMessage, setErrorMessage] = useState(''); const [status, setStatus] = useState(INDEX_STATUS.UNUSED); @@ -58,11 +66,13 @@ export const useExploreData = ( const { fields } = newJobCapsService; if (selectedFields.length === 0 && jobConfig !== undefined) { - const { selectedFields: defaultSelected, docFields } = getDefaultFieldsFromJobCaps( - fields, - jobConfig - ); + const { + selectedFields: defaultSelected, + docFields, + depVarType, + } = getDefaultFieldsFromJobCaps(fields, jobConfig); + setDepVarType(depVarType); setSelectedFields(defaultSelected); setDocFields(docFields); } @@ -80,8 +90,33 @@ export const useExploreData = ( try { const resultsField = jobConfig.dest.results_field; + const searchQueryClone: ResultsSearchQuery = cloneDeep(searchQuery); + let query: ResultsSearchQuery; + + if (JSON.stringify(searchQuery) === JSON.stringify(defaultSearchQuery)) { + query = { + exists: { + field: resultsField, + }, + }; + } else if (isResultsSearchBoolQuery(searchQueryClone)) { + if (searchQueryClone.bool.must === undefined) { + searchQueryClone.bool.must = []; + } + + searchQueryClone.bool.must.push({ + exists: { + field: resultsField, + }, + }); + + query = searchQueryClone; + } else { + query = searchQueryClone; + } + const body: SearchQuery = { - query: searchQuery, + query, }; if (field !== undefined) { diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.test.tsx index 92f438459128e..013ea8ddc78a5 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.test.tsx @@ -7,6 +7,8 @@ import { shallow } from 'enzyme'; import React from 'react'; import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common'; +import { KibanaContext } from '../../../../../contexts/kibana'; +import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__/kibana_context_value'; jest.mock('../../../../../contexts/ui/use_ui_chrome_context'); jest.mock('ui/new_platform'); @@ -22,7 +24,9 @@ jest.mock('react', () => { describe('Data Frame Analytics: ', () => { test('Minimal initialization', () => { const wrapper = shallow( - + + + ); // Without the jobConfig being loaded, the component will just return empty. expect(wrapper.text()).toMatch(''); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx index 9691a0706121c..098f8f07bee44 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx @@ -55,6 +55,7 @@ import { SEARCH_SIZE, defaultSearchQuery, } from '../../../../common'; +import { isKeywordAndTextType } from '../../../../common/fields'; import { getOutlierScoreFieldName } from './common'; import { useExploreData, TableItem } from './use_explore_data'; @@ -64,6 +65,10 @@ import { } from '../../../analytics_management/components/analytics_list/common'; import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns'; import { SavedSearchQuery } from '../../../../../contexts/kibana'; +import { getIndexPatternIdFromName } from '../../../../../util/index_utils'; +import { IIndexPattern } from '../../../../../../../../../../../src/plugins/data/common/index_patterns'; +import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; +import { useKibanaContext } from '../../../../../contexts/kibana'; const FEATURE_INFLUENCE = 'feature_influence'; @@ -110,6 +115,19 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { const [searchError, setSearchError] = useState(undefined); const [searchString, setSearchString] = useState(undefined); + const kibanaContext = useKibanaContext(); + + const initializeJobCapsService = async () => { + if (jobConfig !== undefined) { + const sourceIndex = jobConfig.source.index[0]; + const indexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; + const indexPattern: IIndexPattern = await kibanaContext.indexPatterns.get(indexPatternId); + if (indexPattern !== undefined) { + await newJobCapsService.initializeFromIndexPattern(indexPattern, false, false); + } + } + }; + useEffect(() => { (async function() { const analyticsConfigs: GetDataFrameAnalyticsResponse = await ml.dataFrameAnalytics.getDataFrameAnalytics( @@ -124,6 +142,10 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { })(); }, []); + useEffect(() => { + initializeJobCapsService(); + }, [jobConfig && jobConfig.id]); + const [selectedFields, setSelectedFields] = useState([] as EsFieldName[]); const [isColumnsPopoverVisible, setColumnsPopoverVisible] = useState(false); @@ -293,10 +315,16 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { if (jobConfig !== undefined) { const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); const outlierScoreFieldSelected = selectedFields.includes(outlierScoreFieldName); + let requiresKeyword = false; const field = outlierScoreFieldSelected ? outlierScoreFieldName : selectedFields[0]; const direction = outlierScoreFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC; - loadExploreData({ field, direction, searchQuery }); + + if (outlierScoreFieldSelected === false) { + requiresKeyword = isKeywordAndTextType(field); + } + + loadExploreData({ field, direction, searchQuery, requiresKeyword }); } }, [JSON.stringify(searchQuery)]); @@ -307,10 +335,16 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { if (jobConfig !== undefined && columns.length > 0 && !selectedFields.includes(sortField)) { const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); const outlierScoreFieldSelected = selectedFields.includes(outlierScoreFieldName); + let requiresKeyword = false; const field = outlierScoreFieldSelected ? outlierScoreFieldName : selectedFields[0]; const direction = outlierScoreFieldSelected ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC; - loadExploreData({ field, direction, searchQuery }); + + if (outlierScoreFieldSelected === false) { + requiresKeyword = isKeywordAndTextType(field); + } + + loadExploreData({ field, direction, searchQuery, requiresKeyword }); return; } }, [jobConfig, columns.length, sortField, sortDirection, tableItems.length]); @@ -334,8 +368,17 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { setPageIndex(index); setPageSize(size); - if (sort.field !== sortField || sort.direction !== sortDirection) { - loadExploreData({ ...sort, searchQuery }); + if ( + (sort.field !== sortField || sort.direction !== sortDirection) && + jobConfig !== undefined + ) { + const outlierScoreFieldName = getOutlierScoreFieldName(jobConfig); + let requiresKeyword = false; + + if (outlierScoreFieldName !== sort.field) { + requiresKeyword = isKeywordAndTextType(sort.field); + } + loadExploreData({ ...sort, searchQuery, requiresKeyword }); } }; } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts index e76cbaa463f1d..24cc8d000de7e 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts @@ -23,18 +23,12 @@ import { defaultSearchQuery, SearchQuery, } from '../../../../common'; +import { LoadExploreDataArg } from '../../../../common/analytics'; import { getOutlierScoreFieldName } from './common'; -import { SavedSearchQuery } from '../../../../../contexts/kibana'; export type TableItem = Record; -interface LoadExploreDataArg { - field: string; - direction: SortDirection; - searchQuery: SavedSearchQuery; -} - export interface UseExploreDataReturnType { errorMessage: string; loadExploreData: (arg: LoadExploreDataArg) => void; @@ -55,7 +49,12 @@ export const useExploreData = ( const [sortField, setSortField] = useState(''); const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); - const loadExploreData = async ({ field, direction, searchQuery }: LoadExploreDataArg) => { + const loadExploreData = async ({ + field, + direction, + searchQuery, + requiresKeyword, + }: LoadExploreDataArg) => { if (jobConfig !== undefined) { setErrorMessage(''); setStatus(INDEX_STATUS.LOADING); @@ -70,7 +69,7 @@ export const useExploreData = ( if (field !== undefined) { body.sort = [ { - [field]: { + [`${field}${requiresKeyword ? '.keyword' : ''}`]: { order: direction, }, }, diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx index 6c576003f27f8..5e96b1f1bdd08 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx @@ -68,6 +68,21 @@ const PAGE_SIZE_OPTIONS = [5, 10, 25, 50]; const MlInMemoryTableBasic = mlInMemoryTableBasicFactory(); +const showingDocs = i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.documentsShownHelpText', + { + defaultMessage: 'Showing documents for which predictions exist', + } +); + +const showingFirstDocs = i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.firstDocumentsShownHelpText', + { + defaultMessage: 'Showing first {searchSize} documents for which predictions exist', + values: { searchSize: SEARCH_SIZE }, + } +); + interface Props { jobConfig: DataFrameAnalyticsConfig; jobStatus: DATA_FRAME_TASK_STATE; @@ -80,6 +95,7 @@ export const ResultsTable: FC = React.memo( const [pageSize, setPageSize] = useState(25); const [selectedFields, setSelectedFields] = useState([] as Field[]); const [docFields, setDocFields] = useState([] as Field[]); + const [depVarType, setDepVarType] = useState(undefined); const [isColumnsPopoverVisible, setColumnsPopoverVisible] = useState(false); const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); const [searchError, setSearchError] = useState(undefined); @@ -103,7 +119,9 @@ export const ResultsTable: FC = React.memo( function toggleColumn(column: EsFieldName) { if (tableItems.length > 0 && jobConfig !== undefined) { // spread to a new array otherwise the component wouldn't re-render - setSelectedFields([...toggleSelectedField(selectedFields, column)]); + setSelectedFields([ + ...toggleSelectedField(selectedFields, column, jobConfig.dest.results_field, depVarType), + ]); } } @@ -114,7 +132,7 @@ export const ResultsTable: FC = React.memo( sortDirection, status, tableItems, - } = useExploreData(jobConfig, selectedFields, setSelectedFields, setDocFields); + } = useExploreData(jobConfig, selectedFields, setSelectedFields, setDocFields, setDepVarType); const columns: Array> = selectedFields.map(field => { const { type } = field; @@ -465,19 +483,12 @@ export const ResultsTable: FC = React.memo( )} {(columns.length > 0 || searchQuery !== defaultSearchQuery) && ( - {tableItems.length === SEARCH_SIZE && ( - - - - )} + + + + ; @@ -41,7 +48,8 @@ export const useExploreData = ( jobConfig: DataFrameAnalyticsConfig | undefined, selectedFields: Field[], setSelectedFields: React.Dispatch>, - setDocFields: React.Dispatch> + setDocFields: React.Dispatch>, + setDepVarType: React.Dispatch> ): UseExploreDataReturnType => { const [errorMessage, setErrorMessage] = useState(''); const [status, setStatus] = useState(INDEX_STATUS.UNUSED); @@ -53,11 +61,13 @@ export const useExploreData = ( const { fields } = newJobCapsService; if (selectedFields.length === 0 && jobConfig !== undefined) { - const { selectedFields: defaultSelected, docFields } = getDefaultFieldsFromJobCaps( - fields, - jobConfig - ); + const { + selectedFields: defaultSelected, + docFields, + depVarType, + } = getDefaultFieldsFromJobCaps(fields, jobConfig); + setDepVarType(depVarType); setSelectedFields(defaultSelected); setDocFields(docFields); } @@ -75,8 +85,32 @@ export const useExploreData = ( try { const resultsField = jobConfig.dest.results_field; + const searchQueryClone: ResultsSearchQuery = cloneDeep(searchQuery); + let query: ResultsSearchQuery; + + if (JSON.stringify(searchQuery) === JSON.stringify(defaultSearchQuery)) { + query = { + exists: { + field: resultsField, + }, + }; + } else if (isResultsSearchBoolQuery(searchQueryClone)) { + if (searchQueryClone.bool.must === undefined) { + searchQueryClone.bool.must = []; + } + + searchQueryClone.bool.must.push({ + exists: { + field: resultsField, + }, + }); + + query = searchQueryClone; + } else { + query = searchQueryClone; + } const body: SearchQuery = { - query: searchQuery, + query, }; if (field !== undefined) { diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 1f4ad65bd1879..34f281cec57d3 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -6,12 +6,14 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiBadge, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiProgress, + EuiScreenReaderOnly, EuiText, EuiToolTip, RIGHT_ALIGNMENT, @@ -142,6 +144,16 @@ export const getColumns = ( // update possible column types to something like (FieldDataColumn | ComputedColumn | ActionsColumn)[] when they have been added to EUI const columns: any[] = [ { + name: ( + +

+ +

+
+ ), align: RIGHT_ALIGNMENT, width: '40px', isExpander: true, @@ -170,6 +182,7 @@ export const getColumns = ( sortable: true, truncateText: true, 'data-test-subj': 'mlAnalyticsTableColumnId', + scope: 'row', }, { field: DataFrameAnalyticsListColumn.description, diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss index 2702817a55749..48aab16d85be6 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss @@ -17,7 +17,7 @@ border-color: $euiColorVis5; .field-type-icon-container { - background-color: rgba($euiColorVis5, 0.5); + background-color: rgba($euiColorVis5, 0.2); } } @@ -26,7 +26,7 @@ border-color: $euiColorVis7; .field-type-icon-container { - background-color: rgba($euiColorVis7, 0.5); + background-color: rgba($euiColorVis7, 0.2); } } @@ -35,7 +35,7 @@ border-color: $euiColorVis2; .field-type-icon-container { - background-color: rgba($euiColorVis2, 0.5); + background-color: rgba($euiColorVis2, 0.2); } } @@ -44,7 +44,7 @@ border-color: $euiColorVis8; .field-type-icon-container { - background-color: rgba($euiColorVis8, 0.5); + background-color: rgba($euiColorVis8, 0.2); } } @@ -53,7 +53,7 @@ border-color: $euiColorVis3; .field-type-icon-container { - background-color: rgba($euiColorVis3, 0.5); + background-color: rgba($euiColorVis3, 0.2); } } @@ -62,7 +62,7 @@ border-color: $euiColorVis0; .field-type-icon-container { - background-color: rgba($euiColorVis0, 0.5); + background-color: rgba($euiColorVis0, 0.2); } } @@ -71,7 +71,7 @@ border-color: $euiColorVis1; .field-type-icon-container { - background-color: rgba($euiColorVis1, 0.5); + background-color: rgba($euiColorVis1, 0.2); } } @@ -80,7 +80,7 @@ border-color: $euiColorVis9; .field-type-icon-container { - background-color: rgba($euiColorVis9, 0.5); + background-color: rgba($euiColorVis9, 0.2); } } @@ -90,7 +90,7 @@ border-color: $euiColorVis6; .field-type-icon-container { - background-color: rgba($euiColorVis6, 0.5); + background-color: rgba($euiColorVis6, 0.2); } } diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js index 29051a45d719f..5dfae43f223b1 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/fields_stats.js @@ -32,7 +32,7 @@ export class FieldsStats extends Component {
{this.state.fields.map(f => ( - + ))} diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss index d517be0a9358d..6790b947f6f59 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss @@ -9,7 +9,7 @@ border-color: $euiColorVis5; .field-type-icon-container { - background-color: rgba($euiColorVis5, 0.5); + background-color: rgba($euiColorVis5, 0.2); } } @@ -18,7 +18,7 @@ border-color: $euiColorVis7; .field-type-icon-container { - background-color: rgba($euiColorVis7, 0.5); + background-color: rgba($euiColorVis7, 0.2); } } @@ -27,7 +27,7 @@ border-color: $euiColorVis2; .field-type-icon-container { - background-color: rgba($euiColorVis2, 0.5); + background-color: rgba($euiColorVis2, 0.2); } } @@ -36,7 +36,7 @@ border-color: $euiColorVis8; .field-type-icon-container { - background-color: rgba($euiColorVis8, 0.5); + background-color: rgba($euiColorVis8, 0.2); } } @@ -45,7 +45,7 @@ border-color: $euiColorVis3; .field-type-icon-container { - background-color: rgba($euiColorVis3, 0.5); + background-color: rgba($euiColorVis3, 0.2); } } @@ -54,7 +54,7 @@ border-color: $euiColorVis0; .field-type-icon-container { - background-color: rgba($euiColorVis0, 0.5); + background-color: rgba($euiColorVis0, 0.2); } } @@ -63,7 +63,7 @@ border-color: $euiColorVis1; .field-type-icon-container { - background-color: rgba($euiColorVis1, 0.5); + background-color: rgba($euiColorVis1, 0.2); } } @@ -72,7 +72,7 @@ border-color: $euiColorVis9; .field-type-icon-container { - background-color: rgba($euiColorVis9, 0.5); + background-color: rgba($euiColorVis9, 0.2); } } @@ -82,7 +82,7 @@ border-color: $euiColorVis6; .field-type-icon-container { - background-color: rgba($euiColorVis6, 0.5); + background-color: rgba($euiColorVis6, 0.2); } } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js index 1adb1e311dc68..e70198b36e0df 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js @@ -204,6 +204,7 @@ class ForecastsTableUI extends Component { render: date => formatDate(date, TIME_FORMAT), textOnly: true, sortable: true, + scope: 'row', }, { field: 'forecast_start_timestamp', diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js index 818f1b79b3143..b691bc34295c5 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -16,8 +16,8 @@ import { JobDescription } from './job_description'; import { JobIcon } from '../../../../components/job_message_icon'; import { getJobIdUrl } from '../utils'; -import { EuiBadge, EuiBasicTable, EuiButtonIcon, EuiLink } from '@elastic/eui'; -import { injectI18n } from '@kbn/i18n/react'; +import { EuiBadge, EuiBasicTable, EuiButtonIcon, EuiLink, EuiScreenReaderOnly } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; const PAGE_SIZE = 10; @@ -124,7 +124,16 @@ class JobsListUI extends Component { // be updated if we move to always using props for width. const columns = [ { - name: '', + name: ( + +

+ +

+
+ ), render: item => ( this.toggleRow(item)} @@ -163,11 +172,21 @@ class JobsListUI extends Component { sortable: true, truncateText: false, width: '20%', + scope: 'row', render: isManagementTable ? id => this.getJobIdLink(id) : undefined, }, { field: 'auditMessage', - name: '', + name: ( + +

+ +

+
+ ), render: item => , }, { @@ -272,7 +291,16 @@ class JobsListUI extends Component { width: '15%', }); columns.push({ - name: '', + name: ( + +

+ +

+
+ ), actions: actionsMenuContent( this.props.showEditJobFlyout, this.props.showDeleteJobModal, diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts b/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts index 89ed35d5588f2..44111ae32cd30 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/index.ts @@ -10,6 +10,6 @@ export * from './new_job'; export * from './datavisualizer'; export * from './settings'; export * from './data_frame_analytics'; -export * from './timeseriesexplorer'; +export { timeSeriesExplorerRoute } from './timeseriesexplorer'; export * from './explorer'; export * from './access_denied'; diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx new file mode 100644 index 0000000000000..6917ec718d3a8 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx @@ -0,0 +1,34 @@ +/* + * 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 { MemoryRouter } from 'react-router-dom'; +import { render } from '@testing-library/react'; + +import { I18nProvider } from '@kbn/i18n/react'; + +import { TimeSeriesExplorerUrlStateManager } from './timeseriesexplorer'; + +jest.mock('ui/new_platform'); + +describe('TimeSeriesExplorerUrlStateManager', () => { + test('Initial render shows "No single metric jobs found"', () => { + const props = { + config: { get: () => 'Browser' }, + jobsWithTimeRange: [], + }; + + const { container } = render( + + + + + + ); + + expect(container.textContent).toContain('No single metric jobs found'); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index cbf54a70ea74f..c3c644d43fa59 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -74,7 +74,7 @@ interface TimeSeriesExplorerUrlStateManager { jobsWithTimeRange: MlJobWithTimeRange[]; } -const TimeSeriesExplorerUrlStateManager: FC = ({ +export const TimeSeriesExplorerUrlStateManager: FC = ({ config, jobsWithTimeRange, }) => { @@ -102,23 +102,27 @@ const TimeSeriesExplorerUrlStateManager: FC = timefilter.enableAutoRefreshSelector(); }, []); + // We cannot simply infer bounds from the globalState's `time` attribute + // with `moment` since it can contain custom strings such as `now-15m`. + // So when globalState's `time` changes, we update the timefilter and use + // `timefilter.getBounds()` to update `bounds` in this component's state. + const [bounds, setBounds] = useState(undefined); useEffect(() => { if (globalState?.time !== undefined) { timefilter.setTime({ from: globalState.time.from, to: globalState.time.to, }); + + const timefilterBounds = timefilter.getBounds(); + // Only if both min/max bounds are valid moment times set the bounds. + // An invalid string restored from globalState might return `undefined`. + if (timefilterBounds?.min !== undefined && timefilterBounds?.max !== undefined) { + setBounds(timefilter.getBounds()); + } } }, [globalState?.time?.from, globalState?.time?.to]); - let bounds: TimeRangeBounds | undefined; - if (globalState?.time !== undefined) { - bounds = { - min: moment(globalState.time.from), - max: moment(globalState.time.to), - }; - } - const selectedJobIds = globalState?.ml?.jobIds; // Sort selectedJobIds so we can be sure comparison works when stringifying. if (Array.isArray(selectedJobIds)) { @@ -140,14 +144,17 @@ const TimeSeriesExplorerUrlStateManager: FC = }, [JSON.stringify(selectedJobIds)]); // Next we get globalState and appState information to pass it on as props later. - // If a job change is going on, we fall back to defaults (as if appState was already cleard), + // If a job change is going on, we fall back to defaults (as if appState was already cleared), // otherwise the page could break. const selectedDetectorIndex = isJobChange ? 0 : +appState?.mlTimeSeriesExplorer?.detectorIndex || 0; const selectedEntities = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.entities; const selectedForecastId = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.forecastId; - const zoom = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.zoom; + const zoom: { + from: string; + to: string; + } = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.zoom; const selectedJob = selectedJobIds && mlJobService.getJob(selectedJobIds[0]); diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap index a4ad3777f4be5..7b59fb0ea61da 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/__snapshots__/events_table.test.js.snap @@ -11,6 +11,7 @@ exports[`EventsTable Renders events table with no search bar 1`] = ` Object { "field": "description", "name": "Description", + "scope": "row", "sortable": true, "truncateText": true, }, @@ -79,6 +80,7 @@ exports[`EventsTable Renders events table with search bar 1`] = ` Object { "field": "description", "name": "Description", + "scope": "row", "sortable": true, "truncateText": true, }, diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js index d5b4207e283ed..125c75d438af9 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js @@ -63,6 +63,7 @@ export const EventsTable = injectI18n(function EventsTable({ }), sortable: true, truncateText: true, + scope: 'row', }, { field: 'start_time', diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap index 8c518f42e0ec7..ff74c592b2b0f 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap @@ -9,6 +9,7 @@ exports[`CalendarsListTable renders the table with all calendars 1`] = ` "field": "calendar_id", "name": "ID", "render": [Function], + "scope": "row", "sortable": true, "truncateText": true, }, diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js index 45c214aede851..774cc96517cc6 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js @@ -43,6 +43,7 @@ export const CalendarsListTable = injectI18n(function CalendarsListTable({ }), sortable: true, truncateText: true, + scope: 'row', render: id => {id}, }, { diff --git a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap index 0ad66c78b9e2b..8985469a807af 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/__snapshots__/table.test.js.snap @@ -10,6 +10,7 @@ exports[`Filter Lists Table renders with filter lists and selection supplied 1`] "field": "filter_id", "name": "ID", "render": [Function], + "scope": "row", "sortable": true, }, Object { @@ -118,6 +119,7 @@ exports[`Filter Lists Table renders with filter lists supplied 1`] = ` "field": "filter_id", "name": "ID", "render": [Function], + "scope": "row", "sortable": true, }, Object { diff --git a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js index 79b71e65c3d1a..0d1ca66de5775 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/filter_lists/list/table.js @@ -85,6 +85,7 @@ function getColumns() { }), render: id => {id}, sortable: true, + scope: 'row', }, { field: 'description', diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts index 3edbbc1af2323..651c609004236 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts @@ -4,12 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Timefilter } from 'ui/timefilter'; import { FC } from 'react'; +import { Timefilter } from 'ui/timefilter'; + +import { getDateFormatTz, TimeRangeBounds } from '../explorer/explorer_utils'; + declare const TimeSeriesExplorer: FC<{ appStateHandler: (action: string, payload: any) => void; + autoZoomDuration?: number; + bounds?: TimeRangeBounds; dateFormatTz: string; + jobsWithTimeRange: any[]; + lastRefresh: number; selectedJobIds: string[]; selectedDetectorIndex: number; selectedEntities: any[]; @@ -17,5 +24,5 @@ declare const TimeSeriesExplorer: FC<{ setGlobalState: (arg: any) => void; tableInterval: string; tableSeverity: number; - timefilter: Timefilter; + zoom?: { from: string; to: string }; }>; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 016f054430fa3..1862ead045743 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -195,8 +195,10 @@ export class TimeSeriesExplorer extends React.Component { selectedDetectorIndex: PropTypes.number, selectedEntities: PropTypes.object, selectedForecastId: PropTypes.string, + setGlobalState: PropTypes.func.isRequired, tableInterval: PropTypes.string, tableSeverity: PropTypes.number, + zoom: PropTypes.object, }; state = getTimeseriesexplorerDefaultState(); @@ -481,7 +483,7 @@ export class TimeSeriesExplorer extends React.Component { zoom, } = this.props; - if (selectedJobIds === undefined) { + if (selectedJobIds === undefined || bounds === undefined) { return; } diff --git a/x-pack/legacy/plugins/ml/public/application/util/chart_utils.js b/x-pack/legacy/plugins/ml/public/application/util/chart_utils.js index d54b1a9c9ac22..dfa896b3124c6 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/chart_utils.js +++ b/x-pack/legacy/plugins/ml/public/application/util/chart_utils.js @@ -107,7 +107,7 @@ export function drawLineChartDots(data, lineChartGroup, lineChartValuesLine, rad } // this replicates Kibana's filterAxisLabels() behavior -// which can be found in ui/vislib/lib/axis/axis_labels.js +// which can be found in src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js // axis labels which overflow the chart's boundaries will be removed export function filterAxisLabels(selection, chartWidth) { if (selection === undefined || selection.selectAll === undefined) { diff --git a/x-pack/legacy/plugins/ml/public/application/util/url_state.test.ts b/x-pack/legacy/plugins/ml/public/application/util/url_state.test.ts new file mode 100644 index 0000000000000..91bbef2dba6c2 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/util/url_state.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import { getUrlState, useUrlState } from './url_state'; + +const mockHistoryPush = jest.fn(); + +jest.mock('react-router-dom', () => ({ + useHistory: () => ({ + push: mockHistoryPush, + }), + useLocation: () => ({ + search: + "?_a=(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFieldName:action),query:(query_string:(analyze_wildcard:!t,query:'*')))&_g=(ml:(jobIds:!(dec-2)),refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2019-01-01T00:03:40.000Z',mode:absolute,to:'2019-08-30T11:55:07.000Z'))&savedSearchId=571aaf70-4c88-11e8-b3d7-01146121b73d", + }), +})); + +describe('getUrlState', () => { + test('properly decode url with _g and _a', () => { + expect( + getUrlState( + "?_a=(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFieldName:action),query:(query_string:(analyze_wildcard:!t,query:'*')))&_g=(ml:(jobIds:!(dec-2)),refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2019-01-01T00:03:40.000Z',mode:absolute,to:'2019-08-30T11:55:07.000Z'))&savedSearchId=571aaf70-4c88-11e8-b3d7-01146121b73d" + ) + ).toEqual({ + _a: { + mlExplorerFilter: {}, + mlExplorerSwimlane: { + viewByFieldName: 'action', + }, + query: { + query_string: { + analyze_wildcard: true, + query: '*', + }, + }, + }, + _g: { + ml: { + jobIds: ['dec-2'], + }, + refreshInterval: { + display: 'Off', + pause: false, + value: 0, + }, + time: { + from: '2019-01-01T00:03:40.000Z', + mode: 'absolute', + to: '2019-08-30T11:55:07.000Z', + }, + }, + savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + }); + }); +}); + +describe('useUrlState', () => { + beforeEach(() => { + mockHistoryPush.mockClear(); + }); + + test('pushes a properly encoded search string to history', () => { + const { result } = renderHook(() => useUrlState('_a')); + + act(() => { + const [, setUrlState] = result.current; + setUrlState({ + query: {}, + }); + }); + + expect(mockHistoryPush).toHaveBeenCalledWith({ + search: + '_a=%28mlExplorerFilter%3A%28%29%2CmlExplorerSwimlane%3A%28viewByFieldName%3Aaction%29%2Cquery%3A%28%29%29&_g=%28ml%3A%28jobIds%3A%21%28dec-2%29%29%2CrefreshInterval%3A%28display%3AOff%2Cpause%3A%21f%2Cvalue%3A0%29%2Ctime%3A%28from%3A%272019-01-01T00%3A03%3A40.000Z%27%2Cmode%3Aabsolute%2Cto%3A%272019-08-30T11%3A55%3A07.000Z%27%29%29&savedSearchId=%27571aaf70-4c88-11e8-b3d7-01146121b73d%27', + }); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/application/util/url_state.ts b/x-pack/legacy/plugins/ml/public/application/util/url_state.ts index 4402155815a5b..546944b1a33bf 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/url_state.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/url_state.ts @@ -18,13 +18,18 @@ import { getNestedProperty } from './object_utils'; export type SetUrlState = (attribute: string | Dictionary, value?: any) => void; export type UrlState = [Dictionary, SetUrlState]; -function getUrlState(search: string) { +const decodedParams = new Set(['_a', '_g']); +export function getUrlState(search: string): Dictionary { const urlState: Dictionary = {}; const parsedQueryString = queryString.parse(search); try { Object.keys(parsedQueryString).forEach(a => { - urlState[a] = decode(parsedQueryString[a]) as Dictionary; + if (decodedParams.has(a)) { + urlState[a] = decode(parsedQueryString[a]) as Dictionary; + } else { + urlState[a] = parsedQueryString[a]; + } }); } catch (error) { // eslint-disable-next-line no-console diff --git a/x-pack/legacy/plugins/monitoring/common/constants.js b/x-pack/legacy/plugins/monitoring/common/constants.js index 917f7fab1ed30..ff16b0e9c5167 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.js +++ b/x-pack/legacy/plugins/monitoring/common/constants.js @@ -141,12 +141,23 @@ export const CLUSTER_ALERTS_ADDRESS_CONFIG_KEY = 'cluster_alerts.email_notificat export const STANDALONE_CLUSTER_CLUSTER_UUID = '__standalone_cluster__'; -export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*'; -export const INDEX_PATTERN_KIBANA = '.monitoring-kibana-6-*,.monitoring-kibana-7-*'; -export const INDEX_PATTERN_LOGSTASH = '.monitoring-logstash-6-*,.monitoring-logstash-7-*'; -export const INDEX_PATTERN_BEATS = '.monitoring-beats-6-*,.monitoring-beats-7-*'; -export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7'; -export const INDEX_PATTERN_ELASTICSEARCH = '.monitoring-es-6-*,.monitoring-es-7-*'; +const INDEX_PATTERN_NEW = ',monitoring-*-7-*,monitoring-*-8-*'; +const INDEX_PATTERN_KIBANA_NEW = ',monitoring-kibana-7-*,monitoring-kibana-8-*'; +const INDEX_PATTERN_LOGSTASH_NEW = ',monitoring-logstash-7-*,monitoring-logstash-8-*'; +const INDEX_PATTERN_BEATS_NEW = ',monitoring-beats-7-*,monitoring-beats-8-*'; +const INDEX_ALERTS_NEW = ',monitoring-alerts-7,monitoring-alerts-8'; +const INDEX_PATTERN_ELASTICSEARCH_NEW = ',monitoring-es-7-*,monitoring-es-8-*'; + +export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*' + INDEX_PATTERN_NEW; +export const INDEX_PATTERN_KIBANA = + '.monitoring-kibana-6-*,.monitoring-kibana-7-*' + INDEX_PATTERN_KIBANA_NEW; +export const INDEX_PATTERN_LOGSTASH = + '.monitoring-logstash-6-*,.monitoring-logstash-7-*' + INDEX_PATTERN_LOGSTASH_NEW; +export const INDEX_PATTERN_BEATS = + '.monitoring-beats-6-*,.monitoring-beats-7-*' + INDEX_PATTERN_BEATS_NEW; +export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7' + INDEX_ALERTS_NEW; +export const INDEX_PATTERN_ELASTICSEARCH = + '.monitoring-es-6-*,.monitoring-es-7-*' + INDEX_PATTERN_ELASTICSEARCH_NEW; export const INDEX_PATTERN_FILEBEAT = 'filebeat-*'; diff --git a/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js b/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js index f6dafb5bb8c7e..1e5615cc907ba 100644 --- a/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js +++ b/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_cluster_search.js @@ -55,7 +55,7 @@ describe('Alerts Cluster Search', () => { const { mockReq, callWithRequestStub } = createStubs(mockQueryResult, featureStub); return alertsClusterSearch( mockReq, - '.monitoring-alerts', + '.monitoring-alerts,monitoring-alerts', { cluster_uuid: 'cluster-1234' }, checkLicense ).then(alerts => { @@ -68,7 +68,7 @@ describe('Alerts Cluster Search', () => { const { mockReq, callWithRequestStub } = createStubs(mockQueryResult, featureStub); return alertsClusterSearch( mockReq, - '.monitoring-alerts', + '.monitoring-alerts,monitoring-alerts', { cluster_uuid: 'cluster-1234' }, checkLicense, { size: 3 } @@ -89,9 +89,15 @@ describe('Alerts Cluster Search', () => { issue_date: 'fake-issue_date', }, }; - return alertsClusterSearch(mockReq, '.monitoring-alerts', cluster, checkLicense, { - size: 3, - }).then(alerts => { + return alertsClusterSearch( + mockReq, + '.monitoring-alerts,monitoring-alerts', + cluster, + checkLicense, + { + size: 3, + } + ).then(alerts => { expect(alerts).to.have.length(3); expect(alerts[0]).to.eql(mockAlerts[0]); expect(alerts[1]).to.eql({ @@ -122,9 +128,15 @@ describe('Alerts Cluster Search', () => { issue_date: 'fake-issue_date', }, }; - return alertsClusterSearch(mockReq, '.monitoring-alerts', cluster, checkLicense, { - size: 3, - }).then(alerts => { + return alertsClusterSearch( + mockReq, + '.monitoring-alerts,monitoring-alerts', + cluster, + checkLicense, + { + size: 3, + } + ).then(alerts => { expect(alerts).to.have.length(1); expect(alerts[0]).to.eql({ metadata: { @@ -155,7 +167,7 @@ describe('Alerts Cluster Search', () => { const { mockReq, callWithRequestStub } = createStubs({}, featureStub); return alertsClusterSearch( mockReq, - '.monitoring-alerts', + '.monitoring-alerts,monitoring-alerts', { cluster_uuid: 'cluster-1234' }, checkLicense ).then(alerts => { @@ -177,7 +189,7 @@ describe('Alerts Cluster Search', () => { const { mockReq, callWithRequestStub } = createStubs({}, featureStub); return alertsClusterSearch( mockReq, - '.monitoring-alerts', + '.monitoring-alerts,monitoring-alerts', { cluster_uuid: 'cluster-1234' }, checkLicense ).then(alerts => { diff --git a/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js b/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js index efd9a933e1f81..9ee0d512a3c1c 100644 --- a/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js +++ b/x-pack/legacy/plugins/monitoring/server/cluster_alerts/__tests__/alerts_clusters_aggregation.js @@ -73,34 +73,37 @@ describe('Alerts Clusters Aggregation', () => { it('aggregates alert count summary by cluster', () => { const { mockReq } = createStubs(mockQueryResult, featureStub); - return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - result => { - expect(result).to.eql({ - alertsMeta: { enabled: true }, - 'cluster-abc0': undefined, - 'cluster-abc1': { - count: 1, - high: 0, - low: 1, - medium: 0, - }, - 'cluster-abc2': { - count: 2, - high: 0, - low: 0, - medium: 2, - }, - 'cluster-abc3': { - count: 3, - high: 3, - low: 0, - medium: 0, - }, - 'cluster-no-license': undefined, - 'cluster-invalid': undefined, - }); - } - ); + return alertsClustersAggregation( + mockReq, + '.monitoring-alerts,monitoring-alerts', + clusters, + checkLicense + ).then(result => { + expect(result).to.eql({ + alertsMeta: { enabled: true }, + 'cluster-abc0': undefined, + 'cluster-abc1': { + count: 1, + high: 0, + low: 1, + medium: 0, + }, + 'cluster-abc2': { + count: 2, + high: 0, + low: 0, + medium: 2, + }, + 'cluster-abc3': { + count: 3, + high: 3, + low: 0, + medium: 0, + }, + 'cluster-no-license': undefined, + 'cluster-invalid': undefined, + }); + }); }); it('aggregates alert count summary by cluster include static alert', () => { @@ -113,7 +116,7 @@ describe('Alerts Clusters Aggregation', () => { return alertsClustersAggregation( mockReq, - '.monitoring-alerts', + '.monitoring-alerts,monitoring-alerts', newClusters, checkLicense ).then(result => { @@ -163,13 +166,16 @@ describe('Alerts Clusters Aggregation', () => { const checkLicense = () => ({ clusterAlerts: { enabled: true } }); const { mockReq } = createStubs(mockQueryResult, featureStub); - return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - result => { - expect(result).to.eql({ - alertsMeta: { enabled: false, message: 'monitoring cluster license is fail' }, - }); - } - ); + return alertsClustersAggregation( + mockReq, + '.monitoring-alerts,monitoring-alerts', + clusters, + checkLicense + ).then(result => { + expect(result).to.eql({ + alertsMeta: { enabled: false, message: 'monitoring cluster license is fail' }, + }); + }); }); it('returns the input set if disabled because production cluster checks', () => { @@ -181,53 +187,56 @@ describe('Alerts Clusters Aggregation', () => { const checkLicense = () => ({ clusterAlerts: { enabled: false } }); const { mockReq } = createStubs(mockQueryResult, featureStub); - return alertsClustersAggregation(mockReq, '.monitoring-alerts', clusters, checkLicense).then( - result => { - expect(result).to.eql({ - alertsMeta: { enabled: true }, - 'cluster-abc0': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc0-name] license type [test_license] does not support Cluster Alerts', - }, + return alertsClustersAggregation( + mockReq, + '.monitoring-alerts,monitoring-alerts', + clusters, + checkLicense + ).then(result => { + expect(result).to.eql({ + alertsMeta: { enabled: true }, + 'cluster-abc0': { + clusterMeta: { + enabled: false, + message: + 'Cluster [cluster-abc0-name] license type [test_license] does not support Cluster Alerts', }, - 'cluster-abc1': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc1-name] license type [test_license] does not support Cluster Alerts', - }, + }, + 'cluster-abc1': { + clusterMeta: { + enabled: false, + message: + 'Cluster [cluster-abc1-name] license type [test_license] does not support Cluster Alerts', }, - 'cluster-abc2': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc2-name] license type [test_license] does not support Cluster Alerts', - }, + }, + 'cluster-abc2': { + clusterMeta: { + enabled: false, + message: + 'Cluster [cluster-abc2-name] license type [test_license] does not support Cluster Alerts', }, - 'cluster-abc3': { - clusterMeta: { - enabled: false, - message: - 'Cluster [cluster-abc3-name] license type [test_license] does not support Cluster Alerts', - }, + }, + 'cluster-abc3': { + clusterMeta: { + enabled: false, + message: + 'Cluster [cluster-abc3-name] license type [test_license] does not support Cluster Alerts', }, - 'cluster-no-license': { - clusterMeta: { - enabled: false, - message: `Cluster [cluster-no-license-name] license type [undefined] does not support Cluster Alerts`, - }, + }, + 'cluster-no-license': { + clusterMeta: { + enabled: false, + message: `Cluster [cluster-no-license-name] license type [undefined] does not support Cluster Alerts`, }, - 'cluster-invalid': { - clusterMeta: { - enabled: false, - message: `Cluster [cluster-invalid-name] license type [undefined] does not support Cluster Alerts`, - }, + }, + 'cluster-invalid': { + clusterMeta: { + enabled: false, + message: `Cluster [cluster-invalid-name] license type [undefined] does not support Cluster Alerts`, }, - }); - } - ); + }, + }); + }); }); }); }); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/__tests__/ccs_utils.js b/x-pack/legacy/plugins/monitoring/server/lib/__tests__/ccs_utils.js index c92c00720837b..844dfc96bb19b 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/__tests__/ccs_utils.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/__tests__/ccs_utils.js @@ -10,7 +10,8 @@ import { parseCrossClusterPrefix, prefixIndexPattern } from '../ccs_utils'; describe('ccs_utils', () => { describe('prefixIndexPattern', () => { - const indexPattern = '.monitoring-xyz-1-*,.monitoring-xyz-2-*'; + const indexPattern = + '.monitoring-xyz-1-*,.monitoring-xyz-2-*,monitoring-xyz-1-*,monitoring-xyz-2-*'; it('returns the index pattern if ccs is not enabled', () => { const get = sinon.stub(); @@ -53,9 +54,11 @@ describe('ccs_utils', () => { const abcPattern = prefixIndexPattern(config, indexPattern, 'aBc'); const underscorePattern = prefixIndexPattern(config, indexPattern, 'cluster_one'); - expect(abcPattern).to.eql('aBc:.monitoring-xyz-1-*,aBc:.monitoring-xyz-2-*'); + expect(abcPattern).to.eql( + 'aBc:.monitoring-xyz-1-*,aBc:.monitoring-xyz-2-*,aBc:monitoring-xyz-1-*,aBc:monitoring-xyz-2-*' + ); expect(underscorePattern).to.eql( - 'cluster_one:.monitoring-xyz-1-*,cluster_one:.monitoring-xyz-2-*' + 'cluster_one:.monitoring-xyz-1-*,cluster_one:.monitoring-xyz-2-*,cluster_one:monitoring-xyz-1-*,cluster_one:monitoring-xyz-2-*' ); expect(get.callCount).to.eql(2); }); @@ -69,7 +72,11 @@ describe('ccs_utils', () => { const pattern = prefixIndexPattern(config, indexPattern, '*'); // it should have BOTH patterns so that it searches all CCS clusters and the local cluster - expect(pattern).to.eql('*:.monitoring-xyz-1-*,*:.monitoring-xyz-2-*' + ',' + indexPattern); + expect(pattern).to.eql( + '*:.monitoring-xyz-1-*,*:.monitoring-xyz-2-*,*:monitoring-xyz-1-*,*:monitoring-xyz-2-*' + + ',' + + indexPattern + ); expect(get.callCount).to.eql(1); }); }); @@ -77,18 +84,25 @@ describe('ccs_utils', () => { describe('parseCrossClusterPrefix', () => { it('returns ccs prefix for index with one', () => { expect(parseCrossClusterPrefix('abc:.monitoring-es-6-2017.07.28')).to.eql('abc'); + expect(parseCrossClusterPrefix('abc:monitoring-es-6-2017.07.28')).to.eql('abc'); expect(parseCrossClusterPrefix('abc_123:.monitoring-es-6-2017.07.28')).to.eql('abc_123'); + expect(parseCrossClusterPrefix('abc_123:monitoring-es-6-2017.07.28')).to.eql('abc_123'); expect(parseCrossClusterPrefix('broken:example:.monitoring-es-6-2017.07.28')).to.eql( 'broken' ); + expect(parseCrossClusterPrefix('broken:example:monitoring-es-6-2017.07.28')).to.eql('broken'); expect(parseCrossClusterPrefix('with-a-dash:.monitoring-es-6-2017.07.28')).to.eql( 'with-a-dash' ); + expect(parseCrossClusterPrefix('with-a-dash:monitoring-es-6-2017.07.28')).to.eql( + 'with-a-dash' + ); expect(parseCrossClusterPrefix('something:not-monitoring')).to.eql('something'); }); it('returns null when no prefix exists', () => { expect(parseCrossClusterPrefix('.monitoring-es-6-2017.07.28')).to.be(null); + expect(parseCrossClusterPrefix('monitoring-es-6-2017.07.28')).to.be(null); expect(parseCrossClusterPrefix('random')).to.be(null); }); }); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_node_ids.test.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_node_ids.test.js index 19fa99a783302..a7827c7474a15 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_node_ids.test.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_node_ids.test.js @@ -47,7 +47,7 @@ describe('getNodeIds', () => { }; const clusterUuid = '1cb'; - const result = await getNodeIds(req, '.monitoring-es-*', { clusterUuid }, 10); + const result = await getNodeIds(req, '.monitoring-es-*,monitoring-es-*', { clusterUuid }, 10); expect(result).toEqual([ { name: 'foobar', uuid: 1 }, { name: 'barfoo', uuid: 2 }, diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js index c08ae91769b9d..d1126e79b6940 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js @@ -45,7 +45,7 @@ describe('getPaginatedNodes', () => { }), }, }; - const esIndexPattern = '.monitoring-es-*'; + const esIndexPattern = '.monitoring-es-*,monitoring-es-*'; const clusterUuid = '1abc'; const metricSet = ['foo', 'bar']; const pagination = { index: 0, size: 10 }; diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js index 0023b9515ad1c..51c61046e9cda 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js @@ -20,7 +20,7 @@ import { getMetrics } from '../../../details/get_metrics'; * and returns that so the caller can perform their normal call to get the time-series data. * * @param {*} req - Server request object - * @param {*} esIndexPattern - The index pattern to search against (`.monitoring-es-*`) + * @param {*} esIndexPattern - The index pattern to search against (`.monitoring-es-*,monitoring-es-*`) * @param {*} uuids - The optional `clusterUuid` and `nodeUuid` to filter the results from * @param {*} metricSet - The array of metrics that are sortable in the UI * @param {*} pagination - ({ index, size }) diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js index 8362ebec0206b..1d48d300f8eb6 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js @@ -30,7 +30,7 @@ export async function verifyMonitoringAuth(req) { /** * Reach out to the Monitoring cluster and ensure that it believes the current user has the privileges necessary - * to make API calls against .monitoring-* indices. + * to make API calls against .monitoring-*,monitoring-* indices. * * @param req {Object} the server route handler request object * @return {Promise} That either resolves with no response (void) or an exception. diff --git a/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js b/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js index 79aa0d21641ca..c09df240d4f35 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js @@ -21,7 +21,7 @@ import { getMetrics } from '../details/get_metrics'; * and returns that so the caller can perform their normal call to get the time-series data. * * @param {*} req - Server request object - * @param {*} lsIndexPattern - The index pattern to search against (`.monitoring-logstash-*`) + * @param {*} lsIndexPattern - The index pattern to search against (`.monitoring-logstash-*,monitoring-logstash-*`) * @param {*} uuids - The optional `clusterUuid` and `logstashUuid` to filter the results from * @param {*} metricSet - The array of metrics that are sortable in the UI * @param {*} pagination - ({ index, size }) diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status_regular_index.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status_regular_index.js new file mode 100644 index 0000000000000..86a0533ee07db --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status_regular_index.js @@ -0,0 +1,241 @@ +/* + * 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 expect from '@kbn/expect'; +import sinon from 'sinon'; +import { getCollectionStatus } from '../'; +import { getIndexPatterns } from '../../../cluster/get_index_patterns'; + +const liveClusterUuid = 'a12'; +const mockReq = (searchResult = {}) => { + return { + server: { + newPlatform: { + setup: { + plugins: { + usageCollection: { + getCollectorByType: () => ({ + isReady: () => false, + }), + }, + }, + }, + }, + config() { + return { + get: sinon + .stub() + .withArgs('server.uuid') + .returns('kibana-1234'), + }; + }, + usage: { + collectorSet: { + getCollectorByType: () => ({ + isReady: () => false, + }), + }, + }, + plugins: { + elasticsearch: { + getCluster() { + return { + callWithRequest(_req, type, params) { + if ( + type === 'transport.request' && + params && + params.path === '/_cluster/state/cluster_uuid' + ) { + return Promise.resolve({ cluster_uuid: liveClusterUuid }); + } + if (type === 'transport.request' && params && params.path === '/_nodes') { + return Promise.resolve({ nodes: {} }); + } + if (type === 'cat.indices') { + return Promise.resolve([1]); + } + return Promise.resolve(searchResult); + }, + }; + }, + }, + }, + }, + }; +}; + +describe('getCollectionStatus none system index', () => { + it('should handle all stack products with internal monitoring', async () => { + const req = mockReq({ + aggregations: { + indices: { + buckets: [ + { + key: 'monitoring-es-7-2019', + es_uuids: { buckets: [{ key: 'es_1' }] }, + }, + { + key: 'monitoring-kibana-7-2019', + kibana_uuids: { buckets: [{ key: 'kibana_1' }] }, + }, + { + key: 'monitoring-beats-7-2019', + beats_uuids: { + buckets: [ + { key: 'apm_1', beat_type: { buckets: [{ key: 'apm-server' }] } }, + { key: 'beats_1' }, + ], + }, + }, + { + key: 'monitoring-logstash-7-2019', + logstash_uuids: { buckets: [{ key: 'logstash_1' }] }, + }, + ], + }, + }, + }); + + const result = await getCollectionStatus(req, getIndexPatterns(req.server)); + + expect(result.kibana.totalUniqueInstanceCount).to.be(1); + expect(result.kibana.totalUniqueFullyMigratedCount).to.be(0); + expect(result.kibana.byUuid.kibana_1.isInternalCollector).to.be(true); + + expect(result.beats.totalUniqueInstanceCount).to.be(1); + expect(result.beats.totalUniqueFullyMigratedCount).to.be(0); + expect(result.beats.byUuid.beats_1.isInternalCollector).to.be(true); + + expect(result.apm.totalUniqueInstanceCount).to.be(1); + expect(result.apm.totalUniqueFullyMigratedCount).to.be(0); + expect(result.apm.byUuid.apm_1.isInternalCollector).to.be(true); + + expect(result.logstash.totalUniqueInstanceCount).to.be(1); + expect(result.logstash.totalUniqueFullyMigratedCount).to.be(0); + expect(result.logstash.byUuid.logstash_1.isInternalCollector).to.be(true); + + expect(result.elasticsearch.totalUniqueInstanceCount).to.be(1); + expect(result.elasticsearch.totalUniqueFullyMigratedCount).to.be(0); + expect(result.elasticsearch.byUuid.es_1.isInternalCollector).to.be(true); + }); + + it('should handle some stack products as fully migrated', async () => { + const req = mockReq({ + aggregations: { + indices: { + buckets: [ + { + key: 'monitoring-es-7-mb-2019', + es_uuids: { buckets: [{ key: 'es_1' }] }, + }, + { + key: 'monitoring-kibana-7-mb-2019', + kibana_uuids: { buckets: [{ key: 'kibana_1' }] }, + }, + { + key: 'monitoring-beats-7-2019', + beats_uuids: { buckets: [{ key: 'beats_1' }] }, + }, + { + key: 'monitoring-logstash-7-2019', + logstash_uuids: { buckets: [{ key: 'logstash_1' }] }, + }, + ], + }, + }, + }); + + const result = await getCollectionStatus(req, getIndexPatterns(req.server)); + + expect(result.kibana.totalUniqueInstanceCount).to.be(1); + expect(result.kibana.totalUniqueFullyMigratedCount).to.be(1); + expect(result.kibana.byUuid.kibana_1.isFullyMigrated).to.be(true); + + expect(result.beats.totalUniqueInstanceCount).to.be(1); + expect(result.beats.totalUniqueFullyMigratedCount).to.be(0); + expect(result.beats.byUuid.beats_1.isInternalCollector).to.be(true); + + expect(result.logstash.totalUniqueInstanceCount).to.be(1); + expect(result.logstash.totalUniqueFullyMigratedCount).to.be(0); + expect(result.logstash.byUuid.logstash_1.isInternalCollector).to.be(true); + + expect(result.elasticsearch.totalUniqueInstanceCount).to.be(1); + expect(result.elasticsearch.totalUniqueFullyMigratedCount).to.be(1); + expect(result.elasticsearch.byUuid.es_1.isFullyMigrated).to.be(true); + }); + + it('should handle some stack products as partially migrated', async () => { + const req = mockReq({ + aggregations: { + indices: { + buckets: [ + { + key: 'monitoring-es-7-mb-2019', + es_uuids: { buckets: [{ key: 'es_1' }] }, + }, + { + key: 'monitoring-kibana-7-mb-2019', + kibana_uuids: { buckets: [{ key: 'kibana_1' }, { key: 'kibana_2' }] }, + }, + { + key: 'monitoring-kibana-7-2019', + kibana_uuids: { buckets: [{ key: 'kibana_1', by_timestamp: { value: 12 } }] }, + }, + { + key: 'monitoring-beats-7-2019', + beats_uuids: { buckets: [{ key: 'beats_1' }] }, + }, + { + key: 'monitoring-logstash-7-2019', + logstash_uuids: { buckets: [{ key: 'logstash_1' }] }, + }, + ], + }, + }, + }); + + const result = await getCollectionStatus(req, getIndexPatterns(req.server)); + + expect(result.kibana.totalUniqueInstanceCount).to.be(2); + expect(result.kibana.totalUniqueFullyMigratedCount).to.be(1); + expect(result.kibana.byUuid.kibana_1.isPartiallyMigrated).to.be(true); + expect(result.kibana.byUuid.kibana_1.lastInternallyCollectedTimestamp).to.be(12); + + expect(result.beats.totalUniqueInstanceCount).to.be(1); + expect(result.beats.totalUniqueFullyMigratedCount).to.be(0); + expect(result.beats.byUuid.beats_1.isInternalCollector).to.be(true); + + expect(result.logstash.totalUniqueInstanceCount).to.be(1); + expect(result.logstash.totalUniqueFullyMigratedCount).to.be(0); + expect(result.logstash.byUuid.logstash_1.isInternalCollector).to.be(true); + + expect(result.elasticsearch.totalUniqueInstanceCount).to.be(1); + expect(result.elasticsearch.totalUniqueFullyMigratedCount).to.be(1); + expect(result.elasticsearch.byUuid.es_1.isFullyMigrated).to.be(true); + }); + + it('should detect products based on other indices', async () => { + const req = mockReq( + {}, + { + responses: [ + { hits: { total: { value: 1 } } }, + { hits: { total: { value: 1 } } }, + { hits: { total: { value: 1 } } }, + { hits: { total: { value: 1 } } }, + { hits: { total: { value: 1 } } }, + ], + } + ); + + const result = await getCollectionStatus(req, getIndexPatterns(req.server), liveClusterUuid); + + expect(result.kibana.detected.doesExist).to.be(true); + expect(result.elasticsearch.detected.doesExist).to.be(true); + expect(result.beats.detected.mightExist).to.be(true); + expect(result.logstash.detected.mightExist).to.be(true); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index 73c37dc7f3f28..5f52e0c6a983b 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -242,7 +242,7 @@ async function hasNecessaryPermissions(req) { * Determines if we should ignore this bucket from this product. * * We need this logic because APM and Beats are separate products, but their - * monitoring data appears in the same index (.monitoring-beats-*) and the single + * monitoring data appears in the same index (.monitoring-beats-*,monitoring-beats-*) and the single * way to determine the difference between two documents in that index * is `beats_stats.beat.type` which we are performing a terms agg in the above query. * If that value is `apm-server` and we're attempting to calculating status for beats @@ -325,7 +325,7 @@ async function getLiveElasticsearchCollectionEnabled(req) { * } * @param {*} req Standard request object. Can contain a timeRange to use for the query - * @param {*} indexPatterns Map of index patterns to search against (will be all .monitoring-* indices) + * @param {*} indexPatterns Map of index patterns to search against (will be all .monitoring-*,monitoring-* indices) * @param {*} clusterUuid Optional and will be used to filter down the query if used * @param {*} nodeUuid Optional and will be used to filter down the query if used * @param {*} skipLiveData Optional and will not make any live api calls if set to true diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js index f27fde50242f4..ea6792433ea0b 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js @@ -19,11 +19,11 @@ describe.skip('get_all_stats', () => { get: sinon .stub() .withArgs('xpack.monitoring.elasticsearch.index_pattern') - .returns('.monitoring-es-N-*') + .returns('.monitoring-es-N-*,monitoring-es-N-*') .withArgs('xpack.monitoring.kibana.index_pattern') - .returns('.monitoring-kibana-N-*') + .returns('.monitoring-kibana-N-*,monitoring-kibana-N-*') .withArgs('xpack.monitoring.logstash.index_pattern') - .returns('.monitoring-logstash-N-*') + .returns('.monitoring-logstash-N-*,monitoring-logstash-N-*') .withArgs('xpack.monitoring.max_bucket_size') .returns(size), }), diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js index 1f62c677dbb21..820e6e2c1ec28 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js @@ -20,7 +20,7 @@ describe('get_cluster_uuids', () => { get: sinon .stub() .withArgs('xpack.monitoring.elasticsearch.index_pattern') - .returns('.monitoring-es-N-*') + .returns('.monitoring-es-N-*,monitoring-es-N-*') .withArgs('xpack.monitoring.max_bucket_size') .returns(size), }), diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_es_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_es_stats.js index 536e831640fad..570a19e0766d4 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_es_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_es_stats.js @@ -20,7 +20,7 @@ describe('get_es_stats', () => { get: sinon .stub() .withArgs('xpack.monitoring.elasticsearch.index_pattern') - .returns('.monitoring-es-N-*') + .returns('.monitoring-es-N-*,monitoring-es-N-*') .withArgs('xpack.monitoring.max_bucket_size') .returns(size), }), diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_high_level_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_high_level_stats.js index 1c1f8dc888d01..dbdd3b8dbce4a 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_high_level_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_high_level_stats.js @@ -24,7 +24,7 @@ describe('get_high_level_stats', () => { get: sinon .stub() .withArgs(`xpack.monitoring.${product}.index_pattern`) - .returns(`.monitoring-${product}-N-*`) + .returns(`.monitoring-${product}-N-*,monitoring-${product}-N-*`) .withArgs('xpack.monitoring.max_bucket_size') .returns(size), }), diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css index ab88e4780936e..2c203e507260f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css @@ -53,9 +53,10 @@ discover-app .discover-table-footer { * Visualize Editor Tweaks */ -/* hide unusable controls */ -visualization-editor .visEditor--default > :not(.visEditor__canvas) { - display: none; +/* hide unusable controls +* !important is required to override resizable panel inline display */ +visualization-editor .visEditor--default > :not(.visEditor__visualization) { + display: none !important; } /** THIS IS FOR TSVB UNTIL REFACTOR **/ diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css index 8aca042144b3b..b5c9861208b7b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css @@ -52,9 +52,10 @@ discover-app .discover-table-footer { * Visualize Editor Tweaks */ -/* hide unusable controls */ -visualization-editor .visEditor--default > :not(.visEditor__canvas) { - display: none; +/* hide unusable controls +* !important is required to override resizable panel inline display */ +visualization-editor .visEditor--default > :not(.visEditor__visualization) { + display: none !important; } /** THIS IS FOR TSVB UNTIL REFACTOR **/ .tvbEditorVisualization { 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 a8ac99a48ca89..83be303191bad 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 @@ -590,8 +590,7 @@ describe('CSV Execute Job', function() { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/43069 - describe.skip('cancellation', function() { + describe('cancellation', function() { const scrollId = getRandomScrollId(); beforeEach(function() { @@ -618,10 +617,10 @@ describe('CSV Execute Job', function() { cancellationToken ); - await delay(100); + await delay(250); const callCount = callWithRequestStub.callCount; cancellationToken.cancel(); - await delay(100); + await delay(250); expect(callWithRequestStub.callCount).to.be(callCount + 1); // last call is to clear the scroll }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index fef45f5a5eae8..ae603d93245a3 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -86,7 +86,6 @@ export const executeJobFactory: ExecuteJobFactory { - // @ts-ignore fieldFormatServiceFactory' does not exist on type 'ServerFacade TODO const fieldFormats = await server.fieldFormatServiceFactory(uiConfig); return fieldFormatMapFactory(indexPatternSavedObject, fieldFormats); })(), diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index 7e12adefca38d..d39d2bbf08c9f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -57,7 +57,7 @@ export async function generateCsvSearch( jobParams: JobParamsDiscoverCsv ): Promise { const { savedObjects, uiSettingsServiceFactory } = server; - const savedObjectsClient = savedObjects.getScopedSavedObjectsClient(req); + const savedObjectsClient = savedObjects.getScopedSavedObjectsClient(req.getRawRequest()); const { indexPatternSavedObjectId, timerange } = searchPanel; const savedSearchObjectAttr = searchPanel.attributes as SavedSearchObjectAttributes; const { indexPatternSavedObject } = await getDataSource( diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index ef0ab37738362..82d94422b70ce 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -6,6 +6,9 @@ import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; +import { Legacy } from 'kibana'; +import { IUiSettingsClient } from 'src/core/server'; +import { XPackMainPlugin } from '../xpack_main/server/xpack_main'; import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; // @ts-ignore untyped module defintition import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; @@ -16,16 +19,35 @@ import { getExportTypesRegistry, runValidations, } from './server/lib'; -import { config as reportingConfig } from './config'; -import { logConfiguration } from './log_configuration'; import { createBrowserDriverFactory } from './server/browsers'; import { registerReportingUsageCollector } from './server/usage'; -import { ReportingConfigOptions, ReportingPluginSpecOptions, ServerFacade } from './types.d'; +import { ReportingConfigOptions, ReportingPluginSpecOptions } from './types.d'; +import { config as reportingConfig } from './config'; +import { logConfiguration } from './log_configuration'; const kbToBase64Length = (kb: number) => { return Math.floor((kb * 1024 * 8) / 6); }; +type LegacyPlugins = Legacy.Server['plugins']; + +export interface ServerFacade { + config: Legacy.Server['config']; + info: Legacy.Server['info']; + log: Legacy.Server['log']; + plugins: { + elasticsearch: LegacyPlugins['elasticsearch']; + security: LegacyPlugins['security']; + xpack_main: XPackMainPlugin & { + status?: any; + }; + }; + route: Legacy.Server['route']; + savedObjects: Legacy.Server['savedObjects']; + uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory']; + fieldFormatServiceFactory: (uiConfig: IUiSettingsClient) => unknown; +} + export const reporting = (kibana: any) => { return new kibana.Plugin({ id: PLUGIN_ID, @@ -42,7 +64,7 @@ export const reporting = (kibana: any) => { embeddableActions: ['plugins/reporting/panel_actions/get_csv_panel_action'], home: ['plugins/reporting/register_feature'], managementSections: ['plugins/reporting/views/management'], - injectDefaultVars(server: ServerFacade, options?: ReportingConfigOptions) { + injectDefaultVars(server: Legacy.Server, options?: ReportingConfigOptions) { const config = server.config(); return { reportingPollConfig: options ? options.poll : {}, @@ -70,9 +92,22 @@ export const reporting = (kibana: any) => { }, }, - // TODO: Decouple Hapi: Build a server facade object based on the server to - // pass through to the libs. Do not pass server directly - async init(server: ServerFacade) { + async init(server: Legacy.Server) { + const serverFacade: ServerFacade = { + config: server.config, + info: server.info, + route: server.route.bind(server), + plugins: { + elasticsearch: server.plugins.elasticsearch, + xpack_main: server.plugins.xpack_main, + security: server.plugins.security, + }, + savedObjects: server.savedObjects, + uiSettingsServiceFactory: server.uiSettingsServiceFactory, + // @ts-ignore Property 'fieldFormatServiceFactory' does not exist on type 'Server'. + fieldFormatServiceFactory: server.fieldFormatServiceFactory, + log: server.log.bind(server), + }; const exportTypesRegistry = getExportTypesRegistry(); let isCollectorReady = false; @@ -80,18 +115,18 @@ export const reporting = (kibana: any) => { const { usageCollection } = server.newPlatform.setup.plugins; registerReportingUsageCollector( usageCollection, - server, + serverFacade, () => isCollectorReady, exportTypesRegistry ); - const logger = LevelLogger.createForServer(server, [PLUGIN_ID]); - const browserDriverFactory = await createBrowserDriverFactory(server); + const logger = LevelLogger.createForServer(serverFacade, [PLUGIN_ID]); + const browserDriverFactory = await createBrowserDriverFactory(serverFacade); - logConfiguration(server, logger); - runValidations(server, logger, browserDriverFactory); + logConfiguration(serverFacade, logger); + runValidations(serverFacade, logger, browserDriverFactory); - const { xpack_main: xpackMainPlugin } = server.plugins; + const { xpack_main: xpackMainPlugin } = serverFacade.plugins; mirrorPluginStatus(xpackMainPlugin, this); const checkLicense = checkLicenseFactory(exportTypesRegistry); (xpackMainPlugin as any).status.once('green', () => { @@ -104,7 +139,7 @@ export const reporting = (kibana: any) => { isCollectorReady = true; // Reporting routes - registerRoutes(server, exportTypesRegistry, browserDriverFactory, logger); + registerRoutes(serverFacade, exportTypesRegistry, browserDriverFactory, logger); }, deprecations({ unused }: any) { diff --git a/x-pack/legacy/plugins/reporting/public/lib/download_report.ts b/x-pack/legacy/plugins/reporting/public/lib/download_report.ts index da22093da2ab0..18bae64f8788d 100644 --- a/x-pack/legacy/plugins/reporting/public/lib/download_report.ts +++ b/x-pack/legacy/plugins/reporting/public/lib/download_report.ts @@ -7,8 +7,15 @@ import chrome from 'ui/chrome'; import { API_BASE_URL } from '../../common/constants'; -export function downloadReport(jobId: string) { +export function getReportURL(jobId: string) { const apiBaseUrl = chrome.addBasePath(API_BASE_URL); const downloadLink = `${apiBaseUrl}/jobs/download/${jobId}`; - window.open(downloadLink); + + return downloadLink; +} + +export function downloadReport(jobId: string) { + const location = getReportURL(jobId); + + window.open(location); } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index c1777038cc7d4..de8449ff29132 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { trunc } from 'lodash'; +import { trunc, map } from 'lodash'; import open from 'opn'; import { parse as parseUrl } from 'url'; import { Page, SerializableOrJSHandle, EvaluateFn } from 'puppeteer'; @@ -15,6 +15,7 @@ import { ConditionalHeaders, ConditionalHeadersConditions, ElementPosition, + InterceptedRequest, NetworkPolicy, } from '../../../../types'; @@ -59,35 +60,57 @@ export class HeadlessChromiumDriver { }: { conditionalHeaders: ConditionalHeaders; waitForSelector: string }, logger: LevelLogger ) { - await this.page.setRequestInterception(true); logger.info(`opening url ${url}`); + // @ts-ignore + const client = this.page._client; let interceptedCount = 0; - this.page.on('request', interceptedRequest => { - const interceptedUrl = interceptedRequest.url(); + await this.page.setRequestInterception(true); + + // We have to reach into the Chrome Devtools Protocol to apply headers as using + // puppeteer's API will cause map tile requests to hang indefinitely: + // https://github.com/puppeteer/puppeteer/issues/5003 + // Docs on this client/protocol can be found here: + // https://chromedevtools.github.io/devtools-protocol/tot/Fetch + client.on('Fetch.requestPaused', (interceptedRequest: InterceptedRequest) => { + const { + requestId, + request: { url: interceptedUrl }, + } = interceptedRequest; const allowed = !interceptedUrl.startsWith('file://'); const isData = interceptedUrl.startsWith('data:'); // We should never ever let file protocol requests go through if (!allowed || !this.allowRequest(interceptedUrl)) { logger.error(`Got bad URL: "${interceptedUrl}", closing browser.`); - interceptedRequest.abort('blockedbyclient'); + client.send('Fetch.failRequest', { + errorReason: 'Aborted', + requestId, + }); this.page.browser().close(); throw new Error(`Received disallowed outgoing URL: "${interceptedUrl}", exiting`); } if (this._shouldUseCustomHeaders(conditionalHeaders.conditions, interceptedUrl)) { logger.debug(`Using custom headers for ${interceptedUrl}`); - interceptedRequest.continue({ - headers: { - ...interceptedRequest.headers(), + const headers = map( + { + ...interceptedRequest.request.headers, ...conditionalHeaders.headers, }, + (value, name) => ({ + name, + value, + }) + ); + client.send('Fetch.continueRequest', { + requestId, + headers, }); } else { const loggedUrl = isData ? this.truncateUrl(interceptedUrl) : interceptedUrl; logger.debug(`No custom headers for ${loggedUrl}`); - interceptedRequest.continue(); + client.send('Fetch.continueRequest', { requestId }); } interceptedCount = interceptedCount + (isData ? 0 : 1); }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/get_user.js b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts similarity index 57% rename from x-pack/legacy/plugins/reporting/server/lib/get_user.js rename to x-pack/legacy/plugins/reporting/server/lib/get_user.ts index 04c9516cb99d4..e2921de795012 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/get_user.js +++ b/x-pack/legacy/plugins/reporting/server/lib/get_user.ts @@ -4,8 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -export function getUserFactory(server) { - return async request => { +import { Legacy } from 'kibana'; +import { ServerFacade } from '../../types'; + +export function getUserFactory(server: ServerFacade) { + /* + * Legacy.Request because this is called from routing middleware + */ + return async (request: Legacy.Request) => { if (!server.plugins.security) { return null; } @@ -13,7 +19,7 @@ export function getUserFactory(server) { try { return await server.plugins.security.getUser(request); } catch (err) { - server.log(['reporting', 'getUser', 'debug'], err); + server.log(['reporting', 'getUser', 'error'], err); return null; } }; 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 c67a9cd32d50d..839fa16a716b7 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/level_logger.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/level_logger.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -type ServerLog = (tags: string[], msg: string) => void; +import { ServerFacade } from '../../types'; const trimStr = (toTrim: string) => { return typeof toTrim === 'string' ? toTrim.trim() : toTrim; @@ -16,12 +16,12 @@ export class LevelLogger { public warn: (msg: string, tags?: string[]) => void; - static createForServer(server: any, tags: string[]) { - const serverLog: ServerLog = (tgs: string[], msg: string) => server.log(tgs, msg); + static createForServer(server: ServerFacade, tags: string[]) { + const serverLog: ServerFacade['log'] = (tgs: string[], msg: string) => server.log(tgs, msg); return new LevelLogger(serverLog, tags); } - constructor(logger: ServerLog, tags: string[]) { + constructor(logger: ServerFacade['log'], tags: string[]) { this._logger = logger; this._tags = tags; diff --git a/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts b/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts index d73a5b73fecd0..ae3636079a9bb 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts @@ -27,10 +27,6 @@ export function oncePerServer(fn: ServerFn) { throw new TypeError('This function expects to be called with a single argument'); } - if (!server || typeof server.expose !== 'function') { - throw new TypeError('This function expects to be passed the server'); - } - // @ts-ignore return fn.call(this, server); }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 128cc44db4dc4..c9225dfee6978 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -4,16 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Legacy } from 'kibana'; import boom from 'boom'; import Joi from 'joi'; import rison from 'rison-node'; import { API_BASE_URL } from '../../common/constants'; -import { ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; +import { ServerFacade, ReportingResponseToolkit } from '../../types'; import { getRouteConfigFactoryReportingPre, GetRouteConfigFactoryFn, RouteConfigFactory, } from './lib/route_config_factories'; +import { makeRequestFacade } from './lib/make_request_facade'; import { HandlerErrorFunction, HandlerFunction } from './types'; const BASE_GENERATE = `${API_BASE_URL}/generate`; @@ -54,7 +56,8 @@ export function registerGenerateFromJobParams( path: `${BASE_GENERATE}/{exportType}`, method: 'POST', options: getRouteConfig(), - handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { + handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { + const request = makeRequestFacade(legacyRequest); let jobParamsRison: string | null; if (request.payload) { @@ -80,7 +83,7 @@ export function registerGenerateFromJobParams( if (!jobParams) { throw new Error('missing jobParams!'); } - response = await handler(exportType, jobParams, request, h); + response = await handler(exportType, jobParams, legacyRequest, h); } catch (err) { throw boom.badRequest(`invalid rison: ${jobParamsRison}`); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index 72c8055614065..2c509136b1b44 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Legacy } from 'kibana'; import { get } from 'lodash'; import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../common/constants'; -import { ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; +import { ServerFacade, ReportingResponseToolkit } from '../../types'; import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types'; import { getRouteOptionsCsv } from './lib/route_config_factories'; +import { makeRequestFacade } from './lib/make_request_facade'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; /* @@ -31,17 +33,18 @@ export function registerGenerateCsvFromSavedObject( path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, method: 'POST', options: routeOptions, - handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { + handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { + const requestFacade = makeRequestFacade(legacyRequest); + /* * 1. Build `jobParams` object: job data that execution will need to reference in various parts of the lifecycle * 2. Pass the jobParams and other common params to `handleRoute`, a shared function to enqueue the job with the params * 3. Ensure that details for a queued job were returned */ - let result: QueuedJobPayload; try { - const jobParams = getJobParamsFromRequest(request, { isImmediate: false }); - result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, request, h); + const jobParams = getJobParamsFromRequest(requestFacade, { isImmediate: false }); + result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, legacyRequest, h); // pass the original request because the handler will make the request facade on its own } catch (err) { throw handleRouteError(CSV_FROM_SAVEDOBJECT_JOB_TYPE, err); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index bc96c27f64c10..8d1c84664cbe9 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Legacy } from 'kibana'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; import { createJobFactory, executeJobFactory } from '../../export_types/csv_from_savedobject'; import { ServerFacade, - RequestFacade, ResponseFacade, HeadlessChromiumDriverFactory, ReportingResponseToolkit, @@ -16,8 +16,9 @@ import { JobDocOutputExecuted, } from '../../types'; import { JobDocPayloadPanelCsv } from '../../export_types/csv_from_savedobject/types'; -import { getRouteOptionsCsv } from './lib/route_config_factories'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; +import { getRouteOptionsCsv } from './lib/route_config_factories'; +import { makeRequestFacade } from './lib/make_request_facade'; /* * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: @@ -43,7 +44,8 @@ export function registerGenerateCsvFromSavedObjectImmediate( path: `${API_BASE_GENERATE_V1}/immediate/csv/saved-object/{savedObjectType}:{savedObjectId}`, method: 'POST', options: routeOptions, - handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { + handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { + const request = makeRequestFacade(legacyRequest); const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(request, { isImmediate: true }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 73450b7641c8e..21af54ddf11e3 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -5,12 +5,12 @@ */ import boom from 'boom'; +import { Legacy } from 'kibana'; import { API_BASE_URL } from '../../common/constants'; import { ServerFacade, ExportTypesRegistry, HeadlessChromiumDriverFactory, - RequestFacade, ReportingResponseToolkit, Logger, } from '../../types'; @@ -18,6 +18,7 @@ import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; import { createQueueFactory, enqueueJobFactory } from '../lib'; +import { makeRequestFacade } from './lib/make_request_facade'; export function registerJobGenerationRoutes( server: ServerFacade, @@ -39,9 +40,10 @@ export function registerJobGenerationRoutes( async function handler( exportTypeId: string, jobParams: object, - request: RequestFacade, + legacyRequest: Legacy.Request, h: ReportingResponseToolkit ) { + const request = makeRequestFacade(legacyRequest); const user = request.pre.user; const headers = request.headers; diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index fd5014911d262..a0be15d60f316 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -4,16 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Legacy } from 'kibana'; import boom from 'boom'; import { API_BASE_URL } from '../../common/constants'; import { ServerFacade, ExportTypesRegistry, Logger, - RequestFacade, ReportingResponseToolkit, JobDocOutput, JobSource, + ListQuery, } from '../../types'; // @ts-ignore import { jobsQueryFactory } from '../lib/jobs_query'; @@ -23,6 +24,7 @@ import { getRouteConfigFactoryDownloadPre, getRouteConfigFactoryManagementPre, } from './lib/route_config_factories'; +import { makeRequestFacade } from './lib/make_request_facade'; const MAIN_ENTRY = `${API_BASE_URL}/jobs`; @@ -40,8 +42,9 @@ export function registerJobInfoRoutes( path: `${MAIN_ENTRY}/list`, method: 'GET', options: getRouteConfig(), - handler: (request: RequestFacade) => { - const { page: queryPage, size: querySize, ids: queryIds } = request.query; + handler: (legacyRequest: Legacy.Request) => { + const request = makeRequestFacade(legacyRequest); + const { page: queryPage, size: querySize, ids: queryIds } = request.query as ListQuery; const page = parseInt(queryPage, 10) || 0; const size = Math.min(100, parseInt(querySize, 10) || 10); const jobIds = queryIds ? queryIds.split(',') : null; @@ -62,7 +65,8 @@ export function registerJobInfoRoutes( path: `${MAIN_ENTRY}/count`, method: 'GET', options: getRouteConfig(), - handler: (request: RequestFacade) => { + handler: (legacyRequest: Legacy.Request) => { + const request = makeRequestFacade(legacyRequest); const results = jobsQuery.count(request.pre.management.jobTypes, request.pre.user); return results; }, @@ -73,7 +77,8 @@ export function registerJobInfoRoutes( path: `${MAIN_ENTRY}/output/{docId}`, method: 'GET', options: getRouteConfig(), - handler: (request: RequestFacade) => { + handler: (legacyRequest: Legacy.Request) => { + const request = makeRequestFacade(legacyRequest); const { docId } = request.params; return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then( @@ -98,7 +103,8 @@ export function registerJobInfoRoutes( path: `${MAIN_ENTRY}/info/{docId}`, method: 'GET', options: getRouteConfig(), - handler: (request: RequestFacade) => { + handler: (legacyRequest: Legacy.Request) => { + const request = makeRequestFacade(legacyRequest); const { docId } = request.params; return jobsQuery @@ -130,7 +136,8 @@ export function registerJobInfoRoutes( path: `${MAIN_ENTRY}/download/{docId}`, method: 'GET', options: getRouteConfigDownload(), - handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { + handler: async (legacyRequest: Legacy.Request, h: ReportingResponseToolkit) => { + const request = makeRequestFacade(legacyRequest); const { docId } = request.params; let response = await jobResponseHandler( diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts new file mode 100644 index 0000000000000..8cdb7b4c018d7 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.test.ts @@ -0,0 +1,62 @@ +/* + * 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 { Legacy } from 'kibana'; +import { makeRequestFacade } from './make_request_facade'; + +describe('makeRequestFacade', () => { + test('creates a default object', () => { + const legacyRequest = ({ + getBasePath: () => 'basebase', + params: { + param1: 123, + }, + payload: { + payload1: 123, + }, + headers: { + user: 123, + }, + } as unknown) as Legacy.Request; + + expect(makeRequestFacade(legacyRequest)).toMatchInlineSnapshot(` + Object { + "getBasePath": [Function], + "getRawRequest": [Function], + "getSavedObjectsClient": undefined, + "headers": Object { + "user": 123, + }, + "params": Object { + "param1": 123, + }, + "payload": Object { + "payload1": 123, + }, + "pre": undefined, + "query": undefined, + "route": undefined, + } + `); + }); + + test('getRawRequest', () => { + const legacyRequest = ({ + getBasePath: () => 'basebase', + params: { + param1: 123, + }, + payload: { + payload1: 123, + }, + headers: { + user: 123, + }, + } as unknown) as Legacy.Request; + + expect(makeRequestFacade(legacyRequest).getRawRequest()).toBe(legacyRequest); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts new file mode 100644 index 0000000000000..fb8a2dbbff17b --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/make_request_facade.ts @@ -0,0 +1,32 @@ +/* + * 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 { RequestQuery } from 'hapi'; +import { Legacy } from 'kibana'; +import { + RequestFacade, + ReportingRequestPayload, + ReportingRequestPre, + ReportingRequestQuery, +} from '../../../types'; + +export function makeRequestFacade(request: Legacy.Request): RequestFacade { + // This condition is for unit tests + const getSavedObjectsClient = request.getSavedObjectsClient + ? request.getSavedObjectsClient.bind(request) + : request.getSavedObjectsClient; + return { + getSavedObjectsClient, + headers: request.headers, + params: request.params, + payload: (request.payload as object) as ReportingRequestPayload, + query: ((request.query as RequestQuery) as object) as ReportingRequestQuery, + pre: (request.pre as Record) as ReportingRequestPre, + getBasePath: request.getBasePath, + route: request.route, + getRawRequest: () => request, + }; +} diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts index b50d443ec00b9..f3660a22cbac1 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Legacy } from 'kibana'; import { RequestFacade, ReportingResponseToolkit, JobDocPayload } from '../../types'; export type HandlerFunction = ( exportType: string, jobParams: object, - request: RequestFacade, + request: Legacy.Request, h: ReportingResponseToolkit ) => any; diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index c17b969d5d7fa..f67f44860f14a 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -7,7 +7,6 @@ import { ResponseObject } from 'hapi'; import { EventEmitter } from 'events'; import { Legacy } from 'kibana'; -import { XPackMainPlugin } from '../xpack_main/server/xpack_main'; import { ElasticsearchPlugin, CallCluster, @@ -16,6 +15,7 @@ import { CancellationToken } from './common/cancellation_token'; import { LevelLogger } from './server/lib/level_logger'; import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; import { BrowserType } from './server/browsers/types'; +import { ServerFacade } from './index'; export type ReportingPlugin = object; // For Plugin contract @@ -53,7 +53,7 @@ export interface NetworkPolicy { rules: NetworkPolicyRule[]; } -interface ListQuery { +export interface ListQuery { page: string; size: string; ids?: string; // optional field forbids us from extending RequestQuery @@ -64,9 +64,6 @@ interface GenerateQuery { interface GenerateExportTypePayload { jobParams: string; } -interface DownloadParams { - docId: string; -} /* * Legacy System @@ -74,26 +71,6 @@ interface DownloadParams { export type ReportingPluginSpecOptions = Legacy.PluginSpecOptions; -export type ServerFacade = Legacy.Server & { - plugins: { - xpack_main?: XPackMainPlugin & { - status?: any; - }; - }; -}; - -interface ReportingRequest { - query: ListQuery & GenerateQuery; - params: DownloadParams; - payload: GenerateExportTypePayload; - pre: { - management: { - jobTypes: any; - }; - user: any; - }; -} - export type EnqueueJobFn = ( parentLogger: LevelLogger, exportTypeId: string, @@ -103,7 +80,27 @@ export type EnqueueJobFn = ( request: RequestFacade ) => Promise; -export type RequestFacade = ReportingRequest & Legacy.Request; +export type ReportingRequestPayload = GenerateExportTypePayload | JobParamPostPayload; +export type ReportingRequestQuery = ListQuery | GenerateQuery; + +export interface ReportingRequestPre { + management: { + jobTypes: any; + }; + user: any; // TODO import AuthenticatedUser from security/server +} + +export interface RequestFacade { + getBasePath: Legacy.Request['getBasePath']; + getSavedObjectsClient: Legacy.Request['getSavedObjectsClient']; + headers: Legacy.Request['headers']; + params: Legacy.Request['params']; + payload: JobParamPostPayload | GenerateExportTypePayload; + query: ReportingRequestQuery; + route: Legacy.Request['route']; + pre: ReportingRequestPre; + getRawRequest: () => Legacy.Request; +} export type ResponseFacade = ResponseObject & { isBoom: boolean; @@ -341,3 +338,20 @@ export interface AbsoluteURLFactoryOptions { hostname: string; port: string | number; } + +export interface InterceptedRequest { + requestId: string; + request: { + url: string; + method: string; + headers: { + [key: string]: string; + }; + initialPriority: string; + referrerPolicy: string; + }; + frameId: string; + resourceType: string; +} + +export { ServerFacade }; diff --git a/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js b/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js index 590f3dc85740e..897caa07fd873 100644 --- a/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js +++ b/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js @@ -9,7 +9,7 @@ import { editorConfigProviders } from 'ui/vis/editors/config/editor_config_provi export function initEditorConfig() { // Limit agg params based on rollup capabilities - editorConfigProviders.register((aggType, indexPattern, aggConfig) => { + editorConfigProviders.register((indexPattern, aggConfig) => { if (indexPattern.type !== 'rollup') { return {}; } diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/inspect/helpers.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/inspect/helpers.ts new file mode 100644 index 0000000000000..c431d0551b29a --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/inspect/helpers.ts @@ -0,0 +1,29 @@ +/* + * 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 { DEFAULT_TIMEOUT } from '../util/helpers'; + +import { INSPECT_BUTTON_ICON, InspectButtonMetadata } from './selectors'; + +export const openStatsAndTables = (table: InspectButtonMetadata) => { + if (table.tabId) { + cy.get(table.tabId).click({ force: true }); + } + cy.get(table.id, { timeout: DEFAULT_TIMEOUT }); + if (table.altInspectId) { + cy.get(table.altInspectId, { timeout: DEFAULT_TIMEOUT }).trigger('click', { + force: true, + }); + } else { + cy.get(`${table.id} ${INSPECT_BUTTON_ICON}`, { + timeout: DEFAULT_TIMEOUT, + }).trigger('click', { force: true }); + } +}; + +export const closesModal = () => { + cy.get('[data-test-subj="modal-inspect-close"]', { timeout: DEFAULT_TIMEOUT }).click(); +}; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/inspect/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/inspect/selectors.ts index 689c98de9598a..a6d4b37be9f00 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/inspect/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/inspect/selectors.ts @@ -4,95 +4,86 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HOSTS_PAGE, HOSTS_PAGE_TAB_URLS, NETWORK_PAGE, NETWORK_TAB_URLS } from '../urls'; - export const INSPECT_BUTTON_ICON = '[data-test-subj="inspect-icon-button"]'; export const INSPECT_MODAL = '[data-test-subj="modal-inspect-euiModal"]'; export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-gear"]'; export const TIMELINE_INSPECT_BUTTON = '[data-test-subj="inspect-empty-button"]'; -interface InspectButtonMetadata { +export interface InspectButtonMetadata { altInspectId?: string; id: string; title: string; - url: string; + tabId?: string; } -export const INSPECT_BUTTONS_IN_SIEM: InspectButtonMetadata[] = [ +export const INSPECT_HOSTS_BUTTONS_IN_SIEM: InspectButtonMetadata[] = [ { id: '[data-test-subj="stat-hosts"]', title: 'Hosts Stat', - url: HOSTS_PAGE, }, { id: '[data-test-subj="stat-authentication"]', title: 'User Authentications Stat', - url: HOSTS_PAGE, }, { id: '[data-test-subj="stat-uniqueIps"]', title: 'Unique IPs Stat', - url: HOSTS_PAGE, }, + { + id: '[data-test-subj="table-allHosts-loading-false"]', + title: 'All Hosts Table', + tabId: '[data-test-subj="navigation-allHosts"]', + }, + { + id: '[data-test-subj="table-authentications-loading-false"]', + title: 'Authentications Table', + tabId: '[data-test-subj="navigation-authentications"]', + }, + { + id: '[data-test-subj="table-uncommonProcesses-loading-false"]', + title: 'Uncommon processes Table', + tabId: '[data-test-subj="navigation-uncommonProcesses"]', + }, + { + altInspectId: `[data-test-subj="events-viewer-panel"] ${INSPECT_BUTTON_ICON}`, + id: '[data-test-subj="events-container-loading-false"]', + title: 'Events Table', + tabId: '[data-test-subj="navigation-events"]', + }, +]; + +export const INSPECT_NETWORK_BUTTONS_IN_SIEM: InspectButtonMetadata[] = [ { id: '[data-test-subj="stat-networkEvents"]', title: 'Network events Stat', - url: NETWORK_PAGE, }, { id: '[data-test-subj="stat-dnsQueries"]', title: 'DNS queries Stat', - url: NETWORK_PAGE, }, { id: '[data-test-subj="stat-uniqueFlowId"]', title: 'Unique flow IDs Stat', - url: NETWORK_PAGE, }, { id: '[data-test-subj="stat-tlsHandshakes"]', title: 'TLS handshakes Stat', - url: NETWORK_PAGE, }, { id: '[data-test-subj="stat-UniqueIps"]', title: 'Unique private IPs Stat', - url: NETWORK_PAGE, }, { id: '[data-test-subj="table-topNFlowSource-loading-false"]', title: 'Source IPs Table', - url: NETWORK_PAGE, }, { id: '[data-test-subj="table-topNFlowDestination-loading-false"]', title: 'Destination IPs Table', - url: NETWORK_PAGE, }, { id: '[data-test-subj="table-dns-loading-false"]', title: 'Top DNS Domains Table', - url: NETWORK_TAB_URLS.dns, - }, - { - id: '[data-test-subj="table-allHosts-loading-false"]', - title: 'All Hosts Table', - url: HOSTS_PAGE_TAB_URLS.allHosts, - }, - { - id: '[data-test-subj="table-authentications-loading-false"]', - title: 'Authentications Table', - url: HOSTS_PAGE_TAB_URLS.authentications, - }, - { - id: '[data-test-subj="table-uncommonProcesses-loading-false"]', - title: 'Uncommon processes Table', - url: HOSTS_PAGE_TAB_URLS.uncommonProcesses, - }, - { - altInspectId: `[data-test-subj="events-viewer-panel"] ${INSPECT_BUTTON_ICON}`, - id: '[data-test-subj="events-container-loading-false"]', - title: 'Events Table', - url: HOSTS_PAGE_TAB_URLS.events, + tabId: '[data-test-subj="navigation-dns"]', }, ]; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/helpers.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/helpers.ts index 8fa1a03840e3b..ef2c19bd7e737 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/helpers.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/helpers.ts @@ -7,9 +7,12 @@ import { drag, drop } from '../drag_n_drop/helpers'; import { ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS } from '../hosts/selectors'; import { + CLOSE_TIMELINE_BTN, + CREATE_NEW_TIMELINE, SEARCH_OR_FILTER_CONTAINER, SERVER_SIDE_EVENT_COUNT, TIMELINE_DATA_PROVIDERS, + TIMELINE_SETTINGS, TIMELINE_TOGGLE_BUTTON, TOGGLE_TIMELINE_EXPAND_EVENT, } from './selectors'; @@ -19,12 +22,17 @@ import { DEFAULT_TIMEOUT } from '../util/helpers'; export const toggleTimelineVisibility = () => cy.get(TIMELINE_TOGGLE_BUTTON, { timeout: DEFAULT_TIMEOUT }).click(); +export const createNewTimeline = () => { + cy.get(TIMELINE_SETTINGS).click(); + cy.get(CREATE_NEW_TIMELINE).click(); + cy.get(CLOSE_TIMELINE_BTN).click({ force: true }); +}; + /** Drags and drops a host from the `All Hosts` widget on the `Hosts` page to the timeline */ export const dragFromAllHostsToTimeline = () => { cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS) .first() .then(host => drag(host)); - cy.get(TIMELINE_DATA_PROVIDERS).then(dataProvidersDropArea => drop(dataProvidersDropArea)); }; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/selectors.ts index 7dc98072b52f8..0ec0c506cbb1a 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/timeline/selectors.ts @@ -36,3 +36,9 @@ export const TOGGLE_TIMELINE_EXPAND_EVENT = '[data-test-subj="expand-event"]'; /** The body of the timeline flyout */ export const TIMELINE_FLYOUT_BODY = '[data-test-subj="eui-flyout-body"]'; + +export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; + +export const TIMELINE_SETTINGS = '[data-test-subj="settings-gear"]'; + +export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts index ee25705a83989..7b0b10831c4a0 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts @@ -4,34 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HOSTS_PAGE } from '../../lib/urls'; +import { HOSTS_PAGE, NETWORK_PAGE } from '../../lib/urls'; import { - INSPECT_BUTTON_ICON, INSPECT_MODAL, - INSPECT_BUTTONS_IN_SIEM, + INSPECT_NETWORK_BUTTONS_IN_SIEM, + INSPECT_HOSTS_BUTTONS_IN_SIEM, TIMELINE_SETTINGS_ICON, TIMELINE_INSPECT_BUTTON, } from '../../lib/inspect/selectors'; import { DEFAULT_TIMEOUT, loginAndWaitForPage } from '../../lib/util/helpers'; import { executeKQL, hostExistsQuery, toggleTimelineVisibility } from '../../lib/timeline/helpers'; +import { closesModal, openStatsAndTables } from '../../lib/inspect/helpers'; describe('Inspect', () => { - describe('Hosts and network stats and tables', () => { - INSPECT_BUTTONS_IN_SIEM.map(table => + context('Hosts stats and tables', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + }); + afterEach(() => { + closesModal(); + }); + + INSPECT_HOSTS_BUTTONS_IN_SIEM.forEach(table => + it(`inspects the ${table.title}`, () => { + openStatsAndTables(table); + cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('be.visible'); + }) + ); + }); + + context('Network stats and tables', () => { + before(() => { + loginAndWaitForPage(NETWORK_PAGE); + }); + afterEach(() => { + closesModal(); + }); + + INSPECT_NETWORK_BUTTONS_IN_SIEM.forEach(table => it(`inspects the ${table.title}`, () => { - loginAndWaitForPage(table.url); - cy.get(table.id, { timeout: DEFAULT_TIMEOUT }); - if (table.altInspectId) { - cy.get(table.altInspectId).trigger('click', { force: true }); - } else { - cy.get(`${table.id} ${INSPECT_BUTTON_ICON}`).trigger('click', { force: true }); - } + openStatsAndTables(table); cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('be.visible'); }) ); }); - describe('Timeline', () => { + context('Timeline', () => { it('inspects the timeline', () => { loginAndWaitForPage(HOSTS_PAGE); toggleTimelineVisibility(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts index 824e403185238..24c1974cf8343 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts @@ -9,7 +9,11 @@ import { TIMELINE_DROPPED_DATA_PROVIDERS, TIMELINE_DATA_PROVIDERS_EMPTY, } from '../../lib/timeline/selectors'; -import { dragFromAllHostsToTimeline, toggleTimelineVisibility } from '../../lib/timeline/helpers'; +import { + createNewTimeline, + dragFromAllHostsToTimeline, + toggleTimelineVisibility, +} from '../../lib/timeline/helpers'; import { ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS } from '../../lib/hosts/selectors'; import { HOSTS_PAGE } from '../../lib/urls'; import { waitForAllHostsWidget } from '../../lib/hosts/helpers'; @@ -17,15 +21,20 @@ import { DEFAULT_TIMEOUT, loginAndWaitForPage } from '../../lib/util/helpers'; import { drag, dragWithoutDrop } from '../../lib/drag_n_drop/helpers'; describe('timeline data providers', () => { - beforeEach(() => { + before(() => { loginAndWaitForPage(HOSTS_PAGE); - }); - - it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => { waitForAllHostsWidget(); + }); + beforeEach(() => { toggleTimelineVisibility(); + }); + + afterEach(() => { + createNewTimeline(); + }); + it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => { dragFromAllHostsToTimeline(); cy.get(TIMELINE_DROPPED_DATA_PROVIDERS, { @@ -45,10 +54,6 @@ describe('timeline data providers', () => { }); it('sets the background to euiColorSuccess with a 10% alpha channel when the user starts dragging a host, but is not hovering over the data providers', () => { - waitForAllHostsWidget(); - - toggleTimelineVisibility(); - cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS) .first() .then(host => drag(host)); @@ -61,10 +66,6 @@ describe('timeline data providers', () => { }); it('sets the background to euiColorSuccess with a 20% alpha channel when the user starts dragging a host AND is hovering over the data providers', () => { - waitForAllHostsWidget(); - - toggleTimelineVisibility(); - cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS) .first() .then(host => drag(host)); @@ -81,10 +82,6 @@ describe('timeline data providers', () => { }); it('renders the dashed border color as euiColorSuccess when hovering over the data providers', () => { - waitForAllHostsWidget(); - - toggleTimelineVisibility(); - cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS) .first() .then(host => drag(host)); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts index 5b0ac03ae87dc..63fe56371a4cd 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts @@ -13,13 +13,23 @@ import { HOSTS_PAGE } from '../../lib/urls'; import { waitForAllHostsWidget } from '../../lib/hosts/helpers'; import { loginAndWaitForPage } from '../../lib/util/helpers'; import { drag } from '../../lib/drag_n_drop/helpers'; -import { toggleTimelineVisibility } from '../../lib/timeline/helpers'; +import { createNewTimeline, toggleTimelineVisibility } from '../../lib/timeline/helpers'; describe('timeline flyout button', () => { - beforeEach(() => { + before(() => { loginAndWaitForPage(HOSTS_PAGE); }); + afterEach(() => { + cy.get('[data-test-subj="kibanaChrome"]').then($page => { + if ($page.find('[data-test-subj="flyoutOverlay"]').length === 1) { + toggleTimelineVisibility(); + } + }); + + createNewTimeline(); + }); + it('toggles open the timeline', () => { toggleTimelineVisibility(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts index 9a915b0e77d44..fbf75e8a854c6 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts @@ -6,15 +6,19 @@ import { drag, drop } from '../../lib/drag_n_drop/helpers'; import { populateTimeline } from '../../lib/fields_browser/helpers'; -import { toggleFirstTimelineEventDetails } from '../../lib/timeline/helpers'; +import { createNewTimeline, toggleFirstTimelineEventDetails } from '../../lib/timeline/helpers'; import { HOSTS_PAGE } from '../../lib/urls'; import { loginAndWaitForPage, DEFAULT_TIMEOUT } from '../../lib/util/helpers'; describe('toggle column in timeline', () => { - beforeEach(() => { + before(() => { loginAndWaitForPage(HOSTS_PAGE); }); + afterEach(() => { + createNewTimeline(); + }); + const timestampField = '@timestamp'; const idField = '_id'; diff --git a/x-pack/legacy/plugins/siem/default_index_pattern.ts b/x-pack/legacy/plugins/siem/default_index_pattern.ts index 6719e245e0289..4d53aeb000c55 100644 --- a/x-pack/legacy/plugins/siem/default_index_pattern.ts +++ b/x-pack/legacy/plugins/siem/default_index_pattern.ts @@ -6,6 +6,7 @@ /** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ export const defaultIndexPattern = [ + 'apm-*-transaction*', 'auditbeat-*', 'endgame-*', 'filebeat-*', diff --git a/x-pack/legacy/plugins/siem/public/components/and_or_badge/__examples__/index.stories.tsx b/x-pack/legacy/plugins/siem/public/components/and_or_badge/__examples__/index.stories.tsx new file mode 100644 index 0000000000000..f34e9ee214537 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/and_or_badge/__examples__/index.stories.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { AndOrBadge } from '..'; + +storiesOf('components/AndOrBadge', module) + .add('and', () => ) + .add('or', () => ); diff --git a/x-pack/legacy/plugins/siem/public/components/auto_sizer/__examples__/index.stories.tsx b/x-pack/legacy/plugins/siem/public/components/auto_sizer/__examples__/index.stories.tsx new file mode 100644 index 0000000000000..414cea0d3f40d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/auto_sizer/__examples__/index.stories.tsx @@ -0,0 +1,27 @@ +/* + * 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 { storiesOf } from '@storybook/react'; +import React from 'react'; +import { AutoSizer } from '..'; + +storiesOf('components/AutoSizer', module).add('example', () => ( +
+ + {({ measureRef, content }) => ( +
+
+ {'width: '} + {content.width} +
+
+ {'height: '} + {content.height} +
+
+ )} +
+
+)); diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx new file mode 100644 index 0000000000000..4d92e8cb1335d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/__examples__/index.stories.tsx @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; +import { storiesOf } from '@storybook/react'; +import { ThemeProvider } from 'styled-components'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; +import { SuggestionItem } from '../suggestion_item'; + +const suggestion: autocomplete.QuerySuggestion = { + description: 'Description...', + end: 3, + start: 1, + text: 'Text...', + type: 'value', +}; + +storiesOf('components/SuggestionItem', module).add('example', () => ( + ({ + eui: euiLightVars, + darkMode: false, + })} + > + + +)); diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx index 27e87d25e286f..ef16f79a4b83c 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx @@ -10,13 +10,13 @@ import { mount, shallow } from 'enzyme'; import { noop } from 'lodash/fp'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { TestProviders } from '../../mock'; import { AutocompleteField } from '.'; -const mockAutoCompleteData: AutocompleteSuggestion[] = [ +const mockAutoCompleteData: autocomplete.QuerySuggestion[] = [ { type: 'field', text: 'agent.ephemeral_id ', diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx index 124ef26602f35..2f76ae21944be 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.tsx @@ -11,7 +11,7 @@ import { EuiPanel, } from '@elastic/eui'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import euiStyled from '../../../../../common/eui_styled_components'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx index aaf7be2f7f5a6..44bc65bb0dc15 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/suggestion_item.tsx @@ -9,13 +9,13 @@ import { transparentize } from 'polished'; import React from 'react'; import styled from 'styled-components'; import euiStyled from '../../../../../common/eui_styled_components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; interface SuggestionItemProps { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; } export const SuggestionItem = React.memo( diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap index 666a8249c27d8..07cbd6dfe0370 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap @@ -365,6 +365,7 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", 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 2a3e69787c05c..1f06385e12c94 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 @@ -5,11 +5,16 @@ */ import { IndexPatternMapping } from '../types'; +import { IndexPatternSavedObject } from '../../ml_popover/types'; export const mockIndexPatternIds: IndexPatternMapping[] = [ { title: 'filebeat-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' }, ]; +export const mockAPMIndexPatternIds: IndexPatternMapping[] = [ + { title: 'apm-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' }, +]; + export const mockSourceLayer = { sourceDescriptor: { id: 'uuid.v4()', @@ -113,6 +118,109 @@ export const mockDestinationLayer = { query: { query: '', language: 'kuery' }, }; +export const mockClientLayer = { + sourceDescriptor: { + id: 'uuid.v4()', + type: 'ES_SEARCH', + applyGlobalQuery: true, + geoField: 'client.geo.location', + filterByMapBounds: false, + tooltipProperties: [ + 'host.name', + 'client.ip', + 'client.domain', + 'client.geo.country_iso_code', + 'client.as.organization.name', + ], + useTopHits: false, + topHitsTimeField: '@timestamp', + topHitsSize: 1, + indexPatternId: '8c7323ac-97ad-4b53-ac0a-40f8f691a918', + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: '#6092C0' }, + }, + lineColor: { + type: 'STATIC', + options: { color: '#FFFFFF' }, + }, + lineWidth: { type: 'STATIC', options: { size: 2 } }, + iconSize: { type: 'STATIC', options: { size: 8 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbol: { + options: { symbolizeAs: 'icon', symbolId: 'home' }, + }, + }, + }, + id: 'uuid.v4()', + label: `apm-* | Client Point`, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + type: 'VECTOR', + query: { query: '', language: 'kuery' }, + joins: [], +}; + +export const mockServerLayer = { + sourceDescriptor: { + id: 'uuid.v4()', + type: 'ES_SEARCH', + applyGlobalQuery: true, + geoField: 'server.geo.location', + filterByMapBounds: true, + tooltipProperties: [ + 'host.name', + 'server.ip', + 'server.domain', + 'server.geo.country_iso_code', + 'server.as.organization.name', + ], + useTopHits: false, + topHitsTimeField: '@timestamp', + topHitsSize: 1, + indexPatternId: '8c7323ac-97ad-4b53-ac0a-40f8f691a918', + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: '#D36086' }, + }, + lineColor: { + type: 'STATIC', + options: { color: '#FFFFFF' }, + }, + lineWidth: { type: 'STATIC', options: { size: 2 } }, + iconSize: { type: 'STATIC', options: { size: 8 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbol: { + options: { symbolizeAs: 'icon', symbolId: 'marker' }, + }, + }, + }, + id: 'uuid.v4()', + label: `apm-* | Server Point`, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + type: 'VECTOR', + query: { query: '', language: 'kuery' }, +}; + export const mockLineLayer = { sourceDescriptor: { type: 'ES_PEW_PEW', @@ -173,6 +281,66 @@ export const mockLineLayer = { query: { query: '', language: 'kuery' }, }; +export const mockClientServerLineLayer = { + sourceDescriptor: { + type: 'ES_PEW_PEW', + applyGlobalQuery: true, + id: 'uuid.v4()', + indexPatternId: '8c7323ac-97ad-4b53-ac0a-40f8f691a918', + sourceGeoField: 'client.geo.location', + destGeoField: 'server.geo.location', + metrics: [ + { type: 'sum', field: 'client.bytes', label: 'client.bytes' }, + { type: 'sum', field: 'server.bytes', label: 'server.bytes' }, + ], + }, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { color: '#1EA593' }, + }, + lineColor: { + type: 'STATIC', + options: { color: '#6092C0' }, + }, + lineWidth: { + type: 'DYNAMIC', + options: { + field: { + label: 'count', + name: 'doc_count', + origin: 'source', + }, + minSize: 1, + maxSize: 8, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, + }, + }, + iconSize: { type: 'STATIC', options: { size: 10 } }, + iconOrientation: { + type: 'STATIC', + options: { orientation: 0 }, + }, + symbol: { + options: { symbolizeAs: 'circle', symbolId: 'airfield' }, + }, + }, + }, + id: 'uuid.v4()', + label: `apm-* | Line`, + minZoom: 0, + maxZoom: 24, + alpha: 0.5, + visible: true, + type: 'VECTOR', + query: { query: '', language: 'kuery' }, +}; + export const mockLayerList = [ { sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true }, @@ -209,3 +377,83 @@ export const mockLayerListDouble = [ mockDestinationLayer, mockSourceLayer, ]; + +export const mockLayerListMixed = [ + { + sourceDescriptor: { type: 'EMS_TMS', isAutoSelect: true }, + id: 'uuid.v4()', + label: null, + minZoom: 0, + maxZoom: 24, + alpha: 1, + visible: true, + style: null, + type: 'VECTOR_TILE', + }, + mockLineLayer, + mockDestinationLayer, + mockSourceLayer, + mockClientServerLineLayer, + mockServerLayer, + mockClientLayer, +]; + +export const mockAPMIndexPattern: IndexPatternSavedObject = { + id: 'apm-*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'apm-*', + }, +}; + +export const mockAPMRegexIndexPattern: IndexPatternSavedObject = { + id: 'apm-7.*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'apm-7.*', + }, +}; + +export const mockFilebeatIndexPattern: IndexPatternSavedObject = { + id: 'filebeat-*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'filebeat-*', + }, +}; + +export const mockAuditbeatIndexPattern: IndexPatternSavedObject = { + id: 'auditbeat-*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'auditbeat-*', + }, +}; + +export const mockAPMTransactionIndexPattern: IndexPatternSavedObject = { + id: 'apm-*-transaction*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: 'apm-*-transaction*', + }, +}; + +export const mockGlobIndexPattern: IndexPatternSavedObject = { + id: '*', + type: 'index-pattern', + updated_at: '', + version: 'abc', + attributes: { + title: '*', + }, +}; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index 771e220a2a0b3..cbbb4f8c6249e 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -18,7 +18,7 @@ import { Loader } from '../loader'; import { displayErrorToast, useStateToaster } from '../toasters'; import { Embeddable } from './embeddable'; import { EmbeddableHeader } from './embeddable_header'; -import { createEmbeddable } from './embedded_map_helpers'; +import { createEmbeddable, findMatchingIndexPatterns } from './embedded_map_helpers'; import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; @@ -107,10 +107,12 @@ export const EmbeddedMapComponent = ({ useEffect(() => { let isSubscribed = true; async function setupEmbeddable() { - // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import - const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => - siemDefaultIndices.includes(ip.attributes.title) - ); + // Ensure at least one `siem:defaultIndex` kibana index pattern exists before creating embeddable + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns, + siemDefaultIndices, + }); + if (matchingIndexPatterns.length === 0 && isSubscribed) { setIsLoading(false); setIsIndexError(true); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx index a83e8377deeb6..0ffb13cd66028 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx @@ -4,9 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createEmbeddable } from './embedded_map_helpers'; +import { createEmbeddable, findMatchingIndexPatterns } from './embedded_map_helpers'; import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; import { createPortalNode } from 'react-reverse-portal'; +import { + mockAPMIndexPattern, + mockAPMRegexIndexPattern, + mockAPMTransactionIndexPattern, + mockAuditbeatIndexPattern, + mockFilebeatIndexPattern, + mockGlobIndexPattern, +} from './__mocks__/mock'; jest.mock('ui/new_platform'); @@ -51,4 +59,58 @@ describe('embedded_map_helpers', () => { expect(embeddable.reload).toHaveBeenCalledTimes(1); }); }); + + describe('findMatchingIndexPatterns', () => { + const siemDefaultIndices = [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'packetbeat-*', + 'winlogbeat-*', + ]; + + test('finds exact matching index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockFilebeatIndexPattern, mockAuditbeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockFilebeatIndexPattern, mockAuditbeatIndexPattern]); + }); + + test('finds glob-matched index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockAPMIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockAPMIndexPattern, mockFilebeatIndexPattern]); + }); + + test('does not find glob-matched index pattern containing regex', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockAPMRegexIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockFilebeatIndexPattern]); + }); + + test('finds exact glob-matched index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockAPMTransactionIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([ + mockAPMTransactionIndexPattern, + mockFilebeatIndexPattern, + ]); + }); + + test('finds glob-only index patterns ', () => { + const matchingIndexPatterns = findMatchingIndexPatterns({ + kibanaIndexPatterns: [mockGlobIndexPattern, mockFilebeatIndexPattern], + siemDefaultIndices, + }); + expect(matchingIndexPatterns).toEqual([mockGlobIndexPattern, mockFilebeatIndexPattern]); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx index 838e74cc5624c..2d4714401f3b3 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx @@ -7,6 +7,7 @@ import uuid from 'uuid'; import React from 'react'; import { OutPortal, PortalNode } from 'react-reverse-portal'; +import minimatch from 'minimatch'; import { ViewMode } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { IndexPatternMapping, @@ -20,6 +21,7 @@ import { getLayerList } from './map_config'; import { MAP_SAVED_OBJECT_TYPE } from '../../../../maps/common/constants'; import * as i18n from './translations'; import { Query, esFilters } from '../../../../../../../src/plugins/data/public'; +import { IndexPatternSavedObject } from '../ml_popover/types'; /** * Creates MapEmbeddable with provided initial configuration @@ -108,3 +110,25 @@ export const createEmbeddable = async ( return embeddableObject; }; + +/** + * Returns kibanaIndexPatterns that wildcard match at least one of siemDefaultIndices + * + * @param kibanaIndexPatterns + * @param siemDefaultIndices + */ +export const findMatchingIndexPatterns = ({ + kibanaIndexPatterns, + siemDefaultIndices, +}: { + kibanaIndexPatterns: IndexPatternSavedObject[]; + siemDefaultIndices: string[]; +}): IndexPatternSavedObject[] => { + try { + return kibanaIndexPatterns.filter(kip => + siemDefaultIndices.some(sdi => minimatch(sdi, kip.attributes.title)) + ); + } catch { + return []; + } +}; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts index 4004db5f21795..1707293ff6fd8 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_config.test.ts @@ -4,13 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getDestinationLayer, getLayerList, getLineLayer, getSourceLayer } from './map_config'; +import { getDestinationLayer, getLayerList, getLineLayer, getSourceLayer, lmc } from './map_config'; import { + mockAPMIndexPatternIds, + mockClientLayer, + mockClientServerLineLayer, mockDestinationLayer, mockIndexPatternIds, mockLayerList, mockLayerListDouble, + mockLayerListMixed, mockLineLayer, + mockServerLayer, mockSourceLayer, } from './__mocks__/mock'; @@ -32,29 +37,70 @@ describe('map_config', () => { const layerList = getLayerList([...mockIndexPatternIds, ...mockIndexPatternIds]); expect(layerList).toStrictEqual(mockLayerListDouble); }); + + test('it returns the complete layerList for multiple indices with custom layer mapping', () => { + const layerList = getLayerList([...mockIndexPatternIds, ...mockAPMIndexPatternIds]); + expect(layerList).toStrictEqual(mockLayerListMixed); + }); }); describe('#getSourceLayer', () => { test('it returns a source layer', () => { - const layerList = getSourceLayer(mockIndexPatternIds[0].title, mockIndexPatternIds[0].id); + const layerList = getSourceLayer( + mockIndexPatternIds[0].title, + mockIndexPatternIds[0].id, + lmc.default.source + ); expect(layerList).toStrictEqual(mockSourceLayer); }); + + test('it returns a source layer for custom layer mapping', () => { + const layerList = getSourceLayer( + mockAPMIndexPatternIds[0].title, + mockAPMIndexPatternIds[0].id, + lmc[mockAPMIndexPatternIds[0].title].source + ); + expect(layerList).toStrictEqual(mockClientLayer); + }); }); describe('#getDestinationLayer', () => { test('it returns a destination layer', () => { const layerList = getDestinationLayer( mockIndexPatternIds[0].title, - mockIndexPatternIds[0].id + mockIndexPatternIds[0].id, + lmc.default.destination ); expect(layerList).toStrictEqual(mockDestinationLayer); }); + + test('it returns a destination layer for custom layer mapping', () => { + const layerList = getDestinationLayer( + mockAPMIndexPatternIds[0].title, + mockAPMIndexPatternIds[0].id, + lmc[mockAPMIndexPatternIds[0].title].destination + ); + expect(layerList).toStrictEqual(mockServerLayer); + }); }); describe('#getLineLayer', () => { test('it returns a line layer', () => { - const layerList = getLineLayer(mockIndexPatternIds[0].title, mockIndexPatternIds[0].id); + const layerList = getLineLayer( + mockIndexPatternIds[0].title, + mockIndexPatternIds[0].id, + lmc.default + ); expect(layerList).toStrictEqual(mockLineLayer); }); + + test('it returns a line layer for custom layer mapping', () => { + const layerList = getLineLayer( + mockAPMIndexPatternIds[0].title, + mockAPMIndexPatternIds[0].id, + lmc[mockAPMIndexPatternIds[0].title] + ); + expect(layerList).toStrictEqual(mockClientServerLineLayer); + }); }); }); 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 f2d4505ffd1bf..f34376421e9b2 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 @@ -6,11 +6,16 @@ import uuid from 'uuid'; import { euiPaletteColorBlind } from '@elastic/eui'; -import { IndexPatternMapping } from './types'; +import { + IndexPatternMapping, + LayerMapping, + LayerMappingCollection, + LayerMappingDetails, +} from './types'; import * as i18n from './translations'; const euiVisColorPalette = euiPaletteColorBlind(); -// Update source/destination field mappings to modify what fields will be returned to map tooltip +// Update field mappings to modify what fields will be returned to map tooltip const sourceFieldMappings: Record = { 'host.name': i18n.HOST, 'source.ip': i18n.SOURCE_IP, @@ -25,16 +30,67 @@ const destinationFieldMappings: Record = { 'destination.geo.country_iso_code': i18n.LOCATION, 'destination.as.organization.name': i18n.ASN, }; +const clientFieldMappings: Record = { + 'host.name': i18n.HOST, + 'client.ip': i18n.CLIENT_IP, + 'client.domain': i18n.CLIENT_DOMAIN, + 'client.geo.country_iso_code': i18n.LOCATION, + 'client.as.organization.name': i18n.ASN, +}; +const serverFieldMappings: Record = { + 'host.name': i18n.HOST, + 'server.ip': i18n.SERVER_IP, + 'server.domain': i18n.SERVER_DOMAIN, + 'server.geo.country_iso_code': i18n.LOCATION, + 'server.as.organization.name': i18n.ASN, +}; // Mapping of field -> display name for use within map tooltip export const sourceDestinationFieldMappings: Record = { ...sourceFieldMappings, ...destinationFieldMappings, + ...clientFieldMappings, + ...serverFieldMappings, }; // Field names of LineLayer props returned from Maps API export const SUM_OF_SOURCE_BYTES = 'sum_of_source.bytes'; export const SUM_OF_DESTINATION_BYTES = 'sum_of_destination.bytes'; +export const SUM_OF_CLIENT_BYTES = 'sum_of_client.bytes'; +export const SUM_OF_SERVER_BYTES = 'sum_of_server.bytes'; + +// Mapping to fields for creating specific layers for a given index pattern +// e.g. The apm-* index pattern needs layers for client/server instead of source/destination +export const lmc: LayerMappingCollection = { + default: { + source: { + metricField: 'source.bytes', + geoField: 'source.geo.location', + tooltipProperties: Object.keys(sourceFieldMappings), + label: i18n.SOURCE_LAYER, + }, + destination: { + metricField: 'destination.bytes', + geoField: 'destination.geo.location', + tooltipProperties: Object.keys(destinationFieldMappings), + label: i18n.DESTINATION_LAYER, + }, + }, + 'apm-*': { + source: { + metricField: 'client.bytes', + geoField: 'client.geo.location', + tooltipProperties: Object.keys(clientFieldMappings), + label: i18n.CLIENT_LAYER, + }, + destination: { + metricField: 'server.bytes', + geoField: 'server.geo.location', + tooltipProperties: Object.keys(serverFieldMappings), + label: i18n.SERVER_LAYER, + }, + }, +}; /** * Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source, @@ -58,9 +114,9 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { ...indexPatternIds.reduce((acc: object[], { title, id }) => { return [ ...acc, - getLineLayer(title, id), - getDestinationLayer(title, id), - getSourceLayer(title, id), + getLineLayer(title, id, lmc[title] ?? lmc.default), + getDestinationLayer(title, id, lmc[title]?.destination ?? lmc.default.destination), + getSourceLayer(title, id, lmc[title]?.source ?? lmc.default.source), ]; }, []), ]; @@ -72,15 +128,20 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Source point" * @param indexPatternId used as layer's indexPattern to query for data + * @param layerDetails layer-specific field details */ -export const getSourceLayer = (indexPatternTitle: string, indexPatternId: string) => ({ +export const getSourceLayer = ( + indexPatternTitle: string, + indexPatternId: string, + layerDetails: LayerMappingDetails +) => ({ sourceDescriptor: { id: uuid.v4(), type: 'ES_SEARCH', applyGlobalQuery: true, - geoField: 'source.geo.location', + geoField: layerDetails.geoField, filterByMapBounds: false, - tooltipProperties: Object.keys(sourceFieldMappings), + tooltipProperties: layerDetails.tooltipProperties, useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, @@ -109,7 +170,7 @@ export const getSourceLayer = (indexPatternTitle: string, indexPatternId: string }, }, id: uuid.v4(), - label: `${indexPatternTitle} | ${i18n.SOURCE_LAYER}`, + label: `${indexPatternTitle} | ${layerDetails.label}`, minZoom: 0, maxZoom: 24, alpha: 1, @@ -125,15 +186,21 @@ export const getSourceLayer = (indexPatternTitle: string, indexPatternId: string * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Destination point" * @param indexPatternId used as layer's indexPattern to query for data + * @param layerDetails layer-specific field details + * */ -export const getDestinationLayer = (indexPatternTitle: string, indexPatternId: string) => ({ +export const getDestinationLayer = ( + indexPatternTitle: string, + indexPatternId: string, + layerDetails: LayerMappingDetails +) => ({ sourceDescriptor: { id: uuid.v4(), type: 'ES_SEARCH', applyGlobalQuery: true, - geoField: 'destination.geo.location', + geoField: layerDetails.geoField, filterByMapBounds: true, - tooltipProperties: Object.keys(destinationFieldMappings), + tooltipProperties: layerDetails.tooltipProperties, useTopHits: false, topHitsTimeField: '@timestamp', topHitsSize: 1, @@ -162,7 +229,7 @@ export const getDestinationLayer = (indexPatternTitle: string, indexPatternId: s }, }, id: uuid.v4(), - label: `${indexPatternTitle} | ${i18n.DESTINATION_LAYER}`, + label: `${indexPatternTitle} | ${layerDetails.label}`, minZoom: 0, maxZoom: 24, alpha: 1, @@ -177,18 +244,31 @@ export const getDestinationLayer = (indexPatternTitle: string, indexPatternId: s * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Line" * @param indexPatternId used as layer's indexPattern to query for data + * @param layerDetails layer-specific field details */ -export const getLineLayer = (indexPatternTitle: string, indexPatternId: string) => ({ +export const getLineLayer = ( + indexPatternTitle: string, + indexPatternId: string, + layerDetails: LayerMapping +) => ({ sourceDescriptor: { type: 'ES_PEW_PEW', applyGlobalQuery: true, id: uuid.v4(), indexPatternId, - sourceGeoField: 'source.geo.location', - destGeoField: 'destination.geo.location', + sourceGeoField: layerDetails.source.geoField, + destGeoField: layerDetails.destination.geoField, metrics: [ - { type: 'sum', field: 'source.bytes', label: 'source.bytes' }, - { type: 'sum', field: 'destination.bytes', label: 'destination.bytes' }, + { + type: 'sum', + field: layerDetails.source.metricField, + label: layerDetails.source.metricField, + }, + { + type: 'sum', + field: layerDetails.destination.metricField, + label: layerDetails.destination.metricField, + }, ], }, style: { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap index 05f507ec3a775..bf62efe6a8263 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/line_tool_tip_content.test.tsx.snap @@ -49,3 +49,53 @@ exports[`LineToolTipContent renders correctly against snapshot 1`] = `
`; + +exports[`LineToolTipContent renders correctly against snapshot when rendering client & server 1`] = ` + + + + + + Client + + + + + + + + + + Server + + + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx index 824c717427763..a0e57a2e850c1 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx @@ -8,7 +8,12 @@ import { shallow } from 'enzyme'; import React from 'react'; import { LineToolTipContentComponent } from './line_tool_tip_content'; import { FeatureProperty } from '../types'; -import { SUM_OF_DESTINATION_BYTES, SUM_OF_SOURCE_BYTES } from '../map_config'; +import { + SUM_OF_CLIENT_BYTES, + SUM_OF_DESTINATION_BYTES, + SUM_OF_SERVER_BYTES, + SUM_OF_SOURCE_BYTES, +} from '../map_config'; describe('LineToolTipContent', () => { const mockFeatureProps: FeatureProperty[] = [ @@ -22,10 +27,31 @@ describe('LineToolTipContent', () => { }, ]; + const mockClientServerFeatureProps: FeatureProperty[] = [ + { + _propertyKey: SUM_OF_SERVER_BYTES, + _rawValue: 'testPropValue', + }, + { + _propertyKey: SUM_OF_CLIENT_BYTES, + _rawValue: 'testPropValue', + }, + ]; + test('renders correctly against snapshot', () => { const wrapper = shallow( ); expect(wrapper).toMatchSnapshot(); }); + + test('renders correctly against snapshot when rendering client & server', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx index 0c416868bfb03..eff4769944765 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx @@ -8,7 +8,12 @@ import React from 'react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { SourceDestinationArrows } from '../../source_destination/source_destination_arrows'; -import { SUM_OF_DESTINATION_BYTES, SUM_OF_SOURCE_BYTES } from '../map_config'; +import { + SUM_OF_CLIENT_BYTES, + SUM_OF_DESTINATION_BYTES, + SUM_OF_SERVER_BYTES, + SUM_OF_SOURCE_BYTES, +} from '../map_config'; import { FeatureProperty } from '../types'; import * as i18n from '../translations'; @@ -38,25 +43,29 @@ export const LineToolTipContentComponent = ({ {} ); + const isSrcDest = Object.keys(lineProps).includes(SUM_OF_SOURCE_BYTES); + return ( - {i18n.SOURCE} + {isSrcDest ? i18n.SOURCE : i18n.CLIENT} - {i18n.DESTINATION} + {isSrcDest ? i18n.DESTINATION : i18n.SERVER} diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx index 8741cfaa26ca6..a9d70e4f35d35 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx @@ -12,6 +12,7 @@ import { TestProviders } from '../../../mock'; import { getEmptyStringTag } from '../../empty_value'; import { HostDetailsLink, IPDetailsLink } from '../../links'; import { useMountAppended } from '../../../utils/use_mount_appended'; +import { FlowTarget } from '../../../graphql/types'; jest.mock('../../search_bar', () => ({ siemFilterManager: { @@ -91,13 +92,15 @@ describe('PointToolTipContent', () => { test('it returns IPDetailsLink if field is source.ip', () => { const value = '127.0.0.1'; - expect(getRenderedFieldValue('source.ip', value)).toStrictEqual(); + expect(getRenderedFieldValue('source.ip', value)).toStrictEqual( + + ); }); test('it returns IPDetailsLink if field is destination.ip', () => { const value = '127.0.0.1'; expect(getRenderedFieldValue('destination.ip', value)).toStrictEqual( - + ); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx index b0d21b100eef3..c635061ca7b7a 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx @@ -15,6 +15,7 @@ import { DescriptionListStyled } from '../../page'; import { FeatureProperty } from '../types'; import { HostDetailsLink, IPDetailsLink } from '../../links'; import { DefaultFieldRenderer } from '../../field_renderers/field_renderers'; +import { FlowTarget } from '../../../graphql/types'; interface PointToolTipContentProps { contextId: string; @@ -66,7 +67,8 @@ export const getRenderedFieldValue = (field: string, value: string) => { } else if (['host.name'].includes(field)) { return ; } else if (['source.ip', 'destination.ip'].includes(field)) { - return ; + const flowTarget = field.split('.')[0] as FlowTarget; + return ; } return <>{value}; }; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts index 958619bee19d3..1e99a7219d427 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts @@ -41,6 +41,20 @@ export const DESTINATION_LAYER = i18n.translate( } ); +export const CLIENT_LAYER = i18n.translate( + 'xpack.siem.components.embeddables.embeddedMap.clientLayerLabel', + { + defaultMessage: 'Client Point', + } +); + +export const SERVER_LAYER = i18n.translate( + 'xpack.siem.components.embeddables.embeddedMap.serverLayerLabel', + { + defaultMessage: 'Server Point', + } +); + export const LINE_LAYER = i18n.translate( 'xpack.siem.components.embeddables.embeddedMap.lineLayerLabel', { @@ -118,6 +132,20 @@ export const DESTINATION_IP = i18n.translate( } ); +export const CLIENT_IP = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.clientIPTitle', + { + defaultMessage: 'Client IP', + } +); + +export const SERVER_IP = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.serverIPTitle', + { + defaultMessage: 'Server IP', + } +); + export const SOURCE_DOMAIN = i18n.translate( 'xpack.siem.components.embeddables.mapToolTip.pointContent.sourceDomainTitle', { @@ -132,6 +160,20 @@ export const DESTINATION_DOMAIN = i18n.translate( } ); +export const CLIENT_DOMAIN = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.clientDomainTitle', + { + defaultMessage: 'Client domain', + } +); + +export const SERVER_DOMAIN = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.pointContent.serverDomainTitle', + { + defaultMessage: 'Server domain', + } +); + export const LOCATION = i18n.translate( 'xpack.siem.components.embeddables.mapToolTip.pointContent.locationTitle', { @@ -159,3 +201,17 @@ export const DESTINATION = i18n.translate( defaultMessage: 'Destination', } ); + +export const CLIENT = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.lineContent.clientLabel', + { + defaultMessage: 'Client', + } +); + +export const SERVER = i18n.translate( + 'xpack.siem.components.embeddables.mapToolTip.lineContent.serverLabel', + { + defaultMessage: 'Server', + } +); 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 fdda9f949280a..6715a83e1b509 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts @@ -31,6 +31,22 @@ export interface IndexPatternMapping { id: string; } +export interface LayerMappingDetails { + metricField: string; + geoField: string; + tooltipProperties: string[]; + label: string; +} + +export interface LayerMapping { + source: LayerMappingDetails; + destination: LayerMappingDetails; +} + +export interface LayerMappingCollection { + [indexPatternTitle: string]: LayerMapping; +} + export type SetQuery = (params: { id: string; inspect: inputsModel.InspectQuery | null; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap index 4cf7cbb43cdc7..33ed6a8c87b5f 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/event_details/__snapshots__/event_details.test.tsx.snap @@ -373,6 +373,7 @@ exports[`EventDetails rendering should match snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", @@ -1064,6 +1065,7 @@ In other use cases the message field can be used to concatenate different values "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap index a9b48c8ee16be..efc4d4be9e957 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_target_select.test.tsx.snap @@ -7,7 +7,7 @@ exports[`FlowTargetSelect Component rendering it renders the FlowTargetSelect 1` hasDividers={false} isInvalid={false} isLoading={false} - onChange={[Function]} + onChange={[MockFunction]} options={ Array [ Object { diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx index 373cc3cdc4a92..67006d8a7a121 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx @@ -7,7 +7,6 @@ import { mount, shallow } from 'enzyme'; import { clone } from 'lodash/fp'; import React from 'react'; -import { ActionCreator } from 'typescript-fsa'; import { FlowDirection, FlowTarget } from '../../graphql/types'; @@ -21,9 +20,7 @@ describe('FlowTargetSelect Component', () => { selectedDirection: FlowDirection.uniDirectional, isLoading: false, selectedTarget: FlowTarget.source, - updateFlowTargetAction: (jest.fn() as unknown) as ActionCreator<{ - flowTarget: FlowTarget; - }>, + updateFlowTargetAction: jest.fn(), }; describe('rendering', () => { @@ -51,10 +48,7 @@ describe('FlowTargetSelect Component', () => { wrapper.update(); - // @ts-ignore property mock does not exists - expect(mockProps.updateFlowTargetAction.mock.calls[0][0]).toEqual({ - flowTarget: 'destination', - }); + expect(mockProps.updateFlowTargetAction.mock.calls[0][0]).toEqual('destination'); }); test('when selectedDirection=unidirectional only source/destination are options', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.tsx b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.tsx index f5dd4650159cd..15d1c66363837 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.tsx @@ -5,8 +5,7 @@ */ import { EuiSuperSelect } from '@elastic/eui'; -import React, { useCallback } from 'react'; -import { ActionCreator } from 'typescript-fsa'; +import React from 'react'; import { FlowDirection, FlowTarget } from '../../graphql/types'; @@ -45,47 +44,31 @@ interface OwnProps { selectedTarget: FlowTarget; displayTextOverride?: string[]; selectedDirection?: FlowDirection; - updateFlowTargetAction: ActionCreator<{ flowTarget: FlowTarget }>; + updateFlowTargetAction: (flowTarget: FlowTarget) => void; } -const onChangeTarget = ( - flowTarget: FlowTarget, - updateFlowTargetSelectAction: ActionCreator<{ flowTarget: FlowTarget }> -) => { - updateFlowTargetSelectAction({ flowTarget }); -}; - export type FlowTargetSelectProps = OwnProps; -export const FlowTargetSelect = React.memo( - ({ - id, - isLoading = false, - selectedDirection, - selectedTarget, - displayTextOverride = [], - updateFlowTargetAction, - }) => { - const handleChange = useCallback( - (newFlowTarget: FlowTarget) => onChangeTarget(newFlowTarget, updateFlowTargetAction), - [updateFlowTargetAction] - ); - - return ( - - option.directions.includes(selectedDirection) - ) - : toggleTargetOptions(id, displayTextOverride) - } - valueOfSelected={selectedTarget} - onChange={handleChange} - isLoading={isLoading} - /> - ); - } +const FlowTargetSelectComponent: React.FC = ({ + id, + isLoading = false, + selectedDirection, + selectedTarget, + displayTextOverride = [], + updateFlowTargetAction, +}) => ( + + option.directions.includes(selectedDirection) + ) + : toggleTargetOptions(id, displayTextOverride) + } + valueOfSelected={selectedTarget} + onChange={updateFlowTargetAction} + isLoading={isLoading} + /> ); -FlowTargetSelect.displayName = 'FlowTargetSelect'; +export const FlowTargetSelect = React.memo(FlowTargetSelectComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx index 48d34451404be..ba97e8d61451c 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx @@ -59,13 +59,13 @@ const getDataProvider = ({ and: [], }); -const NonDecoratedIp = React.memo<{ +const NonDecoratedIpComponent: React.FC<{ contextId: string; eventId: string; fieldName: string; truncate?: boolean; value: string | object | null | undefined; -}>(({ contextId, eventId, fieldName, truncate, value }) => ( +}> = ({ contextId, eventId, fieldName, truncate, value }) => ( -)); +); -NonDecoratedIp.displayName = 'NonDecoratedIp'; +const NonDecoratedIp = React.memo(NonDecoratedIpComponent); -const AddressLinks = React.memo<{ +const AddressLinksComponent: React.FC<{ addresses: string[]; contextId: string; eventId: string; fieldName: string; truncate?: boolean; -}>(({ addresses, contextId, eventId, fieldName, truncate }) => ( +}> = ({ addresses, contextId, eventId, fieldName, truncate }) => ( <> {uniq(addresses).map(address => ( ))} -)); +); -AddressLinks.displayName = 'AddressLinks'; +const AddressLinks = React.memo(AddressLinksComponent); -export const FormattedIp = React.memo<{ +const FormattedIpComponent: React.FC<{ contextId: string; eventId: string; fieldName: string; truncate?: boolean; value: string | object | null | undefined; -}>(({ contextId, eventId, fieldName, truncate, value }) => { +}> = ({ contextId, eventId, fieldName, truncate, value }) => { if (isString(value) && !isEmpty(value)) { try { const addresses = JSON.parse(value); @@ -173,6 +173,6 @@ export const FormattedIp = React.memo<{ /> ); } -}); +}; -FormattedIp.displayName = 'FormattedIp'; +export const FormattedIp = React.memo(FormattedIpComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap index 0199742242e59..a2c71b914b989 100644 --- a/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ip/__snapshots__/index.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Port renders correctly against snapshot 1`] = ` - { .find('a') .first() .props().href - ).toEqual('#/link-to/network/ip/10.1.2.3'); + ).toEqual('#/link-to/network/ip/10.1.2.3/source'); }); }); 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 b19ed8e44be9d..7c15af3fe642a 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 @@ -55,7 +55,7 @@ export const LinkToPage = React.memo(({ match }) => ( /> ; export const RedirectToNetworkPage = ({ match: { - params: { detailName }, + params: { detailName, flowTarget }, }, location: { search }, }: NetworkComponentProps) => ( @@ -32,4 +34,7 @@ export const RedirectToNetworkPage = ({ const baseNetworkUrl = `#/link-to/${SiemPageName.network}`; export const getNetworkUrl = () => baseNetworkUrl; -export const getIPDetailsUrl = (detailName: string) => `${baseNetworkUrl}/ip/${detailName}`; +export const getIPDetailsUrl = ( + detailName: string, + flowTarget?: FlowTarget | FlowTargetSourceDest +) => `${baseNetworkUrl}/ip/${detailName}/${flowTarget || FlowTarget.source}`; diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx index dd5c3bf8bb5d5..ceef7e353b521 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx @@ -50,7 +50,7 @@ describe('Custom Links', () => { test('should render valid link to IP Details with ipv4 as the display text', () => { const wrapper = mount(); expect(wrapper.find('EuiLink').prop('href')).toEqual( - `#/link-to/network/ip/${encodeURIComponent(ipv4)}` + `#/link-to/network/ip/${encodeURIComponent(ipv4)}/source` ); expect(wrapper.text()).toEqual(ipv4); }); @@ -58,7 +58,7 @@ describe('Custom Links', () => { test('should render valid link to IP Details with child text as the display text', () => { const wrapper = mount({hostName}); expect(wrapper.find('EuiLink').prop('href')).toEqual( - `#/link-to/network/ip/${encodeURIComponent(ipv4)}` + `#/link-to/network/ip/${encodeURIComponent(ipv4)}/source` ); expect(wrapper.text()).toEqual(hostName); }); @@ -66,7 +66,7 @@ describe('Custom Links', () => { test('should render valid link to IP Details with ipv6 as the display text', () => { const wrapper = mount(); expect(wrapper.find('EuiLink').prop('href')).toEqual( - `#/link-to/network/ip/${encodeURIComponent(ipv6Encoded)}` + `#/link-to/network/ip/${encodeURIComponent(ipv6Encoded)}/source` ); expect(wrapper.text()).toEqual(ipv6); }); diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.tsx index f63d13fcda7f0..e122b3e235a9e 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.tsx @@ -9,27 +9,31 @@ import React from 'react'; import { encodeIpv6 } from '../../lib/helpers'; import { getHostDetailsUrl, getIPDetailsUrl } from '../link_to'; +import { FlowTarget, FlowTargetSourceDest } from '../../graphql/types'; // Internal Links -export const HostDetailsLink = React.memo<{ children?: React.ReactNode; hostName: string }>( - ({ children, hostName }) => ( - - {children ? children : hostName} - - ) +const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; hostName: string }> = ({ + children, + hostName, +}) => ( + + {children ? children : hostName} + ); -HostDetailsLink.displayName = 'HostDetailsLink'; +export const HostDetailsLink = React.memo(HostDetailsLinkComponent); -export const IPDetailsLink = React.memo<{ children?: React.ReactNode; ip: string }>( - ({ children, ip }) => ( - - {children ? children : ip} - - ) +const IPDetailsLinkComponent: React.FC<{ + children?: React.ReactNode; + ip: string; + flowTarget?: FlowTarget | FlowTargetSourceDest; +}> = ({ children, ip, flowTarget = FlowTarget.source }) => ( + + {children ? children : ip} + ); -IPDetailsLink.displayName = 'IPDetailsLink'; +export const IPDetailsLink = React.memo(IPDetailsLinkComponent); // External Links export const GoogleLink = React.memo<{ children?: React.ReactNode; link: string }>( diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx index 32dce0e2e5576..56ebbb06f3eb9 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { ScaleType } from '@elastic/charts'; import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; @@ -26,14 +26,12 @@ import { HistogramAggregation, MatrixHistogramQueryProps, } from './types'; -import { generateTablePaginationOptions } from '../paginated_table/helpers'; import { ChartSeriesData } from '../charts/common'; import { InspectButtonContainer } from '../inspect'; export const MatrixHistogramComponent: React.FC = ({ activePage, - dataKey, defaultStackByOption, endDate, @@ -45,12 +43,10 @@ export const MatrixHistogramComponent: React.FC - activePage != null && limit != null - ? generateTablePaginationOptions(activePage, limit) - : undefined; const { data, loading, inspect, totalCount, refetch = noop } = useQuery<{}, HistogramAggregation>( { @@ -118,16 +110,13 @@ export const MatrixHistogramComponent: React.FC getPagination(), [activePage, limit]), stackByField: selectedStackByOption.value, } ); @@ -179,7 +168,7 @@ export const MatrixHistogramComponent: React.FC - {stackByOptions && ( + {stackByOptions?.length > 1 && ( ( - ({ startDate, endDate, narrowDateRange, hostName, skip, type }): JSX.Element | null => { - const capabilities = useContext(MlCapabilitiesContext); - const [loading, tableData] = useAnomaliesTableData({ - startDate, - endDate, - skip, - criteriaFields: getCriteriaFromHostType(type, hostName), - }); +const AnomaliesHostTableComponent: React.FC = ({ + startDate, + endDate, + narrowDateRange, + hostName, + skip, + type, +}) => { + const capabilities = useContext(MlCapabilitiesContext); + const [loading, tableData] = useAnomaliesTableData({ + startDate, + endDate, + skip, + criteriaFields: getCriteriaFromHostType(type, hostName), + }); - const hosts = convertAnomaliesToHosts(tableData, hostName); + const hosts = convertAnomaliesToHosts(tableData, hostName); - const interval = getIntervalFromAnomalies(tableData); - const columns = getAnomaliesHostTableColumnsCurated( - type, - startDate, - endDate, - interval, - narrowDateRange - ); - const pagination = { - initialPageIndex: 0, - initialPageSize: 10, - totalItemCount: hosts.length, - pageSizeOptions: [5, 10, 20, 50], - hidePerPageOptions: false, - }; + const interval = getIntervalFromAnomalies(tableData); + const columns = getAnomaliesHostTableColumnsCurated( + type, + startDate, + endDate, + interval, + narrowDateRange + ); + const pagination = { + initialPageIndex: 0, + initialPageSize: 10, + totalItemCount: hosts.length, + pageSizeOptions: [5, 10, 20, 50], + hidePerPageOptions: false, + }; - if (!hasMlUserPermissions(capabilities)) { - return null; - } else { - return ( - - + if (!hasMlUserPermissions(capabilities)) { + return null; + } else { + return ( + + - type is not as specific as EUI's... - columns={columns} - compressed - // @ts-ignore ...which leads to `networks` not "matching" the columns - items={hosts} - pagination={pagination} - sorting={sorting} - /> + type is not as specific as EUI's... + columns={columns} + compressed + // @ts-ignore ...which leads to `networks` not "matching" the columns + items={hosts} + pagination={pagination} + sorting={sorting} + /> - {loading && ( - - )} - - ); - } - }, - hostEquality -); + {loading && ( + + )} + + ); + } +}; -AnomaliesHostTable.displayName = 'AnomaliesHostTable'; +export const AnomaliesHostTable = React.memo(AnomaliesHostTableComponent, hostEquality); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/anomalies_network_table.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/anomalies_network_table.tsx index 341e1c6e49235..05f3044ff2929 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/tables/anomalies_network_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/anomalies_network_table.tsx @@ -13,7 +13,6 @@ import { convertAnomaliesToNetwork } from './convert_anomalies_to_network'; import { Loader } from '../../loader'; import { AnomaliesNetworkTableProps } from '../types'; import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns'; -import { getIntervalFromAnomalies } from '../anomaly/get_interval_from_anomalies'; import { hasMlUserPermissions } from '../permissions/has_ml_user_permissions'; import { MlCapabilitiesContext } from '../permissions/ml_capabilities_provider'; import { BasicTable } from './basic_table'; @@ -28,64 +27,61 @@ const sorting = { }, } as const; -export const AnomaliesNetworkTable = React.memo( - ({ startDate, endDate, narrowDateRange, skip, ip, type, flowTarget }): JSX.Element | null => { - const capabilities = useContext(MlCapabilitiesContext); - const [loading, tableData] = useAnomaliesTableData({ - startDate, - endDate, - skip, - criteriaFields: getCriteriaFromNetworkType(type, ip, flowTarget), - }); +const AnomaliesNetworkTableComponent: React.FC = ({ + startDate, + endDate, + skip, + ip, + type, + flowTarget, +}) => { + const capabilities = useContext(MlCapabilitiesContext); + const [loading, tableData] = useAnomaliesTableData({ + startDate, + endDate, + skip, + criteriaFields: getCriteriaFromNetworkType(type, ip, flowTarget), + }); - const networks = convertAnomaliesToNetwork(tableData, ip); - const interval = getIntervalFromAnomalies(tableData); - const columns = getAnomaliesNetworkTableColumnsCurated( - type, - startDate, - endDate, - interval, - narrowDateRange - ); - const pagination = { - initialPageIndex: 0, - initialPageSize: 10, - totalItemCount: networks.length, - pageSizeOptions: [5, 10, 20, 50], - hidePerPageOptions: false, - }; + const networks = convertAnomaliesToNetwork(tableData, ip); + const columns = getAnomaliesNetworkTableColumnsCurated(type, startDate, endDate, flowTarget); + const pagination = { + initialPageIndex: 0, + initialPageSize: 10, + totalItemCount: networks.length, + pageSizeOptions: [5, 10, 20, 50], + hidePerPageOptions: false, + }; - if (!hasMlUserPermissions(capabilities)) { - return null; - } else { - return ( - - + if (!hasMlUserPermissions(capabilities)) { + return null; + } else { + return ( + + - type is not as specific as EUI's... - columns={columns} - compressed - // @ts-ignore ...which leads to `networks` not "matching" the columns - items={networks} - pagination={pagination} - sorting={sorting} - /> + type is not as specific as EUI's... + columns={columns} + compressed + // @ts-ignore ...which leads to `networks` not "matching" the columns + items={networks} + pagination={pagination} + sorting={sorting} + /> - {loading && ( - - )} - - ); - } - }, - networkEquality -); + {loading && ( + + )} + + ); + } +}; -AnomaliesNetworkTable.displayName = 'AnomaliesNetworkTable'; +export const AnomaliesNetworkTable = React.memo(AnomaliesNetworkTableComponent, networkEquality); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx index b27ccaf1ca7de..658444bfeda5c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.test.tsx @@ -15,65 +15,33 @@ import { useMountAppended } from '../../../utils/use_mount_appended'; const startDate = new Date(2001).valueOf(); const endDate = new Date(3000).valueOf(); -const interval = 'days'; -const narrowDateRange = jest.fn(); describe('get_anomalies_network_table_columns', () => { const mount = useMountAppended(); test('on network page, we expect to get all columns', () => { expect( - getAnomaliesNetworkTableColumnsCurated( - NetworkType.page, - startDate, - endDate, - interval, - narrowDateRange - ).length + getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate).length ).toEqual(6); }); test('on network details page, we expect to remove one columns', () => { - const columns = getAnomaliesNetworkTableColumnsCurated( - NetworkType.details, - startDate, - endDate, - interval, - narrowDateRange - ); + const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.details, startDate, endDate); expect(columns.length).toEqual(5); }); test('on network page, we should have Network Name', () => { - const columns = getAnomaliesNetworkTableColumnsCurated( - NetworkType.page, - startDate, - endDate, - interval, - narrowDateRange - ); + const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate); expect(columns.some(col => col.name === i18n.NETWORK_NAME)).toEqual(true); }); test('on network details page, we should not have Network Name', () => { - const columns = getAnomaliesNetworkTableColumnsCurated( - NetworkType.details, - startDate, - endDate, - interval, - narrowDateRange - ); + const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.details, startDate, endDate); expect(columns.some(col => col.name === i18n.NETWORK_NAME)).toEqual(false); }); test('on network page, we should escape the draggable id', () => { - const columns = getAnomaliesNetworkTableColumnsCurated( - NetworkType.page, - startDate, - endDate, - interval, - narrowDateRange - ); + const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate); const column = columns.find(col => col.name === i18n.SCORE) as Columns< string, AnomaliesByNetwork @@ -129,13 +97,7 @@ describe('get_anomalies_network_table_columns', () => { }); test('on network page, undefined influencers should turn into an empty column string', () => { - const columns = getAnomaliesNetworkTableColumnsCurated( - NetworkType.page, - startDate, - endDate, - interval, - narrowDateRange - ); + const columns = getAnomaliesNetworkTableColumnsCurated(NetworkType.page, startDate, endDate); const column = columns.find(col => col.name === i18n.INFLUENCED_BY) as Columns< Anomaly['influencers'], AnomaliesByNetwork diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx index 0c823ef75cf26..f6a493f80eb78 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import { Columns } from '../../paginated_table'; -import { Anomaly, NarrowDateRange, AnomaliesByNetwork } from '../types'; +import { Anomaly, AnomaliesByNetwork } from '../types'; import { getRowItemDraggable } from '../../tables/helpers'; import { EntityDraggable } from '../entity_draggable'; import { createCompoundNetworkKey } from './create_compound_key'; @@ -23,12 +23,12 @@ import { createExplorerLink } from '../links/create_explorer_link'; import { FormattedRelativePreferenceDate } from '../../formatted_date'; import { NetworkType } from '../../../store/network/model'; import { escapeDataProviderId } from '../../drag_and_drop/helpers'; +import { FlowTarget } from '../../../graphql/types'; export const getAnomaliesNetworkTableColumns = ( startDate: number, endDate: number, - interval: string, - narrowDateRange: NarrowDateRange + flowTarget?: FlowTarget ): [ Columns, Columns, @@ -46,7 +46,7 @@ export const getAnomaliesNetworkTableColumns = ( rowItem: ip, attrName: anomaliesByNetwork.type, idPrefix: `anomalies-network-table-ip-${createCompoundNetworkKey(anomaliesByNetwork)}`, - render: item => , + render: item => , }), }, { @@ -129,10 +129,9 @@ export const getAnomaliesNetworkTableColumnsCurated = ( pageType: NetworkType, startDate: number, endDate: number, - interval: string, - narrowDateRange: NarrowDateRange + flowTarget?: FlowTarget ) => { - const columns = getAnomaliesNetworkTableColumns(startDate, endDate, interval, narrowDateRange); + const columns = getAnomaliesNetworkTableColumns(startDate, endDate, flowTarget); // Columns to exclude from ip details pages if (pageType === NetworkType.details) { 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 f9d63a8594180..fc0a37bc751c7 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 @@ -194,7 +194,7 @@ describe('Navigation Breadcrumbs', () => { }, { text: ipv4, - href: `#/link-to/network/ip/${ipv4}?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)))`, + href: `#/link-to/network/ip/${ipv4}/source?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: 'Flows', href: '' }, ]); @@ -211,7 +211,7 @@ describe('Navigation Breadcrumbs', () => { }, { text: ipv6, - href: `#/link-to/network/ip/${ipv6Encoded}?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)))`, + href: `#/link-to/network/ip/${ipv6Encoded}/source?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: 'Flows', href: '' }, ]); 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 d623f5f389ce5..61ac84667d80f 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx @@ -19,7 +19,7 @@ import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; export const SiemNavigationComponent = React.memo< SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState >( - ({ detailName, display, navTabs, pageName, pathName, search, tabName, urlState }) => { + ({ detailName, display, navTabs, pageName, pathName, search, tabName, urlState, flowTarget }) => { useEffect(() => { if (pathName) { setBreadcrumbs({ @@ -32,6 +32,7 @@ export const SiemNavigationComponent = React.memo< savedQuery: urlState.savedQuery, search, tabName, + flowTarget, timerange: urlState.timerange, timeline: urlState.timeline, }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx index 8c744c6573ee8..006587d8fc294 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx @@ -7,38 +7,26 @@ import { mount } from 'enzyme'; import React from 'react'; -import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock'; -import { createStore, State } from '../../../../store'; - +import { TestProviders } from '../../../../mock'; import { FlowTargetSelectConnected } from './index'; -import { IpOverviewId } from '../../../field_renderers/field_renderers'; - -describe('Flow Target Select Connected', () => { - const state: State = mockGlobalState; - let store = createStore(state, apolloClientObservable); +import { FlowTarget } from '../../../../graphql/types'; - beforeEach(() => { - store = createStore(state, apolloClientObservable); - }); - test('Pick Relative Date', () => { +describe.skip('Flow Target Select Connected', () => { + test('renders correctly against snapshot flowTarget source', () => { const wrapper = mount( - - + + ); - expect(store.getState().network.details.flowTarget).toEqual('source'); - wrapper - .find('button') - .first() - .simulate('click'); - - wrapper.update(); - wrapper - .find(`button#${IpOverviewId}-select-flow-target-destination`) - .first() - .simulate('click'); + expect(wrapper.find('FlowTargetSelectConnected')).toMatchSnapshot(); + }); - wrapper.update(); - expect(store.getState().network.details.flowTarget).toEqual('destination'); + test('renders correctly against snapshot flowTarget destination', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('FlowTargetSelectConnected')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx index 956271b416ee8..1b87c36902159 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Location } from 'history'; import { EuiFlexItem } from '@elastic/eui'; -import React from 'react'; -import { connect } from 'react-redux'; +import React, { useCallback } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; import styled from 'styled-components'; -import { ActionCreator } from 'typescript-fsa'; import { FlowDirection, FlowTarget } from '../../../../graphql/types'; -import { State } from '../../../../store'; -import { networkActions, networkSelectors } from '../../../../store/network'; import * as i18nIp from '../ip_overview/translations'; import { FlowTargetSelect } from '../../../flow_controls/flow_target_select'; @@ -24,20 +22,33 @@ const SelectTypeItem = styled(EuiFlexItem)` SelectTypeItem.displayName = 'SelectTypeItem'; -interface FlowTargetSelectReduxProps { +interface Props { flowTarget: FlowTarget; } -export interface FlowTargetSelectDispatchProps { - updateIpDetailsFlowTarget: ActionCreator<{ - flowTarget: FlowTarget; - }>; -} +const getUpdatedFlowTargetPath = ( + location: Location, + currentFlowTarget: FlowTarget, + newFlowTarget: FlowTarget +) => { + const newPathame = location.pathname.replace(currentFlowTarget, newFlowTarget); + + return `${newPathame}${location.search}`; +}; -type FlowTargetSelectProps = FlowTargetSelectReduxProps & FlowTargetSelectDispatchProps; +const FlowTargetSelectConnectedComponent: React.FC = ({ flowTarget }) => { + const history = useHistory(); + const location = useLocation(); -const FlowTargetSelectComponent = React.memo( - ({ flowTarget, updateIpDetailsFlowTarget }) => ( + const updateIpDetailsFlowTarget = useCallback( + (newFlowTarget: FlowTarget) => { + const newPath = getUpdatedFlowTargetPath(location, flowTarget, newFlowTarget); + history.push(newPath); + }, + [history, location, flowTarget] + ); + + return ( ( updateFlowTargetAction={updateIpDetailsFlowTarget} /> - ) -); - -FlowTargetSelectComponent.displayName = 'FlowTargetSelectComponent'; - -const makeMapStateToProps = () => { - const getIpDetailsFlowTargetSelector = networkSelectors.ipDetailsFlowTargetSelector(); - return (state: State) => { - return { - flowTarget: getIpDetailsFlowTargetSelector(state), - }; - }; + ); }; -export const FlowTargetSelectConnected = connect(makeMapStateToProps, { - updateIpDetailsFlowTarget: networkActions.updateIpDetailsFlowTarget, -})(FlowTargetSelectComponent); +export const FlowTargetSelectConnected = React.memo(FlowTargetSelectConnectedComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap index f80a61836b86e..3d47e398ed395 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/__snapshots__/index.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NetworkTopNFlow Table Component rendering it renders the default NetworkTopNFlow table on the IP Details page 1`] = ` - [ { @@ -83,7 +80,7 @@ export const getNetworkTopNFlowColumns = ( ) : ( - + ) } /> @@ -233,12 +230,11 @@ export const getNetworkTopNFlowColumns = ( ]; export const getNFlowColumnsCurated = ( - indexPattern: IIndexPattern, flowTarget: FlowTargetSourceDest, type: networkModel.NetworkType, tableId: string ): NetworkTopNFlowColumns | NetworkTopNFlowColumnsIpDetails => { - const columns = getNetworkTopNFlowColumns(indexPattern, flowTarget, type, tableId); + const columns = getNetworkTopNFlowColumns(flowTarget, tableId); // Columns to exclude from host details pages if (type === networkModel.NetworkType.details) { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx index 24f68ef03d891..78e8b15005f43 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx @@ -11,12 +11,7 @@ import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { FlowTargetSourceDest } from '../../../../graphql/types'; -import { - apolloClientObservable, - mockGlobalState, - mockIndexPattern, - TestProviders, -} from '../../../../mock'; +import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock'; import { useMountAppended } from '../../../../utils/use_mount_appended'; import { createStore, networkModel, State } from '../../../../store'; @@ -43,7 +38,6 @@ describe('NetworkTopNFlow Table Component', () => { fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)} flowTargeted={FlowTargetSourceDest.source} id="topNFlowSource" - indexPattern={mockIndexPattern} isInspect={false} loading={false} loadPage={loadPage} @@ -58,7 +52,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot(); + expect(wrapper.find('Connect(Component)')).toMatchSnapshot(); }); test('it renders the default NetworkTopNFlow table on the IP Details page', () => { @@ -69,7 +63,6 @@ describe('NetworkTopNFlow Table Component', () => { fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.NetworkTopNFlow.pageInfo)} flowTargeted={FlowTargetSourceDest.source} id="topNFlowSource" - indexPattern={mockIndexPattern} isInspect={false} loading={false} loadPage={loadPage} @@ -84,7 +77,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot(); + expect(wrapper.find('Connect(Component)')).toMatchSnapshot(); }); }); @@ -99,7 +92,6 @@ describe('NetworkTopNFlow Table Component', () => { flowTargeted={FlowTargetSourceDest.source} id="topNFlowSource" isInspect={false} - indexPattern={mockIndexPattern} loading={false} loadPage={loadPage} showMorePagesIndicator={getOr( diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx index 6cf3401031fa7..7c1fcb2681a8c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx @@ -8,7 +8,6 @@ import React, { useCallback, useMemo } from 'react'; import { connect } from 'react-redux'; import { compose } from 'redux'; import { ActionCreator } from 'typescript-fsa'; -import { IIndexPattern } from 'src/plugins/data/public'; import { networkActions } from '../../../../store/actions'; import { @@ -29,7 +28,6 @@ interface OwnProps { fakeTotalCount: number; flowTargeted: FlowTargetSourceDest; id: string; - indexPattern: IIndexPattern; isInspect: boolean; loading: boolean; loadPage: (newActivePage: number) => void; @@ -69,118 +67,112 @@ const rowItems: ItemsPerRow[] = [ export const NetworkTopNFlowTableId = 'networkTopSourceFlow-top-talkers'; -const NetworkTopNFlowTableComponent = React.memo( - ({ - activePage, - data, - fakeTotalCount, - flowTargeted, - id, - indexPattern, - isInspect, - limit, - loading, - loadPage, - showMorePagesIndicator, - sort, - totalCount, - type, - updateNetworkTable, - }) => { - const columns = useMemo( - () => getNFlowColumnsCurated(indexPattern, flowTargeted, type, NetworkTopNFlowTableId), - [indexPattern, flowTargeted, type] - ); - - let tableType: networkModel.TopNTableType; - const headerTitle: string = - flowTargeted === FlowTargetSourceDest.source ? i18n.SOURCE_IP : i18n.DESTINATION_IP; - - if (type === networkModel.NetworkType.page) { - tableType = - flowTargeted === FlowTargetSourceDest.source - ? networkModel.NetworkTableType.topNFlowSource - : networkModel.NetworkTableType.topNFlowDestination; - } else { - tableType = - flowTargeted === FlowTargetSourceDest.source - ? networkModel.IpDetailsTableType.topNFlowSource - : networkModel.IpDetailsTableType.topNFlowDestination; - } - - const onChange = useCallback( - (criteria: Criteria) => { - if (criteria.sort != null) { - const splitField = criteria.sort.field.split('.'); - const field = last(splitField); - const newSortDirection = field !== sort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click - const newTopNFlowSort: NetworkTopTablesSortField = { - field: field as NetworkTopTablesFields, - direction: newSortDirection as Direction, - }; - if (!isEqual(newTopNFlowSort, sort)) { - updateNetworkTable({ - networkType: type, - tableType, - updates: { - sort: newTopNFlowSort, - }, - }); - } - } - }, - [sort, type, tableType, updateNetworkTable] - ); - - const field = - sort.field === NetworkTopTablesFields.bytes_out || - sort.field === NetworkTopTablesFields.bytes_in - ? `node.network.${sort.field}` - : `node.${flowTargeted}.${sort.field}`; - - const updateActivePage = useCallback( - newPage => - updateNetworkTable({ - networkType: type, - tableType, - updates: { activePage: newPage }, - }), - [updateNetworkTable, type, tableType] - ); - - const updateLimitPagination = useCallback( - newLimit => - updateNetworkTable({ networkType: type, tableType, updates: { limit: newLimit } }), - [updateNetworkTable, type, tableType] - ); - - return ( - - ); +const NetworkTopNFlowTableComponent: React.FC = ({ + activePage, + data, + fakeTotalCount, + flowTargeted, + id, + isInspect, + limit, + loading, + loadPage, + showMorePagesIndicator, + sort, + totalCount, + type, + updateNetworkTable, +}) => { + const columns = useMemo( + () => getNFlowColumnsCurated(flowTargeted, type, NetworkTopNFlowTableId), + [flowTargeted, type] + ); + + let tableType: networkModel.TopNTableType; + const headerTitle: string = + flowTargeted === FlowTargetSourceDest.source ? i18n.SOURCE_IP : i18n.DESTINATION_IP; + + if (type === networkModel.NetworkType.page) { + tableType = + flowTargeted === FlowTargetSourceDest.source + ? networkModel.NetworkTableType.topNFlowSource + : networkModel.NetworkTableType.topNFlowDestination; + } else { + tableType = + flowTargeted === FlowTargetSourceDest.source + ? networkModel.IpDetailsTableType.topNFlowSource + : networkModel.IpDetailsTableType.topNFlowDestination; } -); -NetworkTopNFlowTableComponent.displayName = 'NetworkTopNFlowTableComponent'; + const onChange = useCallback( + (criteria: Criteria) => { + if (criteria.sort != null) { + const splitField = criteria.sort.field.split('.'); + const field = last(splitField); + const newSortDirection = field !== sort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click + const newTopNFlowSort: NetworkTopTablesSortField = { + field: field as NetworkTopTablesFields, + direction: newSortDirection as Direction, + }; + if (!isEqual(newTopNFlowSort, sort)) { + updateNetworkTable({ + networkType: type, + tableType, + updates: { + sort: newTopNFlowSort, + }, + }); + } + } + }, + [sort, type, tableType, updateNetworkTable] + ); + + const field = + sort.field === NetworkTopTablesFields.bytes_out || + sort.field === NetworkTopTablesFields.bytes_in + ? `node.network.${sort.field}` + : `node.${flowTargeted}.${sort.field}`; + + const updateActivePage = useCallback( + newPage => + updateNetworkTable({ + networkType: type, + tableType, + updates: { activePage: newPage }, + }), + [updateNetworkTable, type, tableType] + ); + + const updateLimitPagination = useCallback( + newLimit => updateNetworkTable({ networkType: type, tableType, updates: { limit: newLimit } }), + [updateNetworkTable, type, tableType] + ); + + return ( + + ); +}; const makeMapStateToProps = () => { const getTopNFlowSelector = networkSelectors.topNFlowSelector(); @@ -192,4 +184,4 @@ export const NetworkTopNFlowTable = compose>( connect(makeMapStateToProps, { updateNetworkTable: networkActions.updateNetworkTable, }) -)(NetworkTopNFlowTableComponent); +)(React.memo(NetworkTopNFlowTableComponent)); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index dfea99ffd7091..b8b03be4e4720 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -378,6 +378,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index 75c05dd1455af..93e12a0ed4fcd 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -49,7 +49,7 @@ exports[`Columns it renders the expected columns 1`] = ` - - - - - - -`; +exports[`Events renders correctly against snapshot 1`] = `null`; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap index a79a7ed23e3d1..e2c46a07af8cc 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap @@ -2,7 +2,7 @@ exports[`get_column_renderer renders correctly against snapshot 1`] = ` - - (({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value }) => { +}> = ({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value }) => { if (fieldType === IP_FIELD_TYPE) { return ( ); } -}); +}; -FormattedFieldValue.displayName = 'FormattedFieldValue'; +export const FormattedFieldValue = React.memo(FormattedFieldValueComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap index a0122093e7504..3608a81234677 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/__snapshots__/suricata_row_renderer.test.tsx.snap @@ -370,6 +370,7 @@ exports[`suricata_row_renderer renders correctly against snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index 6fc9145187a36..0a60c8facff9c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -365,6 +365,7 @@ exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap index b0431684050cf..9b59f69cad3a3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_row_renderer.test.tsx.snap @@ -370,6 +370,7 @@ exports[`zeek_row_renderer renders correctly against snapshot 1`] = ` "example": null, "format": "", "indexes": Array [ + "apm-*-transaction*", "auditbeat-*", "endgame-*", "filebeat-*", diff --git a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx index 6361f7abcf977..4eb51dfe6407c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx @@ -5,10 +5,7 @@ */ import React, { useState } from 'react'; -import { - AutocompleteSuggestion, - IIndexPattern, -} from '../../../../../../../src/plugins/data/public'; +import { autocomplete, IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { useKibana } from '../../lib/kibana'; type RendererResult = React.ReactElement | null; @@ -18,7 +15,7 @@ interface KueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; }>; indexPattern: IIndexPattern; } @@ -33,26 +30,19 @@ export const KueryAutocompletion = React.memo const [currentRequest, setCurrentRequest] = useState( null ); - const [suggestions, setSuggestions] = useState([]); + const [suggestions, setSuggestions] = useState([]); const kibana = useKibana(); const loadSuggestions = async ( expression: string, cursorPosition: number, maxSuggestions?: number ) => { - const autocompletionProvider = kibana.services.data.autocomplete.getProvider('kuery'); - const config = { - get: () => true, - }; - if (!autocompletionProvider) { + const language = 'kuery'; + + if (!kibana.services.data.autocomplete.hasQuerySuggestions(language)) { return; } - const getSuggestions = autocompletionProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: [], - }); const futureRequest = { expression, cursorPosition, @@ -62,16 +52,22 @@ export const KueryAutocompletion = React.memo cursorPosition, }); setSuggestions([]); - const newSuggestions = await getSuggestions({ - query: expression, - selectionStart: cursorPosition, - selectionEnd: cursorPosition, - }); + if ( futureRequest && futureRequest.expression !== (currentRequest && currentRequest.expression) && futureRequest.cursorPosition !== (currentRequest && currentRequest.cursorPosition) ) { + const newSuggestions = + (await kibana.services.data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: [], + query: expression, + selectionStart: cursorPosition, + selectionEnd: cursorPosition, + })) || []; + setCurrentRequest(null); setSuggestions(maxSuggestions ? newSuggestions.slice(0, maxSuggestions) : newSuggestions); } diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.gql_query.ts index 4c22223a9ff5d..e21d4c6e34ff8 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.gql_query.ts @@ -11,8 +11,9 @@ export const MatrixHistogramGqlQuery = gql` $isAlertsHistogram: Boolean! $isAnomaliesHistogram: Boolean! $isAuthenticationsHistogram: Boolean! + $isDnsHistogram: Boolean! $defaultIndex: [String!]! - $isEventsType: Boolean! + $isEventsHistogram: Boolean! $filterQuery: String $inspect: Boolean! $sourceId: ID! @@ -77,7 +78,24 @@ export const MatrixHistogramGqlQuery = gql` filterQuery: $filterQuery defaultIndex: $defaultIndex stackByField: $stackByField - ) @include(if: $isEventsType) { + ) @include(if: $isEventsHistogram) { + matrixHistogramData { + x + y + g + } + totalCount + inspect @include(if: $inspect) { + dsl + response + } + } + NetworkDnsHistogram( + timerange: $timerange + filterQuery: $filterQuery + defaultIndex: $defaultIndex + stackByField: $stackByField + ) @include(if: $isDnsHistogram) { matrixHistogramData { x y diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx index b2b38e8c34139..5b1be4ca2c1dc 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx @@ -24,21 +24,23 @@ import { UpdateDateRange } from '../../components/charts/common'; import { SetQuery } from '../../pages/hosts/navigation/types'; export interface OwnProps extends QueryTemplateProps { - isAlertsHistogram?: boolean; - isAnomaliesHistogram?: boolean; - isAuthenticationsHistogram?: boolean; dataKey: string | string[]; defaultStackByOption: MatrixHistogramOption; deleteQuery?: ({ id }: { id: string }) => void; - isEventsType?: boolean; errorMessage: string; headerChildren?: React.ReactNode; hideHistogramIfEmpty?: boolean; + isAlertsHistogram?: boolean; + isAnomaliesHistogram?: boolean; + isAuthenticationsHistogram?: boolean; id: string; + isDnsHistogram?: boolean; + isEventsHistogram?: boolean; legendPosition?: Position; mapping?: MatrixHistogramMappingTypes; query: Maybe; setQuery: SetQuery; + showLegend?: boolean; sourceId: string; stackByOptions: MatrixHistogramOption[]; subtitle?: string | GetSubTitle; diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts index 1fc1fedae9f88..9cda9d8f6115f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts @@ -16,7 +16,7 @@ import { useUiSetting$ } from '../../lib/kibana'; import { createFilter } from '../helpers'; import { useApolloClient } from '../../utils/apollo_context'; import { inputsModel } from '../../store'; -import { GetMatrixHistogramQuery, GetNetworkDnsQuery } from '../../graphql/types'; +import { GetMatrixHistogramQuery } from '../../graphql/types'; export const useQuery = ({ dataKey, @@ -26,15 +26,12 @@ export const useQuery = ({ isAlertsHistogram = false, isAnomaliesHistogram = false, isAuthenticationsHistogram = false, - isEventsType = false, - isDNSHistogram, - isPtrIncluded, + isEventsHistogram = false, + isDnsHistogram = false, isInspected, query, stackByField, startDate, - sort, - pagination, }: MatrixHistogramQueryProps) => { const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); const [, dispatchToaster] = useStateToaster(); @@ -45,21 +42,7 @@ export const useQuery = ({ const [totalCount, setTotalCount] = useState(-1); const apolloClient = useApolloClient(); - const isDNSQuery = ( - variable: Pick< - MatrixHistogramQueryProps, - 'isDNSHistogram' | 'isPtrIncluded' | 'sort' | 'pagination' - > - ): variable is GetNetworkDnsQuery.Variables => { - return ( - !!isDNSHistogram && - variable.isDNSHistogram !== undefined && - variable.isPtrIncluded !== undefined && - variable.sort !== undefined && - variable.pagination !== undefined - ); - }; - const basicVariables = { + const matrixHistogramVariables: GetMatrixHistogramQuery.Variables = { filterQuery: createFilter(filterQuery), sourceId: 'default', timerange: { @@ -70,20 +53,11 @@ export const useQuery = ({ defaultIndex, inspect: isInspected, stackByField, - }; - const dnsVariables = { - ...basicVariables, - isDNSHistogram, - isPtrIncluded, - sort, - pagination, - }; - const matrixHistogramVariables: GetMatrixHistogramQuery.Variables = { - ...basicVariables, isAlertsHistogram, isAnomaliesHistogram, isAuthenticationsHistogram, - isEventsType, + isDnsHistogram, + isEventsHistogram, }; useEffect(() => { @@ -92,16 +66,13 @@ export const useQuery = ({ const abortSignal = abortCtrl.signal; async function fetchData() { - if (!apolloClient || (pagination != null && pagination.querySize < 0)) return null; + if (!apolloClient) return null; setLoading(true); return apolloClient - .query< - GetMatrixHistogramQuery.Query | GetNetworkDnsQuery.Query, - GetMatrixHistogramQuery.Variables | GetNetworkDnsQuery.Variables - >({ + .query({ query, fetchPolicy: 'cache-first', - variables: isDNSQuery(dnsVariables) ? dnsVariables : matrixHistogramVariables, + variables: matrixHistogramVariables, context: { fetchOptions: { abortSignal, @@ -145,11 +116,8 @@ export const useQuery = ({ query, filterQuery, isInspected, - isDNSHistogram, + isDnsHistogram, stackByField, - sort, - isPtrIncluded, - pagination, startDate, endDate, ]); diff --git a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts index 9d82705e9524b..a81d112fa4c50 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts +++ b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.gql_query.ts @@ -11,7 +11,6 @@ export const networkDnsQuery = gql` $defaultIndex: [String!]! $filterQuery: String $inspect: Boolean! - $isDNSHistogram: Boolean! $isPtrIncluded: Boolean! $pagination: PaginationInputPaginated! $sort: NetworkDnsSortField! @@ -31,7 +30,7 @@ export const networkDnsQuery = gql` stackByField: $stackByField ) { totalCount - edges @skip(if: $isDNSHistogram) { + edges { node { _id dnsBytesIn @@ -44,7 +43,7 @@ export const networkDnsQuery = gql` value } } - pageInfo @skip(if: $isDNSHistogram) { + pageInfo { activePage fakeTotalCount showMorePagesIndicator @@ -53,11 +52,6 @@ export const networkDnsQuery = gql` dsl response } - histogram @include(if: $isDNSHistogram) { - x - y - g - } } } } diff --git a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx index 5c5552edcc4ba..04c8783c30a0f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx @@ -26,7 +26,7 @@ import { generateTablePaginationOptions } from '../../components/paginated_table import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { networkDnsQuery } from './index.gql_query'; -import { DEFAULT_TABLE_ACTIVE_PAGE } from '../../store/constants'; +import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../../store/constants'; import { MatrixHistogram } from '../../components/matrix_histogram'; import { MatrixHistogramOption, GetSubTitle } from '../../components/matrix_histogram/types'; import { UpdateDateRange } from '../../components/charts/common'; @@ -57,8 +57,7 @@ interface DnsHistogramOwnProps extends QueryTemplatePaginatedProps { dataKey: string | string[]; defaultStackByOption: MatrixHistogramOption; errorMessage: string; - isDNSHistogram?: boolean; - limit: number; + isDnsHistogram?: boolean; query: DocumentNode; scaleType: ScaleType; setQuery: SetQuery; @@ -105,7 +104,6 @@ export class NetworkDnsComponentQuery extends QueryTemplatePaginated< const variables: GetNetworkDnsQuery.Variables = { defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), - isDNSHistogram: false, inspect: isInspected, isPtrIncluded, pagination: generateTablePaginationOptions(activePage, limit), @@ -186,12 +184,12 @@ const makeMapStateToProps = () => { const makeMapHistogramStateToProps = () => { const getNetworkDnsSelector = networkSelectors.dnsSelector(); const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = HISTOGRAM_ID, limit }: DnsHistogramOwnProps) => { + const mapStateToProps = (state: State, { id = HISTOGRAM_ID }: DnsHistogramOwnProps) => { const { isInspected } = getQuery(state, id); return { ...getNetworkDnsSelector(state), activePage: DEFAULT_TABLE_ACTIVE_PAGE, - limit, + limit: DEFAULT_TABLE_LIMIT, isInspected, id, }; diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json index d73755fb92185..7b9842fa2c2bc 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json @@ -1901,6 +1901,59 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "NetworkDnsHistogram", + "description": "", + "args": [ + { + "name": "filterQuery", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "defaultIndex", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "defaultValue": null + }, + { + "name": "timerange", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } + }, + "defaultValue": null + }, + { + "name": "stackByField", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "NetworkDsOverTimeData", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "NetworkHttp", "description": "", @@ -8744,6 +8797,61 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "NetworkDsOverTimeData", + "description": "", + "fields": [ + { + "name": "inspect", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "matrixHistogramData", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "MatrixOverTimeHistogramData", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "NetworkHttpSortField", diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts index 73049e26f1581..b13e295a8e168 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts @@ -499,6 +499,8 @@ export interface Source { NetworkDns: NetworkDnsData; + NetworkDnsHistogram: NetworkDsOverTimeData; + NetworkHttp: NetworkHttpData; OverviewNetwork?: Maybe; @@ -1752,6 +1754,14 @@ export interface MatrixOverOrdinalHistogramData { g: string; } +export interface NetworkDsOverTimeData { + inspect?: Maybe; + + matrixHistogramData: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + export interface NetworkHttpData { edges: NetworkHttpEdges[]; @@ -2430,6 +2440,15 @@ export interface NetworkDnsSourceArgs { defaultIndex: string[]; } +export interface NetworkDnsHistogramSourceArgs { + filterQuery?: Maybe; + + defaultIndex: string[]; + + timerange: TimerangeInput; + + stackByField?: Maybe; +} export interface NetworkHttpSourceArgs { id?: Maybe; @@ -3306,8 +3325,9 @@ export namespace GetMatrixHistogramQuery { isAlertsHistogram: boolean; isAnomaliesHistogram: boolean; isAuthenticationsHistogram: boolean; + isDnsHistogram: boolean; defaultIndex: string[]; - isEventsType: boolean; + isEventsHistogram: boolean; filterQuery?: Maybe; inspect: boolean; sourceId: string; @@ -3333,6 +3353,8 @@ export namespace GetMatrixHistogramQuery { AuthenticationsHistogram: AuthenticationsHistogram; EventsHistogram: EventsHistogram; + + NetworkDnsHistogram: NetworkDnsHistogram; }; export type AlertsHistogram = { @@ -3446,6 +3468,34 @@ export namespace GetMatrixHistogramQuery { response: string[]; }; + + export type NetworkDnsHistogram = { + __typename?: 'NetworkDsOverTimeData'; + + matrixHistogramData: ____MatrixHistogramData[]; + + totalCount: number; + + inspect: Maybe<____Inspect>; + }; + + export type ____MatrixHistogramData = { + __typename?: 'MatrixOverTimeHistogramData'; + + x: number; + + y: number; + + g: string; + }; + + export type ____Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; } export namespace GetNetworkDnsQuery { @@ -3453,7 +3503,6 @@ export namespace GetNetworkDnsQuery { defaultIndex: string[]; filterQuery?: Maybe; inspect: boolean; - isDNSHistogram: boolean; isPtrIncluded: boolean; pagination: PaginationInputPaginated; sort: NetworkDnsSortField; @@ -3486,8 +3535,6 @@ export namespace GetNetworkDnsQuery { pageInfo: PageInfo; inspect: Maybe; - - histogram: Maybe; }; export type Edges = { @@ -3537,16 +3584,6 @@ export namespace GetNetworkDnsQuery { response: string[]; }; - - export type Histogram = { - __typename?: 'MatrixOverOrdinalHistogramData'; - - x: string; - - y: number; - - g: string; - }; } export namespace GetNetworkHttpQuery { diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx index df83ad056943a..a07cbc8484a1b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx @@ -52,7 +52,7 @@ export const EventsQueryTabBody = ({ defaultStackByOption={eventsStackByOptions[0]} deleteQuery={deleteQuery} endDate={endDate} - isEventsType={true} + isEventsHistogram={true} errorMessage={i18n.ERROR_FETCHING_EVENTS_DATA} filterQuery={filterQuery} query={MatrixHistogramGqlQuery} diff --git a/x-pack/legacy/plugins/siem/public/pages/network/index.tsx b/x-pack/legacy/plugins/siem/public/pages/network/index.tsx index 1bc3d9a054bb8..48fc1421d90bb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/index.tsx @@ -9,6 +9,7 @@ import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider'; import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions'; +import { FlowTarget } from '../../graphql/types'; import { IPDetails } from './ip_details'; import { Network } from './network'; @@ -20,9 +21,9 @@ import { NetworkRouteType } from './navigation/types'; type Props = Partial> & { url: string }; const networkPagePath = `/:pageName(${SiemPageName.network})`; -const ipDetailsPagePath = `${networkPagePath}/ip/:detailName`; +const ipDetailsPageBasePath = `${networkPagePath}/ip/:detailName`; -export const NetworkContainer = React.memo(() => { +const NetworkContainerComponent: React.FC = () => { const capabilities = useContext(MlCapabilitiesContext); const capabilitiesFetched = capabilities.capabilitiesFetched; const userHasMlUserPermissions = useMemo(() => hasMlUserPermissions(capabilities), [ @@ -54,14 +55,15 @@ export const NetworkContainer = React.memo(() => { )} /> ( (() => { /> )} /> + ( + + )} + /> ( @@ -80,6 +95,6 @@ export const NetworkContainer = React.memo(() => { )} ); -}); +}; -NetworkContainer.displayName = 'NetworkContainer'; +export const NetworkContainer = React.memo(NetworkContainerComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx index 99ca12292a52c..2e8044d2c2fe8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx @@ -28,7 +28,7 @@ import { useKibana } from '../../../lib/kibana'; import { decodeIpv6 } from '../../../lib/helpers'; import { convertToBuildEsQuery } from '../../../lib/keury'; import { ConditionalFlexGroup } from '../../../pages/network/navigation/conditional_flex_group'; -import { networkModel, networkSelectors, State, inputsSelectors } from '../../../store'; +import { networkModel, State, inputsSelectors } from '../../../store'; import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions'; import { setIpDetailsTablesActivePageToZero as dispatchIpDetailsTablesActivePageToZero } from '../../../store/network/actions'; import { SpyRoute } from '../../../utils/route/spy_routes'; @@ -102,7 +102,7 @@ export const IPDetailsComponent = ({ subtitle={} title={ip} > - + { const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); - const getIpDetailsFlowTargetSelector = networkSelectors.ipDetailsFlowTargetSelector(); return (state: State) => ({ query: getGlobalQuerySelector(state), filters: getGlobalFiltersQuerySelector(state), - flowTarget: getIpDetailsFlowTargetSelector(state), }); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/network_top_n_flow_query_table.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/network_top_n_flow_query_table.tsx index 47d68471fb69b..06ae3160415d9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/network_top_n_flow_query_table.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/network_top_n_flow_query_table.tsx @@ -22,7 +22,6 @@ export const NetworkTopNFlowQueryTable = ({ skip, startDate, type, - indexPattern, }: NetworkWithIndexComponentsQueryTableProps) => ( i18n.DOMAINS_COUNT_BY(option.text), + [] + ); return ( - - {({ - totalCount, - loading, - networkDns, - pageInfo, - loadPage, - id, - inspect, - isInspected, - refetch, - }) => ( - <> - - + <> + + + + {({ + totalCount, + loading, + networkDns, + pageInfo, + loadPage, + id, + inspect, + isInspected, + refetch, + }) => ( - - )} - + )} + + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx index 95aaa90fe7865..c4391ba2ec90a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/ips_query_tab_body.tsx @@ -22,7 +22,6 @@ export const IPsQueryTabBody = ({ skip, startDate, setQuery, - indexPattern, flowTarget, }: IPsQueryTabBodyProps) => ( + i18n.translate('xpack.siem.network.dns.stackByUniqueSubdomain', { + values: { groupByField }, + defaultMessage: 'Top domains by {groupByField}', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx index b4f945c802e56..52084c4bfc280 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx @@ -108,7 +108,7 @@ export const EventsByDataset = React.memo( })} headerChildren={eventsCountViewEventsButton} id={ID} - isEventsType={true} + isEventsHistogram={true} legendPosition={'right'} query={MatrixHistogramGqlQuery} setQuery={setQuery} diff --git a/x-pack/legacy/plugins/siem/public/store/network/actions.ts b/x-pack/legacy/plugins/siem/public/store/network/actions.ts index 6a6dd48b738cc..be7d9b1ad4518 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/actions.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/actions.ts @@ -6,7 +6,6 @@ import actionCreatorFactory from 'typescript-fsa'; -import { FlowTarget } from '../../graphql/types'; import { networkModel } from '../model'; const actionCreator = actionCreatorFactory('x-pack/siem/local/network'); @@ -24,7 +23,3 @@ export const setIpDetailsTablesActivePageToZero = actionCreator( export const setNetworkTablesActivePageToZero = actionCreator( 'SET_NETWORK_TABLES_ACTIVE_PAGE_TO_ZERO' ); - -export const updateIpDetailsFlowTarget = actionCreator<{ - flowTarget: FlowTarget; -}>('UPDATE_IP_DETAILS_TARGET'); diff --git a/x-pack/legacy/plugins/siem/public/store/network/reducer.ts b/x-pack/legacy/plugins/siem/public/store/network/reducer.ts index 8e4d4555d3bd9..e6d7efc9cbb5f 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/reducer.ts @@ -19,14 +19,13 @@ import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from '../constants'; import { setIpDetailsTablesActivePageToZero, setNetworkTablesActivePageToZero, - updateIpDetailsFlowTarget, updateNetworkTable, } from './actions'; import { setNetworkDetailsQueriesActivePageToZero, setNetworkPageQueriesActivePageToZero, } from './helpers'; -import { IpDetailsTableType, NetworkModel, NetworkTableType, NetworkType } from './model'; +import { IpDetailsTableType, NetworkModel, NetworkTableType } from './model'; export type NetworkState = NetworkModel; @@ -189,11 +188,4 @@ export const networkReducer = reducerWithInitialState(initialNetworkState) queries: setNetworkDetailsQueriesActivePageToZero(state), }, })) - .case(updateIpDetailsFlowTarget, (state, { flowTarget }) => ({ - ...state, - [NetworkType.details]: { - ...state[NetworkType.details], - flowTarget, - }, - })) .build(); diff --git a/x-pack/legacy/plugins/siem/public/store/network/selectors.ts b/x-pack/legacy/plugins/siem/public/store/network/selectors.ts index a33684472b279..64a325b6e1ca4 100644 --- a/x-pack/legacy/plugins/siem/public/store/network/selectors.ts +++ b/x-pack/legacy/plugins/siem/public/store/network/selectors.ts @@ -81,9 +81,5 @@ const selectHttpByType = (state: State, networkType: NetworkType) => { export const httpSelector = () => createSelector(selectHttpByType, httpQueries => httpQueries); -// IP Details Selectors -export const ipDetailsFlowTargetSelector = () => - createSelector(selectNetworkDetails, network => network.flowTarget); - export const usersSelector = () => createSelector(selectNetworkDetails, network => network.queries.users); 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 index bcc256d50d960..e777d281ed51a 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/route/index.test.tsx @@ -75,6 +75,7 @@ describe('Spy Routes', () => { detailName: '', tabName: HostsTableType.hosts, search: '', + flowTarget: undefined, }, }} /> @@ -106,6 +107,7 @@ describe('Spy Routes', () => { detailName: undefined, tabName: HostsTableType.hosts, search: '?IdoNotWantToSeeYou="true"', + flowTarget: undefined, }, }} /> @@ -156,6 +158,7 @@ describe('Spy Routes', () => { detailName: undefined, tabName: HostsTableType.hosts, search: '?IdoNotWantToSeeYou="true"', + flowTarget: undefined, }, }} /> 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 index 3a02d81272344..5c24b2f48488d 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx @@ -17,7 +17,7 @@ export const SpyRouteComponent = memo( location: { pathname, search }, history, match: { - params: { pageName, detailName, tabName }, + params: { pageName, detailName, tabName, flowTarget }, }, }) => { const [isInitializing, setIsInitializing] = useState(true); @@ -43,6 +43,7 @@ export const SpyRouteComponent = memo( tabName, pathName: pathname, history, + flowTarget, }, }); setIsInitializing(false); @@ -56,11 +57,12 @@ export const SpyRouteComponent = memo( search, pathName: pathname, history, + flowTarget, }, }); } } - }, [pathname, search, pageName, detailName, tabName]); + }, [pathname, search, pageName, detailName, tabName, flowTarget]); return null; } ); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/types.ts b/x-pack/legacy/plugins/siem/public/utils/route/types.ts index 002cd4d23786d..79d2677eff06f 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/types.ts +++ b/x-pack/legacy/plugins/siem/public/utils/route/types.ts @@ -10,6 +10,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { HostsTableType } from '../../store/hosts/model'; import { NetworkRouteType } from '../../pages/network/navigation/types'; +import { FlowTarget } from '../../graphql/types'; export type SiemRouteType = HostsTableType | NetworkRouteType; export interface RouteSpyState { @@ -19,6 +20,7 @@ export interface RouteSpyState { search: string; pathName: string; history?: H.History; + flowTarget?: FlowTarget; } export interface HostRouteSpyState extends RouteSpyState { @@ -52,4 +54,5 @@ export type SpyRouteProps = RouteComponentProps<{ detailName: string | undefined; tabName: HostsTableType | undefined; search: string; + flowTarget: FlowTarget | undefined; }>; diff --git a/x-pack/legacy/plugins/siem/scripts/storybook.js b/x-pack/legacy/plugins/siem/scripts/storybook.js new file mode 100644 index 0000000000000..6566236704936 --- /dev/null +++ b/x-pack/legacy/plugins/siem/scripts/storybook.js @@ -0,0 +1,13 @@ +/* + * 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 { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'siem', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.stories.tsx')], +}); diff --git a/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts index 2dabd51c198f7..06d6b8c516d8b 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/network/resolvers.ts @@ -7,7 +7,7 @@ import { SourceResolvers } from '../../graphql/types'; import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; import { Network } from '../../lib/network'; -import { createOptionsPaginated } from '../../utils/build_query/create_options'; +import { createOptionsPaginated, createOptions } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; type QueryNetworkTopCountriesResolver = ChildResolverOf< @@ -30,6 +30,10 @@ type QueryDnsResolver = ChildResolverOf< QuerySourceResolver >; +type QueryDnsHistogramResolver = ChildResolverOf< + AppResolverOf, + QuerySourceResolver +>; export interface NetworkResolversDeps { network: Network; } @@ -42,6 +46,7 @@ export const createNetworkResolvers = ( NetworkTopCountries: QueryNetworkTopCountriesResolver; NetworkTopNFlow: QueryNetworkTopNFlowResolver; NetworkDns: QueryDnsResolver; + NetworkDnsHistogram: QueryDnsHistogramResolver; }; } => ({ Source: { @@ -76,9 +81,15 @@ export const createNetworkResolvers = ( ...createOptionsPaginated(source, args, info), networkDnsSortField: args.sort, isPtrIncluded: args.isPtrIncluded, - stackByField: args.stackByField, }; return libs.network.getNetworkDns(req, options); }, + async NetworkDnsHistogram(source, args, { req }, info) { + const options = { + ...createOptions(source, args, info), + stackByField: args.stackByField, + }; + return libs.network.getNetworkDnsHistogramData(req, options); + }, }, }); diff --git a/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts index a5bca68fb30b9..15e2d832a73c9 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/network/schema.gql.ts @@ -196,6 +196,12 @@ export const networkSchema = gql` inspect: Inspect } + type NetworkDsOverTimeData { + inspect: Inspect + matrixHistogramData: [MatrixOverTimeHistogramData!]! + totalCount: Float! + } + extend type Source { NetworkTopCountries( id: String @@ -227,6 +233,12 @@ export const networkSchema = gql` timerange: TimerangeInput! defaultIndex: [String!]! ): NetworkDnsData! + NetworkDnsHistogram( + filterQuery: String + defaultIndex: [String!]! + timerange: TimerangeInput! + stackByField: String + ): NetworkDsOverTimeData! NetworkHttp( id: String filterQuery: String diff --git a/x-pack/legacy/plugins/siem/server/graphql/types.ts b/x-pack/legacy/plugins/siem/server/graphql/types.ts index 48ca32874dda2..4a2119b6f7631 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/types.ts @@ -501,6 +501,8 @@ export interface Source { NetworkDns: NetworkDnsData; + NetworkDnsHistogram: NetworkDsOverTimeData; + NetworkHttp: NetworkHttpData; OverviewNetwork?: Maybe; @@ -1754,6 +1756,14 @@ export interface MatrixOverOrdinalHistogramData { g: string; } +export interface NetworkDsOverTimeData { + inspect?: Maybe; + + matrixHistogramData: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + export interface NetworkHttpData { edges: NetworkHttpEdges[]; @@ -2432,6 +2442,15 @@ export interface NetworkDnsSourceArgs { defaultIndex: string[]; } +export interface NetworkDnsHistogramSourceArgs { + filterQuery?: Maybe; + + defaultIndex: string[]; + + timerange: TimerangeInput; + + stackByField?: Maybe; +} export interface NetworkHttpSourceArgs { id?: Maybe; @@ -2930,6 +2949,8 @@ export namespace SourceResolvers { NetworkDns?: NetworkDnsResolver; + NetworkDnsHistogram?: NetworkDnsHistogramResolver; + NetworkHttp?: NetworkHttpResolver; OverviewNetwork?: OverviewNetworkResolver, TypeParent, TContext>; @@ -3281,6 +3302,21 @@ export namespace SourceResolvers { defaultIndex: string[]; } + export type NetworkDnsHistogramResolver< + R = NetworkDsOverTimeData, + Parent = Source, + TContext = SiemContext + > = Resolver; + export interface NetworkDnsHistogramArgs { + filterQuery?: Maybe; + + defaultIndex: string[]; + + timerange: TimerangeInput; + + stackByField?: Maybe; + } + export type NetworkHttpResolver< R = NetworkHttpData, Parent = Source, @@ -7547,6 +7583,36 @@ export namespace MatrixOverOrdinalHistogramDataResolvers { > = Resolver; } +export namespace NetworkDsOverTimeDataResolvers { + export interface Resolvers { + inspect?: InspectResolver, TypeParent, TContext>; + + matrixHistogramData?: MatrixHistogramDataResolver< + MatrixOverTimeHistogramData[], + TypeParent, + TContext + >; + + totalCount?: TotalCountResolver; + } + + export type InspectResolver< + R = Maybe, + Parent = NetworkDsOverTimeData, + TContext = SiemContext + > = Resolver; + export type MatrixHistogramDataResolver< + R = MatrixOverTimeHistogramData[], + Parent = NetworkDsOverTimeData, + TContext = SiemContext + > = Resolver; + export type TotalCountResolver< + R = number, + Parent = NetworkDsOverTimeData, + TContext = SiemContext + > = Resolver; +} + export namespace NetworkHttpDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -9227,6 +9293,7 @@ export type IResolvers = { NetworkDnsEdges?: NetworkDnsEdgesResolvers.Resolvers; NetworkDnsItem?: NetworkDnsItemResolvers.Resolvers; MatrixOverOrdinalHistogramData?: MatrixOverOrdinalHistogramDataResolvers.Resolvers; + NetworkDsOverTimeData?: NetworkDsOverTimeDataResolvers.Resolvers; NetworkHttpData?: NetworkHttpDataResolvers.Resolvers; NetworkHttpEdges?: NetworkHttpEdgesResolvers.Resolvers; NetworkHttpItem?: NetworkHttpItemResolvers.Resolvers; diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index 9ceb8e02864ec..a488db3f0c3d7 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -28,6 +28,7 @@ import { deleteRulesBulkRoute } from './lib/detection_engine/routes/rules/delete import { importRulesRoute } from './lib/detection_engine/routes/rules/import_rules_route'; import { exportRulesRoute } from './lib/detection_engine/routes/rules/export_rules_route'; import { findRulesStatusesRoute } from './lib/detection_engine/routes/rules/find_rules_status_route'; +import { getPrepackagedRulesStatusRoute } from './lib/detection_engine/routes/rules/get_prepackaged_rules_status_route'; const APP_ID = 'siem'; @@ -49,12 +50,16 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy updateRulesRoute(__legacy); deleteRulesRoute(__legacy); findRulesRoute(__legacy); + addPrepackedRulesRoute(__legacy); + getPrepackagedRulesStatusRoute(__legacy); createRulesBulkRoute(__legacy); updateRulesBulkRoute(__legacy); deleteRulesBulkRoute(__legacy); + importRulesRoute(__legacy); exportRulesRoute(__legacy); + findRulesStatusesRoute(__legacy); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts index 1e475f2014fa2..8bddd4a1ef456 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts @@ -10,9 +10,12 @@ import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; import { savedObjectsClientMock } from '../../../../../../../../../src/core/server/mocks'; import { alertsClientMock } from '../../../../../../alerting/server/alerts_client.mock'; import { actionsClientMock } from '../../../../../../actions/server/actions_client.mock'; +import { APP_ID, SIGNALS_INDEX_KEY } from '../../../../../common/constants'; +import { ServerFacade } from '../../../../types'; const defaultConfig = { 'kibana.index': '.kibana', + [`xpack.${APP_ID}.${SIGNALS_INDEX_KEY}`]: '.siem-signals', }; const isKibanaConfig = (config: unknown): config is KibanaConfig => @@ -58,10 +61,10 @@ export const createMockServer = (config: Record = defaultConfig) server.decorate('request', 'getBasePath', () => '/s/default'); server.decorate('request', 'getActionsClient', () => actionsClient); server.plugins.elasticsearch = (elasticsearch as unknown) as ElasticsearchPlugin; + server.plugins.spaces = { getSpaceId: () => 'default' }; server.decorate('request', 'getSavedObjectsClient', () => savedObjectsClient); - return { - server, + server: server as ServerFacade & Hapi.Server, alertsClient, actionsClient, elasticsearch, @@ -82,7 +85,10 @@ export const createMockServerWithoutAlertClientDecoration = ( serverWithoutAlertClient.decorate('request', 'getBasePath', () => '/s/default'); serverWithoutAlertClient.decorate('request', 'getActionsClient', () => actionsClient); - return { serverWithoutAlertClient, actionsClient }; + return { + serverWithoutAlertClient: serverWithoutAlertClient as ServerFacade & Hapi.Server, + actionsClient, + }; }; export const createMockServerWithoutActionClientDecoration = ( @@ -98,7 +104,10 @@ export const createMockServerWithoutActionClientDecoration = ( serverWithoutActionClient.decorate('request', 'getBasePath', () => '/s/default'); serverWithoutActionClient.decorate('request', 'getAlertsClient', () => alertsClient); - return { serverWithoutActionClient, alertsClient }; + return { + serverWithoutActionClient: serverWithoutActionClient as ServerFacade & Hapi.Server, + alertsClient, + }; }; export const createMockServerWithoutActionOrAlertClientDecoration = ( @@ -111,6 +120,22 @@ export const createMockServerWithoutActionOrAlertClientDecoration = ( serverWithoutActionOrAlertClient.config = () => createMockKibanaConfig(config); return { - serverWithoutActionOrAlertClient, + serverWithoutActionOrAlertClient: serverWithoutActionOrAlertClient as ServerFacade & + Hapi.Server, }; }; + +export const getMockIndexName = () => + jest.fn().mockImplementation(() => ({ + callWithRequest: jest.fn().mockImplementationOnce(() => 'index-name'), + })); + +export const getMockEmptyIndex = () => + jest.fn().mockImplementation(() => ({ + callWithRequest: jest.fn().mockImplementation(() => ({ _shards: { total: 0 } })), + })); + +export const getMockNonEmptyIndex = () => + jest.fn().mockImplementation(() => ({ + callWithRequest: jest.fn().mockImplementation(() => ({ _shards: { total: 1 } })), + })); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 01f30a3ebbdea..30a8d9d935128 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -15,6 +15,7 @@ import { DETECTION_ENGINE_QUERY_SIGNALS_URL, INTERNAL_RULE_ID_KEY, INTERNAL_IMMUTABLE_KEY, + DETECTION_ENGINE_PREPACKAGED_URL, } from '../../../../../common/constants'; import { RuleAlertType, IRuleSavedAttributesSavedObjectAttributes } from '../../rules/types'; import { RuleAlertParamsRest } from '../../types'; @@ -157,7 +158,17 @@ export const getDeleteAsPostBulkRequest = (): ServerInjectOptions => ({ export const getPrivilegeRequest = (): ServerInjectOptions => ({ method: 'GET', - url: `${DETECTION_ENGINE_PRIVILEGES_URL}`, + url: DETECTION_ENGINE_PRIVILEGES_URL, +}); + +export const addPrepackagedRulesRequest = (): ServerInjectOptions => ({ + method: 'PUT', + url: DETECTION_ENGINE_PREPACKAGED_URL, +}); + +export const getPrepackagedRulesStatusRequest = (): ServerInjectOptions => ({ + method: 'GET', + url: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, }); export interface FindHit { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts index 54872f80a4c6d..1ea681afb7949 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts @@ -7,7 +7,6 @@ import { createMockServer } from '../__mocks__/_mock_server'; import { getPrivilegeRequest, getMockPrivileges } from '../__mocks__/request_responses'; import { readPrivilegesRoute } from './read_privileges_route'; -import { ServerFacade } from '../../../../types'; import * as myUtils from '../utils'; describe('read_privileges', () => { @@ -19,7 +18,7 @@ describe('read_privileges', () => { elasticsearch.getCluster = jest.fn(() => ({ callWithRequest: jest.fn(() => getMockPrivileges()), })); - readPrivilegesRoute((server as unknown) as ServerFacade); + readPrivilegesRoute(server); }); afterEach(() => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts new file mode 100644 index 0000000000000..ed193b6473a9e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -0,0 +1,138 @@ +/* + * 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 { + createMockServer, + createMockServerWithoutActionClientDecoration, + createMockServerWithoutAlertClientDecoration, + createMockServerWithoutActionOrAlertClientDecoration, + getMockEmptyIndex, + getMockNonEmptyIndex, +} from '../__mocks__/_mock_server'; +import { createRulesRoute } from './create_rules_route'; +import { + getFindResult, + getResult, + createActionResult, + addPrepackagedRulesRequest, + getFindResultWithSingleHit, +} from '../__mocks__/request_responses'; + +jest.mock('../../rules/get_prepackaged_rules', () => { + return { + getPrepackagedRules: () => { + return [ + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + version: 2, // set one higher than the mocks which is set to 1 to trigger updates + }, + ]; + }, + }; +}); + +import { addPrepackedRulesRoute } from './add_prepackaged_rules_route'; + +describe('add_prepackaged_rules_route', () => { + let { server, alertsClient, actionsClient, elasticsearch } = createMockServer(); + + beforeEach(() => { + jest.resetAllMocks(); + ({ server, alertsClient, actionsClient, elasticsearch } = createMockServer()); + elasticsearch.getCluster = getMockNonEmptyIndex(); + + addPrepackedRulesRoute(server); + }); + + describe('status codes with actionClient and alertClient', () => { + test('returns 200 when creating a with a valid actionClient and alertClient', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { statusCode } = await server.inject(addPrepackagedRulesRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 404 if actionClient is not available on the route', async () => { + const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); + createRulesRoute(serverWithoutActionClient); + const { statusCode } = await serverWithoutActionClient.inject(addPrepackagedRulesRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient is not available on the route', async () => { + const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); + createRulesRoute(serverWithoutAlertClient); + const { statusCode } = await serverWithoutAlertClient.inject(addPrepackagedRulesRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient and actionClient are both not available on the route', async () => { + const { + serverWithoutActionOrAlertClient, + } = createMockServerWithoutActionOrAlertClientDecoration(); + createRulesRoute(serverWithoutActionOrAlertClient); + const { statusCode } = await serverWithoutActionOrAlertClient.inject( + addPrepackagedRulesRequest() + ); + expect(statusCode).toBe(404); + }); + }); + + describe('validation', () => { + test('it returns a 400 if the index does not exist', async () => { + elasticsearch.getCluster = getMockEmptyIndex(); + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(addPrepackagedRulesRequest()); + expect(JSON.parse(payload)).toEqual({ + error: 'Bad Request', + message: + 'Pre-packaged rules cannot be installed until the space index is created: .siem-signals-default', + statusCode: 400, + }); + }); + }); + + describe('payload', () => { + test('1 rule is installed and 0 are updated when find results are empty', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(addPrepackagedRulesRequest()); + expect(JSON.parse(payload)).toEqual({ + rules_installed: 1, + rules_updated: 0, + }); + }); + + test('1 rule is updated and 0 are installed when we return a single find and the versions are different', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(addPrepackagedRulesRequest()); + expect(JSON.parse(payload)).toEqual({ + rules_installed: 0, + rules_updated: 1, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 922b70e87467e..5ceecdb058e5f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -45,15 +45,15 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR const callWithRequest = callWithRequestFactory(request, server); const rulesFromFileSystem = getPrepackagedRules(); - const prepackedRules = await getExistingPrepackagedRules({ alertsClient }); - const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackedRules); - const rulesToUpdate = getRulesToUpdate(rulesFromFileSystem, prepackedRules); + const prepackagedRules = await getExistingPrepackagedRules({ alertsClient }); + const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackagedRules); + const rulesToUpdate = getRulesToUpdate(rulesFromFileSystem, prepackagedRules); const spaceIndex = getIndex(request, server); if (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0) { const spaceIndexExists = await getIndexExists(callWithRequest, spaceIndex); if (!spaceIndexExists) { - throw new Boom( + return Boom.badRequest( `Pre-packaged rules cannot be installed until the space index is created: ${spaceIndex}` ); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index d5650b078e678..0931e941f8e46 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -9,10 +9,10 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, + getMockEmptyIndex, } from '../__mocks__/_mock_server'; import { createRulesRoute } from './create_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getFindResult, getResult, @@ -29,11 +29,7 @@ describe('create_rules_bulk', () => { beforeEach(() => { jest.resetAllMocks(); ({ server, alertsClient, actionsClient, elasticsearch } = createMockServer()); - elasticsearch.getCluster = jest.fn().mockImplementation(() => ({ - callWithRequest: jest.fn().mockImplementation(() => true), - })); - - createRulesBulkRoute((server as unknown) as ServerFacade); + createRulesBulkRoute(server); }); describe('status codes with actionClient and alertClient', () => { @@ -48,14 +44,14 @@ describe('create_rules_bulk', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - createRulesRoute((serverWithoutActionClient as unknown) as ServerFacade); + createRulesRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getReadBulkRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - createRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade); + createRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getReadBulkRequest()); expect(statusCode).toBe(404); }); @@ -64,13 +60,32 @@ describe('create_rules_bulk', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - createRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + createRulesRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getReadBulkRequest()); expect(statusCode).toBe(404); }); }); describe('validation', () => { + test('it gets a 409 if the index does not exist', async () => { + elasticsearch.getCluster = getMockEmptyIndex(); + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(getReadBulkRequest()); + expect(JSON.parse(payload)).toEqual([ + { + error: { + message: + 'To create a rule, the index must exist first. Index .siem-signals does not exist', + status_code: 400, + }, + rule_id: 'rule-1', + }, + ]); + }); + test('returns 200 if rule_id is not given as the id is auto generated from the alert framework', async () => { alertsClient.find.mockResolvedValue(getFindResult()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 15e3361986ab9..9c18f9040008c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -87,7 +87,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou if (!indexExists) { return createBulkErrorObject({ ruleId: ruleIdOrUuid, - statusCode: 409, + statusCode: 400, message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, }); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 466d150d58466..77c6f6f3b4840 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -9,10 +9,11 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, + getMockNonEmptyIndex, + getMockEmptyIndex, } from '../__mocks__/_mock_server'; import { createRulesRoute } from './create_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getFindResult, @@ -42,13 +43,8 @@ describe('create_rules', () => { elasticsearch, savedObjectsClient, } = createMockServer()); - elasticsearch.getCluster = jest.fn().mockImplementation(() => ({ - callWithRequest: jest - .fn() - .mockImplementation((endpoint, params) => ({ _shards: { total: 1 } })), - })); - - createRulesRoute((server as unknown) as ServerFacade); + elasticsearch.getCluster = getMockNonEmptyIndex(); + createRulesRoute(server); }); describe('status codes with actionClient and alertClient', () => { @@ -64,14 +60,14 @@ describe('create_rules', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - createRulesRoute((serverWithoutActionClient as unknown) as ServerFacade); + createRulesRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getCreateRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - createRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade); + createRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getCreateRequest()); expect(statusCode).toBe(404); }); @@ -80,13 +76,27 @@ describe('create_rules', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - createRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + createRulesRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getCreateRequest()); expect(statusCode).toBe(404); }); }); describe('validation', () => { + test('it returns a 400 if the index does not exist', async () => { + elasticsearch.getCluster = getMockEmptyIndex(); + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(getCreateRequest()); + expect(JSON.parse(payload)).toEqual({ + error: 'Bad Request', + message: 'To create a rule, the index must exist first. Index .siem-signals does not exist', + statusCode: 400, + }); + }); + test('returns 200 if rule_id is not given as the id is auto generated from the alert framework', async () => { alertsClient.find.mockResolvedValue(getFindResult()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index 3e173e358557a..aa535d325f4b9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -78,17 +78,14 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, finalIndex); if (!indexExists) { - return new Boom( - `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, - { - statusCode: 400, - } + return Boom.badRequest( + `To create a rule, the index must exist first. Index ${finalIndex} does not exist` ); } if (ruleId != null) { const rule = await readRules({ alertsClient, ruleId }); if (rule != null) { - return new Boom(`rule_id: "${ruleId}" already exists`, { statusCode: 409 }); + return Boom.conflict(`rule_id: "${ruleId}" already exists`); } } const createdRule = await createRules({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts index 3c429c3e38f3a..7b8496b2fe725 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts @@ -22,7 +22,6 @@ import { getDeleteAsPostBulkRequestById, getFindResultStatus, } from '../__mocks__/request_responses'; -import { ServerFacade } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { deleteRulesBulkRoute } from './delete_rules_bulk_route'; @@ -33,7 +32,7 @@ describe('delete_rules', () => { beforeEach(() => { ({ server, alertsClient, savedObjectsClient } = createMockServer()); - deleteRulesBulkRoute((server as unknown) as ServerFacade); + deleteRulesBulkRoute(server); }); afterEach(() => { @@ -100,14 +99,14 @@ describe('delete_rules', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - deleteRulesBulkRoute((serverWithoutActionClient as unknown) as ServerFacade); + deleteRulesBulkRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getDeleteBulkRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - deleteRulesBulkRoute((serverWithoutAlertClient as unknown) as ServerFacade); + deleteRulesBulkRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getDeleteBulkRequest()); expect(statusCode).toBe(404); }); @@ -116,7 +115,7 @@ describe('delete_rules', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - deleteRulesBulkRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + deleteRulesBulkRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getDeleteBulkRequest()); expect(statusCode).toBe(404); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts index ee4edada52b6a..2854312246c5f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts @@ -13,7 +13,6 @@ import { import { deleteRulesRoute } from './delete_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getFindResult, @@ -30,7 +29,7 @@ describe('delete_rules', () => { beforeEach(() => { ({ server, alertsClient, savedObjectsClient } = createMockServer()); - deleteRulesRoute((server as unknown) as ServerFacade); + deleteRulesRoute(server); }); afterEach(() => { @@ -70,14 +69,14 @@ describe('delete_rules', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - deleteRulesRoute((serverWithoutActionClient as unknown) as ServerFacade); + deleteRulesRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getDeleteRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - deleteRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade); + deleteRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getDeleteRequest()); expect(statusCode).toBe(404); }); @@ -86,7 +85,7 @@ describe('delete_rules', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - deleteRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + deleteRulesRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getDeleteRequest()); expect(statusCode).toBe(404); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index 639e4877f7f72..0aab02281a536 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -13,7 +13,6 @@ import { import { findRulesRoute } from './find_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getFindResult, getResult, getFindRequest } from '../__mocks__/request_responses'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; @@ -23,7 +22,7 @@ describe('find_rules', () => { beforeEach(() => { ({ server, alertsClient, actionsClient } = createMockServer()); - findRulesRoute((server as unknown) as ServerFacade); + findRulesRoute(server); }); afterEach(() => { @@ -46,14 +45,14 @@ describe('find_rules', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - findRulesRoute((serverWithoutActionClient as unknown) as ServerFacade); + findRulesRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getFindRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - findRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade); + findRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getFindRequest()); expect(statusCode).toBe(404); }); @@ -62,7 +61,7 @@ describe('find_rules', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - findRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + findRulesRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getFindRequest()); expect(statusCode).toBe(404); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts new file mode 100644 index 0000000000000..1ae9e87b8eefe --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { + createMockServer, + createMockServerWithoutActionClientDecoration, + createMockServerWithoutAlertClientDecoration, + createMockServerWithoutActionOrAlertClientDecoration, + getMockNonEmptyIndex, +} from '../__mocks__/_mock_server'; +import { createRulesRoute } from './create_rules_route'; +import { + getFindResult, + getResult, + createActionResult, + getFindResultWithSingleHit, + getPrepackagedRulesStatusRequest, +} from '../__mocks__/request_responses'; + +jest.mock('../../rules/get_prepackaged_rules', () => { + return { + getPrepackagedRules: () => { + return [ + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + version: 2, // set one higher than the mocks which is set to 1 to trigger updates + }, + ]; + }, + }; +}); + +import { getPrepackagedRulesStatusRoute } from './get_prepackaged_rules_status_route'; + +describe('get_prepackaged_rule_status_route', () => { + let { server, alertsClient, actionsClient, elasticsearch } = createMockServer(); + + beforeEach(() => { + jest.resetAllMocks(); + ({ server, alertsClient, actionsClient, elasticsearch } = createMockServer()); + elasticsearch.getCluster = getMockNonEmptyIndex(); + getPrepackagedRulesStatusRoute(server); + }); + + describe('status codes with actionClient and alertClient', () => { + test('returns 200 when creating a with a valid actionClient and alertClient', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { statusCode } = await server.inject(getPrepackagedRulesStatusRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 404 if actionClient is not available on the route', async () => { + const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); + createRulesRoute(serverWithoutActionClient); + const { statusCode } = await serverWithoutActionClient.inject( + getPrepackagedRulesStatusRequest() + ); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient is not available on the route', async () => { + const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); + createRulesRoute(serverWithoutAlertClient); + const { statusCode } = await serverWithoutAlertClient.inject( + getPrepackagedRulesStatusRequest() + ); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient and actionClient are both not available on the route', async () => { + const { + serverWithoutActionOrAlertClient, + } = createMockServerWithoutActionOrAlertClientDecoration(); + createRulesRoute(serverWithoutActionOrAlertClient); + const { statusCode } = await serverWithoutActionOrAlertClient.inject( + getPrepackagedRulesStatusRequest() + ); + expect(statusCode).toBe(404); + }); + }); + + describe('payload', () => { + test('0 rules installed, 1 rules not installed, and 1 rule not updated', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(getPrepackagedRulesStatusRequest()); + expect(JSON.parse(payload)).toEqual({ + rules_installed: 0, + rules_not_installed: 1, + rules_not_updated: 0, + }); + }); + + test('1 rule installed, 0 rules not installed, and 1 rule to not updated', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(getPrepackagedRulesStatusRequest()); + expect(JSON.parse(payload)).toEqual({ + rules_installed: 1, + rules_not_installed: 0, + rules_not_updated: 1, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts new file mode 100644 index 0000000000000..99e29242bced0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -0,0 +1,59 @@ +/* + * 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 Hapi from 'hapi'; +import { isFunction } from 'lodash/fp'; + +import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; +import { ServerFacade, RequestFacade } from '../../../../types'; +import { transformError } from '../utils'; +import { getPrepackagedRules } from '../../rules/get_prepackaged_rules'; +import { getRulesToInstall } from '../../rules/get_rules_to_install'; +import { getRulesToUpdate } from '../../rules/get_rules_to_update'; +import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; + +export const createGetPrepackagedRulesStatusRoute = (): Hapi.ServerRoute => { + return { + method: 'GET', + path: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + }, + }, + async handler(request: RequestFacade, headers) { + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = isFunction(request.getActionsClient) + ? request.getActionsClient() + : null; + + if (!alertsClient || !actionsClient) { + return headers.response().code(404); + } + + try { + const rulesFromFileSystem = getPrepackagedRules(); + const prepackagedRules = await getExistingPrepackagedRules({ alertsClient }); + const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackagedRules); + const rulesToUpdate = getRulesToUpdate(rulesFromFileSystem, prepackagedRules); + return { + rules_installed: prepackagedRules.length, + rules_not_installed: rulesToInstall.length, + rules_not_updated: rulesToUpdate.length, + }; + } catch (err) { + return transformError(err); + } + }, + }; +}; + +export const getPrepackagedRulesStatusRoute = (server: ServerFacade): void => { + server.route(createGetPrepackagedRulesStatusRoute()); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts index 92db4be1c9ff9..4190225bea1f1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts @@ -13,7 +13,6 @@ import { import { readRulesRoute } from './read_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getFindResult, @@ -29,7 +28,7 @@ describe('read_signals', () => { beforeEach(() => { ({ server, alertsClient, savedObjectsClient } = createMockServer()); - readRulesRoute((server as unknown) as ServerFacade); + readRulesRoute(server); }); afterEach(() => { @@ -47,14 +46,14 @@ describe('read_signals', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - readRulesRoute((serverWithoutActionClient as unknown) as ServerFacade); + readRulesRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getReadRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - readRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade); + readRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getReadRequest()); expect(statusCode).toBe(404); }); @@ -63,7 +62,7 @@ describe('read_signals', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - readRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + readRulesRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getReadRequest()); expect(statusCode).toBe(404); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts index 2ab2610834195..cc41800671d7d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts @@ -13,7 +13,6 @@ import { import { updateRulesRoute } from './update_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getFindResult, @@ -33,7 +32,7 @@ describe('update_rules_bulk', () => { beforeEach(() => { jest.resetAllMocks(); ({ server, alertsClient, actionsClient } = createMockServer()); - updateRulesBulkRoute((server as unknown) as ServerFacade); + updateRulesBulkRoute(server); }); describe('status codes with actionClient and alertClient', () => { @@ -73,14 +72,14 @@ describe('update_rules_bulk', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - updateRulesRoute((serverWithoutActionClient as unknown) as ServerFacade); + updateRulesRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getUpdateBulkRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - updateRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade); + updateRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getUpdateBulkRequest()); expect(statusCode).toBe(404); }); @@ -89,7 +88,7 @@ describe('update_rules_bulk', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - updateRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + updateRulesRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getUpdateBulkRequest()); expect(statusCode).toBe(404); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index d33989b8a7ce5..a7e8f1b1c0a7e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -13,7 +13,6 @@ import { import { updateRulesRoute } from './update_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getFindResult, @@ -32,7 +31,7 @@ describe('update_rules', () => { beforeEach(() => { jest.resetAllMocks(); ({ server, alertsClient, actionsClient, savedObjectsClient } = createMockServer()); - updateRulesRoute((server as unknown) as ServerFacade); + updateRulesRoute(server); }); describe('status codes with actionClient and alertClient', () => { @@ -58,14 +57,14 @@ describe('update_rules', () => { test('returns 404 if actionClient is not available on the route', async () => { const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - updateRulesRoute((serverWithoutActionClient as unknown) as ServerFacade); + updateRulesRoute(serverWithoutActionClient); const { statusCode } = await serverWithoutActionClient.inject(getUpdateRequest()); expect(statusCode).toBe(404); }); test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); - updateRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade); + updateRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getUpdateRequest()); expect(statusCode).toBe(404); }); @@ -74,7 +73,7 @@ describe('update_rules', () => { const { serverWithoutActionOrAlertClient, } = createMockServerWithoutActionOrAlertClientDecoration(); - updateRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade); + updateRulesRoute(serverWithoutActionOrAlertClient); const { statusCode } = await serverWithoutActionOrAlertClient.inject(getUpdateRequest()); expect(statusCode).toBe(404); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index 7d4fb43b58ef1..ae79b571b2b62 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -34,11 +34,11 @@ export const getIdError = ({ ruleId: string | undefined | null; }) => { if (id != null) { - return new Boom(`id: "${id}" not found`, { statusCode: 404 }); + return Boom.notFound(`id: "${id}" not found`); } else if (ruleId != null) { - return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); + return Boom.notFound(`rule_id: "${ruleId}" not found`); } else { - return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); + return Boom.notFound('id or rule_id should have been defined'); } }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index 74eb4d6c8e918..1993948808ef4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -205,8 +205,8 @@ describe('add prepackaged rules schema', () => { query: 'some query', language: 'kuery', version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('"output_index" is not allowed'); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => { @@ -345,6 +345,48 @@ describe('add prepackaged rules schema', () => { ).toEqual(true); }); + test('immutable cannot be false', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + immutable: false, + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + version: 1, + }).error.message + ).toEqual('child "immutable" fails because ["immutable" must be one of [true]]'); + }); + + test('immutable can be true', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + immutable: true, + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + version: 1, + }).error + ).toBeFalsy(); + }); + test('defaults enabled to false', () => { expect( addPrepackagedRulesSchema.validate>({ @@ -380,8 +422,8 @@ describe('add prepackaged rules schema', () => { query: 'some-query', language: 'kuery', version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "rule_id" fails because ["rule_id" is required]'); }); test('references cannot be numbers', () => { @@ -403,8 +445,10 @@ describe('add prepackaged rules schema', () => { language: 'kuery', references: [5], version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]' + ); }); test('indexes cannot be numbers', () => { @@ -425,8 +469,10 @@ describe('add prepackaged rules schema', () => { query: 'some-query', language: 'kuery', version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]' + ); }); test('defaults interval to 5 min', () => { @@ -478,8 +524,8 @@ describe('add prepackaged rules schema', () => { interval: '5m', type: 'saved_query', version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "saved_id" fails because ["saved_id" is required]'); }); test('saved_id is required when type is saved_query and validates with it', () => { @@ -539,8 +585,8 @@ describe('add prepackaged rules schema', () => { saved_id: 'some id', filters: 'some string', version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "filters" fails because ["filters" must be an array]'); }); test('language validates with kuery', () => { @@ -602,8 +648,8 @@ describe('add prepackaged rules schema', () => { query: 'some query', language: 'something-made-up', version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]'); }); test('max_signals cannot be negative', () => { @@ -624,8 +670,8 @@ describe('add prepackaged rules schema', () => { language: 'kuery', max_signals: -1, version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals cannot be zero', () => { @@ -646,8 +692,8 @@ describe('add prepackaged rules schema', () => { language: 'kuery', max_signals: 0, version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals can be 1', () => { @@ -716,8 +762,10 @@ describe('add prepackaged rules schema', () => { max_signals: 1, tags: [0, 1, 2], version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "tags" fails because ["tags" at position 0 fails because ["0" must be a string]]' + ); }); test('You cannot send in an array of threats that are missing "framework"', () => { @@ -758,9 +806,12 @@ describe('add prepackaged rules schema', () => { }, ], version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]' + ); }); + test('You cannot send in an array of threats that are missing "tactic"', () => { expect( addPrepackagedRulesSchema.validate< @@ -795,9 +846,12 @@ describe('add prepackaged rules schema', () => { }, ], version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]' + ); }); + test('You cannot send in an array of threats that are missing "techniques"', () => { expect( addPrepackagedRulesSchema.validate< @@ -830,8 +884,10 @@ describe('add prepackaged rules schema', () => { }, ], version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]' + ); }); test('You can optionally send in an array of false positives', () => { @@ -878,8 +934,10 @@ describe('add prepackaged rules schema', () => { language: 'kuery', max_signals: 1, version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "false_positives" fails because ["false_positives" at position 0 fails because ["0" must be a string]]' + ); }); test('You can optionally set the immutable to be true', () => { @@ -926,8 +984,8 @@ describe('add prepackaged rules schema', () => { language: 'kuery', max_signals: 1, version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "immutable" fails because ["immutable" must be a boolean]'); }); test('You cannot set the risk_score to 101', () => { @@ -949,8 +1007,8 @@ describe('add prepackaged rules schema', () => { language: 'kuery', max_signals: 1, version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "risk_score" fails because ["risk_score" must be less than 101]'); }); test('You cannot set the risk_score to -1', () => { @@ -972,8 +1030,8 @@ describe('add prepackaged rules schema', () => { language: 'kuery', max_signals: 1, version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "risk_score" fails because ["risk_score" must be greater than -1]'); }); test('You can set the risk_score to 0', () => { @@ -1070,8 +1128,8 @@ describe('add prepackaged rules schema', () => { max_signals: 1, meta: 'should not work', version: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "meta" fails because ["meta" must be an object]'); }); test('You can omit the query string when filters are present', () => { @@ -1140,8 +1198,8 @@ describe('add prepackaged rules schema', () => { max_signals: 1, version: 1, timeline_id: 'timeline-id', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is required]'); }); test('You cannot have a null value for timeline_title when timeline_id is present', () => { @@ -1165,8 +1223,8 @@ describe('add prepackaged rules schema', () => { version: 1, timeline_id: 'timeline-id', timeline_title: null, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]'); }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { @@ -1190,8 +1248,8 @@ describe('add prepackaged rules schema', () => { version: 1, timeline_id: 'timeline-id', timeline_title: '', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed to be empty]'); }); test('You cannot have timeline_title with an empty timeline_id', () => { @@ -1215,8 +1273,8 @@ describe('add prepackaged rules schema', () => { version: 1, timeline_id: '', timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]'); }); test('You cannot have timeline_title without timeline_id', () => { @@ -1239,7 +1297,7 @@ describe('add prepackaged rules schema', () => { max_signals: 1, version: 1, timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts index 49907b4a975e6..9311371d630f7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts @@ -42,7 +42,7 @@ import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; * Big differences between this schema and the createRulesSchema * - rule_id is required here * - output_index is not allowed (and instead the space index must be used) - * - immutable defaults to true instead of to false + * - immutable defaults to true instead of to false and if it is there can only be true * - enabled defaults to false instead of true * - version is a required field that must exist */ @@ -53,7 +53,7 @@ export const addPrepackagedRulesSchema = Joi.object({ filters, from: from.required(), rule_id: rule_id.required(), - immutable: immutable.default(true), + immutable: immutable.default(true).valid(true), index, interval: interval.default('5m'), query: query.allow('').default(''), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 87916bea60649..15f4fa7f05648 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -238,6 +238,7 @@ describe('create rules schema', () => { }).error ).toBeFalsy(); }); + test('You can send in an empty array to threats', () => { expect( createRulesSchema.validate>({ @@ -260,6 +261,7 @@ describe('create rules schema', () => { }).error ).toBeFalsy(); }); + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { expect( createRulesSchema.validate>({ @@ -355,8 +357,10 @@ describe('create rules schema', () => { query: 'some-query', language: 'kuery', references: [5], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]' + ); }); test('indexes cannot be numbers', () => { @@ -377,8 +381,10 @@ describe('create rules schema', () => { query: 'some-query', language: 'kuery', } - ).error - ).toBeTruthy(); + ).error.message + ).toEqual( + 'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]' + ); }); test('defaults interval to 5 min', () => { @@ -430,8 +436,8 @@ describe('create rules schema', () => { severity: 'severity', interval: '5m', type: 'saved_query', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "saved_id" fails because ["saved_id" is required]'); }); test('saved_id is required when type is saved_query and validates with it', () => { @@ -491,8 +497,8 @@ describe('create rules schema', () => { type: 'saved_query', saved_id: 'some id', filters: 'some string', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "filters" fails because ["filters" must be an array]'); }); test('language validates with kuery', () => { @@ -554,8 +560,8 @@ describe('create rules schema', () => { references: ['index-1'], query: 'some query', language: 'something-made-up', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]'); }); test('max_signals cannot be negative', () => { @@ -576,8 +582,8 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', max_signals: -1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals cannot be zero', () => { @@ -598,8 +604,8 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', max_signals: 0, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals can be 1', () => { @@ -666,8 +672,10 @@ describe('create rules schema', () => { language: 'kuery', max_signals: 1, tags: [0, 1, 2], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "tags" fails because ["tags" at position 0 fails because ["0" must be a string]]' + ); }); test('You cannot send in an array of threats that are missing "framework"', () => { @@ -708,9 +716,12 @@ describe('create rules schema', () => { ], }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]' + ); }); + test('You cannot send in an array of threats that are missing "tactic"', () => { expect( createRulesSchema.validate< @@ -745,9 +756,12 @@ describe('create rules schema', () => { ], }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]' + ); }); + test('You cannot send in an array of threats that are missing "techniques"', () => { expect( createRulesSchema.validate< @@ -780,8 +794,10 @@ describe('create rules schema', () => { }, }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]' + ); }); test('You can optionally send in an array of false positives', () => { @@ -828,34 +844,13 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "false_positives" fails because ["false_positives" at position 0 fails because ["0" must be a string]]' + ); }); - test('You can optionally set the immutable to be true', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You cannot set the immutable to be a number', () => { + test('You cannot set the immutable when trying to create a rule', () => { expect( createRulesSchema.validate< Partial> & { immutable: number } @@ -876,8 +871,8 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('"immutable" is not allowed'); }); test('You cannot set the risk_score to 101', () => { @@ -899,8 +894,8 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "risk_score" fails because ["risk_score" must be less than 101]'); }); test('You cannot set the risk_score to -1', () => { @@ -922,8 +917,8 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "risk_score" fails because ["risk_score" must be greater than -1]'); }); test('You can set the risk_score to 0', () => { @@ -935,7 +930,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -958,7 +952,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -981,7 +974,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -1018,8 +1010,8 @@ describe('create rules schema', () => { language: 'kuery', max_signals: 1, meta: 'should not work', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "meta" fails because ["meta" must be an object]'); }); test('You can omit the query string when filters are present', () => { @@ -1031,7 +1023,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -1086,8 +1077,8 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', timeline_id: 'some_id', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is required]'); }); test('You cannot have a null value for timeline_title when timeline_id is present', () => { @@ -1109,8 +1100,8 @@ describe('create rules schema', () => { language: 'kuery', timeline_id: 'some_id', timeline_title: null, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]'); }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { @@ -1132,8 +1123,8 @@ describe('create rules schema', () => { language: 'kuery', timeline_id: 'some_id', timeline_title: '', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed to be empty]'); }); test('You cannot have timeline_title with an empty timeline_id', () => { @@ -1155,8 +1146,8 @@ describe('create rules schema', () => { language: 'kuery', timeline_id: '', timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]'); }); test('You cannot have timeline_title without timeline_id', () => { @@ -1177,7 +1168,7 @@ describe('create rules schema', () => { query: 'some query', language: 'kuery', timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts index df5c1694d6c78..5d9972453fb1a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts @@ -13,7 +13,6 @@ import { false_positives, filters, from, - immutable, index, rule_id, interval, @@ -46,7 +45,6 @@ export const createRulesSchema = Joi.object({ filters, from: from.required(), rule_id, - immutable: immutable.default(false), index, interval: interval.default('5m'), query: query.allow('').default(''), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.test.ts index 7850e3a733f09..dd3adf53f503b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.test.ts @@ -37,8 +37,10 @@ describe('create rules schema', () => { expect( exportRulesSchema.validate>({ objects: [{ id: 'test-1' }], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "objects" fails because ["objects" at position 0 fails because ["id" is not allowed]]' + ); }); }); @@ -70,8 +72,8 @@ describe('create rules schema', () => { Partial & { file_name: number }> >({ file_name: 5, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "file_name" fails because ["file_name" must be a string]'); }); test('exclude_export_details validates with a boolean true', () => { @@ -92,8 +94,10 @@ describe('create rules schema', () => { > >({ exclude_export_details: 'blah', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "exclude_export_details" fails because ["exclude_export_details" must be a boolean]' + ); }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts index 14b3bdb298739..339874e19c33a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts @@ -69,8 +69,10 @@ describe('find rules schema', () => { expect( findRulesSchema.validate> & { fields: number[] }>({ fields: [5], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "fields" fails because ["fields" at position 0 fails because ["0" must be a string]]' + ); }); test('per page has a default of 20', () => { @@ -93,16 +95,16 @@ describe('find rules schema', () => { expect( findRulesSchema.validate> & { filter: number }>({ filter: 5, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "filter" fails because ["filter" must be a string]'); }); test('sort_order requires sort_field to work', () => { expect( findRulesSchema.validate>({ sort_order: 'asc', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "sort_field" fails because ["sort_field" is required]'); }); test('sort_order and sort_field validate together', () => { @@ -130,7 +132,7 @@ describe('find rules schema', () => { >({ sort_order: 'some other string', sort_field: 'some field', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "sort_order" fails because ["sort_order" must be one of [asc, desc]]'); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index 09bc7a70711ec..bed64cc6e7a02 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -243,6 +243,7 @@ describe('import rules schema', () => { }).error ).toBeFalsy(); }); + test('You can send in an empty array to threats', () => { expect( importRulesSchema.validate>({ @@ -265,6 +266,7 @@ describe('import rules schema', () => { }).error ).toBeFalsy(); }); + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { expect( importRulesSchema.validate>({ @@ -360,8 +362,10 @@ describe('import rules schema', () => { query: 'some-query', language: 'kuery', references: [5], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]' + ); }); test('indexes cannot be numbers', () => { @@ -382,8 +386,10 @@ describe('import rules schema', () => { type: 'query', query: 'some-query', language: 'kuery', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]' + ); }); test('defaults interval to 5 min', () => { @@ -435,8 +441,8 @@ describe('import rules schema', () => { severity: 'severity', interval: '5m', type: 'saved_query', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "saved_id" fails because ["saved_id" is required]'); }); test('saved_id is required when type is saved_query and validates with it', () => { @@ -496,8 +502,8 @@ describe('import rules schema', () => { type: 'saved_query', saved_id: 'some id', filters: 'some string', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "filters" fails because ["filters" must be an array]'); }); test('language validates with kuery', () => { @@ -559,8 +565,8 @@ describe('import rules schema', () => { references: ['index-1'], query: 'some query', language: 'something-made-up', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]'); }); test('max_signals cannot be negative', () => { @@ -581,8 +587,8 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', max_signals: -1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals cannot be zero', () => { @@ -603,8 +609,8 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', max_signals: 0, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals can be 1', () => { @@ -673,8 +679,10 @@ describe('import rules schema', () => { max_signals: 1, tags: [0, 1, 2], } - ).error - ).toBeTruthy(); + ).error.message + ).toEqual( + 'child "tags" fails because ["tags" at position 0 fails because ["0" must be a string]]' + ); }); test('You cannot send in an array of threats that are missing "framework"', () => { @@ -715,9 +723,12 @@ describe('import rules schema', () => { ], }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]' + ); }); + test('You cannot send in an array of threats that are missing "tactic"', () => { expect( importRulesSchema.validate< @@ -752,9 +763,12 @@ describe('import rules schema', () => { ], }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]' + ); }); + test('You cannot send in an array of threats that are missing "techniques"', () => { expect( importRulesSchema.validate< @@ -787,8 +801,10 @@ describe('import rules schema', () => { }, }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]' + ); }); test('You can optionally send in an array of false positives', () => { @@ -835,8 +851,10 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "false_positives" fails because ["false_positives" at position 0 fails because ["0" must be a string]]' + ); }); test('You can optionally set the immutable to be true', () => { @@ -883,8 +901,8 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "immutable" fails because ["immutable" must be a boolean]'); }); test('You cannot set the risk_score to 101', () => { @@ -906,8 +924,8 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "risk_score" fails because ["risk_score" must be less than 101]'); }); test('You cannot set the risk_score to -1', () => { @@ -929,8 +947,8 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', max_signals: 1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "risk_score" fails because ["risk_score" must be greater than -1]'); }); test('You can set the risk_score to 0', () => { @@ -1025,8 +1043,8 @@ describe('import rules schema', () => { language: 'kuery', max_signals: 1, meta: 'should not work', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "meta" fails because ["meta" must be an object]'); }); test('You can omit the query string when filters are present', () => { @@ -1093,8 +1111,8 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', timeline_id: 'some_id', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is required]'); }); test('You cannot have a null value for timeline_title when timeline_id is present', () => { @@ -1116,8 +1134,8 @@ describe('import rules schema', () => { language: 'kuery', timeline_id: 'some_id', timeline_title: null, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]'); }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { @@ -1139,8 +1157,10 @@ describe('import rules schema', () => { language: 'kuery', timeline_id: 'some_id', timeline_title: '', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "timeline_title" fails because ["timeline_title" is not allowed to be empty]' + ); }); test('You cannot have timeline_title with an empty timeline_id', () => { @@ -1162,8 +1182,8 @@ describe('import rules schema', () => { language: 'kuery', timeline_id: '', timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]'); }); test('You cannot have timeline_title without timeline_id', () => { @@ -1184,8 +1204,8 @@ describe('import rules schema', () => { query: 'some query', language: 'kuery', timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); test('rule_id is required and you cannot get by with just id', () => { @@ -1205,8 +1225,8 @@ describe('import rules schema', () => { references: ['index-1'], query: 'some query', language: 'kuery', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "rule_id" fails because ["rule_id" is required]'); }); test('it validates with created_at, updated_at, created_by, updated_by values', () => { @@ -1255,8 +1275,8 @@ describe('import rules schema', () => { updated_at: '2020-01-09T06:15:24.749Z', created_by: 'Braden Hassanabad', updated_by: 'Evan Hassanabad', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "created_at" fails because ["created_at" must be a valid ISO 8601 date]'); }); test('it does not validate with epoch strings for updated_at', () => { @@ -1280,8 +1300,8 @@ describe('import rules schema', () => { updated_at: '1578550728650', created_by: 'Braden Hassanabad', updated_by: 'Evan Hassanabad', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "updated_at" fails because ["updated_at" must be a valid ISO 8601 date]'); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.test.ts index 6450da37699d8..ab1ffaab49165 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.test.ts @@ -24,8 +24,10 @@ describe('query_rules_bulk_schema', () => { rule_id: '1', id: '1', }, - ]).error - ).toBeTruthy(); + ]).error.message + ).toEqual( + '"value" at position 0 fails because ["value" contains a conflict between exclusive peers [id, rule_id]]' + ); }); test('both rule_id and id being supplied do not validate if one array element works but the second does not', () => { @@ -38,8 +40,10 @@ describe('query_rules_bulk_schema', () => { rule_id: '1', id: '1', }, - ]).error - ).toBeTruthy(); + ]).error.message + ).toEqual( + '"value" at position 1 fails because ["value" contains a conflict between exclusive peers [id, rule_id]]' + ); }); test('only id validates', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts index 6c4e96abd2b98..c89d60e773a77 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts @@ -12,10 +12,11 @@ describe('queryRulesSchema', () => { expect(queryRulesSchema.validate>({}).error).toBeTruthy(); }); - test('both rule_id and id being supplied dot not validate', () => { + test('both rule_id and id being supplied do not validate', () => { expect( queryRulesSchema.validate>({ rule_id: '1', id: '1' }).error - ).toBeTruthy(); + .message + ).toEqual('"value" contains a conflict between exclusive peers [id, rule_id]'); }); test('only id validates', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts index 792c7afad05b1..a6ba9b19a9d7d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts @@ -30,24 +30,24 @@ describe('set signal status schema', () => { expect( setSignalsStatusSchema.validate>({ signal_ids: ['somefakeid'], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "status" fails because ["status" is required]'); }); test('query and missing status is invalid', () => { expect( setSignalsStatusSchema.validate>({ query: {}, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "status" fails because ["status" is required]'); }); test('status is present but query or signal_ids is missing is invalid', () => { expect( setSignalsStatusSchema.validate>({ status: 'closed', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('"value" must contain at least one of [signal_ids, query]'); }); test('signal_ids is present but status has wrong value', () => { @@ -60,7 +60,7 @@ describe('set signal status schema', () => { > >({ status: 'fakeVal', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "status" fails because ["status" must be one of [open, closed]]'); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index f713840ab43f9..823ebb90a3b3c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -442,8 +442,10 @@ describe('update rules schema', () => { query: 'some-query', language: 'kuery', references: [5], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]' + ); }); test('indexes cannot be numbers', () => { @@ -462,8 +464,10 @@ describe('update rules schema', () => { type: 'query', query: 'some-query', language: 'kuery', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]' + ); }); test('saved_id is not required when type is saved_query and will validate without it', () => { @@ -570,8 +574,8 @@ describe('update rules schema', () => { references: ['index-1'], query: 'some query', language: 'something-made-up', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]'); }); test('max_signals cannot be negative', () => { @@ -590,8 +594,8 @@ describe('update rules schema', () => { query: 'some query', language: 'kuery', max_signals: -1, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals cannot be zero', () => { @@ -610,8 +614,8 @@ describe('update rules schema', () => { query: 'some query', language: 'kuery', max_signals: 0, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]'); }); test('max_signals can be 1', () => { @@ -643,15 +647,15 @@ describe('update rules schema', () => { ).toBeFalsy(); }); - test('You update meta as a string', () => { + test('You cannot update meta as a string', () => { expect( updateRulesSchema.validate< Partial & { meta: string }> >({ id: 'rule-1', meta: 'should not work', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "meta" fails because ["meta" must be an object]'); }); test('filters cannot be a string', () => { @@ -662,8 +666,8 @@ describe('update rules schema', () => { rule_id: 'rule-1', type: 'query', filters: 'some string', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "filters" fails because ["filters" must be an array]'); }); test('threats is not defaulted to empty array on update', () => { @@ -706,6 +710,7 @@ describe('update rules schema', () => { }).value.threats ).toMatchObject([]); }); + test('threats is valid when updated with all sub-objects', () => { const expected: ThreatParams[] = [ { @@ -759,6 +764,7 @@ describe('update rules schema', () => { }).value.threats ).toMatchObject(expected); }); + test('threats is invalid when updated with missing property framework', () => { expect( updateRulesSchema.validate< @@ -795,9 +801,12 @@ describe('update rules schema', () => { ], }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]' + ); }); + test('threats is invalid when updated with missing tactic sub-object', () => { expect( updateRulesSchema.validate< @@ -830,9 +839,12 @@ describe('update rules schema', () => { ], }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]' + ); }); + test('threats is invalid when updated with missing techniques', () => { expect( updateRulesSchema.validate< @@ -863,8 +875,10 @@ describe('update rules schema', () => { }, }, ], - }).error - ).toBeTruthy(); + }).error.message + ).toEqual( + 'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]' + ); }); test('validates with timeline_id and timeline_title', () => { @@ -900,8 +914,8 @@ describe('update rules schema', () => { type: 'saved_query', saved_id: 'some id', timeline_id: 'some-id', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is required]'); }); test('You cannot have a null value for timeline_title when timeline_id is present', () => { @@ -919,8 +933,8 @@ describe('update rules schema', () => { saved_id: 'some id', timeline_id: 'timeline-id', timeline_title: null, - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]'); }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { @@ -938,8 +952,8 @@ describe('update rules schema', () => { saved_id: 'some id', timeline_id: 'some-id', timeline_title: '', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed to be empty]'); }); test('You cannot have timeline_title with an empty timeline_id', () => { @@ -957,8 +971,8 @@ describe('update rules schema', () => { saved_id: 'some id', timeline_id: '', timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]'); }); test('You cannot have timeline_title without timeline_id', () => { @@ -975,7 +989,7 @@ describe('update rules schema', () => { type: 'saved_query', saved_id: 'some id', timeline_title: 'some-title', - }).error - ).toBeTruthy(); + }).error.message + ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts index 9c3188738faea..d363bfca98466 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts @@ -13,7 +13,6 @@ import { false_positives, filters, from, - immutable, index, rule_id, interval, @@ -46,7 +45,6 @@ export const updateRulesSchema = Joi.object({ from, rule_id, id, - immutable, index, interval, query: query.allow(''), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts index 2b7ee443880e5..35e1e5933af64 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -8,7 +8,6 @@ import { createMockServer } from '../__mocks__/_mock_server'; import { setSignalsStatusRoute } from './open_close_signals_route'; import * as myUtils from '../utils'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getSetSignalStatusByIdsRequest, @@ -29,7 +28,7 @@ describe('set signal status', () => { elasticsearch.getCluster = jest.fn(() => ({ callWithRequest: jest.fn(() => true), })); - setSignalsStatusRoute((server as unknown) as ServerFacade); + setSignalsStatusRoute(server); }); describe('status on signal', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts index aef499675b884..5b86d0a4b36c0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts @@ -8,7 +8,6 @@ import { createMockServer } from '../__mocks__/_mock_server'; import { querySignalsRoute } from './query_signals_route'; import * as myUtils from '../utils'; import { ServerInjectOptions } from 'hapi'; -import { ServerFacade } from '../../../../types'; import { getSignalsQueryRequest, @@ -28,7 +27,7 @@ describe('query for signal', () => { elasticsearch.getCluster = jest.fn(() => ({ callWithRequest: jest.fn(() => true), })); - querySignalsRoute((server as unknown) as ServerFacade); + querySignalsRoute(server); }); describe('query and agg on signals index', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_internet_explorer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_internet_explorer.json deleted file mode 100644 index bb9d8c60040f6..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_internet_explorer.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "description": "Command shell started by Internet Explorer", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "process.name", - "negate": false, - "params": { - "query": "cmd.exe" - }, - "type": "phrase", - "value": "cmd.exe" - }, - "query": { - "match": { - "process.name": { - "query": "cmd.exe", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "Process Create (rule: ProcessCreate)" - }, - "type": "phrase", - "value": "Process Create (rule: ProcessCreate)" - }, - "query": { - "match": { - "event.action": { - "query": "Process Create (rule: ProcessCreate)", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Command shell started by Internet Explorer", - "query": "process.parent.name:iexplore.exe", - "risk_score": 50, - "rule_id": "a0b554d2-85ed-4998-ada3-4ca58b508b35", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_powershell.json deleted file mode 100644 index d9820f90c55ee..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_powershell.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "description": "Command shell started by Powershell", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "process.name", - "negate": false, - "params": { - "query": "cmd.exe" - }, - "type": "phrase", - "value": "cmd.exe" - }, - "query": { - "match": { - "process.name": { - "query": "cmd.exe", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "Process Create (rule: ProcessCreate)" - }, - "type": "phrase", - "value": "Process Create (rule: ProcessCreate)" - }, - "query": { - "match": { - "event.action": { - "query": "Process Create (rule: ProcessCreate)", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Command shell started by Powershell", - "query": "process.parent.name:powershell.exe", - "risk_score": 50, - "rule_id": "ab4bbfa5-4127-40bf-852f-bdc6afdb2a06", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_svchost.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_svchost.json deleted file mode 100644 index a11f69fc3048f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/command_shell_started_by_svchost.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "description": "Command shell started by Svchost", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "process.name", - "negate": false, - "params": { - "query": "cmd.exe" - }, - "type": "phrase", - "value": "cmd.exe" - }, - "query": { - "match": { - "process.name": { - "query": "cmd.exe", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "Process Create (rule: ProcessCreate)" - }, - "type": "phrase", - "value": "Process Create (rule: ProcessCreate)" - }, - "query": { - "match": { - "event.action": { - "query": "Process Create (rule: ProcessCreate)", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Command shell started by Svchost", - "query": "process.parent.name:svchost.exe", - "risk_score": 50, - "rule_id": "2e4f8a5e-ce68-44e0-9243-1f57d44c4f30", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_detect_large_outbound_icmp_packets.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_detect_large_outbound_icmp_packets.json deleted file mode 100644 index faa1c97e4bada..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_detect_large_outbound_icmp_packets.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Network - Detect Large Outbound ICMP Packets", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Network - Detect Large Outbound ICMP Packets", - "query": "network.transport:icmp and network.bytes>1000 and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "4fce2a7e-0e11-4f17-bae3-8873c5ae62be", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_detect_long_dns_txt_record_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_detect_long_dns_txt_record_response.json deleted file mode 100644 index f034e4999107f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_detect_long_dns_txt_record_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Network - Detect Long DNS TXT Record Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Network - Detect Long DNS TXT Record Response", - "query": "network.protocol:dns and server.bytes>100 and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16 and not destination.ip:169.254.169.254 and not destination.ip:127.0.0.53", - "risk_score": 50, - "rule_id": "cc28f445-318e-4850-8b0d-5ad53eaded74", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_protocols_passing_authentication_in_cleartext.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_protocols_passing_authentication_in_cleartext.json deleted file mode 100644 index d1b5f6be75040..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_network_protocols_passing_authentication_in_cleartext.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Network - Protocols passing authentication in cleartext", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Network - Protocols passing authentication in cleartext", - "query": "destination.port:(21 or 23 or 110 or 143) and network.transport:tcp", - "risk_score": 50, - "rule_id": "31f32b3c-415a-4a18-b60f-5748a337246b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_child_processes_of_spoolsvexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_child_processes_of_spoolsvexe.json deleted file mode 100644 index 60d5ffe918585..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_child_processes_of_spoolsvexe.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - Child Processes of Spoolsv.exe", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - Child Processes of Spoolsv.exe", - "query": "process.parent.name:spoolsv.exe and not process.name:regsvr32.exe ", - "risk_score": 50, - "rule_id": "dcc45d35-f42e-4f97-81e8-90b0597ea0d1", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_new_local_admin_account.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_new_local_admin_account.json deleted file mode 100644 index ca27234b0d8ae..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_new_local_admin_account.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - Detect New Local Admin account", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - Detect New Local Admin account", - "query": "event.code:(4720 or 4732) and winlog.event_data.TargetUserName:Administrators", - "risk_score": 50, - "rule_id": "461db51b-b1a1-49de-ac63-e1bcbd445602", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_psexec_with_accepteula_flag.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_psexec_with_accepteula_flag.json deleted file mode 100644 index 25dcd8234e092..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_psexec_with_accepteula_flag.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - Detect PsExec With accepteula Flag", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - Detect PsExec With accepteula Flag", - "query": "process.name:PsExec.exe and process.args:\"-accepteula\"", - "risk_score": 50, - "rule_id": "304b0e0c-bd06-46f8-aeda-2e719ae434d1", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_use_of_cmdexe_to_launch_script_interpreters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_use_of_cmdexe_to_launch_script_interpreters.json deleted file mode 100644 index 70d06ca9a4777..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_detect_use_of_cmdexe_to_launch_script_interpreters.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - Detect Use of cmd.exe to Launch Script Interpreters", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - Detect Use of cmd.exe to Launch Script Interpreters", - "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"wscript.exe\" or \"cscript.exe\") and process.parent.name:\"cmd.exe\"", - "risk_score": 50, - "rule_id": "b17c215e-8fa5-4087-b8d1-87761a90d710", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_new_external_device.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_new_external_device.json deleted file mode 100644 index 9dbc8d7cbb7ed..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_new_external_device.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - New External Device Attached", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - New External Device Attached", - "query": "event.code:6416", - "risk_score": 50, - "rule_id": "c0747553-5763-5d85-cd97-898f2daa2bde", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_processes_created_by_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_processes_created_by_netsh.json deleted file mode 100644 index 3f4e1a6243a96..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_processes_created_by_netsh.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - Processes created by netsh", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - Processes created by netsh", - "query": "process.parent.name:netsh.exe", - "risk_score": 50, - "rule_id": "e312dd9e-4760-4a71-a241-9b9a835a51c4", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_processes_launching_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_processes_launching_netsh.json deleted file mode 100644 index 34d08d7596e11..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_processes_launching_netsh.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - Processes launching netsh", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - Processes launching netsh", - "query": "process.name:netsh.exe and event.action:\"Process Create (rule: ProcessCreate)\" ", - "risk_score": 50, - "rule_id": "3b8db8aa-5734-405e-8dda-703129078a35", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_windows_event_log_cleared.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_windows_event_log_cleared.json deleted file mode 100644 index bd82247203f00..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/ece_windows_windows_event_log_cleared.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Windows - Windows Event Log Cleared", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Windows - Windows Event Log Cleared", - "query": "event.code:(1102 or 1100)", - "risk_score": 50, - "rule_id": "b94b5177-ca7f-468a-9a1d-aef39c30a3ae", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts index 8a353e4b2b301..6ef81addd846e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -10,342 +10,306 @@ import rule1 from './403_response_to_a_post.json'; import rule2 from './405_response_method_not_allowed.json'; import rule3 from './500_response_on_admin_page.json'; -import rule4 from './command_shell_started_by_internet_explorer.json'; -import rule5 from './command_shell_started_by_powershell.json'; -import rule6 from './command_shell_started_by_svchost.json'; -import rule7 from './ece_network_detect_large_outbound_icmp_packets.json'; -import rule8 from './ece_network_detect_long_dns_txt_record_response.json'; -import rule9 from './ece_network_protocols_passing_authentication_in_cleartext.json'; -import rule10 from './ece_windows_child_processes_of_spoolsvexe.json'; -import rule11 from './ece_windows_detect_new_local_admin_account.json'; -import rule12 from './ece_windows_detect_psexec_with_accepteula_flag.json'; -import rule13 from './ece_windows_detect_use_of_cmdexe_to_launch_script_interpreters.json'; -import rule14 from './ece_windows_new_external_device.json'; -import rule15 from './ece_windows_processes_created_by_netsh.json'; -import rule16 from './ece_windows_processes_launching_netsh.json'; -import rule17 from './ece_windows_windows_event_log_cleared.json'; -import rule18 from './eql_adding_the_hidden_file_attribute_with_via_attribexe.json'; -import rule19 from './eql_adobe_hijack_persistence.json'; -import rule20 from './eql_audio_capture_via_powershell.json'; -import rule21 from './eql_audio_capture_via_soundrecorder.json'; -import rule22 from './eql_bypass_uac_event_viewer.json'; -import rule23 from './eql_bypass_uac_via_cmstp.json'; -import rule24 from './eql_bypass_uac_via_sdclt.json'; -import rule25 from './eql_clearing_windows_event_logs.json'; -import rule26 from './eql_delete_volume_usn_journal_with_fsutil.json'; -import rule27 from './eql_deleting_backup_catalogs_with_wbadmin.json'; -import rule28 from './eql_direct_outbound_smb_connection.json'; -import rule29 from './eql_disable_windows_firewall_rules_with_netsh.json'; -import rule30 from './eql_dll_search_order_hijack.json'; -import rule31 from './eql_encoding_or_decoding_files_via_certutil.json'; -import rule32 from './eql_local_scheduled_task_commands.json'; -import rule33 from './eql_local_service_commands.json'; -import rule34 from './eql_modification_of_boot_configuration.json'; -import rule35 from './eql_msbuild_making_network_connections.json'; -import rule36 from './eql_mshta_making_network_connections.json'; -import rule37 from './eql_msxsl_making_network_connections.json'; -import rule38 from './eql_psexec_lateral_movement_command.json'; -import rule39 from './eql_suspicious_ms_office_child_process.json'; -import rule40 from './eql_suspicious_ms_outlook_child_process.json'; -import rule41 from './eql_suspicious_pdf_reader_child_process.json'; -import rule42 from './eql_system_shells_via_services.json'; -import rule43 from './eql_unusual_network_connection_via_rundll32.json'; -import rule44 from './eql_unusual_parentchild_relationship.json'; -import rule45 from './eql_unusual_process_network_connection.json'; -import rule46 from './eql_user_account_creation.json'; -import rule47 from './eql_user_added_to_administrator_group.json'; -import rule48 from './eql_volume_shadow_copy_deletion_via_vssadmin.json'; -import rule49 from './eql_volume_shadow_copy_deletion_via_wmic.json'; -import rule50 from './eql_windows_script_executing_powershell.json'; -import rule51 from './eql_wmic_command_lateral_movement.json'; -import rule52 from './linux_hping_activity.json'; -import rule53 from './linux_iodine_activity.json'; -import rule54 from './linux_java_process_connecting_to_the_internet.json'; -import rule55 from './linux_kernel_module_activity.json'; -import rule56 from './linux_ldso_process_activity.json'; -import rule57 from './linux_lzop_activity.json'; -import rule58 from './linux_lzop_activity_possible_julianrunnels.json'; -import rule59 from './linux_mknod_activity.json'; -import rule60 from './linux_netcat_network_connection.json'; -import rule61 from './linux_network_anomalous_process_using_https_ports.json'; -import rule62 from './linux_nmap_activity.json'; -import rule63 from './linux_nping_activity.json'; -import rule64 from './linux_process_started_in_temp_directory.json'; -import rule65 from './linux_ptrace_activity.json'; -import rule66 from './linux_rawshark_activity.json'; -import rule67 from './linux_shell_activity_by_web_server.json'; -import rule68 from './linux_socat_activity.json'; -import rule69 from './linux_ssh_forwarding.json'; -import rule70 from './linux_strace_activity.json'; -import rule71 from './linux_tcpdump_activity.json'; -import rule72 from './linux_unusual_shell_activity.json'; -import rule73 from './linux_web_download.json'; -import rule74 from './linux_whoami_commmand.json'; -import rule75 from './network_dns_directly_to_the_internet.json'; -import rule76 from './network_ftp_file_transfer_protocol_activity_to_the_internet.json'; -import rule77 from './network_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; -import rule78 from './network_nat_traversal_port_activity.json'; -import rule79 from './network_port_26_activity.json'; -import rule80 from './network_port_8000_activity.json'; -import rule81 from './network_port_8000_activity_to_the_internet.json'; -import rule82 from './network_pptp_point_to_point_tunneling_protocol_activity.json'; -import rule83 from './network_proxy_port_activity_to_the_internet.json'; -import rule84 from './network_rdp_remote_desktop_protocol_from_the_internet.json'; -import rule85 from './network_rdp_remote_desktop_protocol_to_the_internet.json'; -import rule86 from './network_rpc_remote_procedure_call_from_the_internet.json'; -import rule87 from './network_rpc_remote_procedure_call_to_the_internet.json'; -import rule88 from './network_smb_windows_file_sharing_activity_to_the_internet.json'; -import rule89 from './network_smtp_to_the_internet.json'; -import rule90 from './network_sql_server_port_activity_to_the_internet.json'; -import rule91 from './network_ssh_secure_shell_from_the_internet.json'; -import rule92 from './network_ssh_secure_shell_to_the_internet.json'; -import rule93 from './network_telnet_port_activity.json'; -import rule94 from './network_tor_activity_to_the_internet.json'; -import rule95 from './network_vnc_virtual_network_computing_from_the_internet.json'; -import rule96 from './network_vnc_virtual_network_computing_to_the_internet.json'; -import rule97 from './null_user_agent.json'; -import rule98 from './powershell_network_connection.json'; -import rule99 from './process_execution_via_wmi.json'; -import rule100 from './process_started_by_acrobat_reader_possible_payload.json'; -import rule101 from './process_started_by_ms_office_program_possible_payload.json'; -import rule102 from './process_started_by_windows_defender.json'; -import rule103 from './psexec_activity.json'; -import rule104 from './search_windows_10.json'; -import rule105 from './splunk_child_processes_of_spoolsvexe.json'; -import rule106 from './splunk_detect_large_outbound_icmp_packets.json'; -import rule107 from './splunk_detect_long_dns_txt_record_response.json'; -import rule108 from './splunk_detect_new_local_admin_account.json'; -import rule109 from './splunk_detect_psexec_with_accepteula_flag.json'; -import rule110 from './splunk_detect_use_of_cmdexe_to_launch_script_interpreters.json'; -import rule111 from './splunk_processes_created_by_netsh.json'; -import rule112 from './splunk_processes_launching_netsh.json'; -import rule113 from './splunk_protocols_passing_authentication_in_cleartext.json'; -import rule114 from './splunk_windows_event_log_cleared.json'; -import rule115 from './sqlmap_user_agent.json'; -import rule116 from './suricata_base64_encoded_invokecommand_powershell_execution.json'; -import rule117 from './suricata_base64_encoded_newobject_powershell_execution.json'; -import rule118 from './suricata_base64_encoded_startprocess_powershell_execution.json'; -import rule119 from './suricata_category_a_suspicious_string_was_detected.json'; -import rule120 from './suricata_category_attempted_administrator_privilege_gain.json'; -import rule121 from './suricata_category_attempted_denial_of_service.json'; -import rule122 from './suricata_category_attempted_information_leak.json'; -import rule123 from './suricata_category_attempted_login_with_suspicious_username.json'; -import rule124 from './suricata_category_attempted_user_privilege_gain.json'; -import rule125 from './suricata_category_client_using_unusual_port.json'; -import rule126 from './suricata_category_crypto_currency_mining_activity.json'; -import rule127 from './suricata_category_decode_of_an_rpc_query.json'; -import rule128 from './suricata_category_default_username_and_password_login_attempt.json'; -import rule129 from './suricata_category_denial_of_service.json'; -import rule130 from './suricata_category_denial_of_service_attack.json'; -import rule131 from './suricata_category_executable_code_was_detected.json'; -import rule132 from './suricata_category_exploit_kit_activity.json'; -import rule133 from './suricata_category_external_ip_address_retrieval.json'; -import rule134 from './suricata_category_generic_icmp_event.json'; -import rule135 from './suricata_category_generic_protocol_command_decode.json'; -import rule136 from './suricata_category_information_leak.json'; -import rule137 from './suricata_category_large_scale_information_leak.json'; -import rule138 from './suricata_category_malware_command_and_control_activity.json'; -import rule139 from './suricata_category_misc_activity.json'; -import rule140 from './suricata_category_misc_attack.json'; -import rule141 from './suricata_category_network_scan_detected.json'; -import rule142 from './suricata_category_network_trojan_detected.json'; -import rule143 from './suricata_category_nonstandard_protocol_or_event.json'; -import rule144 from './suricata_category_not_suspicious_traffic.json'; -import rule145 from './suricata_category_observed_c2_domain.json'; -import rule146 from './suricata_category_possible_social_engineering_attempted.json'; -import rule147 from './suricata_category_possibly_unwanted_program.json'; -import rule148 from './suricata_category_potential_corporate_privacy_violation.json'; -import rule149 from './suricata_category_potentially_bad_traffic.json'; -import rule150 from './suricata_category_potentially_vulnerable_web_application_access.json'; -import rule151 from './suricata_category_successful_administrator_privilege_gain.json'; -import rule152 from './suricata_category_successful_credential_theft.json'; -import rule153 from './suricata_category_successful_user_privilege_gain.json'; -import rule154 from './suricata_category_suspicious_filename_detected.json'; -import rule155 from './suricata_category_system_call_detected.json'; -import rule156 from './suricata_category_targeted_malicious_activity.json'; -import rule157 from './suricata_category_tcp_connection_detected.json'; -import rule158 from './suricata_category_unknown_traffic.json'; -import rule159 from './suricata_category_unsuccessful_user_privilege_gain.json'; -import rule160 from './suricata_category_web_application_attack.json'; -import rule161 from './suricata_cobaltstrike_artifact_in_an_dns_request.json'; -import rule162 from './suricata_commonly_abused_dns_domain_detected.json'; -import rule163 from './suricata_directory_reversal_characters_in_an_http_request.json'; -import rule164 from './suricata_directory_traversal_characters_in_an_http_request.json'; -import rule165 from './suricata_directory_traversal_characters_in_http_response.json'; -import rule166 from './suricata_directory_traversal_in_downloaded_zip_file.json'; -import rule167 from './suricata_dns_traffic_on_unusual_tcp_port.json'; -import rule168 from './suricata_dns_traffic_on_unusual_udp_port.json'; -import rule169 from './suricata_double_encoded_characters_in_a_uri.json'; -import rule170 from './suricata_double_encoded_characters_in_an_http_post.json'; -import rule171 from './suricata_double_encoded_characters_in_http_request.json'; -import rule172 from './suricata_eval_php_function_in_an_http_request.json'; -import rule173 from './suricata_exploit_cve_2018_1000861.json'; -import rule174 from './suricata_exploit_cve_2019_0227.json'; -import rule175 from './suricata_exploit_cve_2019_0232.json'; -import rule176 from './suricata_exploit_cve_2019_0604.json'; -import rule177 from './suricata_exploit_cve_2019_0708.json'; -import rule178 from './suricata_exploit_cve_2019_0752.json'; -import rule179 from './suricata_exploit_cve_2019_1003000.json'; -import rule180 from './suricata_exploit_cve_2019_10149.json'; -import rule181 from './suricata_exploit_cve_2019_11043.json'; -import rule182 from './suricata_exploit_cve_2019_11510.json'; -import rule183 from './suricata_exploit_cve_2019_11580.json'; -import rule184 from './suricata_exploit_cve_2019_11581.json'; -import rule185 from './suricata_exploit_cve_2019_13450.json'; -import rule186 from './suricata_exploit_cve_2019_13505.json'; -import rule187 from './suricata_exploit_cve_2019_15107.json'; -import rule188 from './suricata_exploit_cve_2019_15846.json'; -import rule189 from './suricata_exploit_cve_2019_16072.json'; -import rule190 from './suricata_exploit_cve_2019_1652.json'; -import rule191 from './suricata_exploit_cve_2019_16662.json'; -import rule192 from './suricata_exploit_cve_2019_16759.json'; -import rule193 from './suricata_exploit_cve_2019_16928.json'; -import rule194 from './suricata_exploit_cve_2019_17270.json'; -import rule195 from './suricata_exploit_cve_2019_1821.json'; -import rule196 from './suricata_exploit_cve_2019_19781.json'; -import rule197 from './suricata_exploit_cve_2019_2618.json'; -import rule198 from './suricata_exploit_cve_2019_2725.json'; -import rule199 from './suricata_exploit_cve_2019_3396.json'; -import rule200 from './suricata_exploit_cve_2019_3929.json'; -import rule201 from './suricata_exploit_cve_2019_5533.json'; -import rule202 from './suricata_exploit_cve_2019_6340.json'; -import rule203 from './suricata_exploit_cve_2019_7256.json'; -import rule204 from './suricata_exploit_cve_2019_9978.json'; -import rule205 from './suricata_ftp_traffic_on_unusual_port_internet_destination.json'; -import rule206 from './suricata_http_traffic_on_unusual_port_internet_destination.json'; -import rule207 from './suricata_imap_traffic_on_unusual_port_internet_destination.json'; -import rule208 from './suricata_lazagne_artifact_in_an_http_post.json'; -import rule209 from './suricata_mimikatz_artifacts_in_an_http_post.json'; -import rule210 from './suricata_mimikatz_string_detected_in_http_response.json'; -import rule211 from './suricata_nondns_traffic_on_tcp_port_53.json'; -import rule212 from './suricata_nondns_traffic_on_udp_port_53.json'; -import rule213 from './suricata_nonftp_traffic_on_port_21.json'; -import rule214 from './suricata_nonhttp_traffic_on_tcp_port_80.json'; -import rule215 from './suricata_nonimap_traffic_on_port_1443_imap.json'; -import rule216 from './suricata_nonsmb_traffic_on_tcp_port_139_smb.json'; -import rule217 from './suricata_nonssh_traffic_on_port_22.json'; -import rule218 from './suricata_nontls_on_tls_port.json'; -import rule219 from './suricata_possible_cobalt_strike_malleable_c2_null_response.json'; -import rule220 from './suricata_possible_sql_injection_sql_commands_in_http_transactions.json'; -import rule221 from './suricata_rpc_traffic_on_http_ports.json'; -import rule222 from './suricata_serialized_php_detected.json'; -import rule223 from './suricata_shell_exec_php_function_in_an_http_post.json'; -import rule224 from './suricata_ssh_traffic_not_on_port_22_internet_destination.json'; -import rule225 from './suricata_tls_traffic_on_unusual_port_internet_destination.json'; -import rule226 from './suricata_windows_executable_served_by_jpeg_web_content.json'; -import rule227 from './suspicious_process_started_by_a_script.json'; -import rule228 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; -import rule229 from './windows_burp_ce_activity.json'; -import rule230 from './windows_certutil_connecting_to_the_internet.json'; -import rule231 from './windows_command_prompt_connecting_to_the_internet.json'; -import rule232 from './windows_command_shell_started_by_internet_explorer.json'; -import rule233 from './windows_command_shell_started_by_powershell.json'; -import rule234 from './windows_command_shell_started_by_svchost.json'; -import rule235 from './windows_credential_dumping_commands.json'; -import rule236 from './windows_credential_dumping_via_imageload.json'; -import rule237 from './windows_credential_dumping_via_registry_save.json'; -import rule238 from './windows_data_compression_using_powershell.json'; -import rule239 from './windows_defense_evasion_decoding_using_certutil.json'; -import rule240 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; -import rule241 from './windows_defense_evasion_via_filter_manager.json'; -import rule242 from './windows_defense_evasion_via_windows_event_log_tools.json'; -import rule243 from './windows_execution_via_compiled_html_file.json'; -import rule244 from './windows_execution_via_connection_manager.json'; -import rule245 from './windows_execution_via_microsoft_html_application_hta.json'; -import rule246 from './windows_execution_via_net_com_assemblies.json'; -import rule247 from './windows_execution_via_regsvr32.json'; -import rule248 from './windows_execution_via_trusted_developer_utilities.json'; -import rule249 from './windows_html_help_executable_program_connecting_to_the_internet.json'; -import rule250 from './windows_image_load_from_a_temp_directory.json'; -import rule251 from './windows_indirect_command_execution.json'; -import rule252 from './windows_iodine_activity.json'; -import rule253 from './windows_management_instrumentation_wmi_execution.json'; -import rule254 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; -import rule255 from './windows_mimikatz_activity.json'; -import rule256 from './windows_misc_lolbin_connecting_to_the_internet.json'; -import rule257 from './windows_net_command_activity_by_the_system_account.json'; -import rule258 from './windows_net_user_command_activity.json'; -import rule259 from './windows_netcat_activity.json'; -import rule260 from './windows_netcat_network_activity.json'; -import rule261 from './windows_network_anomalous_windows_process_using_https_ports.json'; -import rule262 from './windows_nmap_activity.json'; -import rule263 from './windows_nmap_scan_activity.json'; -import rule264 from './windows_payload_obfuscation_via_certutil.json'; -import rule265 from './windows_persistence_or_priv_escalation_via_hooking.json'; -import rule266 from './windows_persistence_via_application_shimming.json'; -import rule267 from './windows_persistence_via_bits_jobs.json'; -import rule268 from './windows_persistence_via_modification_of_existing_service.json'; -import rule269 from './windows_persistence_via_netshell_helper_dll.json'; -import rule270 from './windows_powershell_connecting_to_the_internet.json'; -import rule271 from './windows_priv_escalation_via_accessibility_features.json'; -import rule272 from './windows_process_discovery_via_tasklist_command.json'; -import rule273 from './windows_process_execution_via_wmi.json'; -import rule274 from './windows_process_started_by_acrobat_reader_possible_payload.json'; -import rule275 from './windows_process_started_by_ms_office_program_possible_payload.json'; -import rule276 from './windows_process_started_by_the_java_runtime.json'; -import rule277 from './windows_psexec_activity.json'; -import rule278 from './windows_register_server_program_connecting_to_the_internet.json'; -import rule279 from './windows_registry_query_local.json'; -import rule280 from './windows_registry_query_network.json'; -import rule281 from './windows_remote_management_execution.json'; -import rule282 from './windows_scheduled_task_activity.json'; -import rule283 from './windows_script_interpreter_connecting_to_the_internet.json'; -import rule284 from './windows_signed_binary_proxy_execution.json'; -import rule285 from './windows_signed_binary_proxy_execution_download.json'; -import rule286 from './windows_suspicious_process_started_by_a_script.json'; -import rule287 from './windows_whoami_command_activity.json'; -import rule288 from './windows_windump_activity.json'; -import rule289 from './windows_wireshark_activity.json'; -import rule290 from './windump_activity.json'; -import rule291 from './zeek_notice_capturelosstoo_much_loss.json'; -import rule292 from './zeek_notice_conncontent_gap.json'; -import rule293 from './zeek_notice_connretransmission_inconsistency.json'; -import rule294 from './zeek_notice_dnsexternal_name.json'; -import rule295 from './zeek_notice_ftpbruteforcing.json'; -import rule296 from './zeek_notice_ftpsite_exec_success.json'; -import rule297 from './zeek_notice_heartbleedssl_heartbeat_attack.json'; -import rule298 from './zeek_notice_heartbleedssl_heartbeat_attack_success.json'; -import rule299 from './zeek_notice_heartbleedssl_heartbeat_many_requests.json'; -import rule300 from './zeek_notice_heartbleedssl_heartbeat_odd_length.json'; -import rule301 from './zeek_notice_httpsql_injection_attacker.json'; -import rule302 from './zeek_notice_httpsql_injection_victim.json'; -import rule303 from './zeek_notice_intelnotice.json'; -import rule304 from './zeek_notice_noticetally.json'; -import rule305 from './zeek_notice_packetfiltercannot_bpf_shunt_conn.json'; -import rule306 from './zeek_notice_packetfiltercompile_failure.json'; -import rule307 from './zeek_notice_packetfilterdropped_packets.json'; -import rule308 from './zeek_notice_packetfilterinstall_failure.json'; -import rule309 from './zeek_notice_packetfilterno_more_conn_shunts_available.json'; -import rule310 from './zeek_notice_packetfiltertoo_long_to_compile_filter.json'; -import rule311 from './zeek_notice_protocoldetectorprotocol_found.json'; -import rule312 from './zeek_notice_protocoldetectorserver_found.json'; -import rule313 from './zeek_notice_scanaddress_scan.json'; -import rule314 from './zeek_notice_scanport_scan.json'; -import rule315 from './zeek_notice_signaturescount_signature.json'; -import rule316 from './zeek_notice_signaturesmultiple_sig_responders.json'; -import rule317 from './zeek_notice_signaturesmultiple_signatures.json'; -import rule318 from './zeek_notice_signaturessensitive_signature.json'; -import rule319 from './zeek_notice_signaturessignature_summary.json'; -import rule320 from './zeek_notice_smtpblocklist_blocked_host.json'; -import rule321 from './zeek_notice_smtpblocklist_error_message.json'; -import rule322 from './zeek_notice_smtpsuspicious_origination.json'; -import rule323 from './zeek_notice_softwaresoftware_version_change.json'; -import rule324 from './zeek_notice_softwarevulnerable_version.json'; -import rule325 from './zeek_notice_sshinteresting_hostname_login.json'; -import rule326 from './zeek_notice_sshlogin_by_password_guesser.json'; -import rule327 from './zeek_notice_sshpassword_guessing.json'; -import rule328 from './zeek_notice_sshwatched_country_login.json'; -import rule329 from './zeek_notice_sslcertificate_expired.json'; -import rule330 from './zeek_notice_sslcertificate_expires_soon.json'; -import rule331 from './zeek_notice_sslcertificate_not_valid_yet.json'; -import rule332 from './zeek_notice_sslinvalid_ocsp_response.json'; -import rule333 from './zeek_notice_sslinvalid_server_cert.json'; -import rule334 from './zeek_notice_sslold_version.json'; -import rule335 from './zeek_notice_sslweak_cipher.json'; -import rule336 from './zeek_notice_sslweak_key.json'; -import rule337 from './zeek_notice_teamcymrumalwarehashregistrymatch.json'; -import rule338 from './zeek_notice_traceroutedetected.json'; -import rule339 from './zeek_notice_weirdactivity.json'; +import rule4 from './eql_adding_the_hidden_file_attribute_with_via_attribexe.json'; +import rule5 from './eql_adobe_hijack_persistence.json'; +import rule6 from './eql_audio_capture_via_powershell.json'; +import rule7 from './eql_audio_capture_via_soundrecorder.json'; +import rule8 from './eql_bypass_uac_event_viewer.json'; +import rule9 from './eql_bypass_uac_via_cmstp.json'; +import rule10 from './eql_bypass_uac_via_sdclt.json'; +import rule11 from './eql_clearing_windows_event_logs.json'; +import rule12 from './eql_delete_volume_usn_journal_with_fsutil.json'; +import rule13 from './eql_deleting_backup_catalogs_with_wbadmin.json'; +import rule14 from './eql_direct_outbound_smb_connection.json'; +import rule15 from './eql_disable_windows_firewall_rules_with_netsh.json'; +import rule16 from './eql_dll_search_order_hijack.json'; +import rule17 from './eql_encoding_or_decoding_files_via_certutil.json'; +import rule18 from './eql_local_scheduled_task_commands.json'; +import rule19 from './eql_local_service_commands.json'; +import rule20 from './eql_modification_of_boot_configuration.json'; +import rule21 from './eql_msbuild_making_network_connections.json'; +import rule22 from './eql_mshta_making_network_connections.json'; +import rule23 from './eql_msxsl_making_network_connections.json'; +import rule24 from './eql_psexec_lateral_movement_command.json'; +import rule25 from './eql_suspicious_ms_office_child_process.json'; +import rule26 from './eql_suspicious_ms_outlook_child_process.json'; +import rule27 from './eql_suspicious_pdf_reader_child_process.json'; +import rule28 from './eql_system_shells_via_services.json'; +import rule29 from './eql_unusual_network_connection_via_rundll32.json'; +import rule30 from './eql_unusual_parentchild_relationship.json'; +import rule31 from './eql_unusual_process_network_connection.json'; +import rule32 from './eql_user_account_creation.json'; +import rule33 from './eql_user_added_to_administrator_group.json'; +import rule34 from './eql_volume_shadow_copy_deletion_via_vssadmin.json'; +import rule35 from './eql_volume_shadow_copy_deletion_via_wmic.json'; +import rule36 from './eql_windows_script_executing_powershell.json'; +import rule37 from './eql_wmic_command_lateral_movement.json'; +import rule38 from './linux_hping_activity.json'; +import rule39 from './linux_iodine_activity.json'; +import rule40 from './linux_kernel_module_activity.json'; +import rule41 from './linux_ldso_process_activity.json'; +import rule42 from './linux_lzop_activity.json'; +import rule43 from './linux_mknod_activity.json'; +import rule44 from './linux_netcat_network_connection.json'; +import rule45 from './linux_network_anomalous_process_using_https_ports.json'; +import rule46 from './linux_nmap_activity.json'; +import rule47 from './linux_nping_activity.json'; +import rule48 from './linux_process_started_in_temp_directory.json'; +import rule49 from './linux_ptrace_activity.json'; +import rule50 from './linux_rawshark_activity.json'; +import rule51 from './linux_shell_activity_by_web_server.json'; +import rule52 from './linux_socat_activity.json'; +import rule53 from './linux_ssh_forwarding.json'; +import rule54 from './linux_strace_activity.json'; +import rule55 from './linux_tcpdump_activity.json'; +import rule56 from './linux_web_download.json'; +import rule57 from './linux_whoami_commmand.json'; +import rule58 from './network_dns_directly_to_the_internet.json'; +import rule59 from './network_ftp_file_transfer_protocol_activity_to_the_internet.json'; +import rule60 from './network_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; +import rule61 from './network_nat_traversal_port_activity.json'; +import rule62 from './network_port_26_activity.json'; +import rule63 from './network_port_8000_activity.json'; +import rule64 from './network_port_8000_activity_to_the_internet.json'; +import rule65 from './network_pptp_point_to_point_tunneling_protocol_activity.json'; +import rule66 from './network_proxy_port_activity_to_the_internet.json'; +import rule67 from './network_rdp_remote_desktop_protocol_from_the_internet.json'; +import rule68 from './network_rdp_remote_desktop_protocol_to_the_internet.json'; +import rule69 from './network_rpc_remote_procedure_call_from_the_internet.json'; +import rule70 from './network_rpc_remote_procedure_call_to_the_internet.json'; +import rule71 from './network_smb_windows_file_sharing_activity_to_the_internet.json'; +import rule72 from './network_smtp_to_the_internet.json'; +import rule73 from './network_sql_server_port_activity_to_the_internet.json'; +import rule74 from './network_ssh_secure_shell_from_the_internet.json'; +import rule75 from './network_ssh_secure_shell_to_the_internet.json'; +import rule76 from './network_telnet_port_activity.json'; +import rule77 from './network_tor_activity_to_the_internet.json'; +import rule78 from './network_vnc_virtual_network_computing_from_the_internet.json'; +import rule79 from './network_vnc_virtual_network_computing_to_the_internet.json'; +import rule80 from './null_user_agent.json'; +import rule81 from './sqlmap_user_agent.json'; +import rule82 from './suricata_base64_encoded_invokecommand_powershell_execution.json'; +import rule83 from './suricata_base64_encoded_newobject_powershell_execution.json'; +import rule84 from './suricata_base64_encoded_startprocess_powershell_execution.json'; +import rule85 from './suricata_category_a_suspicious_string_was_detected.json'; +import rule86 from './suricata_category_attempted_administrator_privilege_gain.json'; +import rule87 from './suricata_category_attempted_denial_of_service.json'; +import rule88 from './suricata_category_attempted_information_leak.json'; +import rule89 from './suricata_category_attempted_login_with_suspicious_username.json'; +import rule90 from './suricata_category_attempted_user_privilege_gain.json'; +import rule91 from './suricata_category_client_using_unusual_port.json'; +import rule92 from './suricata_category_crypto_currency_mining_activity.json'; +import rule93 from './suricata_category_decode_of_an_rpc_query.json'; +import rule94 from './suricata_category_default_username_and_password_login_attempt.json'; +import rule95 from './suricata_category_denial_of_service.json'; +import rule96 from './suricata_category_denial_of_service_attack.json'; +import rule97 from './suricata_category_executable_code_was_detected.json'; +import rule98 from './suricata_category_exploit_kit_activity.json'; +import rule99 from './suricata_category_external_ip_address_retrieval.json'; +import rule100 from './suricata_category_generic_icmp_event.json'; +import rule101 from './suricata_category_generic_protocol_command_decode.json'; +import rule102 from './suricata_category_information_leak.json'; +import rule103 from './suricata_category_large_scale_information_leak.json'; +import rule104 from './suricata_category_malware_command_and_control_activity.json'; +import rule105 from './suricata_category_misc_activity.json'; +import rule106 from './suricata_category_misc_attack.json'; +import rule107 from './suricata_category_network_scan_detected.json'; +import rule108 from './suricata_category_network_trojan_detected.json'; +import rule109 from './suricata_category_nonstandard_protocol_or_event.json'; +import rule110 from './suricata_category_not_suspicious_traffic.json'; +import rule111 from './suricata_category_observed_c2_domain.json'; +import rule112 from './suricata_category_possible_social_engineering_attempted.json'; +import rule113 from './suricata_category_possibly_unwanted_program.json'; +import rule114 from './suricata_category_potential_corporate_privacy_violation.json'; +import rule115 from './suricata_category_potentially_bad_traffic.json'; +import rule116 from './suricata_category_potentially_vulnerable_web_application_access.json'; +import rule117 from './suricata_category_successful_administrator_privilege_gain.json'; +import rule118 from './suricata_category_successful_credential_theft.json'; +import rule119 from './suricata_category_successful_user_privilege_gain.json'; +import rule120 from './suricata_category_suspicious_filename_detected.json'; +import rule121 from './suricata_category_system_call_detected.json'; +import rule122 from './suricata_category_targeted_malicious_activity.json'; +import rule123 from './suricata_category_tcp_connection_detected.json'; +import rule124 from './suricata_category_unknown_traffic.json'; +import rule125 from './suricata_category_unsuccessful_user_privilege_gain.json'; +import rule126 from './suricata_category_web_application_attack.json'; +import rule127 from './suricata_cobaltstrike_artifact_in_an_dns_request.json'; +import rule128 from './suricata_commonly_abused_dns_domain_detected.json'; +import rule129 from './suricata_directory_reversal_characters_in_an_http_request.json'; +import rule130 from './suricata_directory_traversal_characters_in_an_http_request.json'; +import rule131 from './suricata_directory_traversal_characters_in_http_response.json'; +import rule132 from './suricata_directory_traversal_in_downloaded_zip_file.json'; +import rule133 from './suricata_dns_traffic_on_unusual_tcp_port.json'; +import rule134 from './suricata_dns_traffic_on_unusual_udp_port.json'; +import rule135 from './suricata_double_encoded_characters_in_a_uri.json'; +import rule136 from './suricata_double_encoded_characters_in_an_http_post.json'; +import rule137 from './suricata_double_encoded_characters_in_http_request.json'; +import rule138 from './suricata_eval_php_function_in_an_http_request.json'; +import rule139 from './suricata_exploit_cve_2018_1000861.json'; +import rule140 from './suricata_exploit_cve_2019_0227.json'; +import rule141 from './suricata_exploit_cve_2019_0232.json'; +import rule142 from './suricata_exploit_cve_2019_0604.json'; +import rule143 from './suricata_exploit_cve_2019_0708.json'; +import rule144 from './suricata_exploit_cve_2019_0752.json'; +import rule145 from './suricata_exploit_cve_2019_1003000.json'; +import rule146 from './suricata_exploit_cve_2019_10149.json'; +import rule147 from './suricata_exploit_cve_2019_11043.json'; +import rule148 from './suricata_exploit_cve_2019_11510.json'; +import rule149 from './suricata_exploit_cve_2019_11580.json'; +import rule150 from './suricata_exploit_cve_2019_11581.json'; +import rule151 from './suricata_exploit_cve_2019_13450.json'; +import rule152 from './suricata_exploit_cve_2019_13505.json'; +import rule153 from './suricata_exploit_cve_2019_15107.json'; +import rule154 from './suricata_exploit_cve_2019_15846.json'; +import rule155 from './suricata_exploit_cve_2019_16072.json'; +import rule156 from './suricata_exploit_cve_2019_1652.json'; +import rule157 from './suricata_exploit_cve_2019_16662.json'; +import rule158 from './suricata_exploit_cve_2019_16759.json'; +import rule159 from './suricata_exploit_cve_2019_16928.json'; +import rule160 from './suricata_exploit_cve_2019_17270.json'; +import rule161 from './suricata_exploit_cve_2019_1821.json'; +import rule162 from './suricata_exploit_cve_2019_19781.json'; +import rule163 from './suricata_exploit_cve_2019_2618.json'; +import rule164 from './suricata_exploit_cve_2019_2725.json'; +import rule165 from './suricata_exploit_cve_2019_3396.json'; +import rule166 from './suricata_exploit_cve_2019_3929.json'; +import rule167 from './suricata_exploit_cve_2019_5533.json'; +import rule168 from './suricata_exploit_cve_2019_6340.json'; +import rule169 from './suricata_exploit_cve_2019_7256.json'; +import rule170 from './suricata_exploit_cve_2019_9978.json'; +import rule171 from './suricata_ftp_traffic_on_unusual_port_internet_destination.json'; +import rule172 from './suricata_http_traffic_on_unusual_port_internet_destination.json'; +import rule173 from './suricata_imap_traffic_on_unusual_port_internet_destination.json'; +import rule174 from './suricata_lazagne_artifact_in_an_http_post.json'; +import rule175 from './suricata_mimikatz_artifacts_in_an_http_post.json'; +import rule176 from './suricata_mimikatz_string_detected_in_http_response.json'; +import rule177 from './suricata_nondns_traffic_on_tcp_port_53.json'; +import rule178 from './suricata_nondns_traffic_on_udp_port_53.json'; +import rule179 from './suricata_nonftp_traffic_on_port_21.json'; +import rule180 from './suricata_nonhttp_traffic_on_tcp_port_80.json'; +import rule181 from './suricata_nonimap_traffic_on_port_1443_imap.json'; +import rule182 from './suricata_nonsmb_traffic_on_tcp_port_139_smb.json'; +import rule183 from './suricata_nonssh_traffic_on_port_22.json'; +import rule184 from './suricata_nontls_on_tls_port.json'; +import rule185 from './suricata_possible_cobalt_strike_malleable_c2_null_response.json'; +import rule186 from './suricata_possible_sql_injection_sql_commands_in_http_transactions.json'; +import rule187 from './suricata_rpc_traffic_on_http_ports.json'; +import rule188 from './suricata_serialized_php_detected.json'; +import rule189 from './suricata_shell_exec_php_function_in_an_http_post.json'; +import rule190 from './suricata_ssh_traffic_not_on_port_22_internet_destination.json'; +import rule191 from './suricata_tls_traffic_on_unusual_port_internet_destination.json'; +import rule192 from './suricata_windows_executable_served_by_jpeg_web_content.json'; +import rule193 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; +import rule194 from './windows_burp_ce_activity.json'; +import rule195 from './windows_certutil_connecting_to_the_internet.json'; +import rule196 from './windows_command_prompt_connecting_to_the_internet.json'; +import rule197 from './windows_command_shell_started_by_internet_explorer.json'; +import rule198 from './windows_command_shell_started_by_powershell.json'; +import rule199 from './windows_command_shell_started_by_svchost.json'; +import rule200 from './windows_credential_dumping_commands.json'; +import rule201 from './windows_credential_dumping_via_imageload.json'; +import rule202 from './windows_credential_dumping_via_registry_save.json'; +import rule203 from './windows_data_compression_using_powershell.json'; +import rule204 from './windows_defense_evasion_decoding_using_certutil.json'; +import rule205 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; +import rule206 from './windows_defense_evasion_via_filter_manager.json'; +import rule207 from './windows_defense_evasion_via_windows_event_log_tools.json'; +import rule208 from './windows_execution_via_compiled_html_file.json'; +import rule209 from './windows_execution_via_connection_manager.json'; +import rule210 from './windows_execution_via_microsoft_html_application_hta.json'; +import rule211 from './windows_execution_via_net_com_assemblies.json'; +import rule212 from './windows_execution_via_regsvr32.json'; +import rule213 from './windows_execution_via_trusted_developer_utilities.json'; +import rule214 from './windows_html_help_executable_program_connecting_to_the_internet.json'; +import rule215 from './windows_image_load_from_a_temp_directory.json'; +import rule216 from './windows_indirect_command_execution.json'; +import rule217 from './windows_iodine_activity.json'; +import rule218 from './windows_management_instrumentation_wmi_execution.json'; +import rule219 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; +import rule220 from './windows_mimikatz_activity.json'; +import rule221 from './windows_misc_lolbin_connecting_to_the_internet.json'; +import rule222 from './windows_net_command_activity_by_the_system_account.json'; +import rule223 from './windows_net_user_command_activity.json'; +import rule224 from './windows_netcat_activity.json'; +import rule225 from './windows_netcat_network_activity.json'; +import rule226 from './windows_network_anomalous_windows_process_using_https_ports.json'; +import rule227 from './windows_nmap_activity.json'; +import rule228 from './windows_nmap_scan_activity.json'; +import rule229 from './windows_payload_obfuscation_via_certutil.json'; +import rule230 from './windows_persistence_or_priv_escalation_via_hooking.json'; +import rule231 from './windows_persistence_via_application_shimming.json'; +import rule232 from './windows_persistence_via_bits_jobs.json'; +import rule233 from './windows_persistence_via_modification_of_existing_service.json'; +import rule234 from './windows_persistence_via_netshell_helper_dll.json'; +import rule235 from './windows_powershell_connecting_to_the_internet.json'; +import rule236 from './windows_priv_escalation_via_accessibility_features.json'; +import rule237 from './windows_process_discovery_via_tasklist_command.json'; +import rule238 from './windows_process_execution_via_wmi.json'; +import rule239 from './windows_process_started_by_acrobat_reader_possible_payload.json'; +import rule240 from './windows_process_started_by_ms_office_program_possible_payload.json'; +import rule241 from './windows_process_started_by_the_java_runtime.json'; +import rule242 from './windows_psexec_activity.json'; +import rule243 from './windows_register_server_program_connecting_to_the_internet.json'; +import rule244 from './windows_registry_query_local.json'; +import rule245 from './windows_registry_query_network.json'; +import rule246 from './windows_remote_management_execution.json'; +import rule247 from './windows_scheduled_task_activity.json'; +import rule248 from './windows_script_interpreter_connecting_to_the_internet.json'; +import rule249 from './windows_signed_binary_proxy_execution.json'; +import rule250 from './windows_signed_binary_proxy_execution_download.json'; +import rule251 from './windows_suspicious_process_started_by_a_script.json'; +import rule252 from './windows_whoami_command_activity.json'; +import rule253 from './windows_windump_activity.json'; +import rule254 from './windows_wireshark_activity.json'; +import rule255 from './zeek_notice_capturelosstoo_much_loss.json'; +import rule256 from './zeek_notice_conncontent_gap.json'; +import rule257 from './zeek_notice_connretransmission_inconsistency.json'; +import rule258 from './zeek_notice_dnsexternal_name.json'; +import rule259 from './zeek_notice_ftpbruteforcing.json'; +import rule260 from './zeek_notice_ftpsite_exec_success.json'; +import rule261 from './zeek_notice_heartbleedssl_heartbeat_attack.json'; +import rule262 from './zeek_notice_heartbleedssl_heartbeat_attack_success.json'; +import rule263 from './zeek_notice_heartbleedssl_heartbeat_many_requests.json'; +import rule264 from './zeek_notice_heartbleedssl_heartbeat_odd_length.json'; +import rule265 from './zeek_notice_httpsql_injection_attacker.json'; +import rule266 from './zeek_notice_httpsql_injection_victim.json'; +import rule267 from './zeek_notice_intelnotice.json'; +import rule268 from './zeek_notice_noticetally.json'; +import rule269 from './zeek_notice_packetfiltercannot_bpf_shunt_conn.json'; +import rule270 from './zeek_notice_packetfiltercompile_failure.json'; +import rule271 from './zeek_notice_packetfilterdropped_packets.json'; +import rule272 from './zeek_notice_packetfilterinstall_failure.json'; +import rule273 from './zeek_notice_packetfilterno_more_conn_shunts_available.json'; +import rule274 from './zeek_notice_packetfiltertoo_long_to_compile_filter.json'; +import rule275 from './zeek_notice_protocoldetectorprotocol_found.json'; +import rule276 from './zeek_notice_protocoldetectorserver_found.json'; +import rule277 from './zeek_notice_scanaddress_scan.json'; +import rule278 from './zeek_notice_scanport_scan.json'; +import rule279 from './zeek_notice_signaturescount_signature.json'; +import rule280 from './zeek_notice_signaturesmultiple_sig_responders.json'; +import rule281 from './zeek_notice_signaturesmultiple_signatures.json'; +import rule282 from './zeek_notice_signaturessensitive_signature.json'; +import rule283 from './zeek_notice_signaturessignature_summary.json'; +import rule284 from './zeek_notice_smtpblocklist_blocked_host.json'; +import rule285 from './zeek_notice_smtpblocklist_error_message.json'; +import rule286 from './zeek_notice_smtpsuspicious_origination.json'; +import rule287 from './zeek_notice_softwaresoftware_version_change.json'; +import rule288 from './zeek_notice_softwarevulnerable_version.json'; +import rule289 from './zeek_notice_sshinteresting_hostname_login.json'; +import rule290 from './zeek_notice_sshlogin_by_password_guesser.json'; +import rule291 from './zeek_notice_sshpassword_guessing.json'; +import rule292 from './zeek_notice_sshwatched_country_login.json'; +import rule293 from './zeek_notice_sslcertificate_expired.json'; +import rule294 from './zeek_notice_sslcertificate_expires_soon.json'; +import rule295 from './zeek_notice_sslcertificate_not_valid_yet.json'; +import rule296 from './zeek_notice_sslinvalid_ocsp_response.json'; +import rule297 from './zeek_notice_sslinvalid_server_cert.json'; +import rule298 from './zeek_notice_sslold_version.json'; +import rule299 from './zeek_notice_sslweak_cipher.json'; +import rule300 from './zeek_notice_sslweak_key.json'; +import rule301 from './zeek_notice_teamcymrumalwarehashregistrymatch.json'; +import rule302 from './zeek_notice_traceroutedetected.json'; +import rule303 from './zeek_notice_weirdactivity.json'; export const rawRules = [ rule1, rule2, @@ -650,40 +614,4 @@ export const rawRules = [ rule301, rule302, rule303, - rule304, - rule305, - rule306, - rule307, - rule308, - rule309, - rule310, - rule311, - rule312, - rule313, - rule314, - rule315, - rule316, - rule317, - rule318, - rule319, - rule320, - rule321, - rule322, - rule323, - rule324, - rule325, - rule326, - rule327, - rule328, - rule329, - rule330, - rule331, - rule332, - rule333, - rule334, - rule335, - rule336, - rule337, - rule338, - rule339, ]; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_java_process_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_java_process_connecting_to_the_internet.json deleted file mode 100644 index 57f37e34ad4d5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_java_process_connecting_to_the_internet.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "description": "Linux: Java Process Connecting to the Internet", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "process.name", - "negate": false, - "params": { - "query": "java" - }, - "type": "phrase", - "value": "java" - }, - "query": { - "match": { - "process.name": { - "query": "java", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "socket_opened" - }, - "type": "phrase", - "value": "socket_opened" - }, - "query": { - "match": { - "event.action": { - "query": "socket_opened", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[2].meta.index", - "key": "destination.ip", - "negate": true, - "params": { - "query": "127.0.0.1" - }, - "type": "phrase", - "value": "127.0.0.1" - }, - "query": { - "match": { - "destination.ip": { - "query": "127.0.0.1", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[3].meta.index", - "key": "destination.ip", - "negate": true, - "params": { - "query": "::1" - }, - "type": "phrase", - "value": "::1" - }, - "query": { - "match": { - "destination.ip": { - "query": "::1", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Linux: Java Process Connecting to the Internet", - "query": "not destination.ip: 10.0.0.0/8 and not 172.16.0.0/12", - "risk_score": 50, - "rule_id": "7f65b8c5-27ed-4cf6-a088-3a20d2f84bf5", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_lzop_activity_possible_julianrunnels.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_lzop_activity_possible_julianrunnels.json deleted file mode 100644 index 62203b6c42a5a..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_lzop_activity_possible_julianrunnels.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Linux lzop activity - possible @JulianRunnels", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Linux lzop activity - possible @JulianRunnels", - "query": "process.name:lzop", - "risk_score": 50, - "rule_id": "d89b05b1-9b2b-45ea-9876-4a74550af6a6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_unusual_shell_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_unusual_shell_activity.json deleted file mode 100644 index a63b2ea7dc522..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_unusual_shell_activity.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "description": "Linux unusual shell activity", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "process.name", - "negate": true, - "params": { - "query": "bash" - }, - "type": "phrase", - "value": "bash" - }, - "query": { - "match": { - "process.name": { - "query": "bash", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index", - "key": "process.executable", - "negate": true, - "params": { - "query": "/bin/dash" - }, - "type": "phrase", - "value": "/bin/dash" - }, - "query": { - "match": { - "process.executable": { - "query": "/bin/dash", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[2].meta.index", - "key": "process.name", - "negate": true, - "params": { - "query": "ReportCrash" - }, - "type": "phrase", - "value": "ReportCrash" - }, - "query": { - "match": { - "process.name": { - "query": "ReportCrash", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Linux unusual shell activity", - "query": "process.name:*sh", - "risk_score": 50, - "rule_id": "4cc78842-f8a9-4a20-b703-a596c4f24e4f", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/powershell_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/powershell_network_connection.json deleted file mode 100644 index 075f77490a237..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/powershell_network_connection.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "description": "Powershell network connection", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "Network connection detected (rule: NetworkConnect)" - }, - "type": "phrase", - "value": "Network connection detected (rule: NetworkConnect)" - }, - "query": { - "match": { - "event.action": { - "query": "Network connection detected (rule: NetworkConnect)", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index", - "key": "destination.ip", - "negate": true, - "params": { - "query": "169.254.169.254" - }, - "type": "phrase", - "value": "169.254.169.254" - }, - "query": { - "match": { - "destination.ip": { - "query": "169.254.169.254", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Powershell network connection", - "query": "process.name:powershell.exe", - "risk_score": 50, - "rule_id": "8e792144-39a6-4a63-9779-2f12719dc132", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_execution_via_wmi.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_execution_via_wmi.json deleted file mode 100644 index 5ed0ad3899b4c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_execution_via_wmi.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Process Execution via WMI", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Process Execution via WMI", - "query": "process.name:scrcons.exe", - "risk_score": 50, - "rule_id": "14ba7cd9-1489-459b-99a4-153c7a3f9abb", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_acrobat_reader_possible_payload.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_acrobat_reader_possible_payload.json deleted file mode 100644 index c00b88e5f88ef..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_acrobat_reader_possible_payload.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Process started by Acrobat reader - possible payload", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "Process Create (rule: ProcessCreate)" - }, - "type": "phrase", - "value": "Process Create (rule: ProcessCreate)" - }, - "query": { - "match": { - "event.action": { - "query": "Process Create (rule: ProcessCreate)", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Process started by Acrobat reader - possible payload", - "query": "process.parent.name:AcroRd32.exe", - "risk_score": 50, - "rule_id": "c359628d-d5af-4a20-99df-aeeea109b690", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_ms_office_program_possible_payload.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_ms_office_program_possible_payload.json deleted file mode 100644 index 5237b17e7d69f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_ms_office_program_possible_payload.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Process started by MS Office program - possible payload", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "Process Create (rule: ProcessCreate)" - }, - "type": "phrase", - "value": "Process Create (rule: ProcessCreate)" - }, - "query": { - "match": { - "event.action": { - "query": "Process Create (rule: ProcessCreate)", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Process started by MS Office program - possible payload", - "query": " process.parent.name:EXCEL.EXE or process.parent.name:MSPUB.EXE or process.parent.name:OUTLOOK.EXE or process.parent.name:POWERPNT.EXE or process.parent.name:VISIO.EXE or process.parent.name:WINWORD.EXE", - "risk_score": 50, - "rule_id": "3181b814-08e3-43f9-b77a-a2530603b131", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_windows_defender.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_windows_defender.json deleted file mode 100644 index 1a686a4482df6..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_started_by_windows_defender.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Process started by Windows Defender", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Process started by Windows Defender", - "query": "parent.process.name:MsMpEng.exe", - "risk_score": 50, - "rule_id": "b3da3321-417d-494b-854c-b40369e063f0", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/psexec_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/psexec_activity.json deleted file mode 100644 index b928e7dc80576..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/psexec_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "PSexec activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "PSexec activity", - "query": "process.name:PsExec.exe or process.name:PsExec64.exe", - "risk_score": 50, - "rule_id": "9511b7f4-3898-4813-8bd3-d810b03148ab", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/search_windows_10.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/search_windows_10.json deleted file mode 100644 index ab76b1ed9ff9e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/search_windows_10.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "description": "(Search) Windows 10", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "agent.hostname", - "negate": false, - "params": { - "query": "LAPTOP-CQNI37L2" - }, - "type": "phrase" - }, - "query": { - "match": { - "agent.hostname": { - "query": "LAPTOP-CQNI37L2", - "type": "phrase" - } - } - } - }, - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[1].meta.index", - "key": "event.provider", - "negate": false, - "params": { - "query": "Microsoft-Windows-Sysmon" - }, - "type": "phrase" - }, - "query": { - "match": { - "event.provider": { - "query": "Microsoft-Windows-Sysmon", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "(Search) Windows 10", - "query": "", - "risk_score": 50, - "rule_id": "5d00c579-794c-4f64-be52-1ed8cae2b11e", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_child_processes_of_spoolsvexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_child_processes_of_spoolsvexe.json deleted file mode 100644 index e20197dfd2c92..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_child_processes_of_spoolsvexe.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Child Processes of Spoolsv.exe", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Child Processes of Spoolsv.exe", - "query": "process.parent.name:spoolsv.exe and not process.name:regsvr32.exe ", - "risk_score": 50, - "rule_id": "2f026c73-bb63-455e-abdf-f11f463acf0d", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_large_outbound_icmp_packets.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_large_outbound_icmp_packets.json deleted file mode 100644 index 11186bfb44d62..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_large_outbound_icmp_packets.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Detect Large Outbound ICMP Packets", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Detect Large Outbound ICMP Packets", - "query": "network.transport:icmp and network.bytes>1000 and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "e108c0c6-5ee8-47a0-8c23-ec47ba3a9b00", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_long_dns_txt_record_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_long_dns_txt_record_response.json deleted file mode 100644 index 724985b2d1de8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_long_dns_txt_record_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Detect Long DNS TXT Record Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Detect Long DNS TXT Record Response", - "query": "network.protocol:dns and server.bytes>100 and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16 and not destination.ip:169.254.169.254 and not destination.ip:127.0.0.53", - "risk_score": 50, - "rule_id": "2cdf84be-1c9c-4184-9880-75b9a6ddeaba", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_new_local_admin_account.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_new_local_admin_account.json deleted file mode 100644 index c0e773f09b168..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_new_local_admin_account.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Detect New Local Admin account", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Detect New Local Admin account", - "query": "event.code:(4720 or 4732) and winlog.event_data.TargetUserName:Administrators", - "risk_score": 50, - "rule_id": "030fc8e4-2c5f-4cc9-a6bd-2b6b7b98ae16", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_psexec_with_accepteula_flag.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_psexec_with_accepteula_flag.json deleted file mode 100644 index f9ad5793f2547..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_psexec_with_accepteula_flag.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Detect PsExec With accepteula Flag", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Detect PsExec With accepteula Flag", - "query": "process.name:PsExec.exe and process.args:\"-accepteula\"", - "risk_score": 50, - "rule_id": "4b63cf13-9043-41e3-84ec-6e39eb0d407e", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_use_of_cmdexe_to_launch_script_interpreters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_use_of_cmdexe_to_launch_script_interpreters.json deleted file mode 100644 index 0a67c3adeaea5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_detect_use_of_cmdexe_to_launch_script_interpreters.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Detect Use of cmd.exe to Launch Script Interpreters", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Detect Use of cmd.exe to Launch Script Interpreters", - "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"wscript.exe\" or \"cscript.exe\") and process.parent.name:\"cmd.exe\"", - "risk_score": 50, - "rule_id": "f4388e4c-ec3d-41b3-be5c-27c11f61473c", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_processes_created_by_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_processes_created_by_netsh.json deleted file mode 100644 index 466f9aff01942..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_processes_created_by_netsh.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Processes created by netsh", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Processes created by netsh", - "query": "process.parent.name:netsh.exe", - "risk_score": 50, - "rule_id": "ce7a0bde-7406-4729-a075-a215f4571ff6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_processes_launching_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_processes_launching_netsh.json deleted file mode 100644 index cc54721cd92f2..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_processes_launching_netsh.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Processes launching netsh", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Processes launching netsh", - "query": "process.name:netsh.exe and event.action:\"Process Create (rule: ProcessCreate)\" ", - "risk_score": 50, - "rule_id": "600dba95-f1c6-4a4d-aae1-c79cbd8a5ddd", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_protocols_passing_authentication_in_cleartext.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_protocols_passing_authentication_in_cleartext.json deleted file mode 100644 index c68e074d43817..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_protocols_passing_authentication_in_cleartext.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Protocols passing authentication in cleartext", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Protocols passing authentication in cleartext", - "query": "destination.port:(21 or 23 or 110 or 143) and network.transport:tcp", - "risk_score": 50, - "rule_id": "f4442e7f-856a-4a4a-851b-c1f9b97b0d39", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_windows_event_log_cleared.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_windows_event_log_cleared.json deleted file mode 100644 index 5f36d6623bcfb..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/splunk_windows_event_log_cleared.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Splunk - Windows Event Log Cleared", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Splunk - Windows Event Log Cleared", - "query": "event.code:(1102 or 1100)", - "risk_score": 50, - "rule_id": "c0747553-4652-4e74-bc86-898f2daa2bde", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suspicious_process_started_by_a_script.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suspicious_process_started_by_a_script.json deleted file mode 100644 index 37cf174786f97..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suspicious_process_started_by_a_script.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Suspicious process started by a script", - "enabled": false, - "filters": [ - { - "$state": { - "store": "appState" - }, - "meta": { - "alias": null, - "disabled": false, - "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "key": "event.action", - "negate": false, - "params": { - "query": "Process Create (rule: ProcessCreate)" - }, - "type": "phrase", - "value": "Process Create (rule: ProcessCreate)" - }, - "query": { - "match": { - "event.action": { - "query": "Process Create (rule: ProcessCreate)", - "type": "phrase" - } - } - } - } - ], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suspicious process started by a script", - "query": "(process.parent.name:cmd.exe or process.parent.name:cscript.exe or process.parent.name:mshta.exe or process.parent.name:powershell.exe or process.parent.name:rundll32.exe or process.parent.name:wscript.exe or process.parent.name:wmiprvse.exe) and (process.name:bitsadmin.exe or process.name:certutil.exe or mshta.exe or process.name:nslookup.exe or process.name:schtasks.exe)", - "risk_score": 50, - "rule_id": "e49b532b-3e52-4f3d-90f6-05a86982d347", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windump_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windump_activity.json deleted file mode 100644 index 7b40fc208ecd5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windump_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "WinDump activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "WinDump activity", - "query": "process.name:WinDump.exe", - "risk_score": 50, - "rule_id": "61c56cf4-0c08-4ad5-83ea-d2fe6ac62fa8", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_prepackaged_rules_status.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_prepackaged_rules_status.sh new file mode 100755 index 0000000000000..40b10b15e21f6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_prepackaged_rules_status.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./get_prepackaged_rules_status.sh +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/prepackaged/_status | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json index e9d955f920571..4a90d904f31ab 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json @@ -21,7 +21,6 @@ } ], "enabled": false, - "immutable": false, "index": ["auditbeat-*", "filebeat-*"], "interval": "5m", "query": "user.name: root or user.name: admin", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json index 16d5d6cc2b36a..2b7dbc8cccf0e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json @@ -21,7 +21,6 @@ } ], "enabled": false, - "immutable": true, "index": ["auditbeat-*", "filebeat-*"], "interval": "5m", "query": "user.name: root or user.name: admin", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_immutable.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_immutable.json deleted file mode 100644 index 14706b6e54e7b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_immutable.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rule_id": "query-rule-id", - "immutable": true -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json index 7fc8de9fe8f9e..a47d0155727d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json @@ -21,7 +21,6 @@ } ], "enabled": false, - "immutable": true, "index": ["auditbeat-*", "filebeat-*"], "interval": "5m", "query": "user.name: root or user.name: admin", diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts index 07b748024743c..4bd980fd2ff80 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts @@ -18,10 +18,16 @@ import { NetworkHttpData, NetworkHttpEdges, NetworkTopNFlowEdges, - MatrixOverOrdinalHistogramData, + NetworkDsOverTimeData, + MatrixOverTimeHistogramData, } from '../../graphql/types'; import { inspectStringifyObject } from '../../utils/build_query'; -import { DatabaseSearchResponse, FrameworkAdapter, FrameworkRequest } from '../framework'; +import { + DatabaseSearchResponse, + FrameworkAdapter, + FrameworkRequest, + MatrixHistogramRequestOptions, +} from '../framework'; import { TermAggregation } from '../types'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; @@ -32,6 +38,7 @@ import { NetworkTopNFlowRequestOptions, } from './index'; import { buildDnsQuery } from './query_dns.dsl'; +import { buildDnsHistogramQuery } from './query_dns_histogram.dsl'; import { buildTopNFlowQuery, getOppositeField } from './query_top_n_flow.dsl'; import { buildHttpQuery } from './query_http.dsl'; import { buildTopCountriesQuery } from './query_top_countries.dsl'; @@ -41,7 +48,9 @@ import { NetworkTopCountriesBuckets, NetworkHttpBuckets, NetworkTopNFlowBuckets, + DnsHistogramGroupData, } from './types'; +import { EventHit } from '../events/types'; export class ElasticsearchNetworkAdapter implements NetworkAdapter { constructor(private readonly framework: FrameworkAdapter) {} @@ -141,7 +150,6 @@ export class ElasticsearchNetworkAdapter implements NetworkAdapter { ); const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; const edges = networkDnsEdges.splice(cursorStart, querySize - cursorStart); - const histogram = getHistogramData(edges); const inspect = { dsl: [inspectStringifyObject(dsl)], response: [inspectStringifyObject(response)], @@ -156,7 +164,6 @@ export class ElasticsearchNetworkAdapter implements NetworkAdapter { showMorePagesIndicator, }, totalCount, - histogram, }; } @@ -195,29 +202,36 @@ export class ElasticsearchNetworkAdapter implements NetworkAdapter { totalCount, }; } + + public async getNetworkDnsHistogramData( + request: FrameworkRequest, + options: MatrixHistogramRequestOptions + ): Promise { + const dsl = buildDnsHistogramQuery(options); + const response = await this.framework.callWithRequest( + request, + 'search', + dsl + ); + const totalCount = getOr(0, 'hits.total.value', response); + const matrixHistogramData = getOr([], 'aggregations.NetworkDns.buckets', response); + const inspect = { + dsl: [inspectStringifyObject(dsl)], + response: [inspectStringifyObject(response)], + }; + return { + inspect, + matrixHistogramData: getHistogramData(matrixHistogramData), + totalCount, + }; + } } -const getHistogramData = ( - data: NetworkDnsEdges[] -): MatrixOverOrdinalHistogramData[] | undefined => { - if (!Array.isArray(data)) return undefined; +const getHistogramData = (data: DnsHistogramGroupData[]): MatrixOverTimeHistogramData[] => { return data.reduce( - (acc: MatrixOverOrdinalHistogramData[], { node: { dnsBytesOut, dnsBytesIn, _id } }) => { - if (_id != null && dnsBytesOut != null && dnsBytesIn != null) - return [ - ...acc, - { - x: _id, - y: dnsBytesOut, - g: 'DNS Bytes Out', - }, - { - x: _id, - y: dnsBytesIn, - g: 'DNS Bytes In', - }, - ]; - return acc; + (acc: MatrixOverTimeHistogramData[], { key: time, histogram: { buckets } }) => { + const temp = buckets.map(({ key, doc_count }) => ({ x: time, y: doc_count, g: key })); + return [...acc, ...temp]; }, [] ); diff --git a/x-pack/legacy/plugins/siem/server/lib/network/index.ts b/x-pack/legacy/plugins/siem/server/lib/network/index.ts index 42ce9f0726ddb..cbcd33b753d8a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/index.ts @@ -14,8 +14,13 @@ import { NetworkTopCountriesData, NetworkTopNFlowData, NetworkTopTablesSortField, + NetworkDsOverTimeData, } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { + FrameworkRequest, + RequestOptionsPaginated, + MatrixHistogramRequestOptions, +} from '../framework'; export * from './elasticsearch_adapter'; import { NetworkAdapter } from './types'; @@ -68,6 +73,13 @@ export class Network { return this.adapter.getNetworkDns(req, options); } + public async getNetworkDnsHistogramData( + req: FrameworkRequest, + options: MatrixHistogramRequestOptions + ): Promise { + return this.adapter.getNetworkDnsHistogramData(req, options); + } + public async getNetworkHttp( req: FrameworkRequest, options: NetworkHttpRequestOptions diff --git a/x-pack/legacy/plugins/siem/server/lib/network/query_dns_histogram.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/network/query_dns_histogram.dsl.ts new file mode 100644 index 0000000000000..67457ab4840ac --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/network/query_dns_histogram.dsl.ts @@ -0,0 +1,83 @@ +/* + * 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 { createQueryFilterClauses, calculateTimeseriesInterval } from '../../utils/build_query'; +import { MatrixHistogramRequestOptions } from '../framework'; + +export const buildDnsHistogramQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, + sourceConfiguration: { + fields: { timestamp }, + }, + stackByField, +}: MatrixHistogramRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + [timestamp]: { + gte: from, + lte: to, + }, + }, + }, + ]; + + const getHistogramAggregation = () => { + const interval = calculateTimeseriesInterval(from, to); + const histogramTimestampField = '@timestamp'; + const dateHistogram = { + date_histogram: { + field: histogramTimestampField, + fixed_interval: `${interval}s`, + }, + }; + + return { + NetworkDns: { + ...dateHistogram, + aggs: { + histogram: { + terms: { + field: stackByField, + order: { + orderAgg: 'desc', + }, + size: 10, + }, + aggs: { + orderAgg: { + cardinality: { + field: 'dns.question.name', + }, + }, + }, + }, + }, + }, + }; + }; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: getHistogramAggregation(), + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/types.ts b/x-pack/legacy/plugins/siem/server/lib/network/types.ts index 960fbe425b002..b5563f9a2fef1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/types.ts @@ -9,8 +9,13 @@ import { NetworkHttpData, NetworkTopCountriesData, NetworkTopNFlowData, + NetworkDsOverTimeData, } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { + FrameworkRequest, + RequestOptionsPaginated, + MatrixHistogramRequestOptions, +} from '../framework'; import { TotalValue } from '../types'; import { NetworkDnsRequestOptions } from '.'; @@ -24,6 +29,10 @@ export interface NetworkAdapter { options: RequestOptionsPaginated ): Promise; getNetworkDns(req: FrameworkRequest, options: NetworkDnsRequestOptions): Promise; + getNetworkDnsHistogramData( + request: FrameworkRequest, + options: MatrixHistogramRequestOptions + ): Promise; getNetworkHttp(req: FrameworkRequest, options: RequestOptionsPaginated): Promise; } @@ -143,3 +152,23 @@ export interface NetworkHttpBuckets { buckets: GenericBuckets[]; }; } + +interface DnsHistogramSubBucket { + key: string; + doc_count: number; + orderAgg: { + value: number; + }; +} +interface DnsHistogramBucket { + doc_count_error_upper_bound: number; + sum_other_doc_count: number; + buckets: DnsHistogramSubBucket[]; +} + +export interface DnsHistogramGroupData { + key: number; + doc_count: number; + key_as_string: string; + histogram: DnsHistogramBucket; +} diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx index 0a5e9558f91af..2493a8fbd9ffb 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx @@ -199,7 +199,7 @@ export const PolicyTable: React.FunctionComponent = ({ render: ({ name, inProgress, isManagedPolicy }: SlmPolicy) => { return ( - + {executePolicyPrompt => { return ( @@ -235,7 +235,7 @@ export const PolicyTable: React.FunctionComponent = ({ }} - + = ({ /> - + {deletePolicyPrompt => { const label = !isManagedPolicy diff --git a/x-pack/legacy/plugins/task_manager/server/legacy.ts b/x-pack/legacy/plugins/task_manager/server/legacy.ts index 772309d67c334..f5e81bfd90169 100644 --- a/x-pack/legacy/plugins/task_manager/server/legacy.ts +++ b/x-pack/legacy/plugins/task_manager/server/legacy.ts @@ -17,7 +17,7 @@ import { TaskInstanceWithId, TaskDefinition, } from '../../../../plugins/task_manager/server/task.js'; -import { FetchOpts } from '../../../../plugins/task_manager/server/task_store.js'; +import { SearchOpts } from '../../../../plugins/task_manager/server/task_store.js'; // Once all plugins are migrated to NP and we can remove Legacy TaskManager in version 8.0.0, // this can be removed @@ -46,7 +46,7 @@ export function createLegacyApi(legacyTaskManager: Promise): Legacy registerTaskDefinitions: (taskDefinitions: TaskDictionary) => { legacyTaskManager.then((tm: TaskManager) => tm.registerTaskDefinitions(taskDefinitions)); }, - fetch: (opts: FetchOpts) => legacyTaskManager.then((tm: TaskManager) => tm.fetch(opts)), + fetch: (opts: SearchOpts) => legacyTaskManager.then((tm: TaskManager) => tm.fetch(opts)), remove: (id: string) => legacyTaskManager.then((tm: TaskManager) => tm.remove(id)), schedule: (taskInstance: TaskInstanceWithDeprecatedFields, options?: any) => legacyTaskManager.then((tm: TaskManager) => tm.schedule(taskInstance, options)), diff --git a/x-pack/legacy/plugins/tilemap/index.js b/x-pack/legacy/plugins/tilemap/index.js index b5f7839c7c7f7..767a0fe72985e 100644 --- a/x-pack/legacy/plugins/tilemap/index.js +++ b/x-pack/legacy/plugins/tilemap/index.js @@ -12,7 +12,7 @@ export const tilemap = kibana => { return new kibana.Plugin({ id: 'tilemap', configPrefix: 'xpack.tilemap', - require: ['xpack_main', 'kbn_vislib_vis_types'], + require: ['xpack_main', 'vis_type_vislib'], publicDir: resolve(__dirname, 'public'), uiExports: { visTypeEnhancers: ['plugins/tilemap/vis_type_enhancers/update_tilemap_settings'], diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx index ebe4f7efda055..53627d1cf2f6b 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.tsx @@ -6,12 +6,14 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiBadge, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiProgress, + EuiScreenReaderOnly, EuiText, EuiToolTip, RIGHT_ALIGNMENT, @@ -98,6 +100,16 @@ export const getColumns = ( ActionsColumnType ] = [ { + name: ( + +

+ +

+
+ ), align: RIGHT_ALIGNMENT, width: '40px', isExpander: true, @@ -126,6 +138,7 @@ export const getColumns = ( name: 'ID', sortable: true, truncateText: true, + scope: 'row', }, { field: TRANSFORM_LIST_COLUMN.DESCRIPTION, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx index 731f560d315d6..679106f7e19b4 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx @@ -13,10 +13,10 @@ import { Typeahead } from './typeahead'; import { useUrlParams } from '../../../hooks'; import { toStaticIndexPattern } from '../../../lib/helper'; import { - AutocompleteProviderRegister, - AutocompleteSuggestion, esKuery, IIndexPattern, + autocomplete, + DataPublicPluginStart, } from '../../../../../../../../src/plugins/data/public'; import { useIndexPattern } from '../../../hooks'; @@ -25,7 +25,7 @@ const Container = styled.div` `; interface State { - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; isLoadingIndexPattern: boolean; } @@ -34,38 +34,11 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { return esKuery.toElasticsearchQuery(ast, indexPattern); } -function getSuggestions( - query: string, - selectionStart: number, - apmIndexPattern: IIndexPattern, - autocomplete: Pick -) { - const autocompleteProvider = autocomplete.getProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; - - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [apmIndexPattern], - }); - - const suggestions = getAutocompleteSuggestions({ - query, - selectionStart, - selectionEnd: selectionStart, - }); - return suggestions; -} - interface Props { - autocomplete: Pick; + autocomplete: DataPublicPluginStart['autocomplete']; } -export function KueryBar({ autocomplete }: Props) { +export function KueryBar({ autocomplete: autocompleteService }: Props) { const [state, setState] = useState({ suggestions: [], isLoadingIndexPattern: true, @@ -99,14 +72,16 @@ export function KueryBar({ autocomplete }: Props) { currentRequestCheck = currentRequest; try { - let suggestions = await getSuggestions( - inputValue, - selectionStart, - indexPattern, - autocomplete - ); - suggestions = suggestions - .filter((suggestion: AutocompleteSuggestion) => !startsWith(suggestion.text, 'span.')) + const suggestions = ( + (await autocompleteService.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + query: inputValue, + selectionStart, + selectionEnd: selectionStart, + })) || [] + ) + .filter(suggestion => !startsWith(suggestion.text, 'span.')) .slice(0, 15); if (currentRequest !== currentRequestCheck) { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx index 63a054c0c4889..98780d23c5a62 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx @@ -45,7 +45,7 @@ const EmbeddedPanel = styled.div` } `; -export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { +export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProps) => { const { colors } = useContext(UptimeThemeContext); const [embeddable, setEmbeddable] = useState(); const embeddableRoot: React.RefObject = useRef(null); @@ -55,10 +55,6 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { id: uuid.v4(), filters: [], hidePanelTitles: true, - query: { - query: '', - language: 'kuery', - }, refreshConfig: { value: 0, pause: false, @@ -116,6 +112,6 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => {
); -}; +}); EmbeddedMap.displayName = 'EmbeddedMap'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx index bb87497d335ef..63abb6fc4823c 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { LocationMap } from '../location_map'; import { MonitorStatusBar } from './monitor_status_bar'; +import { UptimeRefreshContext } from '../../../contexts'; interface MonitorStatusBarProps { monitorId: string; @@ -29,6 +30,27 @@ export const MonitorStatusDetailsComponent = ({ useEffect(() => { loadMonitorLocations(monitorId); }, [loadMonitorLocations, monitorId, dateStart, dateEnd]); + const { refreshApp } = useContext(UptimeRefreshContext); + + const [isTabActive] = useState(document.visibilityState); + const onTabActive = () => { + if (document.visibilityState === 'visible' && isTabActive === 'hidden') { + refreshApp(); + } + }; + + // Refreshing application state after Tab becomes active to render latest map state + // If application renders in when tab is not in focus it gives some unexpected behaviors + // Where map is not visible on change + useEffect(() => { + document.addEventListener('visibilitychange', onTabActive); + return () => { + document.removeEventListener('visibilitychange', onTabActive); + }; + + // we want this effect to execute exactly once after the component mounts + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap index 148894920eb45..d19de73c16c5f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap @@ -139,7 +139,9 @@ exports[`PingList component renders sorted list without errors 1`] = ` Object { "align": "right", "field": "http.response.status_code", - "name": "Response code", + "name": + Response code + , "render": [Function], }, Object { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index 3f0ec298e3289..85517144edfc6 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -23,6 +23,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { get } from 'lodash'; import moment from 'moment'; import React, { Fragment, useState } from 'react'; +import styled from 'styled-components'; import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table'; import { Ping, PingResults } from '../../../../common/graphql/types'; import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper'; @@ -69,6 +70,10 @@ export const toggleDetails = ( setItemIdToExpandedRowMap(newItemIdToExpandedRowMap); }; +const SpanWithMargin = styled.span` + margin-right: 16px; +`; + export const PingListComponent = ({ data, loading, @@ -185,10 +190,18 @@ export const PingListComponent = ({ columns.push({ field: 'http.response.status_code', align: 'right', - name: i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { - defaultMessage: 'Response code', - }), - render: (statusCode: string) => {statusCode}, + name: ( + + {i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { + defaultMessage: 'Response code', + })} + + ), + render: (statusCode: string) => ( + + {statusCode}{' '} + + ), }); } diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index fbfbfc06e3c52..1c14d971120be 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -20,14 +20,14 @@ import { useIndexPattern, useUrlParams, useUptimeTelemetry, UptimePage } from '. import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useTrackPageview } from '../../../infra/public'; import { combineFiltersAndUserSearch, stringifyKueries, toStaticIndexPattern } from '../lib/helper'; -import { AutocompleteProviderRegister, esKuery } from '../../../../../../src/plugins/data/public'; import { store } from '../state'; import { setEsKueryString } from '../state/actions'; import { PageHeader } from './page_header'; +import { esKuery, DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { UptimeThemeContext } from '../contexts/uptime_theme_context'; interface OverviewPageProps { - autocomplete: Pick; + autocomplete: DataPublicPluginStart['autocomplete']; setBreadcrumbs: UMUpdateBreadcrumbs; } diff --git a/x-pack/legacy/plugins/uptime/public/routes.tsx b/x-pack/legacy/plugins/uptime/public/routes.tsx index 08d752f5b32ab..028f2d5958325 100644 --- a/x-pack/legacy/plugins/uptime/public/routes.tsx +++ b/x-pack/legacy/plugins/uptime/public/routes.tsx @@ -7,14 +7,14 @@ import React, { FC } from 'react'; import { Route, Switch } from 'react-router-dom'; import { MonitorPage, OverviewPage, NotFoundPage } from './pages'; -import { AutocompleteProviderRegister } from '../../../../../src/plugins/data/public'; +import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { UMUpdateBreadcrumbs } from './lib/lib'; export const MONITOR_ROUTE = '/monitor/:monitorId/:location?'; export const OVERVIEW_ROUTE = '/'; interface RouterProps { - autocomplete: Pick; + autocomplete: DataPublicPluginStart['autocomplete']; basePath: string; setBreadcrumbs: UMUpdateBreadcrumbs; } diff --git a/x-pack/package.json b/x-pack/package.json index 9ead46745747b..ad0be351483f6 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -36,7 +36,6 @@ "@mattapperson/slapshot": "1.4.0", "@storybook/addon-actions": "^5.2.6", "@storybook/addon-console": "^1.2.1", - "@storybook/addon-info": "^5.2.6", "@storybook/addon-knobs": "^5.2.6", "@storybook/addon-storyshots": "^5.2.6", "@storybook/react": "^5.2.6", @@ -149,8 +148,6 @@ "mutation-observer": "^1.0.3", "node-fetch": "^2.6.0", "null-loader": "^3.0.0", - "pdf-image": "2.0.0", - "pdfjs-dist": "^2.0.943", "pixelmatch": "^5.1.0", "proxyquire": "1.8.0", "react-docgen-typescript-loader": "^3.1.1", @@ -179,7 +176,7 @@ "@elastic/apm-rum-react": "^0.3.2", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "7.6.0", - "@elastic/eui": "18.2.0", + "@elastic/eui": "18.2.1", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.1.0", "@elastic/node-crypto": "^1.0.0", @@ -188,6 +185,7 @@ "@kbn/config-schema": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", + "@kbn/storybook": "1.0.0", "@kbn/ui-framework": "1.0.0", "@mapbox/mapbox-gl-draw": "^1.1.1", "@mapbox/mapbox-gl-rtl-text": "0.2.3", @@ -254,7 +252,7 @@ "immer": "^1.5.0", "inline-style": "^2.0.0", "intl": "^1.2.5", - "io-ts": "^2.0.1", + "io-ts": "^2.0.5", "isbinaryfile": "4.0.2", "joi": "^13.5.2", "jquery": "^3.4.1", diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx index 82c95b37ee7b0..d69e068bdea3a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -9,6 +9,9 @@ import ReactDOM from 'react-dom'; import { CoreStart, AppMountParameters } from 'kibana/public'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; import { Route, BrowserRouter, Switch } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { Store } from 'redux'; +import { appStoreFactory } from './store'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. @@ -16,7 +19,9 @@ import { Route, BrowserRouter, Switch } from 'react-router-dom'; export function renderApp(coreStart: CoreStart, { appBasePath, element }: AppMountParameters) { coreStart.http.get('/api/endpoint/hello-world'); - ReactDOM.render(, element); + const store = appStoreFactory(coreStart); + + ReactDOM.render(, element); return () => { ReactDOM.unmountComponentAtNode(element); @@ -25,38 +30,46 @@ export function renderApp(coreStart: CoreStart, { appBasePath, element }: AppMou interface RouterProps { basename: string; + store: Store; } -const AppRoot: React.FunctionComponent = React.memo(({ basename }) => ( - - - - ( -

- -

- )} - /> - ( -

- -

- )} - /> - ( - - )} - /> -
-
-
+const AppRoot: React.FunctionComponent = React.memo(({ basename, store }) => ( + + + + + ( +

+ +

+ )} + /> + { + // FIXME: This is temporary. Will be removed in next PR for endpoint list + store.dispatch({ type: 'userEnteredEndpointListPage' }); + + return ( +

+ +

+ ); + }} + /> + ( + + )} + /> +
+
+
+
)); diff --git a/x-pack/test/typings/hapi_basic.d.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/index.ts similarity index 89% rename from x-pack/test/typings/hapi_basic.d.ts rename to x-pack/plugins/endpoint/public/applications/endpoint/lib/index.ts index b7b27a10599a8..ba2e1ce8f9fe6 100644 --- a/x-pack/test/typings/hapi_basic.d.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/lib/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -declare module '@hapi/basic'; +export * from './saga'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts new file mode 100644 index 0000000000000..0387eac0e7c7f --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.test.ts @@ -0,0 +1,101 @@ +/* + * 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 { createSagaMiddleware, SagaContext } from './index'; +import { applyMiddleware, createStore, Reducer } from 'redux'; + +describe('saga', () => { + const INCREMENT_COUNTER = 'INCREMENT'; + const DELAYED_INCREMENT_COUNTER = 'DELAYED INCREMENT COUNTER'; + const STOP_SAGA_PROCESSING = 'BREAK ASYNC ITERATOR'; + + const sleep = (ms = 1000) => new Promise(resolve => setTimeout(resolve, ms)); + let reducerA: Reducer; + let sideAffect: (a: unknown, s: unknown) => void; + let sagaExe: (sagaContext: SagaContext) => Promise; + + beforeEach(() => { + reducerA = jest.fn((prevState = { count: 0 }, { type }) => { + switch (type) { + case INCREMENT_COUNTER: + return { ...prevState, count: prevState.count + 1 }; + default: + return prevState; + } + }); + + sideAffect = jest.fn(); + + sagaExe = jest.fn(async ({ actionsAndState, dispatch }: SagaContext) => { + for await (const { action, state } of actionsAndState()) { + expect(action).toBeDefined(); + expect(state).toBeDefined(); + + if (action.type === STOP_SAGA_PROCESSING) { + break; + } + + sideAffect(action, state); + + if (action.type === DELAYED_INCREMENT_COUNTER) { + await sleep(1); + dispatch({ + type: INCREMENT_COUNTER, + }); + } + } + }); + }); + + test('it returns Redux Middleware from createSagaMiddleware()', () => { + const sagaMiddleware = createSagaMiddleware(async () => {}); + expect(sagaMiddleware).toBeInstanceOf(Function); + }); + test('it does nothing if saga is not started', () => { + const store = createStore(reducerA, applyMiddleware(createSagaMiddleware(sagaExe))); + expect(store.getState().count).toEqual(0); + expect(reducerA).toHaveBeenCalled(); + expect(sagaExe).toHaveBeenCalled(); + expect(sideAffect).not.toHaveBeenCalled(); + expect(store.getState()).toEqual({ count: 0 }); + }); + test('it updates store once running', async () => { + const sagaMiddleware = createSagaMiddleware(sagaExe); + const store = createStore(reducerA, applyMiddleware(sagaMiddleware)); + + expect(store.getState()).toEqual({ count: 0 }); + expect(sagaExe).toHaveBeenCalled(); + + store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); + expect(store.getState()).toEqual({ count: 0 }); + + await sleep(100); + + expect(sideAffect).toHaveBeenCalled(); + expect(store.getState()).toEqual({ count: 1 }); + }); + test('it stops processing if break out of loop', async () => { + const sagaMiddleware = createSagaMiddleware(sagaExe); + const store = createStore(reducerA, applyMiddleware(sagaMiddleware)); + + store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); + await sleep(100); + + expect(store.getState()).toEqual({ count: 1 }); + expect(sideAffect).toHaveBeenCalledTimes(2); + + store.dispatch({ type: STOP_SAGA_PROCESSING }); + await sleep(100); + + expect(store.getState()).toEqual({ count: 1 }); + expect(sideAffect).toHaveBeenCalledTimes(2); + + store.dispatch({ type: DELAYED_INCREMENT_COUNTER }); + await sleep(100); + + expect(store.getState()).toEqual({ count: 1 }); + expect(sideAffect).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts new file mode 100644 index 0000000000000..b93360ec6b5aa --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/lib/saga.ts @@ -0,0 +1,129 @@ +/* + * 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 { AnyAction, Dispatch, Middleware, MiddlewareAPI } from 'redux'; +import { GlobalState } from '../store'; + +interface QueuedAction { + /** + * The Redux action that was dispatched + */ + action: TAction; + /** + * The Global state at the time the action was dispatched + */ + state: GlobalState; +} + +interface IteratorInstance { + queue: QueuedAction[]; + nextResolve: null | ((inst: QueuedAction) => void); +} + +type Saga = (storeContext: SagaContext) => Promise; + +type StoreActionsAndState = AsyncIterableIterator>; + +export interface SagaContext { + /** + * A generator function that will `yield` `Promise`s that resolve with a `QueuedAction` + */ + actionsAndState: () => StoreActionsAndState; + dispatch: Dispatch; +} + +const noop = () => {}; + +/** + * Creates Saga Middleware for use with Redux. + * + * @param {Saga} saga The `saga` should initialize a long-running `for await...of` loop against + * the return value of the `actionsAndState()` method provided by the `SagaContext`. + * + * @return {Middleware} + * + * @example + * + * type TPossibleActions = { type: 'add', payload: any[] }; + * //... + * const endpointsSaga = async ({ actionsAndState, dispatch }: SagaContext) => { + * for await (const { action, state } of actionsAndState()) { + * if (action.type === "userRequestedResource") { + * const resourceData = await doApiFetch('of/some/resource'); + * dispatch({ + * type: 'add', + * payload: [ resourceData ] + * }); + * } + * } + * } + * const endpointsSagaMiddleware = createSagaMiddleware(endpointsSaga); + * //.... + * const store = createStore(reducers, [ endpointsSagaMiddleware ]); + */ +export function createSagaMiddleware(saga: Saga): Middleware { + const iteratorInstances = new Set(); + let runSaga: () => void = noop; + + async function* getActionsAndStateIterator(): StoreActionsAndState { + const instance: IteratorInstance = { queue: [], nextResolve: null }; + iteratorInstances.add(instance); + try { + while (true) { + yield await nextActionAndState(); + } + } finally { + // If the consumer stops consuming this (e.g. `break` or `return` is called in the `for await` + // then this `finally` block will run and unregister this instance and reset `runSaga` + iteratorInstances.delete(instance); + runSaga = noop; + } + + function nextActionAndState() { + if (instance.queue.length) { + return Promise.resolve(instance.queue.shift() as QueuedAction); + } else { + return new Promise(function(resolve) { + instance.nextResolve = resolve; + }); + } + } + } + + function enqueue(value: QueuedAction) { + for (const iteratorInstance of iteratorInstances) { + iteratorInstance.queue.push(value); + if (iteratorInstance.nextResolve !== null) { + iteratorInstance.nextResolve(iteratorInstance.queue.shift() as QueuedAction); + iteratorInstance.nextResolve = null; + } + } + } + + function middleware({ getState, dispatch }: MiddlewareAPI) { + if (runSaga === noop) { + runSaga = saga.bind>(null, { + actionsAndState: getActionsAndStateIterator, + dispatch, + }); + runSaga(); + } + return (next: Dispatch) => (action: AnyAction) => { + // Call the next dispatch method in the middleware chain. + const returnValue = next(action); + + enqueue({ + action, + state: getState(), + }); + + // This will likely be the action itself, unless a middleware further in chain changed it. + return returnValue; + }; + } + + return middleware; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/actions.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/actions.ts new file mode 100644 index 0000000000000..796dabce1d76a --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/actions.ts @@ -0,0 +1,9 @@ +/* + * 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 { EndpointListAction } from './endpoint_list'; + +export type AppAction = EndpointListAction; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/action.ts new file mode 100644 index 0000000000000..02ec0f9d09035 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/action.ts @@ -0,0 +1,25 @@ +/* + * 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 { EndpointListData } from './types'; + +interface ServerReturnedEndpointList { + type: 'serverReturnedEndpointList'; + payload: EndpointListData; +} + +interface UserEnteredEndpointListPage { + type: 'userEnteredEndpointListPage'; +} + +interface UserExitedEndpointListPage { + type: 'userExitedEndpointListPage'; +} + +export type EndpointListAction = + | ServerReturnedEndpointList + | UserEnteredEndpointListPage + | UserExitedEndpointListPage; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/index.test.ts new file mode 100644 index 0000000000000..a46653f82ee45 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/index.test.ts @@ -0,0 +1,126 @@ +/* + * 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 { createStore, Dispatch, Store } from 'redux'; +import { EndpointListAction, EndpointData, endpointListReducer, EndpointListState } from './index'; +import { endpointListData } from './selectors'; + +describe('endpoint_list store concerns', () => { + let store: Store; + let dispatch: Dispatch; + const createTestStore = () => { + store = createStore(endpointListReducer); + dispatch = store.dispatch; + }; + const generateEndpoint = (): EndpointData => { + return { + machine_id: Math.random() + .toString(16) + .substr(2), + created_at: new Date(), + host: { + name: '', + hostname: '', + ip: '', + mac_address: '', + os: { + name: '', + full: '', + }, + }, + endpoint: { + domain: '', + is_base_image: true, + active_directory_distinguished_name: '', + active_directory_hostname: '', + upgrade: { + status: '', + updated_at: new Date(), + }, + isolation: { + status: false, + request_status: true, + updated_at: new Date(), + }, + policy: { + name: '', + id: '', + }, + sensor: { + persistence: true, + status: {}, + }, + }, + }; + }; + const loadDataToStore = () => { + dispatch({ + type: 'serverReturnedEndpointList', + payload: { + endpoints: [generateEndpoint()], + request_page_size: 1, + request_index: 1, + total: 10, + }, + }); + }; + + describe('# Reducers', () => { + beforeEach(() => { + createTestStore(); + }); + + test('it creates default state', () => { + expect(store.getState()).toEqual({ + endpoints: [], + request_page_size: 10, + request_index: 0, + total: 0, + }); + }); + + test('it handles `serverReturnedEndpointList', () => { + const payload = { + endpoints: [generateEndpoint()], + request_page_size: 1, + request_index: 1, + total: 10, + }; + dispatch({ + type: 'serverReturnedEndpointList', + payload, + }); + + const currentState = store.getState(); + expect(currentState.endpoints).toEqual(payload.endpoints); + expect(currentState.request_page_size).toEqual(payload.request_page_size); + expect(currentState.request_index).toEqual(payload.request_index); + expect(currentState.total).toEqual(payload.total); + }); + + test('it handles `userExitedEndpointListPage`', () => { + loadDataToStore(); + + expect(store.getState().total).toEqual(10); + + dispatch({ type: 'userExitedEndpointListPage' }); + expect(store.getState().endpoints.length).toEqual(0); + expect(store.getState().request_index).toEqual(0); + }); + }); + + describe('# Selectors', () => { + beforeEach(() => { + createTestStore(); + loadDataToStore(); + }); + + test('it selects `endpointListData`', () => { + const currentState = store.getState(); + expect(endpointListData(currentState)).toEqual(currentState.endpoints); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/index.ts new file mode 100644 index 0000000000000..bdf0708457bb0 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/index.ts @@ -0,0 +1,10 @@ +/* + * 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 { endpointListReducer } from './reducer'; +export { EndpointListAction } from './action'; +export { endpointListSaga } from './saga'; +export * from './types'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/reducer.ts new file mode 100644 index 0000000000000..9813777c988ef --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/reducer.ts @@ -0,0 +1,32 @@ +/* + * 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 { EndpointListState } from './types'; +import { EndpointListAction } from './action'; + +const initialState = (): EndpointListState => { + return { + endpoints: [], + request_page_size: 10, + request_index: 0, + total: 0, + }; +}; + +export const endpointListReducer = (state = initialState(), action: EndpointListAction) => { + if (action.type === 'serverReturnedEndpointList') { + return { + ...state, + ...action.payload, + }; + } + + if (action.type === 'userExitedEndpointListPage') { + return initialState(); + } + + return state; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.test.ts new file mode 100644 index 0000000000000..92bf3b7fd92dd --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.test.ts @@ -0,0 +1,111 @@ +/* + * 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 { CoreStart, HttpSetup } from 'kibana/public'; +import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; +import { createSagaMiddleware, SagaContext } from '../../lib'; +import { endpointListSaga } from './saga'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { + EndpointData, + EndpointListAction, + EndpointListData, + endpointListReducer, + EndpointListState, +} from './index'; +import { endpointListData } from './selectors'; + +describe('endpoint list saga', () => { + const sleep = (ms = 100) => new Promise(wakeup => setTimeout(wakeup, ms)); + let fakeCoreStart: jest.Mocked; + let fakeHttpServices: jest.Mocked; + let store: Store; + let dispatch: Dispatch; + + // TODO: consolidate the below ++ helpers in `index.test.ts` into a `test_helpers.ts`?? + const generateEndpoint = (): EndpointData => { + return { + machine_id: Math.random() + .toString(16) + .substr(2), + created_at: new Date(), + host: { + name: '', + hostname: '', + ip: '', + mac_address: '', + os: { + name: '', + full: '', + }, + }, + endpoint: { + domain: '', + is_base_image: true, + active_directory_distinguished_name: '', + active_directory_hostname: '', + upgrade: { + status: '', + updated_at: new Date(), + }, + isolation: { + status: false, + request_status: true, + updated_at: new Date(), + }, + policy: { + name: '', + id: '', + }, + sensor: { + persistence: true, + status: {}, + }, + }, + }; + }; + const getEndpointListApiResponse = (): EndpointListData => { + return { + endpoints: [generateEndpoint()], + request_page_size: 1, + request_index: 1, + total: 10, + }; + }; + + const endpointListSagaFactory = () => { + return async (sagaContext: SagaContext) => { + await endpointListSaga(sagaContext, fakeCoreStart).catch((e: Error) => { + // eslint-disable-next-line no-console + console.error(e); + return Promise.reject(e); + }); + }; + }; + + beforeEach(() => { + fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); + fakeHttpServices = fakeCoreStart.http as jest.Mocked; + store = createStore( + endpointListReducer, + applyMiddleware(createSagaMiddleware(endpointListSagaFactory())) + ); + dispatch = store.dispatch; + }); + + test('it handles `userEnteredEndpointListPage`', async () => { + const apiResponse = getEndpointListApiResponse(); + + fakeHttpServices.post.mockResolvedValue(apiResponse); + expect(fakeHttpServices.post).not.toHaveBeenCalled(); + + dispatch({ type: 'userEnteredEndpointListPage' }); + await sleep(); + + expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/endpoints'); + expect(endpointListData(store.getState())).toEqual(apiResponse.endpoints); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.ts new file mode 100644 index 0000000000000..cc156cfa88002 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/saga.ts @@ -0,0 +1,26 @@ +/* + * 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 { CoreStart } from 'kibana/public'; +import { SagaContext } from '../../lib'; +import { EndpointListAction } from './action'; + +export const endpointListSaga = async ( + { actionsAndState, dispatch }: SagaContext, + coreStart: CoreStart +) => { + const { post: httpPost } = coreStart.http; + + for await (const { action } of actionsAndState()) { + if (action.type === 'userEnteredEndpointListPage') { + const response = await httpPost('/api/endpoint/endpoints'); + dispatch({ + type: 'serverReturnedEndpointList', + payload: response, + }); + } + } +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/selectors.ts new file mode 100644 index 0000000000000..6ffcebc3f41aa --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/selectors.ts @@ -0,0 +1,9 @@ +/* + * 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 { EndpointListState } from './types'; + +export const endpointListData = (state: EndpointListState) => state.endpoints; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/types.ts new file mode 100644 index 0000000000000..f2810dd89f857 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/endpoint_list/types.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// FIXME: temporary until server defined `interface` is moved +export interface EndpointData { + machine_id: string; + created_at: Date; + host: { + name: string; + hostname: string; + ip: string; + mac_address: string; + os: { + name: string; + full: string; + }; + }; + endpoint: { + domain: string; + is_base_image: boolean; + active_directory_distinguished_name: string; + active_directory_hostname: string; + upgrade: { + status?: string; + updated_at?: Date; + }; + isolation: { + status: boolean; + request_status?: string | boolean; + updated_at?: Date; + }; + policy: { + name: string; + id: string; + }; + sensor: { + persistence: boolean; + status: object; + }; + }; +} + +// FIXME: temporary until server defined `interface` is moved to a module we can reference +export interface EndpointListData { + endpoints: EndpointData[]; + request_page_size: number; + request_index: number; + total: number; +} + +export type EndpointListState = EndpointListData; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts new file mode 100644 index 0000000000000..d0dc002031ce2 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { createStore, compose, applyMiddleware } from 'redux'; +import { CoreStart } from 'kibana/public'; +import { appSagaFactory } from './saga'; +import { appReducer } from './reducer'; + +export { GlobalState } from './reducer'; + +const composeWithReduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ name: 'EndpointApp' }) + : compose; + +export const appStoreFactory = (coreStart: CoreStart) => { + const store = createStore( + appReducer, + composeWithReduxDevTools(applyMiddleware(appSagaFactory(coreStart))) + ); + return store; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts new file mode 100644 index 0000000000000..59ca4de91ac83 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts @@ -0,0 +1,16 @@ +/* + * 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 { combineReducers, Reducer } from 'redux'; +import { endpointListReducer, EndpointListState } from './endpoint_list'; +import { AppAction } from './actions'; + +export interface GlobalState { + endpointList: EndpointListState; +} + +export const appReducer: Reducer = combineReducers({ + endpointList: endpointListReducer, +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/saga.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/saga.ts new file mode 100644 index 0000000000000..3b7de79d5443c --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/saga.ts @@ -0,0 +1,18 @@ +/* + * 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 { CoreStart } from 'kibana/public'; +import { createSagaMiddleware, SagaContext } from '../lib'; +import { endpointListSaga } from './endpoint_list'; + +export const appSagaFactory = (coreStart: CoreStart) => { + return createSagaMiddleware(async (sagaContext: SagaContext) => { + await Promise.all([ + // Concerns specific sagas here + endpointListSaga(sagaContext, coreStart), + ]); + }); +}; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts index b21b79e84f741..090d5de901318 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/action.ts @@ -6,18 +6,19 @@ import { Vector2 } from '../../types'; -interface UserScaled { - readonly type: 'userScaled'; +interface UserSetZoomLevel { + readonly type: 'userSetZoomLevel'; /** - * A vector who's `x` and `y` component will be the new scaling factors for the projection. + * A number whose value is always between 0 and 1 and will be the new scaling factor for the projection. */ - readonly payload: Vector2; + readonly payload: number; } interface UserZoomed { readonly type: 'userZoomed'; /** - * A value to zoom in by. Should be a fraction of `1`. For a `'wheel'` event when `event.deltaMode` is `'pixel'`, pass `event.deltaY / -renderHeight` where `renderHeight` is the height of the Resolver element in pixels. + * A value to zoom in by. Should be a fraction of `1`. For a `'wheel'` event when `event.deltaMode` is `'pixel'`, + * pass `event.deltaY / -renderHeight` where `renderHeight` is the height of the Resolver element in pixels. */ payload: number; } @@ -65,7 +66,7 @@ interface UserMovedPointer { } export type CameraAction = - | UserScaled + | UserSetZoomLevel | UserSetRasterSize | UserSetPositionOfCamera | UserStartedPanning diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/inverse_projection_matrix.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/inverse_projection_matrix.test.ts index 3d555b63d8392..41e3bc025f557 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/inverse_projection_matrix.test.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/inverse_projection_matrix.test.ts @@ -10,6 +10,7 @@ import { CameraState } from '../../types'; import { cameraReducer } from './reducer'; import { inverseProjectionMatrix } from './selectors'; import { applyMatrix3 } from '../../lib/vector2'; +import { scaleToZoom } from './scale_to_zoom'; describe('inverseProjectionMatrix', () => { let store: Store; @@ -59,7 +60,7 @@ describe('inverseProjectionMatrix', () => { }); describe('when the user has zoomed to 0.5', () => { beforeEach(() => { - const action: CameraAction = { type: 'userScaled', payload: [0.5, 0.5] }; + const action: CameraAction = { type: 'userSetZoomLevel', payload: scaleToZoom(0.5) }; store.dispatch(action); }); it('should convert 150, 100 (center) to 0, 0 (center) in world space', () => { @@ -89,7 +90,7 @@ describe('inverseProjectionMatrix', () => { describe('when the user has scaled to 2', () => { // the viewport will only cover half, or 150x100 instead of 300x200 beforeEach(() => { - const action: CameraAction = { type: 'userScaled', payload: [2, 2] }; + const action: CameraAction = { type: 'userSetZoomLevel', payload: scaleToZoom(2) }; store.dispatch(action); }); // we expect the viewport to be diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/projection_matrix.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/projection_matrix.test.ts index 025c436a957e8..e21e3d1001794 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/projection_matrix.test.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/projection_matrix.test.ts @@ -10,6 +10,7 @@ import { CameraState } from '../../types'; import { cameraReducer } from './reducer'; import { projectionMatrix } from './selectors'; import { applyMatrix3 } from '../../lib/vector2'; +import { scaleToZoom } from './scale_to_zoom'; describe('projectionMatrix', () => { let store: Store; @@ -56,7 +57,7 @@ describe('projectionMatrix', () => { }); describe('when the user has zoomed to 0.5', () => { beforeEach(() => { - const action: CameraAction = { type: 'userScaled', payload: [0.5, 0.5] }; + const action: CameraAction = { type: 'userSetZoomLevel', payload: scaleToZoom(0.5) }; store.dispatch(action); }); it('should convert 0, 0 (center) in world space to 150, 100 (center)', () => { @@ -92,7 +93,7 @@ describe('projectionMatrix', () => { describe('when the user has scaled to 2', () => { // the viewport will only cover half, or 150x100 instead of 300x200 beforeEach(() => { - const action: CameraAction = { type: 'userScaled', payload: [2, 2] }; + const action: CameraAction = { type: 'userSetZoomLevel', payload: scaleToZoom(2) }; store.dispatch(action); }); // we expect the viewport to be diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts index 457d3904804f2..b7229240684f1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/reducer.ts @@ -10,52 +10,34 @@ import { userIsPanning, translation, projectionMatrix, inverseProjectionMatrix } import { clamp } from '../../lib/math'; import { CameraState, ResolverAction } from '../../types'; +import { scaleToZoom } from './scale_to_zoom'; function initialState(): CameraState { return { - scaling: [1, 1] as const, + scalingFactor: scaleToZoom(1), // Defaulted to 1 to 1 scale rasterSize: [0, 0] as const, translationNotCountingCurrentPanning: [0, 0] as const, latestFocusedWorldCoordinates: null, }; } -/** - * The minimum allowed value for the camera scale. This is the least scale that we will ever render something at. - */ -const minimumScale = 0.1; - -/** - * The maximum allowed value for the camera scale. This is greatest scale that we will ever render something at. - */ -const maximumScale = 6; - export const cameraReducer: Reducer = ( state = initialState(), action ) => { - if (action.type === 'userScaled') { + if (action.type === 'userSetZoomLevel') { /** * Handle the scale being explicitly set, for example by a 'reset zoom' feature, or by a range slider with exact scale values */ - const [deltaX, deltaY] = action.payload; + return { ...state, - scaling: [ - clamp(deltaX, minimumScale, maximumScale), - clamp(deltaY, minimumScale, maximumScale), - ], + scalingFactor: clamp(action.payload, 0, 1), }; } else if (action.type === 'userZoomed') { - /** - * When the user zooms we change the scale. Limit the change in scale so that we aren't liable for supporting crazy values (e.g. infinity or negative scale.) - */ - const newScaleX = clamp(state.scaling[0] + action.payload, minimumScale, maximumScale); - const newScaleY = clamp(state.scaling[1] + action.payload, minimumScale, maximumScale); - const stateWithNewScaling: CameraState = { ...state, - scaling: [newScaleX, newScaleY], + scalingFactor: clamp(state.scalingFactor + action.payload, 0, 1), }; /** diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/scale_to_zoom.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/scale_to_zoom.ts new file mode 100644 index 0000000000000..534e20e9ed3c4 --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/scale_to_zoom.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { maximum, minimum, zoomCurveRate } from './scaling_constants'; + +/** + * Calculates the zoom factor (between 0 and 1) for a given scale value. + */ +export const scaleToZoom = (scale: number): number => { + const delta = maximum - minimum; + return Math.pow((scale - minimum) / delta, 1 / zoomCurveRate); +}; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/scaling_constants.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/scaling_constants.ts new file mode 100644 index 0000000000000..93c41fde64f0e --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/scaling_constants.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * The minimum allowed value for the camera scale. This is the least scale that we will ever render something at. + */ +export const minimum = 0.1; + +/** + * The maximum allowed value for the camera scale. This is greatest scale that we will ever render something at. + */ +export const maximum = 6; + +/** + * The curve of the zoom function growth rate. The higher the scale factor is, the higher the zoom rate will be. + */ +export const zoomCurveRate = 4; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts index e0d2062bfc870..a7b0bbf66052d 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/selectors.ts @@ -13,6 +13,7 @@ import { orthographicProjection, translationTransformation, } from '../../lib/transformation'; +import { maximum, minimum, zoomCurveRate } from './scaling_constants'; interface ClippingPlanes { renderWidth: number; @@ -43,8 +44,8 @@ export function viewableBoundingBox(state: CameraState): AABB { function clippingPlanes(state: CameraState): ClippingPlanes { const renderWidth = state.rasterSize[0]; const renderHeight = state.rasterSize[1]; - const clippingPlaneRight = renderWidth / 2 / state.scaling[0]; - const clippingPlaneTop = renderHeight / 2 / state.scaling[1]; + const clippingPlaneRight = renderWidth / 2 / scale(state)[0]; + const clippingPlaneTop = renderHeight / 2 / scale(state)[1]; return { renderWidth, @@ -112,9 +113,9 @@ export function translation(state: CameraState): Vector2 { return add( state.translationNotCountingCurrentPanning, divide(subtract(state.panning.currentOffset, state.panning.origin), [ - state.scaling[0], + scale(state)[0], // Invert `y` since the `.panning` vectors are in screen coordinates and therefore have backwards `y` - -state.scaling[1], + -scale(state)[1], ]) ); } else { @@ -175,7 +176,11 @@ export const inverseProjectionMatrix: (state: CameraState) => Matrix3 = state => /** * The scale by which world values are scaled when rendered. */ -export const scale = (state: CameraState): Vector2 => state.scaling; +export const scale = (state: CameraState): Vector2 => { + const delta = maximum - minimum; + const value = Math.pow(state.scalingFactor, zoomCurveRate) * delta + minimum; + return [value, value]; +}; /** * Whether or not the user is current panning the map. diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/test_helpers.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/test_helpers.ts index fd446c42116a4..25e0ec642086f 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/test_helpers.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/test_helpers.ts @@ -4,19 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Store } from 'redux'; -import { CameraAction } from './action'; -import { CameraState, Vector2 } from '../../types'; - -type CameraStore = Store; - -/** - * Dispatches a 'userScaled' action. - */ -export function userScaled(store: CameraStore, scalingValue: [number, number]): void { - const action: CameraAction = { type: 'userScaled', payload: scalingValue }; - store.dispatch(action); -} +import { Vector2 } from '../../types'; /** * Used to assert that two Vector2s are close to each other (accounting for round-off errors.) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts index a04ca8376c9b1..4b0915282e86f 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/camera/zooming.test.ts @@ -9,7 +9,8 @@ import { cameraReducer } from './reducer'; import { createStore, Store } from 'redux'; import { CameraState, AABB } from '../../types'; import { viewableBoundingBox, inverseProjectionMatrix } from './selectors'; -import { userScaled, expectVectorsToBeClose } from './test_helpers'; +import { expectVectorsToBeClose } from './test_helpers'; +import { scaleToZoom } from './scale_to_zoom'; import { applyMatrix3 } from '../../lib/vector2'; describe('zooming', () => { @@ -43,7 +44,8 @@ describe('zooming', () => { ); describe('when the user has scaled in to 2x', () => { beforeEach(() => { - userScaled(store, [2, 2]); + const action: CameraAction = { type: 'userSetZoomLevel', payload: scaleToZoom(2) }; + store.dispatch(action); }); it( ...cameraShouldBeBoundBy({ @@ -52,7 +54,7 @@ describe('zooming', () => { }) ); }); - describe('when the user zooms in by 1 zoom unit', () => { + describe('when the user zooms in all the way', () => { beforeEach(() => { const action: CameraAction = { type: 'userZoomed', @@ -60,12 +62,21 @@ describe('zooming', () => { }; store.dispatch(action); }); - it( - ...cameraShouldBeBoundBy({ - minimum: [-75, -50], - maximum: [75, 50], - }) - ); + it('should zoom to maximum scale factor', () => { + const actual = viewableBoundingBox(store.getState()); + expect(actual).toMatchInlineSnapshot(` + Object { + "maximum": Array [ + 25.000000000000007, + 16.666666666666668, + ], + "minimum": Array [ + -25, + -16.666666666666668, + ], + } + `); + }); }); it('the raster position 200, 50 should map to the world position 50, 50', () => { expectVectorsToBeClose(applyMatrix3([200, 50], inverseProjectionMatrix(store.getState())), [ @@ -126,7 +137,8 @@ describe('zooming', () => { }); describe('when the user scales to 2x', () => { beforeEach(() => { - userScaled(store, [2, 2]); + const action: CameraAction = { type: 'userSetZoomLevel', payload: scaleToZoom(2) }; + store.dispatch(action); }); it('should be centered on 100, 0', () => { const worldCenterPoint = applyMatrix3( diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index ed7eb79d621fc..eae9ebf9ee9a6 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -43,9 +43,9 @@ export interface CameraState { readonly panning?: PanningState; /** - * Scales the coordinate system, used for zooming. + * Scales the coordinate system, used for zooming. Should always be between 0 and 1 */ - readonly scaling: Vector2; + readonly scalingFactor: number; /** * The size (in pixels) of the Resolver component. diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx index 2c5c60440522d..d252988d0cdcc 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx @@ -95,7 +95,6 @@ const Resolver = styled( const handleWheel = useCallback( (event: WheelEvent) => { - // we use elementBoundingClientRect to interpret pixel deltas as a fraction of the element's height if ( elementBoundingClientRect !== null && event.ctrlKey && @@ -105,7 +104,9 @@ const Resolver = styled( event.preventDefault(); dispatch({ type: 'userZoomed', - payload: (-2 * event.deltaY) / elementBoundingClientRect.height, + // we use elementBoundingClientRect to interpret pixel deltas as a fraction of the element's height + // when pinch-zooming in on a mac, deltaY is a negative number but we want the payload to be positive + payload: event.deltaY / -elementBoundingClientRect.height, }); } }, @@ -158,5 +159,12 @@ const Resolver = styled( */ display: flex; flex-grow: 1; + /** + * The placeholder components use absolute positioning. + */ position: relative; + /** + * Prevent partially visible components from showing up outside the bounds of Resolver. + */ + overflow: hidden; `; diff --git a/x-pack/plugins/reporting/public/components/download_button.tsx b/x-pack/plugins/reporting/public/components/download_button.tsx index e762c796b0f86..911a19c0176c2 100644 --- a/x-pack/plugins/reporting/public/components/download_button.tsx +++ b/x-pack/plugins/reporting/public/components/download_button.tsx @@ -15,17 +15,12 @@ interface Props { } export const DownloadButton = ({ getUrl, job }: Props) => { - const downloadReport = () => { - window.open(getUrl(job.id)); - }; - return ( { - downloadReport(); - }} + href={getUrl(job.id)} + target="_blank" > { // status running or claiming with a retryAt <= now. shouldBeOneOf(IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt), // Either task has an schedule or the attempts < the maximum configured - shouldBeOneOf( + shouldBeOneOf( TaskWithSchedule, ...Object.entries(definitions).map(([type, { maxAttempts }]) => taskWithLessThanMaxAttempts(type, maxAttempts || defaultMaxAttempts) diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts index 6691b31a546bc..b0d9dc61c9667 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts @@ -3,24 +3,24 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { defaultsDeep } from 'lodash'; import { - BoolClause, - IDsClause, SortClause, ScriptClause, - ExistsBoolClause, - TermBoolClause, - RangeBoolClause, + ExistsFilter, + TermFilter, + RangeFilter, + mustBeAllOf, + MustCondition, + MustNotCondition, } from './query_clauses'; -export const TaskWithSchedule: ExistsBoolClause = { +export const TaskWithSchedule: ExistsFilter = { exists: { field: 'task.schedule' }, }; export function taskWithLessThanMaxAttempts( type: string, maxAttempts: number -): BoolClause { +): MustCondition { return { bool: { must: [ @@ -37,34 +37,37 @@ export function taskWithLessThanMaxAttempts( }; } -export const IdleTaskWithExpiredRunAt: BoolClause = { +export function tasksClaimedByOwner(taskManagerId: string) { + return mustBeAllOf( + { + term: { + 'task.ownerId': taskManagerId, + }, + }, + { term: { 'task.status': 'claiming' } } + ); +} + +export const IdleTaskWithExpiredRunAt: MustCondition = { bool: { must: [{ term: { 'task.status': 'idle' } }, { range: { 'task.runAt': { lte: 'now' } } }], }, }; -export const taskWithIDsAndRunnableStatus = ( - claimTasksById: string[] -): BoolClause => ({ +export const InactiveTasks: MustNotCondition = { bool: { - must: [ + must_not: [ { bool: { - should: [{ term: { 'task.status': 'idle' } }, { term: { 'task.status': 'failed' } }], - }, - }, - { - ids: { - values: claimTasksById, + should: [{ term: { 'task.status': 'running' } }, { term: { 'task.status': 'claiming' } }], }, }, + { range: { 'task.retryAt': { gt: 'now' } } }, ], }, -}); +}; -export const RunningOrClaimingTaskWithExpiredRetryAt: BoolClause< - TermBoolClause | RangeBoolClause -> = { +export const RunningOrClaimingTaskWithExpiredRetryAt: MustCondition = { bool: { must: [ { @@ -95,31 +98,6 @@ if (doc['task.runAt'].size()!=0) { }, }; -const SORT_VALUE_TO_BE_FIRST = 0; -export const sortByIdsThenByScheduling = (claimTasksById: string[]): SortClause => { - const { - _script: { - script: { source }, - }, - } = SortByRunAtAndRetryAt; - return defaultsDeep( - { - _script: { - script: { - source: ` -if(params.ids.contains(doc['_id'].value)){ - return ${SORT_VALUE_TO_BE_FIRST}; -} -${source} -`, - params: { ids: claimTasksById }, - }, - }, - }, - SortByRunAtAndRetryAt - ); -}; - export const updateFields = (fieldUpdates: { [field: string]: string | number | Date; }): ScriptClause => ({ diff --git a/x-pack/plugins/task_manager/server/queries/query_clauses.test.ts b/x-pack/plugins/task_manager/server/queries/query_clauses.test.ts new file mode 100644 index 0000000000000..beb8f864bd754 --- /dev/null +++ b/x-pack/plugins/task_manager/server/queries/query_clauses.test.ts @@ -0,0 +1,51 @@ +/* + * 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 _ from 'lodash'; +import { + MustCondition, + shouldBeOneOf, + mustBeAllOf, + ExistsFilter, + TermFilter, + RangeFilter, + matchesClauses, +} from './query_clauses'; + +describe('matchesClauses', () => { + test('merges multiple types of Bool Clauses into one', () => { + const TaskWithSchedule: ExistsFilter = { + exists: { field: 'task.schedule' }, + }; + + const IdleTaskWithExpiredRunAt: MustCondition = { + bool: { + must: [{ term: { 'task.status': 'idle' } }, { range: { 'task.runAt': { lte: 'now' } } }], + }, + }; + + const RunningTask: MustCondition = { + bool: { + must: [{ term: { 'task.status': 'running' } }], + }, + }; + + expect( + matchesClauses( + mustBeAllOf(TaskWithSchedule), + shouldBeOneOf( + RunningTask, + IdleTaskWithExpiredRunAt + ) + ) + ).toMatchObject({ + bool: { + must: [TaskWithSchedule], + should: [RunningTask, IdleTaskWithExpiredRunAt], + }, + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/queries/query_clauses.ts b/x-pack/plugins/task_manager/server/queries/query_clauses.ts index 1f76ce99e600a..de7e3b085ba2d 100644 --- a/x-pack/plugins/task_manager/server/queries/query_clauses.ts +++ b/x-pack/plugins/task_manager/server/queries/query_clauses.ts @@ -4,30 +4,150 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface TermBoolClause { +/** + * Terminology + * =========== + * The terms for the differenct clauses in an Elasticsearch query can be confusing, here are some + * clarifications that might help you understand the Typescript types we use here. + * + * Given the following Query: + * { + * "query": { (1) + * "bool": { (2) + * "must": + * [ + * (3) { "term" : { "tag" : "wow" } }, + * { "term" : { "tag" : "elasticsearch" } }, + * { + * "must" : { "term" : { "user" : "kimchy" } } + * } + * ] + * } + * } + * } + * + * These are referred to as: + * (1). BoolClause / BoolClauseWithAnyCondition + * (2). BoolCondition / AnyBoolCondition + * (3). BoolClauseFilter + * + */ + +export interface TermFilter { term: { [field: string]: string | string[] }; } -export interface RangeBoolClause { - range: { [field: string]: { lte: string | number } | { lt: string | number } }; +export interface RangeFilter { + range: { + [field: string]: { lte: string | number } | { lt: string | number } | { gt: string | number }; + }; } -export interface ExistsBoolClause { +export interface ExistsFilter { exists: { field: string }; } -export interface IDsClause { - ids: { - values: string[]; - }; -} -export interface ShouldClause { - should: Array | IDsClause | T>; +type BoolClauseFilter = TermFilter | RangeFilter | ExistsFilter; +type BoolClauseFiltering = + | BoolClauseWithAnyCondition + | PinnedQuery + | T; + +enum Conditions { + Should = 'should', + Must = 'must', + MustNot = 'must_not', + Filter = 'filter', } -export interface MustClause { - must: Array | IDsClause | T>; + +/** + * Describe a specific BoolClause Condition with a BoolClauseFilter on it, such as: + * ``` + * { + * must : [ + * T, ... + * ] + * } + * ``` + */ +type BoolCondition = { + [c in C]: Array>; +}; + +/** + * Describe a Bool clause with a specific Condition, such as: + * ``` + * { + * // described by BoolClause + * bool: { + * // described by BoolCondition + * must: [ + * T, ... + * ] + * } + * } + * ``` + */ +interface BoolClause { + bool: BoolCondition; } -export interface BoolClause { - bool: MustClause | ShouldClause; + +/** + * Describe a Bool clause with mixed Conditions, such as: + * ``` + * { + * // described by BoolClause<...> + * bool: { + * // described by BoolCondition + * must : { + * ... + * }, + * // described by BoolCondition + * should : { + * ... + * } + * } + * } + * ``` + */ +type AnyBoolCondition = { + [Condition in Conditions]?: Array>; +}; + +/** + * Describe a Bool Condition with any Condition on it, so it can handle both: + * ``` + * { + * bool: { + * must : { + * ... + * } + * } + * } + * ``` + * + * and: + * + * ``` + * { + * bool: { + * must_not : { + * ... + * } + * } + * } + * ``` + */ +export interface BoolClauseWithAnyCondition { + bool: AnyBoolCondition; } + +/** + * Describe the various Bool Clause Conditions we support, as specified in the Conditions enum + */ +export type ShouldCondition = BoolClause; +export type MustCondition = BoolClause; +export type MustNotCondition = BoolClause; +export type FilterCondition = BoolClause; + export interface SortClause { _script: { type: string; @@ -39,6 +159,8 @@ export interface SortClause { }; }; } +export type SortOptions = string | SortClause | Array; + export interface ScriptClause { source: string; lang: string; @@ -46,18 +168,34 @@ export interface ScriptClause { [field: string]: string | number | Date; }; } -export interface UpdateByQuery { - query: BoolClause; - sort: SortClause; + +export interface UpdateByQuery { + query: PinnedQuery | BoolClauseWithAnyCondition; + sort: SortOptions; seq_no_primary_term: true; script: ScriptClause; } -export function shouldBeOneOf( - ...should: Array | IDsClause | T> -): { - bool: ShouldClause; -} { +export interface PinnedQuery { + pinned: PinnedClause; +} + +export interface PinnedClause { + ids: string[]; + organic: BoolClauseWithAnyCondition; +} + +export function matchesClauses( + ...clauses: Array> +): BoolClauseWithAnyCondition { + return { + bool: Object.assign({}, ...clauses.map(clause => clause.bool)), + }; +} + +export function shouldBeOneOf( + ...should: Array> +): ShouldCondition { return { bool: { should, @@ -65,11 +203,9 @@ export function shouldBeOneOf( }; } -export function mustBeAllOf( - ...must: Array | IDsClause | T> -): { - bool: MustClause; -} { +export function mustBeAllOf( + ...must: Array> +): MustCondition { return { bool: { must, @@ -77,14 +213,36 @@ export function mustBeAllOf( }; } -export function asUpdateByQuery({ +export function filterDownBy( + ...filter: Array> +): FilterCondition { + return { + bool: { + filter, + }, + }; +} + +export function asPinnedQuery( + ids: PinnedClause['ids'], + organic: PinnedClause['organic'] +): PinnedQuery { + return { + pinned: { + ids, + organic, + }, + }; +} + +export function asUpdateByQuery({ query, update, sort, }: { - query: BoolClause; - update: ScriptClause; - sort: SortClause; + query: UpdateByQuery['query']; + update: UpdateByQuery['script']; + sort: UpdateByQuery['sort']; }): UpdateByQuery { return { query, diff --git a/x-pack/plugins/task_manager/server/task_manager.ts b/x-pack/plugins/task_manager/server/task_manager.ts index c0baed3708a0a..93e98f33a30b0 100644 --- a/x-pack/plugins/task_manager/server/task_manager.ts +++ b/x-pack/plugins/task_manager/server/task_manager.ts @@ -48,11 +48,11 @@ import { createTaskPoller, PollingError, PollingErrorType } from './task_poller' import { TaskPool } from './task_pool'; import { TaskManagerRunner, TaskRunner } from './task_runner'; import { - FetchOpts, FetchResult, TaskStore, OwnershipClaimingOpts, ClaimOwnershipResult, + SearchOpts, } from './task_store'; import { identifyEsError } from './lib/identify_es_error'; import { ensureDeprecatedFieldsAreCorrected } from './lib/correct_deprecated_fields'; @@ -323,12 +323,12 @@ export class TaskManager { } /** - * Fetches a paginatable list of scheduled tasks. + * Fetches a list of scheduled tasks. * * @param opts - The query options used to filter tasks * @returns {Promise} */ - public async fetch(opts: FetchOpts): Promise { + public async fetch(opts: SearchOpts): Promise { await this.waitUntilStarted(); return this.store.fetch(opts); } diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index f47cc41c2d045..e6cce7a664d91 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -16,7 +16,7 @@ import { TaskStatus, TaskLifecycleResult, } from './task'; -import { FetchOpts, StoreOpts, OwnershipClaimingOpts, TaskStore } from './task_store'; +import { StoreOpts, OwnershipClaimingOpts, TaskStore, SearchOpts } from './task_store'; import { savedObjectsRepositoryMock } from '../../../../src/core/server/mocks'; import { SavedObjectsSerializer, @@ -175,7 +175,7 @@ describe('TaskStore', () => { }); describe('fetch', () => { - async function testFetch(opts?: FetchOpts, hits: any[] = []) { + async function testFetch(opts?: SearchOpts, hits: any[] = []) { const callCluster = sinon.spy(async (name: string, params?: any) => ({ hits: { hits } })); const store = new TaskStore({ index: 'tasky', @@ -203,7 +203,7 @@ describe('TaskStore', () => { expect(args).toMatchObject({ index: 'tasky', body: { - sort: [{ 'task.runAt': 'asc' }, { _id: 'desc' }], + sort: [{ 'task.runAt': 'asc' }], query: { term: { type: 'task' } }, }, }); @@ -226,122 +226,6 @@ describe('TaskStore', () => { }, }); }); - - test('sorts by id if custom sort does not have an id sort in it', async () => { - const { args } = await testFetch({ - sort: [{ 'task.taskType': 'desc' }], - }); - - expect(args).toMatchObject({ - body: { - sort: [{ 'task.taskType': 'desc' }, { _id: 'desc' }], - }, - }); - }); - - test('allows custom sort by id', async () => { - const { args } = await testFetch({ - sort: [{ _id: 'asc' }], - }); - - expect(args).toMatchObject({ - body: { - sort: [{ _id: 'asc' }], - }, - }); - }); - - test('allows specifying pagination', async () => { - const now = new Date(); - const searchAfter = [now, '143243sdafa32']; - const { args } = await testFetch({ - searchAfter, - }); - - expect(args).toMatchObject({ - body: { - search_after: searchAfter, - }, - }); - }); - - test('returns paginated tasks', async () => { - const runAt = new Date(); - const { result } = await testFetch(undefined, [ - { - _id: 'aaa', - _source: { - type: 'task', - task: { - runAt, - taskType: 'foo', - schedule: undefined, - attempts: 0, - status: 'idle', - params: '{ "hello": "world" }', - state: '{ "baby": "Henhen" }', - user: 'jimbo', - scope: ['reporting'], - }, - }, - sort: ['a', 1], - }, - { - _id: 'bbb', - _source: { - type: 'task', - task: { - runAt, - taskType: 'bar', - schedule: { interval: '5m' }, - attempts: 2, - status: 'running', - params: '{ "shazm": 1 }', - state: '{ "henry": "The 8th" }', - user: 'dabo', - scope: ['reporting', 'ceo'], - }, - }, - sort: ['b', 2], - }, - ]); - - expect(result).toEqual({ - docs: [ - { - attempts: 0, - id: 'aaa', - schedule: undefined, - params: { hello: 'world' }, - runAt, - scheduledAt: mockedDate, - scope: ['reporting'], - state: { baby: 'Henhen' }, - status: 'idle', - taskType: 'foo', - user: 'jimbo', - retryAt: undefined, - startedAt: undefined, - }, - { - attempts: 2, - id: 'bbb', - schedule: { interval: '5m' }, - params: { shazm: 1 }, - runAt, - scheduledAt: mockedDate, - scope: ['reporting', 'ceo'], - state: { henry: 'The 8th' }, - status: 'running', - taskType: 'bar', - user: 'dabo', - retryAt: undefined, - startedAt: undefined, - }, - ], - searchAfter: ['b', 2], - }); - }); }); describe('claimAvailableTasks', () => { @@ -450,65 +334,88 @@ describe('TaskStore', () => { must: [ { bool: { - should: [ + must: [ { bool: { - must: [ - { term: { 'task.status': 'idle' } }, - { range: { 'task.runAt': { lte: 'now' } } }, + should: [ + { + bool: { + must: [ + { term: { 'task.status': 'idle' } }, + { range: { 'task.runAt': { lte: 'now' } } }, + ], + }, + }, + { + bool: { + must: [ + { + bool: { + should: [ + { term: { 'task.status': 'running' } }, + { term: { 'task.status': 'claiming' } }, + ], + }, + }, + { range: { 'task.retryAt': { lte: 'now' } } }, + ], + }, + }, ], }, }, { bool: { - must: [ + should: [ + { exists: { field: 'task.schedule' } }, + { + bool: { + must: [ + { term: { 'task.taskType': 'foo' } }, + { + range: { + 'task.attempts': { + lt: maxAttempts, + }, + }, + }, + ], + }, + }, { bool: { - should: [ - { term: { 'task.status': 'running' } }, - { term: { 'task.status': 'claiming' } }, + must: [ + { term: { 'task.taskType': 'bar' } }, + { + range: { + 'task.attempts': { + lt: customMaxAttempts, + }, + }, + }, ], }, }, - { range: { 'task.retryAt': { lte: 'now' } } }, ], }, }, ], }, }, + ], + filter: [ { bool: { - should: [ - { exists: { field: 'task.schedule' } }, - { - bool: { - must: [ - { term: { 'task.taskType': 'foo' } }, - { - range: { - 'task.attempts': { - lt: maxAttempts, - }, - }, - }, - ], - }, - }, + must_not: [ { bool: { - must: [ - { term: { 'task.taskType': 'bar' } }, - { - range: { - 'task.attempts': { - lt: customMaxAttempts, - }, - }, - }, + should: [ + { term: { 'task.status': 'running' } }, + { term: { 'task.status': 'claiming' } }, ], }, }, + { range: { 'task.retryAt': { gt: 'now' } } }, ], }, }, @@ -562,96 +469,99 @@ describe('TaskStore', () => { { term: { type: 'task' } }, { bool: { - should: [ + must: [ { - bool: { - must: [ - { - bool: { - should: [ - { - bool: { - must: [ - { term: { 'task.status': 'idle' } }, - { range: { 'task.runAt': { lte: 'now' } } }, - ], - }, - }, - { - bool: { - must: [ - { - bool: { - should: [ - { term: { 'task.status': 'running' } }, - { term: { 'task.status': 'claiming' } }, - ], - }, + pinned: { + ids: [ + 'task:33c6977a-ed6d-43bd-98d9-3f827f7b7cd8', + 'task:a208b22c-14ec-4fb4-995f-d2ff7a3b03b8', + ], + organic: { + bool: { + must: [ + { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'task.status': 'idle' } }, + { range: { 'task.runAt': { lte: 'now' } } }, + ], }, - { range: { 'task.retryAt': { lte: 'now' } } }, - ], - }, - }, - ], - }, - }, - { - bool: { - should: [ - { exists: { field: 'task.schedule' } }, - { - bool: { - must: [ - { term: { 'task.taskType': 'foo' } }, - { - range: { - 'task.attempts': { - lt: maxAttempts, + }, + { + bool: { + must: [ + { + bool: { + should: [ + { term: { 'task.status': 'running' } }, + { term: { 'task.status': 'claiming' } }, + ], + }, }, - }, + { range: { 'task.retryAt': { lte: 'now' } } }, + ], }, - ], - }, + }, + ], }, - { - bool: { - must: [ - { term: { 'task.taskType': 'bar' } }, - { - range: { - 'task.attempts': { - lt: customMaxAttempts, + }, + { + bool: { + should: [ + { exists: { field: 'task.schedule' } }, + { + bool: { + must: [ + { term: { 'task.taskType': 'foo' } }, + { + range: { + 'task.attempts': { + lt: maxAttempts, + }, + }, }, - }, + ], }, - ], - }, + }, + { + bool: { + must: [ + { term: { 'task.taskType': 'bar' } }, + { + range: { + 'task.attempts': { + lt: customMaxAttempts, + }, + }, + }, + ], + }, + }, + ], }, - ], - }, + }, + ], }, - ], + }, }, }, + ], + filter: [ { bool: { - must: [ + must_not: [ { bool: { should: [ - { term: { 'task.status': 'idle' } }, - { term: { 'task.status': 'failed' } }, - ], - }, - }, - { - ids: { - values: [ - 'task:33c6977a-ed6d-43bd-98d9-3f827f7b7cd8', - 'task:a208b22c-14ec-4fb4-995f-d2ff7a3b03b8', + { term: { 'task.status': 'running' } }, + { term: { 'task.status': 'claiming' } }, ], }, }, + { range: { 'task.retryAt': { gt: 'now' } } }, ], }, }, @@ -662,34 +572,26 @@ describe('TaskStore', () => { }, }); - expect(sort).toMatchObject({ - _script: { - type: 'number', - order: 'asc', - script: { - lang: 'painless', - source: ` -if(params.ids.contains(doc['_id'].value)){ - return 0; -} - + expect(sort).toMatchObject([ + '_score', + { + _script: { + type: 'number', + order: 'asc', + script: { + lang: 'painless', + source: ` if (doc['task.retryAt'].size()!=0) { return doc['task.retryAt'].value.toInstant().toEpochMilli(); } if (doc['task.runAt'].size()!=0) { return doc['task.runAt'].value.toInstant().toEpochMilli(); } - -`, - params: { - ids: [ - 'task:33c6977a-ed6d-43bd-98d9-3f827f7b7cd8', - 'task:a208b22c-14ec-4fb4-995f-d2ff7a3b03b8', - ], + `, }, }, }, - }); + ]); }); test('it claims tasks by setting their ownerId, status and retryAt', async () => { diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index f4695b152237a..4f2e97704941f 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -36,22 +36,23 @@ import { asUpdateByQuery, shouldBeOneOf, mustBeAllOf, - ExistsBoolClause, - TermBoolClause, - RangeBoolClause, - BoolClause, - IDsClause, + filterDownBy, + ExistsFilter, + TermFilter, + RangeFilter, + asPinnedQuery, + matchesClauses, } from './queries/query_clauses'; import { updateFields, IdleTaskWithExpiredRunAt, + InactiveTasks, RunningOrClaimingTaskWithExpiredRetryAt, TaskWithSchedule, taskWithLessThanMaxAttempts, SortByRunAtAndRetryAt, - taskWithIDsAndRunnableStatus, - sortByIdsThenByScheduling, + tasksClaimedByOwner, } from './queries/mark_available_tasks_as_claimed'; export interface StoreOpts { @@ -65,18 +66,13 @@ export interface StoreOpts { } export interface SearchOpts { - searchAfter?: any[]; - sort?: object | object[]; + sort?: string | object | object[]; query?: object; size?: number; seq_no_primary_term?: boolean; search_after?: any[]; } -export interface FetchOpts extends SearchOpts { - sort?: object[]; -} - export interface UpdateByQuerySearchOpts extends SearchOpts { script?: object; } @@ -92,7 +88,6 @@ export interface OwnershipClaimingOpts { } export interface FetchResult { - searchAfter: any[]; docs: ConcreteTaskInstance[]; } @@ -152,8 +147,8 @@ export class TaskStore { return this.events$; } - private emitEvent = (event: TaskClaim) => { - this.events$.next(event); + private emitEvents = (events: TaskClaim[]) => { + events.forEach(event => this.events$.next(event)); }; /** @@ -180,16 +175,16 @@ export class TaskStore { } /** - * Fetches a paginatable list of scheduled tasks. + * Fetches a list of scheduled tasks with default sorting. * * @param opts - The query options used to filter tasks */ - public async fetch(opts: FetchOpts = {}): Promise { - const sort = paginatableSort(opts.sort); + public async fetch({ sort = [{ 'task.runAt': 'asc' }], ...opts }: SearchOpts = {}): Promise< + FetchResult + > { return this.search({ + ...opts, sort, - search_after: opts.searchAfter, - query: opts.query, }); } @@ -211,28 +206,30 @@ export class TaskStore { this.serializer.generateRawId(undefined, 'task', id) ); - const claimedTasks = await this.markAvailableTasksAsClaimed( + const numberOfTasksClaimed = await this.markAvailableTasksAsClaimed( claimOwnershipUntil, claimTasksByIdWithRawIds, size ); const docs = - claimedTasks > 0 ? await this.sweepForClaimedTasks(claimTasksByIdWithRawIds, size) : []; + numberOfTasksClaimed > 0 + ? await this.sweepForClaimedTasks(claimTasksByIdWithRawIds, size) + : []; // emit success/fail events for claimed tasks by id if (claimTasksById && claimTasksById.length) { - docs.map(doc => asTaskClaimEvent(doc.id, asOk(doc))).forEach(this.emitEvent); + this.emitEvents(docs.map(doc => asTaskClaimEvent(doc.id, asOk(doc)))); - difference( - claimTasksById, - docs.map(doc => doc.id) - ) - .map(id => asTaskClaimEvent(id, asErr(new Error(`failed to claim task '${id}'`)))) - .forEach(this.emitEvent); + this.emitEvents( + difference( + claimTasksById, + docs.map(doc => doc.id) + ).map(id => asTaskClaimEvent(id, asErr(new Error(`failed to claim task '${id}'`)))) + ); } return { - claimedTasks, + claimedTasks: numberOfTasksClaimed, docs, }; }; @@ -247,7 +244,7 @@ export class TaskStore { // status running or claiming with a retryAt <= now. shouldBeOneOf(IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt), // Either task has a schedule or the attempts < the maximum configured - shouldBeOneOf( + shouldBeOneOf( TaskWithSchedule, ...Object.entries(this.definitions).map(([type, { maxAttempts }]) => taskWithLessThanMaxAttempts(type, maxAttempts || this.maxAttempts) @@ -255,36 +252,33 @@ export class TaskStore { ) ); - const { query, sort } = - claimTasksById && claimTasksById.length - ? { - query: shouldBeOneOf< - | ExistsBoolClause - | TermBoolClause - | RangeBoolClause - | BoolClause - >(queryForScheduledTasks, taskWithIDsAndRunnableStatus(claimTasksById)), - sort: sortByIdsThenByScheduling(claimTasksById), - } - : { - query: queryForScheduledTasks, - sort: SortByRunAtAndRetryAt, - }; - const { updated } = await this.updateByQuery( asUpdateByQuery({ - query, + query: matchesClauses( + mustBeAllOf( + claimTasksById && claimTasksById.length + ? asPinnedQuery(claimTasksById, queryForScheduledTasks) + : queryForScheduledTasks + ), + filterDownBy(InactiveTasks) + ), update: updateFields({ ownerId: this.taskManagerId, status: 'claiming', retryAt: claimOwnershipUntil, }), - sort, + sort: [ + // sort by score first, so the "pinned" Tasks are first + '_score', + // the nsort by other fields + SortByRunAtAndRetryAt, + ], }), { max_docs: size, } ); + return updated; } @@ -295,24 +289,14 @@ export class TaskStore { claimTasksById: OwnershipClaimingOpts['claimTasksById'], size: OwnershipClaimingOpts['size'] ): Promise { + const claimedTasksQuery = tasksClaimedByOwner(this.taskManagerId); const { docs } = await this.search({ - query: { - bool: { - must: [ - { - term: { - 'task.ownerId': this.taskManagerId, - }, - }, - { term: { 'task.status': 'claiming' } }, - ], - }, - }, - size, - sort: + query: claimTasksById && claimTasksById.length - ? sortByIdsThenByScheduling(claimTasksById) - : SortByRunAtAndRetryAt, + ? asPinnedQuery(claimTasksById, claimedTasksQuery) + : claimedTasksQuery, + size, + sort: SortByRunAtAndRetryAt, seq_no_primary_term: true, }); @@ -397,7 +381,6 @@ export class TaskStore { .map(doc => this.serializer.rawToSavedObject(doc)) .map(doc => omit(doc, 'namespace') as SavedObject) .map(savedObjectToConcreteTaskInstance), - searchAfter: (rawDocs.length && rawDocs[rawDocs.length - 1].sort) || [], }; } @@ -427,20 +410,6 @@ export class TaskStore { } } -function paginatableSort(sort: any[] = []) { - const sortById = { _id: 'desc' }; - - if (!sort.length) { - return [{ 'task.runAt': 'asc' }, sortById]; - } - - if (sort.find(({ _id }) => !!_id)) { - return sort; - } - - return [...sort, sortById]; -} - function taskInstanceToAttributes(doc: TaskInstance): SavedObjectAttributes { return { ...omit(doc, 'id', 'version'), diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8ec3d605f4fba..ce3edbbb59828 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -503,15 +503,7 @@ "common.ui.vis.editors.aggGroups.bucketsText": "バケット", "common.ui.vis.editors.aggGroups.metricsText": "メトリック", "common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage": "「{schema}」集約は他のバケットの前に実行する必要があります!", - "common.ui.vis.editors.resizeAriaLabels": "左右のキーでエディターのサイズを変更します", - "common.ui.vis.editors.sidebar.applyChangesAriaLabel": "ビジュアライゼーションを変更と共に更新します", - "common.ui.vis.editors.sidebar.applyChangesTooltip": "変更を適用", "common.ui.vis.editors.sidebar.autoApplyChangesAriaLabel": "変更されるごとにビジュアライゼーションを自動的に更新します", - "common.ui.vis.editors.sidebar.autoApplyChangesLabel": "自動適用", - "common.ui.vis.editors.sidebar.autoApplyChangesTooltip": "変更を自動適用", - "common.ui.vis.editors.sidebar.discardChangesAriaLabel": "ビジュアライゼーションをリセット", - "common.ui.vis.editors.sidebar.discardChangesTooltip": "変更を破棄", - "common.ui.vis.editors.sidebar.errorButtonAriaLabel": "ハイライトされたフィールドのエラーを解決する必要があります。", "common.ui.vis.editors.sidebar.errorButtonTooltip": "ハイライトされたフィールドのエラーを解決する必要があります。", "common.ui.vis.editors.sidebar.tabs.dataLabel": "データ", "common.ui.vis.editors.sidebar.tabs.optionsLabel": "オプション", @@ -530,7 +522,6 @@ "common.ui.vislib.colormaps.greysText": "グレー", "common.ui.vislib.colormaps.redsText": "赤", "common.ui.vislib.colormaps.yellowToRedText": "黄色から赤", - "common.ui.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr}).構成されている最高値は {max} です。", "common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "バウンドを取得できませんでした", "console.autocomplete.addMethodMetaText": "メソド", "console.consoleDisplayName": "コンソール", @@ -1636,7 +1627,6 @@ "kbn.embeddable.inspectorRequestDataTitle": "データ", "kbn.embeddable.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", "kbn.embeddable.search.displayName": "検索", - "kbn.embeddable.visualizations.displayName": "ビジュアライゼーション", "kbn.home.addData.addDataToKibanaDescription": "これらのソリューションで、データを作成済みのダッシュボードと監視システムへとすぐに変えることができます。", "kbn.home.addData.addDataToKibanaTitle": "Kibana にデータを追加", "kbn.home.addData.apm.addApmButtonLabel": "APM を追加", @@ -2440,8 +2430,6 @@ "kbn.visualize.badge.readOnly.text": "読み込み専用", "kbn.visualize.badge.readOnly.tooltip": "ビジュアライゼーションを保存できません", "kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "indexPattern または savedSearchId が必要です", - "kbn.visualize.disabledLabVisualizationMessage": "ラボビジュアライゼーションを表示するには、高度な設定でラボモードをオンにしてください。", - "kbn.visualize.disabledLabVisualizationTitle": "{title} はラボビジュアライゼーションです。", "kbn.visualize.editor.createBreadcrumb": "作成", "kbn.visualize.experimentalVisInfoText": "このビジュアライゼーションは実験的なものです。", "kbn.visualize.linkedToSearch.unlinkButtonTooltip": "保存された検索からリンクを解除するにはダブルクリックします", @@ -2481,7 +2469,6 @@ "kbn.visualize.newVisWizard.visTypeAliasTitle": "Kibanaアプリケーション", "kbn.visualize.pageHeading": "{chartName} {chartType} ビジュアライゼーション", "kbn.visualize.saveDialog.saveAndAddToDashboardButtonLabel": "保存してダッシュボードに追加", - "kbn.visualize.savedObjectName": "ビジュアライゼーション", "kbn.visualize.topNavMenu.openInspectorButtonAriaLabel": "ビジュアライゼーションのインスペクターを開く", "kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip": "このビジュアライゼーションはインスペクターをサポートしていません。", "kbn.visualize.topNavMenu.refreshButtonAriaLabel": "更新", @@ -2595,6 +2582,7 @@ "kbnVislibVisTypes.editors.heatmap.basicSettingsTitle": "基本設定", "kbnVislibVisTypes.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", "kbnVislibVisTypes.editors.heatmap.highlightLabel": "ハイライト範囲", + "kbnVislibVisTypes.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr}).構成されている最高値は {max} です。", "kbnVislibVisTypes.editors.heatmap.highlightLabelTooltip": "チャートのカーソルを当てた部分と凡例の対応するラベルをハイライトします。", "kbnVislibVisTypes.editors.pie.donutLabel": "ドーナッツ", "kbnVislibVisTypes.editors.pie.labelsSettingsTitle": "ラベル設定", @@ -3153,12 +3141,12 @@ "visTypeMetric.function.bucket.help": "バケットディメンションの構成です。", "visTypeMetric.function.colorMode.help": "色を変更するメトリックの部分", "visTypeMetric.function.colorRange.help": "別の色が適用される値のグループを指定する範囲オブジェクト。", - "visTypeMetric.function.colorScheme.help": "使用する配色", + "visTypeMetric.function.colorSchema.help": "使用する配色", "visTypeMetric.function.font.help": "フォント設定です。", "visTypeMetric.function.help": "メトリックビジュアライゼーション", "visTypeMetric.function.invertColors.help": "色範囲を反転します", "visTypeMetric.function.metric.help": "メトリックディメンションの構成です。", - "visTypeMetric.function.percentage.help": "パーセンテージモードでメトリックを表示します。colorRange を設定する必要があります。", + "visTypeMetric.function.percentageMode.help": "パーセンテージモードでメトリックを表示します。colorRange を設定する必要があります。", "visTypeMetric.function.showLabels.help": "メトリック値の下にラベルを表示します。", "visTypeMetric.function.subText.help": "メトリックの下に表示するカスタムテキスト", "visTypeMetric.function.useRanges.help": "有効な色範囲です。", @@ -3225,7 +3213,6 @@ "visTypeTimeseries.addDeleteButtons.deleteButtonDefaultTooltip": "削除", "visTypeTimeseries.addDeleteButtons.reEnableTooltip": "再度有効にする", "visTypeTimeseries.addDeleteButtons.temporarilyDisableTooltip": "一時的に無効にする", - "visTypeTimeseries.aggLookup.addPipelineAggDescription": "{label} (「+」ボタンでこのパイプライン集約を追加します)", "visTypeTimeseries.aggLookup.averageLabel": "平均", "visTypeTimeseries.aggLookup.calculationLabel": "計算", "visTypeTimeseries.aggLookup.cardinalityLabel": "基数", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f20de892f6d0d..dc72e83ebe3c3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -503,15 +503,7 @@ "common.ui.vis.editors.aggGroups.bucketsText": "存储桶", "common.ui.vis.editors.aggGroups.metricsText": "指标", "common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage": "“{schema}” 聚合必须在所有其他存储桶之前运行!", - "common.ui.vis.editors.resizeAriaLabels": "按向左/向右键以调整编辑器的大小", - "common.ui.vis.editors.sidebar.applyChangesAriaLabel": "使用您的更改更新可视化", - "common.ui.vis.editors.sidebar.applyChangesTooltip": "应用更改", "common.ui.vis.editors.sidebar.autoApplyChangesAriaLabel": "每次更改时自动更新可视化", - "common.ui.vis.editors.sidebar.autoApplyChangesLabel": "自动应用", - "common.ui.vis.editors.sidebar.autoApplyChangesTooltip": "自动应用更改", - "common.ui.vis.editors.sidebar.discardChangesAriaLabel": "重置可视化", - "common.ui.vis.editors.sidebar.discardChangesTooltip": "放弃更改", - "common.ui.vis.editors.sidebar.errorButtonAriaLabel": "需要解决突出显示的字段中的错误。", "common.ui.vis.editors.sidebar.errorButtonTooltip": "需要解决突出显示的字段中的错误。", "common.ui.vis.editors.sidebar.tabs.dataLabel": "数据", "common.ui.vis.editors.sidebar.tabs.optionsLabel": "选项", @@ -530,7 +522,6 @@ "common.ui.vislib.colormaps.greysText": "灰色", "common.ui.vislib.colormaps.redsText": "红色", "common.ui.vislib.colormaps.yellowToRedText": "黄到红", - "common.ui.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。", "common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "无法获取边界", "console.autocomplete.addMethodMetaText": "方法", "console.consoleDisplayName": "控制台", @@ -1636,7 +1627,6 @@ "kbn.embeddable.inspectorRequestDataTitle": "数据", "kbn.embeddable.inspectorRequestDescription": "此请求将查询 Elasticsearch 以获取搜索的数据。", "kbn.embeddable.search.displayName": "搜索", - "kbn.embeddable.visualizations.displayName": "可视化", "kbn.home.addData.addDataToKibanaDescription": "使用这些解决方案可快速将您的数据转换成预建仪表板和监测系统。", "kbn.home.addData.addDataToKibanaTitle": "将数据添加到 Kibana", "kbn.home.addData.apm.addApmButtonLabel": "添加 APM", @@ -2440,8 +2430,6 @@ "kbn.visualize.badge.readOnly.text": "只读", "kbn.visualize.badge.readOnly.tooltip": "无法保存可视化", "kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "必须提供 indexPattern 或 savedSearchId", - "kbn.visualize.disabledLabVisualizationMessage": "请在高级设置中打开实验室模式,以查看实验室可视化。", - "kbn.visualize.disabledLabVisualizationTitle": "{title} 为实验室可视化。", "kbn.visualize.editor.createBreadcrumb": "创建", "kbn.visualize.experimentalVisInfoText": "此可视化标记为“实验”。", "kbn.visualize.linkedToSearch.unlinkButtonTooltip": "双击可取消与“已保存搜索”的链接", @@ -2481,7 +2469,6 @@ "kbn.visualize.newVisWizard.visTypeAliasTitle": "Kibana 应用程序", "kbn.visualize.pageHeading": "{chartName} {chartType}可视化", "kbn.visualize.saveDialog.saveAndAddToDashboardButtonLabel": "保存并添加到仪表板", - "kbn.visualize.savedObjectName": "可视化", "kbn.visualize.topNavMenu.openInspectorButtonAriaLabel": "打开检查器查看可视化", "kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip": "此可视化不支持任何检查器。", "kbn.visualize.topNavMenu.refreshButtonAriaLabel": "刷新", @@ -2595,6 +2582,7 @@ "kbnVislibVisTypes.editors.heatmap.basicSettingsTitle": "基本设置", "kbnVislibVisTypes.editors.heatmap.heatmapSettingsTitle": "热图设置", "kbnVislibVisTypes.editors.heatmap.highlightLabel": "高亮范围", + "kbnVislibVisTypes.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。", "kbnVislibVisTypes.editors.heatmap.highlightLabelTooltip": "高亮显示图表中鼠标悬停的范围以及图例中对应的标签。", "kbnVislibVisTypes.editors.pie.donutLabel": "圆环图", "kbnVislibVisTypes.editors.pie.labelsSettingsTitle": "标签设置", @@ -3153,12 +3141,12 @@ "visTypeMetric.function.bucket.help": "存储桶维度配置", "visTypeMetric.function.colorMode.help": "指标的哪部分要上色", "visTypeMetric.function.colorRange.help": "指定应将不同颜色应用到的值组的范围对象。", - "visTypeMetric.function.colorScheme.help": "要使用的颜色方案", + "visTypeMetric.function.colorSchema.help": "要使用的颜色方案", "visTypeMetric.function.font.help": "字体设置。", "visTypeMetric.function.help": "指标可视化", "visTypeMetric.function.invertColors.help": "反转颜色范围", "visTypeMetric.function.metric.help": "指标维度配置", - "visTypeMetric.function.percentage.help": "以百分比模式显示指标。需要设置 colorRange。", + "visTypeMetric.function.percentageMode.help": "以百分比模式显示指标。需要设置 colorRange。", "visTypeMetric.function.showLabels.help": "在指标值下显示标签。", "visTypeMetric.function.subText.help": "要在指标下显示的定制文本", "visTypeMetric.function.useRanges.help": "已启用颜色范围。", @@ -3225,7 +3213,6 @@ "visTypeTimeseries.addDeleteButtons.deleteButtonDefaultTooltip": "删除", "visTypeTimeseries.addDeleteButtons.reEnableTooltip": "重新启用", "visTypeTimeseries.addDeleteButtons.temporarilyDisableTooltip": "暂时禁用", - "visTypeTimeseries.aggLookup.addPipelineAggDescription": "{label}(使用“+”按钮添加此管道聚合)", "visTypeTimeseries.aggLookup.averageLabel": "平均值", "visTypeTimeseries.aggLookup.calculationLabel": "计算", "visTypeTimeseries.aggLookup.cardinalityLabel": "基数", diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 8356954073ec9..f322945e236b8 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -18,17 +18,18 @@ interface CreateTestConfigOptions { // test.not-enabled is specifically not enabled const enabledActionTypes = [ - '.server-log', - '.slack', '.email', '.index', '.pagerduty', + '.server-log', + '.servicenow', + '.slack', '.webhook', - 'test.noop', - 'test.index-record', + 'test.authorization', 'test.failing', + 'test.index-record', + 'test.noop', 'test.rate-limit', - 'test.authorization', ]; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/index.ts index a5a9353d83cbc..02adae72db46f 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/index.ts @@ -6,16 +6,18 @@ import Hapi from 'hapi'; import { ActionType } from '../../../../../../legacy/plugins/actions'; +import { initPlugin as initPagerduty } from './pagerduty_simulation'; +import { initPlugin as initServiceNow } from './servicenow_simulation'; import { initPlugin as initSlack } from './slack_simulation'; import { initPlugin as initWebhook } from './webhook_simulation'; -import { initPlugin as initPagerduty } from './pagerduty_simulation'; const NAME = 'actions-FTS-external-service-simulators'; export enum ExternalServiceSimulator { + PAGERDUTY = 'pagerduty', + SERVICENOW = 'servicenow', SLACK = 'slack', WEBHOOK = 'webhook', - PAGERDUTY = 'pagerduty', } export function getExternalServiceSimulatorPath(service: ExternalServiceSimulator): string { @@ -23,9 +25,11 @@ export function getExternalServiceSimulatorPath(service: ExternalServiceSimulato } export function getAllExternalServiceSimulatorPaths(): string[] { - return Object.values(ExternalServiceSimulator).map(service => + const allPaths = Object.values(ExternalServiceSimulator).map(service => getExternalServiceSimulatorPath(service) ); + allPaths.push(`/api/_${NAME}/${ExternalServiceSimulator.SERVICENOW}/api/now/v1/table/incident`); + return allPaths; } // eslint-disable-next-line import/no-default-export @@ -67,9 +71,10 @@ export default function(kibana: any) { }, }); + initPagerduty(server, getExternalServiceSimulatorPath(ExternalServiceSimulator.PAGERDUTY)); + initServiceNow(server, getExternalServiceSimulatorPath(ExternalServiceSimulator.SERVICENOW)); initSlack(server, getExternalServiceSimulatorPath(ExternalServiceSimulator.SLACK)); initWebhook(server, getExternalServiceSimulatorPath(ExternalServiceSimulator.WEBHOOK)); - initPagerduty(server, getExternalServiceSimulatorPath(ExternalServiceSimulator.PAGERDUTY)); }, }); } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/servicenow_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/servicenow_simulation.ts new file mode 100644 index 0000000000000..f215b63560339 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions/servicenow_simulation.ts @@ -0,0 +1,120 @@ +/* + * 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 Joi from 'joi'; +import Hapi from 'hapi'; + +interface ServiceNowRequest extends Hapi.Request { + payload: { + comments: string; + short_description: string; + }; +} +export function initPlugin(server: Hapi.Server, path: string) { + server.route({ + method: 'POST', + path, + options: { + auth: false, + validate: { + options: { abortEarly: false }, + payload: Joi.object().keys({ + comments: Joi.string(), + short_description: Joi.string(), + }), + }, + }, + handler: servicenowHandler, + }); + + server.route({ + method: 'POST', + path: `${path}/api/now/v1/table/incident`, + options: { + auth: false, + validate: { + options: { abortEarly: false }, + payload: Joi.object().keys({ + comments: Joi.string(), + short_description: Joi.string(), + }), + }, + }, + handler: servicenowHandler, + }); +} +// ServiceNow simulator: create a servicenow action pointing here, and you can get +// different responses based on the message posted. See the README.md for +// more info. + +function servicenowHandler(request: ServiceNowRequest, h: any) { + const body = request.payload; + const text = body && body.short_description; + if (text == null) { + return jsonResponse(h, 400, 'bad request to servicenow simulator'); + } + + switch (text) { + case 'success': + return jsonResponse(h, 200, 'Success'); + + case 'created': + return jsonResponse(h, 201, 'Created'); + + case 'no_text': + return jsonResponse(h, 204, 'Success'); + + case 'invalid_payload': + return jsonResponse(h, 400, 'Bad Request'); + + case 'unauthorized': + return jsonResponse(h, 401, 'Unauthorized'); + + case 'forbidden': + return jsonResponse(h, 403, 'Forbidden'); + + case 'not_found': + return jsonResponse(h, 404, 'Not found'); + + case 'not_allowed': + return jsonResponse(h, 405, 'Method not allowed'); + + case 'not_acceptable': + return jsonResponse(h, 406, 'Not acceptable'); + + case 'unsupported': + return jsonResponse(h, 415, 'Unsupported media type'); + + case 'status_500': + return jsonResponse(h, 500, 'simulated servicenow 500 response'); + + case 'rate_limit': + const response = { + retry_after: 1, + ok: false, + error: 'rate_limited', + }; + + return h + .response(response) + .type('application/json') + .header('retry-after', '1') + .code(429); + } + + return jsonResponse(h, 400, 'unknown request to servicenow simulator'); +} + +function jsonResponse(h: any, code: number, object?: any) { + if (object == null) { + return h.response('').code(code); + } + + return h + .response(JSON.stringify(object)) + .type('application/json') + .code(code); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts new file mode 100644 index 0000000000000..15662649266ae --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts @@ -0,0 +1,271 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +import { + getExternalServiceSimulatorPath, + ExternalServiceSimulator, +} from '../../../../common/fixtures/plugins/actions'; + +// node ../scripts/functional_test_runner.js --grep "Actions.servicenddd" --config=test/alerting_api_integration/security_and_spaces/config.ts + +// eslint-disable-next-line import/no-default-export +export default function servicenowTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const mockServiceNow = { + config: { + apiUrl: 'www.servicenowisinkibanaactions.com', + }, + secrets: { + password: 'elastic', + username: 'changeme', + }, + params: { + comments: 'hello cool service now incident', + short_description: 'this is a cool service now incident', + }, + }; + describe('servicenow', () => { + let simulatedActionId = ''; + let servicenowSimulatorURL: string = ''; + + // need to wait for kibanaServer to settle ... + before(() => { + servicenowSimulatorURL = kibanaServer.resolveUrl( + getExternalServiceSimulatorPath(ExternalServiceSimulator.SERVICENOW) + ); + }); + + after(() => esArchiver.unload('empty_kibana')); + + it('should return 200 when creating a servicenow action successfully', async () => { + const { body: createdAction } = await supertest + .post('/api/action') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A servicenow action', + actionTypeId: '.servicenow', + config: { + apiUrl: servicenowSimulatorURL, + }, + secrets: mockServiceNow.secrets, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + name: 'A servicenow action', + actionTypeId: '.servicenow', + config: { + apiUrl: servicenowSimulatorURL, + }, + }); + + expect(typeof createdAction.id).to.be('string'); + + const { body: fetchedAction } = await supertest + .get(`/api/action/${createdAction.id}`) + .expect(200); + + expect(fetchedAction).to.eql({ + id: fetchedAction.id, + name: 'A servicenow action', + actionTypeId: '.servicenow', + config: { + apiUrl: servicenowSimulatorURL, + }, + }); + }); + + it('should respond with a 400 Bad Request when creating a servicenow action with no webhookUrl', async () => { + await supertest + .post('/api/action') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A servicenow action', + actionTypeId: '.servicenow', + config: {}, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type config: [apiUrl]: expected value of type [string] but got [undefined]', + }); + }); + }); + + it('should respond with a 400 Bad Request when creating a servicenow action with a non whitelisted webhookUrl', async () => { + await supertest + .post('/api/action') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A servicenow action', + actionTypeId: '.servicenow', + config: { + apiUrl: 'http://servicenow.mynonexistent.com', + }, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type config: error configuring servicenow action: target url "http://servicenow.mynonexistent.com" is not whitelisted in the Kibana config xpack.actions.whitelistedHosts', + }); + }); + }); + + it('should respond with a 400 Bad Request when creating a servicenow action without secrets', async () => { + await supertest + .post('/api/action') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A servicenow action', + actionTypeId: '.servicenow', + config: { + apiUrl: servicenowSimulatorURL, + }, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type secrets: [password]: expected value of type [string] but got [undefined]', + }); + }); + }); + + it('should create our servicenow simulator action successfully', async () => { + const { body: createdSimulatedAction } = await supertest + .post('/api/action') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A servicenow simulator', + actionTypeId: '.servicenow', + config: { + apiUrl: servicenowSimulatorURL, + }, + secrets: mockServiceNow.secrets, + }) + .expect(200); + + simulatedActionId = createdSimulatedAction.id; + }); + + it('should handle executing with a simulated success', async () => { + const { body: result } = await supertest + .post(`/api/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + comments: 'success', + short_description: 'success', + }, + }) + .expect(200); + + expect(result.status).to.eql('ok'); + }); + + it('should handle executing with a simulated success without comments', async () => { + const { body: result } = await supertest + .post(`/api/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + short_description: 'success', + }, + }) + .expect(200); + + expect(result.status).to.eql('ok'); + }); + + it('should handle failing with a simulated success without short_description', async () => { + await supertest + .post(`/api/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + comments: 'success', + }, + }) + .then((resp: any) => { + expect(resp.body).to.eql({ + actionId: simulatedActionId, + status: 'error', + retry: false, + message: + 'error validating action params: [short_description]: expected value of type [string] but got [undefined]', + }); + }); + }); + + it('should handle a 40x servicenow error', async () => { + const { body: result } = await supertest + .post(`/api/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + comments: 'invalid_payload', + short_description: 'invalid_payload', + }, + }) + .expect(200); + expect(result.status).to.equal('error'); + expect(result.message).to.match(/error posting servicenow event: unexpected status 400/); + }); + + it('should handle a 429 servicenow error', async () => { + const { body: result } = await supertest + .post(`/api/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + comments: 'rate_limit', + short_description: 'rate_limit', + }, + }) + .expect(200); + + expect(result.status).to.equal('error'); + expect(result.message).to.equal( + 'error posting servicenow event: http status 429, retry later' + ); + expect(result.retry).to.equal(true); + }); + + it('should handle a 500 servicenow error', async () => { + const { body: result } = await supertest + .post(`/api/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + comments: 'status_500', + short_description: 'status_500', + }, + }) + .expect(200); + + expect(result.status).to.equal('error'); + expect(result.message).to.equal( + 'error posting servicenow event: http status 500, retry later' + ); + expect(result.retry).to.equal(true); + }); + }); +} 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..c6960a4eedd25 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 @@ -9,18 +9,19 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function actionsTests({ loadTestFile }: FtrProviderContext) { describe('Actions', () => { + 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/server_log')); + loadTestFile(require.resolve('./builtin_action_types/servicenow')); + loadTestFile(require.resolve('./builtin_action_types/slack')); + loadTestFile(require.resolve('./builtin_action_types/webhook')); loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); + loadTestFile(require.resolve('./execute')); loadTestFile(require.resolve('./find')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./list_action_types')); loadTestFile(require.resolve('./update')); - loadTestFile(require.resolve('./execute')); - loadTestFile(require.resolve('./builtin_action_types/server_log')); - loadTestFile(require.resolve('./builtin_action_types/slack')); - 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/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index d20450f8ec47e..551498e22d5c8 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -761,8 +761,7 @@ export default function alertTests({ getService }: FtrProviderContext) { } }); - // Flaky: https://github.com/elastic/kibana/issues/54125 - it.skip(`should unmute all instances when unmuting an alert`, async () => { + it(`should unmute all instances when unmuting an alert`, async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await alertUtils.createAlwaysFiringAction({ diff --git a/x-pack/test/api_integration/apis/siem/network_dns.ts b/x-pack/test/api_integration/apis/siem/network_dns.ts index 5de7ea3a67087..13e98f09d072b 100644 --- a/x-pack/test/api_integration/apis/siem/network_dns.ts +++ b/x-pack/test/api_integration/apis/siem/network_dns.ts @@ -30,7 +30,6 @@ export default function({ getService }: FtrProviderContext) { query: networkDnsQuery, variables: { defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - isDNSHistogram: false, inspect: false, isPtrIncluded: false, pagination: { @@ -66,7 +65,7 @@ export default function({ getService }: FtrProviderContext) { query: networkDnsQuery, variables: { defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - isDNSHistogram: false, + isDnsHistogram: false, inspect: false, isPtrIncluded: false, pagination: { diff --git a/x-pack/test/functional/apps/lens/lens_reporting.ts b/x-pack/test/functional/apps/lens/lens_reporting.ts index 3d70390a2de41..2e3e630680ff0 100644 --- a/x-pack/test/functional/apps/lens/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/lens_reporting.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'dashboard', 'reporting']); - const find = getService('find'); const esArchiver = getService('esArchiver'); const listingTable = getService('listingTable'); @@ -28,8 +27,9 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await listingTable.clickItemLink('dashboard', 'Lens reportz'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); + const url = await PageObjects.reporting.getReportURL(60000); - expect(await find.byButtonText('Download report', undefined, 60000)).to.be.ok(); + expect(url).to.be.ok(); }); }); } diff --git a/x-pack/test/functional/page_objects/reporting_page.js b/x-pack/test/functional/page_objects/reporting_page.js index acbc1dd100e5d..7cdd1c083239b 100644 --- a/x-pack/test/functional/page_objects/reporting_page.js +++ b/x-pack/test/functional/page_objects/reporting_page.js @@ -37,34 +37,6 @@ export function ReportingPageProvider({ getService, getPageObjects }) { await browser.setWindowSize(1600, 850); } - async getUrlOfTab(tabIndex) { - return await retry.try(async () => { - log.debug(`reportingPage.getUrlOfTab(${tabIndex}`); - const handles = await browser.getAllWindowHandles(); - log.debug(`Switching to window ${handles[tabIndex]}`); - await browser.switchToWindow(handles[tabIndex]); - - const url = await browser.getCurrentUrl(); - if (!url || url === 'about:blank') { - throw new Error('url is blank'); - } - - await browser.switchToWindow(handles[0]); - return url; - }); - } - - async closeTab(tabIndex) { - return await retry.try(async () => { - log.debug(`reportingPage.closeTab(${tabIndex}`); - const handles = await browser.getAllWindowHandles(); - log.debug(`Switching to window ${handles[tabIndex]}`); - await browser.switchToWindow(handles[tabIndex]); - await browser.closeCurrentWindow(); - await browser.switchToWindow(handles[0]); - }); - } - async forceSharedItemsContainerSize({ width }) { await browser.execute(` var el = document.querySelector('[data-shared-items-container]'); @@ -73,6 +45,16 @@ export function ReportingPageProvider({ getService, getPageObjects }) { `); } + async getReportURL(timeout) { + log.debug('getReportURL'); + + const url = await testSubjects.getAttribute('downloadCompletedReportButton', 'href', timeout); + + log.debug(`getReportURL got url: ${url}`); + + return url; + } + async removeForceSharedItemsContainerSize() { await browser.execute(` var el = document.querySelector('[data-shared-items-container]'); @@ -81,9 +63,8 @@ export function ReportingPageProvider({ getService, getPageObjects }) { `); } - getRawPdfReportData(url) { - log.debug(`getRawPdfReportData for ${url}`); - let data = []; // List of Buffer objects + getResponse(url) { + log.debug(`getResponse for ${url}`); const auth = config.get('servers.elasticsearch.auth'); const headers = { Authorization: `Basic ${Buffer.from(auth).toString('base64')}`, @@ -100,13 +81,7 @@ export function ReportingPageProvider({ getService, getPageObjects }) { headers, }, res => { - res.on('data', function(chunk) { - data.push(chunk); - }); - res.on('end', function() { - data = Buffer.concat(data); - resolve(data); - }); + resolve(res); } ) .on('error', e => { @@ -115,6 +90,18 @@ export function ReportingPageProvider({ getService, getPageObjects }) { }); } + async getRawPdfReportData(url) { + const data = []; // List of Buffer objects + log.debug(`getRawPdfReportData for ${url}`); + + return new Promise(async (resolve, reject) => { + const response = await this.getResponse(url).catch(reject); + + response.on('data', chunk => data.push(chunk)); + response.on('end', () => resolve(Buffer.concat(data))); + }); + } + async openCsvReportingPanel() { log.debug('openCsvReportingPanel'); await PageObjects.share.openShareMenuItem('CSV Reports'); @@ -130,10 +117,6 @@ export function ReportingPageProvider({ getService, getPageObjects }) { await PageObjects.share.openShareMenuItem('PNG Reports'); } - async clickDownloadReportButton(timeout) { - await testSubjects.click('downloadCompletedReportButton', timeout); - } - async clearToastNotifications() { const toasts = await testSubjects.findAll('toastCloseButton'); await Promise.all(toasts.map(async t => await t.click())); @@ -175,7 +158,9 @@ export function ReportingPageProvider({ getService, getPageObjects }) { async setTimepickerInDataRange() { log.debug('Reporting:setTimepickerInDataRange'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); + const fromTime = 'Sep 19, 2015 @ 06:31:44.000'; + const toTime = 'Sep 19, 2015 @ 18:01:44.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); } async setTimepickerInNoDataRange() { diff --git a/x-pack/test/plugin_api_integration/plugins/task_manager/index.js b/x-pack/test/plugin_api_integration/plugins/task_manager/index.js index 50fb9571c2687..e5b645367b8b7 100644 --- a/x-pack/test/plugin_api_integration/plugins/task_manager/index.js +++ b/x-pack/test/plugin_api_integration/plugins/task_manager/index.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +const { DEFAULT_MAX_WORKERS } = require('../../../../plugins/task_manager/server/config.ts'); const { EventEmitter } = require('events'); import { initRoutes } from './init_routes'; @@ -16,6 +17,7 @@ const once = function(emitter, event) { export default function TaskTestingAPI(kibana) { const taskTestingEvents = new EventEmitter(); + taskTestingEvents.setMaxListeners(DEFAULT_MAX_WORKERS * 2); return new kibana.Plugin({ name: 'sampleTask', diff --git a/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js b/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js index c0dcd99525915..9e818f050c929 100644 --- a/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js +++ b/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js @@ -193,15 +193,44 @@ export function initRoutes(server, taskManager, legacyTaskManager, taskTestingEv }, }); + server.route({ + path: '/api/sample_tasks/task/{taskId}', + method: 'GET', + async handler(request) { + try { + return taskManager.fetch({ + query: { + bool: { + must: [ + { + ids: { + values: [`task:${request.params.taskId}`], + }, + }, + ], + }, + }, + }); + } catch (err) { + return err; + } + }, + }); + server.route({ path: '/api/sample_tasks', method: 'DELETE', async handler() { try { - const { docs: tasks } = await taskManager.fetch({ - query: taskManagerQuery, - }); - return Promise.all(tasks.map(task => taskManager.remove(task.id))); + let tasksFound = 0; + do { + const { docs: tasks } = await taskManager.fetch({ + query: taskManagerQuery, + }); + tasksFound = tasks.length; + await Promise.all(tasks.map(task => taskManager.remove(task.id))); + } while (tasksFound > 0); + return 'OK'; } catch (err) { return err; } diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js index 0b1c1cbb5af29..7ec0e9b5efa5b 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js @@ -13,6 +13,13 @@ const { task: { properties: taskManagerIndexMapping }, } = require('../../../../legacy/plugins/task_manager/server/mappings.json'); +const { + DEFAULT_MAX_WORKERS, + DEFAULT_POLL_INTERVAL, +} = require('../../../../plugins/task_manager/server/config.ts'); + +const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + export default function({ getService }) { const es = getService('legacyEs'); const log = getService('log'); @@ -22,11 +29,12 @@ export default function({ getService }) { const supertest = supertestAsPromised(url.format(config.get('servers.kibana'))); describe('scheduling and running tasks', () => { - beforeEach(() => - supertest - .delete('/api/sample_tasks') - .set('kbn-xsrf', 'xxx') - .expect(200) + beforeEach( + async () => + await supertest + .delete('/api/sample_tasks') + .set('kbn-xsrf', 'xxx') + .expect(200) ); beforeEach(async () => { @@ -56,11 +64,19 @@ export default function({ getService }) { .then(response => response.body); } - function historyDocs() { + function currentTask(task) { + return supertest + .get(`/api/sample_tasks/task/${task}`) + .send({ task }) + .expect(200) + .then(response => response.body.docs[0]); + } + + function historyDocs(taskId) { return es .search({ index: testHistoryIndex, - q: 'type:task', + q: taskId ? `taskId:${taskId}` : 'type:task', }) .then(result => result.hits.hits); } @@ -223,7 +239,7 @@ export default function({ getService }) { }); await retry.try(async () => { - expect((await historyDocs()).length).to.eql(1); + expect((await historyDocs(originalTask.id)).length).to.eql(1); const [task] = (await currentTasks()).docs; expect(task.attempts).to.eql(0); @@ -318,6 +334,81 @@ export default function({ getService }) { }); }); + it('should prioritize tasks which are called using runNow', async () => { + const originalTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `30m` }, + params: {}, + }); + + await retry.try(async () => { + const docs = await historyDocs(originalTask.id); + expect(docs.length).to.eql(1); + + const task = await currentTask(originalTask.id); + + expect(task.state.count).to.eql(1); + + // ensure this task shouldnt run for another half hour + expectReschedule(Date.parse(originalTask.runAt), task, 30 * 60000); + }); + + const taskToBeReleased = await scheduleTask({ + taskType: 'sampleTask', + params: { waitForEvent: 'releaseSingleTask' }, + }); + + await retry.try(async () => { + // wait for taskToBeReleased to stall + expect((await historyDocs(taskToBeReleased.id)).length).to.eql(1); + }); + + // schedule multiple tasks that should force + // Task Manager to use up its worker capacity + // causing tasks to pile up + await Promise.all( + _.times(DEFAULT_MAX_WORKERS + _.random(1, DEFAULT_MAX_WORKERS), () => + scheduleTask({ + taskType: 'sampleTask', + params: { + waitForEvent: 'releaseTheOthers', + }, + }) + ) + ); + + // we need to ensure that TM has a chance to fill its queue with the stalling tasks + await delay(DEFAULT_POLL_INTERVAL); + + // call runNow for our task + const runNowResult = runTaskNow({ + id: originalTask.id, + }); + + // we need to ensure that TM has a chance to push the runNow task into the queue + // before we release the stalled task, so lets give it a chance + await delay(DEFAULT_POLL_INTERVAL); + + // and release only one slot in our worker queue + await releaseTasksWaitingForEventToComplete('releaseSingleTask'); + + expect(await runNowResult).to.eql({ id: originalTask.id }); + + await retry.try(async () => { + const task = await currentTask(originalTask.id); + expect(task.state.count).to.eql(2); + }); + + // drain tasks, othrwise they'll keep Task Manager stalled + await retry.try(async () => { + await releaseTasksWaitingForEventToComplete('releaseTheOthers'); + const tasks = (await currentTasks()).docs.filter( + task => task.params.originalParams.waitForEvent === 'releaseTheOthers' + ); + expect(tasks.length).to.eql(0); + }); + }); + it('should return a task run error result when running a task now fails', async () => { const originalTask = await scheduleTask({ taskType: 'sampleTask', @@ -329,10 +420,7 @@ export default function({ getService }) { const docs = await historyDocs(); expect(docs.filter(taskDoc => taskDoc._source.taskId === originalTask.id).length).to.eql(1); - const [task] = (await currentTasks()).docs.filter( - taskDoc => taskDoc.id === originalTask.id - ); - + const task = await currentTask(originalTask.id); expect(task.state.count).to.eql(1); // ensure this task shouldnt run for another half hour @@ -364,9 +452,7 @@ export default function({ getService }) { (await historyDocs()).filter(taskDoc => taskDoc._source.taskId === originalTask.id).length ).to.eql(2); - const [task] = (await currentTasks()).docs.filter( - taskDoc => taskDoc.id === originalTask.id - ); + const task = await currentTask(originalTask.id); expect(task.attempts).to.eql(1); }); }); diff --git a/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx b/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx index 55fd436de40a1..498aff62d40d4 100644 --- a/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx +++ b/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx @@ -13,7 +13,7 @@ import { useEffect } from 'react'; import styled from 'styled-components'; /** - * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. + * Render the Resolver Test app. Returns a cleanup function. */ export function renderApp( { element }: AppMountParameters, diff --git a/x-pack/test/plugin_functional/plugins/resolver_test/public/plugin.ts b/x-pack/test/plugin_functional/plugins/resolver_test/public/plugin.ts index 9252825ea1107..045cc56238ac9 100644 --- a/x-pack/test/plugin_functional/plugins/resolver_test/public/plugin.ts +++ b/x-pack/test/plugin_functional/plugins/resolver_test/public/plugin.ts @@ -23,43 +23,35 @@ export class ResolverTestPlugin ResolverTestPluginSetupDependencies, ResolverTestPluginStartDependencies > { - private resolveEmbeddable!: ( - value: IEmbeddable | undefined | PromiseLike | undefined - ) => void; - /** - * We register our application during the `setup` phase, but the embeddable - * plugin API is not available until the `start` phase. In order to access - * the embeddable API from our application, we pass a Promise to the application - * which we resolve during the `start` phase. - */ - private embeddablePromise: Promise = new Promise< - IEmbeddable | undefined - >(resolve => { - this.resolveEmbeddable = resolve; - }); - public setup(core: CoreSetup) { + public setup(core: CoreSetup) { core.application.register({ id: 'resolver_test', title: i18n.translate('xpack.resolver_test.pluginTitle', { defaultMessage: 'Resolver Test', }), mount: async (_context, params) => { + let resolveEmbeddable: ( + value: IEmbeddable | undefined | PromiseLike | undefined + ) => void; + + const promise = new Promise(resolve => { + resolveEmbeddable = resolve; + }); + + (async () => { + const [, { embeddable }] = await core.getStartServices(); + const factory = embeddable.getEmbeddableFactory('resolver'); + resolveEmbeddable!(factory.create({ id: 'test basic render' })); + })(); + const { renderApp } = await import('./applications/resolver_test'); /** * Pass a promise which resolves to the Resolver embeddable. */ - return renderApp(params, this.embeddablePromise); + return renderApp(params, promise); }, }); } - public start(...args: [unknown, { embeddable: IEmbeddableStart }]) { - const [, plugins] = args; - const factory = plugins.embeddable.getEmbeddableFactory('resolver'); - /** - * Provide the Resolver embeddable to the application - */ - this.resolveEmbeddable(factory.create({ id: 'test basic render' })); - } - public stop() {} + public start() {} } diff --git a/x-pack/test/reporting/README.md b/x-pack/test/reporting/README.md index d4a6c20a835e2..fc5147ad8c454 100644 --- a/x-pack/test/reporting/README.md +++ b/x-pack/test/reporting/README.md @@ -9,8 +9,8 @@ Reporting tests have their own top level test folder because: ### Running the tests There is more information on running x-pack tests here: https://github.com/elastic/kibana/blob/master/x-pack/README.md#running-functional-tests. Similar to running the API tests, you need to specify a reporting configuration file. Reporting currently has two configuration files you can point to: - - test/reporting/configs/chromium_api.js - - test/reporting/configs/chromium_functional.js + - test/reporting/configs/chromium_api.js + - test/reporting/configs/chromium_functional.js The `api` versions hit the reporting api and ensure report generation completes successfully, but does not verify the output of the reports. This is done in the `functional` test versions, which does a snapshot comparison of the generated URL against a baseline to determine success. @@ -33,10 +33,6 @@ node scripts/functional_tests_server.js --config test/reporting/configs/[test_co node ../scripts/functional_test_runner.js --config test/reporting/configs/[test_config_name_here].js ``` -**Prerequisites** -The reporting functional tests use [pdf-image](https://www.npmjs.com/package/pdf-image) to convert PDF's pages to png files for image comparisons between generated reports and baseline reports. -pdf-image requires the system commands `convert`, `gs`, and `pdfinfo` to function. Those can be set up by running the following. - ```sh //OSX brew install imagemagick ghostscript poppler @@ -99,7 +95,7 @@ node ../scripts/es_archiver.js --es-url http://elastic:changeme@localhost:9200 l 7. Generate some reporting URLs - Use a mixture of Visualize, Discover (CSV), Dashboard - Can view the current test coverage by checkout out [api/generation_urls.js](https://github.com/elastic/kibana/blob/master/x-pack/test/reporting/api/generation_urls.js). You can use different ones for better test coverage (e.g. different dashboards, different visualizations). - - Don’t generate urls from huge dashboards since this is time consuming. + - Don’t generate urls from huge dashboards since this is time consuming. - Use dashboards that have time saved with them if you wish to have data included. 8. Save these reporting urls. 9. Navigate back to the main branch via `git checkout master`. Then create, or work off your branch as usual to add the extra test coverage. diff --git a/x-pack/test/reporting/configs/chromium_functional.js b/x-pack/test/reporting/configs/chromium_functional.js index 22640e858cc6c..81a51f44c1c5f 100644 --- a/x-pack/test/reporting/configs/chromium_functional.js +++ b/x-pack/test/reporting/configs/chromium_functional.js @@ -25,6 +25,8 @@ export default async function({ readConfigFile }) { '["info","warning","error","fatal","optimize","reporting"]', '--xpack.endpoint.enabled=true', '--xpack.reporting.csv.enablePanelActionDownload=true', + '--xpack.reporting.csv.checkForFormulas=false', + '--xpack.reporting.csv.maxSizeBytes=25000000', '--xpack.security.session.idleTimeout=3600000', '--xpack.spaces.enabled=false', ], diff --git a/x-pack/test/reporting/functional/lib/compare_pdfs.js b/x-pack/test/reporting/functional/lib/compare_pdfs.js deleted file mode 100644 index 56e57b5564694..0000000000000 --- a/x-pack/test/reporting/functional/lib/compare_pdfs.js +++ /dev/null @@ -1,85 +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 path from 'path'; -import fs from 'fs'; -import { promisify } from 'util'; -import { PDFImage } from 'pdf-image'; -import PDFJS from 'pdfjs-dist'; -import { comparePngs } from '../../../../../test/functional/services/lib/compare_pngs'; - -const mkdirAsync = promisify(fs.mkdir); - -export async function checkIfPdfsMatch(actualPdfPath, baselinePdfPath, screenshotsDirectory, log) { - log.debug(`checkIfPdfsMatch: ${actualPdfPath} vs ${baselinePdfPath}`); - // Copy the pdfs into the screenshot session directory, as that's where the generated pngs will automatically be - // stored. - const sessionDirectoryPath = path.resolve(screenshotsDirectory, 'session'); - const failureDirectoryPath = path.resolve(screenshotsDirectory, 'failure'); - - await mkdirAsync(sessionDirectoryPath, { recursive: true }); - await mkdirAsync(failureDirectoryPath, { recursive: true }); - - const actualPdfFileName = path.basename(actualPdfPath, '.pdf'); - const baselinePdfFileName = path.basename(baselinePdfPath, '.pdf'); - - const baselineCopyPath = path.resolve( - sessionDirectoryPath, - `${baselinePdfFileName}_baseline.pdf` - ); - const actualCopyPath = path.resolve(sessionDirectoryPath, `${actualPdfFileName}_actual.pdf`); - - // Don't cause a test failure if the baseline snapshot doesn't exist - we don't have all OS's covered and we - // don't want to start causing failures for other devs working on OS's which are lacking snapshots. We have - // mac and linux covered which is better than nothing for now. - try { - log.debug(`writeFileSync: ${baselineCopyPath}`); - fs.writeFileSync(baselineCopyPath, fs.readFileSync(baselinePdfPath)); - } catch (error) { - log.error(`No baseline pdf found at ${baselinePdfPath}`); - return 0; - } - log.debug(`writeFileSync: ${actualCopyPath}`); - fs.writeFileSync(actualCopyPath, fs.readFileSync(actualPdfPath)); - - const convertOptions = {}; - - const actualPdfImage = new PDFImage(actualCopyPath, { convertOptions }); - const expectedPdfImage = new PDFImage(baselineCopyPath, { convertOptions }); - - log.debug(`Calculating numberOfPages`); - - const actualDoc = await PDFJS.getDocument(actualCopyPath); - const expectedDoc = await PDFJS.getDocument(baselineCopyPath); - const actualPages = actualDoc.numPages; - const expectedPages = expectedDoc.numPages; - - if (actualPages !== expectedPages) { - throw new Error( - `Expected ${expectedPages} pages but got ${actualPages} in PDFs expected: "${baselineCopyPath}" actual: "${actualCopyPath}".` - ); - } - - let diffTotal = 0; - - for (let pageNum = 0; pageNum <= expectedPages; ++pageNum) { - log.debug(`Converting expected pdf page ${pageNum} to png`); - const expectedPagePng = await expectedPdfImage.convertPage(pageNum); - log.debug(`Converting actual pdf page ${pageNum} to png`); - const actualPagePng = await actualPdfImage.convertPage(pageNum); - const diffPngPath = path.resolve(failureDirectoryPath, `${baselinePdfFileName}-${pageNum}.png`); - diffTotal += await comparePngs( - actualPagePng, - expectedPagePng, - diffPngPath, - sessionDirectoryPath, - log - ); - pageNum++; - } - - return diffTotal; -} diff --git a/x-pack/test/reporting/functional/lib/index.js b/x-pack/test/reporting/functional/lib/index.js index 3590995002c08..e7a08753b591f 100644 --- a/x-pack/test/reporting/functional/lib/index.js +++ b/x-pack/test/reporting/functional/lib/index.js @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { checkIfPdfsMatch } from './compare_pdfs'; export { checkIfPngsMatch } from './compare_pngs'; diff --git a/x-pack/test/reporting/functional/reporting.js b/x-pack/test/reporting/functional/reporting.js index 8458f4e537b70..0e1078a2a4c8b 100644 --- a/x-pack/test/reporting/functional/reporting.js +++ b/x-pack/test/reporting/functional/reporting.js @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import path from 'path'; import fs from 'fs'; import { promisify } from 'util'; -import { checkIfPdfsMatch, checkIfPngsMatch } from './lib'; +import { checkIfPngsMatch } from './lib'; const writeFileAsync = promisify(fs.writeFile); const mkdirAsync = promisify(fs.mkdir); @@ -29,9 +29,7 @@ export default function({ getService, getPageObjects }) { ]); const log = getService('log'); - // FLAKY: https://github.com/elastic/kibana/issues/45499 - // FLAKY: https://github.com/elastic/kibana/issues/48721 - describe.skip('Reporting', () => { + describe('Reporting', () => { before('initialize tests', async () => { await PageObjects.reporting.initTests(); }); @@ -68,7 +66,9 @@ export default function({ getService, getPageObjects }) { const getBaselineReportPath = (fileName, reportExt = 'pdf') => { const baselineFolder = path.resolve(REPORTS_FOLDER, 'baseline'); - return path.resolve(baselineFolder, `${fileName}.${reportExt}`); + const fullPath = path.resolve(baselineFolder, `${fileName}.${reportExt}`); + log.debug(`getBaselineReportPath (${fullPath})`); + return fullPath; }; describe('Dashboard', () => { @@ -83,118 +83,40 @@ export default function({ getService, getPageObjects }) { }); it('becomes available when saved', async () => { - await PageObjects.dashboard.saveDashboard('mypdfdash'); + await PageObjects.dashboard.saveDashboard('My PDF Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); await expectEnabledGenerateReportButton(); }); }); - describe.skip('Print Layout', () => { - it('matches baseline report', async function() { + describe('Print Layout', () => { + it('downloads a PDF file', async function() { // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs // function is taking about 15 seconds per comparison in jenkins. - this.timeout(360000); + this.timeout(300000); await PageObjects.dashboard.switchToEditMode(); await PageObjects.reporting.setTimepickerInDataRange(); const visualizations = PageObjects.dashboard.getTestVisualizationNames(); - // There is a current issue causing reports with tilemaps to timeout: - // https://github.com/elastic/kibana/issues/14136. Once that is resolved, add the tilemap visualization - // back in! const tileMapIndex = visualizations.indexOf('Visualization TileMap'); visualizations.splice(tileMapIndex, 1); - await PageObjects.dashboard.addVisualizations(visualizations); - - await PageObjects.dashboard.saveDashboard('report test'); - - await PageObjects.reporting.openPdfReportingPanel(); - await PageObjects.reporting.checkUsePrintLayout(); - await PageObjects.reporting.clickGenerateReportButton(); - await PageObjects.reporting.clickDownloadReportButton(60000); - PageObjects.reporting.clearToastNotifications(); - const url = await PageObjects.reporting.getUrlOfTab(1); - await PageObjects.reporting.closeTab(1); - const reportData = await PageObjects.reporting.getRawPdfReportData(url); - const reportFileName = 'dashboard_print'; - const sessionReportPath = await writeSessionReport(reportFileName, reportData); - const percentSimilar = await checkIfPdfsMatch( - sessionReportPath, - getBaselineReportPath(reportFileName), - config.get('screenshots.directory'), - log - ); - // After expected OS differences, the diff count came to be around 128k - expect(percentSimilar).to.be.lessThan(0.05); - }); - - it('matches same baseline report with margins turned on', async function() { - // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs - // function is taking about 15 seconds per comparison in jenkins. - this.timeout(360000); - - await PageObjects.dashboard.switchToEditMode(); - await PageObjects.dashboard.useMargins(true); + await PageObjects.dashboard.addVisualizations(visualizations); await PageObjects.dashboard.saveDashboard('report test'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.checkUsePrintLayout(); await PageObjects.reporting.clickGenerateReportButton(); - await PageObjects.reporting.clickDownloadReportButton(60000); - PageObjects.reporting.clearToastNotifications(); - - const url = await PageObjects.reporting.getUrlOfTab(1); - const reportData = await PageObjects.reporting.getRawPdfReportData(url); - - await PageObjects.reporting.closeTab(1); - const reportFileName = 'dashboard_print'; - const sessionReportPath = await writeSessionReport(reportFileName, reportData); - const percentSimilar = await checkIfPdfsMatch( - sessionReportPath, - getBaselineReportPath(reportFileName), - config.get('screenshots.directory'), - log - ); - // After expected OS differences, the diff count came to be around 128k - expect(percentSimilar).to.be.lessThan(0.05); - }); - }); - - // TODO Re-enable the tests after removing Phantom: - // https://github.com/elastic/kibana/issues/21485 - describe.skip('Preserve Layout', () => { - it('matches baseline report', async function() { - // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs - // function is taking about 15 seconds per comparison in jenkins. - this.timeout(360000); - - await PageObjects.reporting.openPdfReportingPanel(); - await PageObjects.reporting.forceSharedItemsContainerSize({ width: 1405 }); - await PageObjects.reporting.clickGenerateReportButton(); - await PageObjects.reporting.removeForceSharedItemsContainerSize(); - - await PageObjects.reporting.clickDownloadReportButton(60000); - PageObjects.reporting.clearToastNotifications(); - const url = await PageObjects.reporting.getUrlOfTab(1); - await PageObjects.reporting.closeTab(1); - const reportData = await PageObjects.reporting.getRawPdfReportData(url); - - const reportFileName = 'dashboard_preserve_layout'; - const sessionReportPath = await writeSessionReport(reportFileName, reportData); + const url = await PageObjects.reporting.getReportURL(60000); + const res = await PageObjects.reporting.getResponse(url); - const percentSimilar = await checkIfPdfsMatch( - sessionReportPath, - getBaselineReportPath(reportFileName), - config.get('screenshots.directory'), - log - ); - expect(percentSimilar).to.be.lessThan(0.05); + expect(res.statusCode).to.equal(200); + expect(res.headers['content-type']).to.equal('application/pdf'); }); }); - // FLAKY: https://github.com/elastic/kibana/issues/43131 - describe.skip('Print PNG button', () => { + describe('Print PNG button', () => { it('is not available if new', async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); @@ -203,45 +125,32 @@ export default function({ getService, getPageObjects }) { }); it('becomes available when saved', async () => { - await PageObjects.dashboard.saveDashboard('mypngdash'); + await PageObjects.dashboard.saveDashboard('My PNG Dash'); await PageObjects.reporting.openPngReportingPanel(); await expectEnabledGenerateReportButton(); }); }); - // TODO Re-enable the tests after removing Phantom: - // https://github.com/elastic/kibana/issues/21485 - describe.skip('Preserve Layout', () => { + describe('Preserve Layout', () => { it('matches baseline report', async function() { - // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs - // function is taking about 15 seconds per comparison in jenkins. - this.timeout(360000); + this.timeout(300000); await PageObjects.dashboard.switchToEditMode(); await PageObjects.reporting.setTimepickerInDataRange(); - const visualizations = PageObjects.dashboard.getTestVisualizationNames(); - // There is a current issue causing reports with tilemaps to timeout: - // https://github.com/elastic/kibana/issues/14136. Once that is resolved, add the tilemap visualization - // back in! + const visualizations = PageObjects.dashboard.getTestVisualizationNames(); const tileMapIndex = visualizations.indexOf('Visualization TileMap'); visualizations.splice(tileMapIndex, 1); - await PageObjects.dashboard.addVisualizations(visualizations); + await PageObjects.dashboard.addVisualizations(visualizations); await PageObjects.dashboard.saveDashboard('PNG report test'); - await PageObjects.reporting.openPngReportingPanel(); await PageObjects.reporting.forceSharedItemsContainerSize({ width: 1405 }); await PageObjects.reporting.clickGenerateReportButton(); await PageObjects.reporting.removeForceSharedItemsContainerSize(); - await PageObjects.reporting.clickDownloadReportButton(60000); - PageObjects.reporting.clearToastNotifications(); - - const url = await PageObjects.reporting.getUrlOfTab(1); - await PageObjects.reporting.closeTab(1); + const url = await PageObjects.reporting.getReportURL(60000); const reportData = await PageObjects.reporting.getRawPdfReportData(url); - const reportFileName = 'dashboard_preserve_layout'; const sessionReportPath = await writeSessionReport(reportFileName, reportData, 'png'); const percentSimilar = await checkIfPngsMatch( @@ -250,14 +159,14 @@ export default function({ getService, getPageObjects }) { config.get('screenshots.directory'), log ); - expect(percentSimilar).to.be.lessThan(0.05); + + expect(percentSimilar).to.be.lessThan(0.1); }); }); }); describe('Discover', () => { - // FLAKY: https://github.com/elastic/kibana/issues/31379 - describe.skip('Generate CSV button', () => { + describe('Generate CSV button', () => { beforeEach(() => PageObjects.common.navigateToApp('discover')); it('is not available if new', async () => { @@ -307,31 +216,19 @@ export default function({ getService, getPageObjects }) { await expectEnabledGenerateReportButton(); }); - // TODO Re-enable the tests after removing Phantom: - // https://github.com/elastic/kibana/issues/21485 - it.skip('matches baseline report', async function() { + it('matches baseline report', async function() { // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs // function is taking about 15 seconds per comparison in jenkins. this.timeout(180000); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); - await PageObjects.reporting.clickDownloadReportButton(60000); - PageObjects.reporting.clearToastNotifications(); - const url = await PageObjects.reporting.getUrlOfTab(1); - const reportData = await PageObjects.reporting.getRawPdfReportData(url); + const url = await PageObjects.reporting.getReportURL(60000); + const res = await PageObjects.reporting.getResponse(url); - await PageObjects.reporting.closeTab(1); - const reportFileName = 'visualize_print'; - const sessionReportPath = await writeSessionReport(reportFileName, reportData); - const percentSimilar = await checkIfPdfsMatch( - sessionReportPath, - getBaselineReportPath(reportFileName), - config.get('screenshots.directory'), - log - ); - expect(percentSimilar).to.be.lessThan(0.05); + expect(res.statusCode).to.equal(200); + expect(res.headers['content-type']).to.equal('application/pdf'); }); }); }); diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index e309d0e127dfc..e9aab9b47535f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -7,7 +7,10 @@ ] }, "include": [ - "**/*" + "**/*", + "../typings/**/*" ], - "exclude": [] + "exclude": [ + "../typings/jest.d.ts" + ] } diff --git a/x-pack/test/typings/hapi.d.ts b/x-pack/test/typings/hapi.d.ts deleted file mode 100644 index fc5ce09e5e618..0000000000000 --- a/x-pack/test/typings/hapi.d.ts +++ /dev/null @@ -1,25 +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 'hapi'; - -import { XPackMainPlugin } from '../../legacy/plugins/xpack_main/server/xpack_main'; -import { SecurityPlugin } from '../../legacy/plugins/security'; -import { ActionsPlugin, ActionsClient } from '../../legacy/plugins/actions'; -import { AlertingPlugin, AlertsClient } from '../../legacy/plugins/alerting'; - -declare module 'hapi' { - interface Request { - getActionsClient?: () => ActionsClient; - getAlertsClient?: () => AlertsClient; - } - interface PluginProperties { - xpack_main: XPackMainPlugin; - security?: SecurityPlugin; - actions?: ActionsPlugin; - alerting?: AlertingPlugin; - } -} diff --git a/x-pack/test/typings/index.d.ts b/x-pack/test/typings/index.d.ts deleted file mode 100644 index db6b2d38b2c27..0000000000000 --- a/x-pack/test/typings/index.d.ts +++ /dev/null @@ -1,28 +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. - */ - -declare module '*.html' { - const template: string; - // eslint-disable-next-line import/no-default-export - export default template; -} - -declare module 'lodash/internal/toPath' { - function toPath(value: string | string[]): string[]; - export = toPath; -} - -declare module '*.json' { - const json: any; - // eslint-disable-next-line import/no-default-export - export default json; -} - -type MethodKeysOf = { - [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; -}[keyof T]; - -type PublicMethodsOf = Pick>; diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 7070e3f5aa493..7d2933f9d9238 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -38,7 +38,8 @@ "monitoring/common/*": [ "x-pack/monitoring/common/*" ], - "plugins/*": ["src/legacy/core_plugins/*/public/"] + "plugins/*": ["src/legacy/core_plugins/*/public/"], + "fixtures/*": ["src/fixtures/*"] }, "types": [ "node", diff --git a/x-pack/typings/index.d.ts b/x-pack/typings/index.d.ts index 2413f986922ed..7696ddbfd73cb 100644 --- a/x-pack/typings/index.d.ts +++ b/x-pack/typings/index.d.ts @@ -27,15 +27,6 @@ type Writable = { -readonly [K in keyof T]: T[K]; }; -type MockedKeys = { [P in keyof T]: jest.Mocked> }; - -type DeeplyMockedKeys = { - [P in keyof T]: T[P] extends (...args: any[]) => any - ? jest.MockInstance, Parameters> - : DeeplyMockedKeys; -} & - T; - // allow JSON files to be imported directly without lint errors // see: https://github.com/palantir/tslint/issues/1264#issuecomment-228433367 // and: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#arbitrary-expressions-are-forbidden-in-export-assignments-in-ambient-contexts diff --git a/x-pack/typings/jest_styled_components.d.ts b/x-pack/typings/jest.d.ts similarity index 75% rename from x-pack/typings/jest_styled_components.d.ts rename to x-pack/typings/jest.d.ts index 86f82ffb013c7..5d2aa51284e5e 100644 --- a/x-pack/typings/jest_styled_components.d.ts +++ b/x-pack/typings/jest.d.ts @@ -4,8 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -// https://github.com/styled-components/jest-styled-components/issues/264 +type MockedKeys = { [P in keyof T]: jest.Mocked> }; + +type DeeplyMockedKeys = { + [P in keyof T]: T[P] extends (...args: any[]) => any + ? jest.MockInstance, Parameters> + : DeeplyMockedKeys; +} & + T; +// https://github.com/styled-components/jest-styled-components/issues/264 declare namespace jest { interface AsymmetricMatcher { $$typeof: Symbol; //eslint-disable-line diff --git a/yarn.lock b/yarn.lock index e7e1a3920fb2c..b534a7a4fcb79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -114,6 +114,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-annotate-as-pure@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz#bb3faf1e74b74bd547e867e48f551fa6b098b6ce" + integrity sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og== + dependencies: + "@babel/types" "^7.7.4" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" @@ -122,6 +129,14 @@ "@babel/helper-explode-assignable-expression" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz#5f73f2b28580e224b5b9bd03146a4015d6217f5f" + integrity sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.7.4" + "@babel/types" "^7.7.4" + "@babel/helper-builder-react-jsx@^7.3.0": version "7.3.0" resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz#a1ac95a5d2b3e88ae5e54846bf462eeb81b318a4" @@ -147,6 +162,15 @@ "@babel/traverse" "^7.4.4" "@babel/types" "^7.4.4" +"@babel/helper-call-delegate@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz#621b83e596722b50c0066f9dc37d3232e461b801" + integrity sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA== + dependencies: + "@babel/helper-hoist-variables" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + "@babel/helper-create-class-features-plugin@^7.4.4", "@babel/helper-create-class-features-plugin@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz#401f302c8ddbc0edd36f7c6b2887d8fa1122e5a4" @@ -171,6 +195,14 @@ "@babel/helper-replace-supers" "^7.7.4" "@babel/helper-split-export-declaration" "^7.7.4" +"@babel/helper-create-regexp-features-plugin@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz#6d5762359fd34f4da1500e4cff9955b5299aaf59" + integrity sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A== + dependencies: + "@babel/helper-regex" "^7.4.4" + regexpu-core "^4.6.0" + "@babel/helper-define-map@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz#3dec32c2046f37e09b28c93eb0b103fd2a25d369" @@ -180,6 +212,15 @@ "@babel/types" "^7.5.5" lodash "^4.17.13" +"@babel/helper-define-map@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz#2841bf92eb8bd9c906851546fe6b9d45e162f176" + integrity sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg== + dependencies: + "@babel/helper-function-name" "^7.7.4" + "@babel/types" "^7.7.4" + lodash "^4.17.13" + "@babel/helper-explode-assignable-expression@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" @@ -188,6 +229,14 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-explode-assignable-expression@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz#fa700878e008d85dc51ba43e9fb835cddfe05c84" + integrity sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg== + dependencies: + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + "@babel/helper-function-name@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" @@ -227,6 +276,13 @@ dependencies: "@babel/types" "^7.4.4" +"@babel/helper-hoist-variables@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz#612384e3d823fdfaaf9fce31550fe5d4db0f3d12" + integrity sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ== + dependencies: + "@babel/types" "^7.7.4" + "@babel/helper-member-expression-to-functions@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590" @@ -248,6 +304,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-module-imports@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz#e5a92529f8888bf319a6376abfbd1cebc491ad91" + integrity sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ== + dependencies: + "@babel/types" "^7.7.4" + "@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz#f84ff8a09038dcbca1fd4355661a500937165b4a" @@ -260,6 +323,18 @@ "@babel/types" "^7.5.5" lodash "^4.17.13" +"@babel/helper-module-transforms@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.4.tgz#8d7cdb1e1f8ea3d8c38b067345924ac4f8e0879a" + integrity sha512-ehGBu4mXrhs0FxAqN8tWkzF8GSIGAiEumu4ONZ/hD9M88uHcD+Yu2ttKfOCgwzoesJOJrtQh7trI5YPbRtMmnA== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-simple-access" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" + lodash "^4.17.13" + "@babel/helper-optimise-call-expression@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" @@ -297,6 +372,17 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-remap-async-to-generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz#c68c2407350d9af0e061ed6726afb4fff16d0234" + integrity sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-wrap-function" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + "@babel/helper-replace-supers@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2" @@ -325,6 +411,14 @@ "@babel/template" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-simple-access@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz#a169a0adb1b5f418cfc19f22586b2ebf58a9a294" + integrity sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A== + dependencies: + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" + "@babel/helper-split-export-declaration@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" @@ -349,6 +443,16 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" +"@babel/helper-wrap-function@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz#37ab7fed5150e22d9d7266e830072c0cdd8baace" + integrity sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg== + dependencies: + "@babel/helper-function-name" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + "@babel/helpers@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.5.tgz#63908d2a73942229d1e6685bc2a0e730dde3b75e" @@ -410,6 +514,15 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" +"@babel/plugin-proposal-async-generator-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz#0351c5ac0a9e927845fffd5b82af476947b7ce6d" + integrity sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.7.4" + "@babel/plugin-syntax-async-generators" "^7.7.4" + "@babel/plugin-proposal-class-properties@7.5.5", "@babel/plugin-proposal-class-properties@^7.3.3", "@babel/plugin-proposal-class-properties@^7.5.1", "@babel/plugin-proposal-class-properties@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz#a974cfae1e37c3110e71f3c6a2e48b8e71958cd4" @@ -418,6 +531,14 @@ "@babel/helper-create-class-features-plugin" "^7.5.5" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-proposal-class-properties@^7.7.0": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.7.4.tgz#2f964f0cb18b948450362742e33e15211e77c2ba" + integrity sha512-EcuXeV4Hv1X3+Q1TsuOmyyxeTRiSqurGJ26+I/FW1WbymmRRapVORm6x1Zl3iDIHyRxEs+VXWp6qnlcfcJSbbw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-decorators@7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz#de9b2a1a8ab0196f378e2a82f10b6e2a36f21cc0" @@ -435,6 +556,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-dynamic-import" "^7.2.0" +"@babel/plugin-proposal-dynamic-import@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz#dde64a7f127691758cbfed6cf70de0fa5879d52d" + integrity sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-dynamic-import" "^7.7.4" + "@babel/plugin-proposal-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" @@ -443,6 +572,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-json-strings" "^7.2.0" +"@babel/plugin-proposal-json-strings@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz#7700a6bfda771d8dc81973249eac416c6b4c697d" + integrity sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings" "^7.7.4" + "@babel/plugin-proposal-nullish-coalescing-operator@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.4.4.tgz#41c360d59481d88e0ce3a3f837df10121a769b39" @@ -459,6 +596,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" +"@babel/plugin-proposal-object-rest-spread@^7.6.2", "@babel/plugin-proposal-object-rest-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz#cc57849894a5c774214178c8ab64f6334ec8af71" + integrity sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.7.4" + "@babel/plugin-proposal-optional-catch-binding@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" @@ -467,6 +612,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" +"@babel/plugin-proposal-optional-catch-binding@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz#ec21e8aeb09ec6711bc0a39ca49520abee1de379" + integrity sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.7.4" + "@babel/plugin-proposal-optional-chaining@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.6.0.tgz#e9bf1f9b9ba10c77c033082da75f068389041af8" @@ -484,6 +637,14 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" +"@babel/plugin-proposal-unicode-property-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz#7c239ccaf09470dbe1d453d50057460e84517ebb" + integrity sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-async-generators@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" @@ -491,6 +652,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-async-generators@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz#331aaf310a10c80c44a66b238b6e49132bd3c889" + integrity sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-decorators@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz#c50b1b957dcc69e4b1127b65e1c33eef61570c1b" @@ -505,6 +673,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-dynamic-import@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz#29ca3b4415abfe4a5ec381e903862ad1a54c3aec" + integrity sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-flow@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz#a765f061f803bc48f240c26f8747faf97c26bf7c" @@ -519,6 +694,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-json-strings@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz#86e63f7d2e22f9e27129ac4e83ea989a382e86cc" + integrity sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-jsx@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz#0b85a3b4bc7cdf4cc4b8bf236335b907ca22e7c7" @@ -547,6 +729,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-object-rest-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz#47cf220d19d6d0d7b154304701f468fc1cc6ff46" + integrity sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" @@ -554,6 +743,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-optional-catch-binding@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz#a3e38f59f4b6233867b4a92dcb0ee05b2c334aa6" + integrity sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-chaining@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.2.0.tgz#a59d6ae8c167e7608eaa443fda9fa8fa6bf21dff" @@ -561,6 +757,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-top-level-await@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.4.tgz#bd7d8fa7b9fee793a36e4027fd6dd1aa32f946da" + integrity sha512-wdsOw0MvkL1UIgiQ/IFr3ETcfv1xb8RMM0H9wbiDyLaJFyiDg5oZvDLCXosIXmFeIlweML5iOBXAkqddkYNizg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-typescript@^7.2.0": version "7.3.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz#a7cc3f66119a9f7ebe2de5383cce193473d65991" @@ -582,6 +785,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-arrow-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz#76309bd578addd8aee3b379d809c802305a98a12" + integrity sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-async-to-generator@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz#89a3848a0166623b5bc481164b5936ab947e887e" @@ -591,6 +801,15 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-remap-async-to-generator" "^7.1.0" +"@babel/plugin-transform-async-to-generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz#694cbeae6d613a34ef0292713fa42fb45c4470ba" + integrity sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.7.4" + "@babel/plugin-transform-block-scoped-functions@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" @@ -598,6 +817,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-block-scoped-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz#d0d9d5c269c78eaea76227ace214b8d01e4d837b" + integrity sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-block-scoping@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz#a35f395e5402822f10d2119f6f8e045e3639a2ce" @@ -614,6 +840,14 @@ "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.13" +"@babel/plugin-transform-block-scoping@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz#200aad0dcd6bb80372f94d9e628ea062c58bf224" + integrity sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + lodash "^4.17.13" + "@babel/plugin-transform-classes@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9" @@ -628,6 +862,20 @@ "@babel/helper-split-export-declaration" "^7.4.4" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz#c92c14be0a1399e15df72667067a8f510c9400ec" + integrity sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-define-map" "^7.7.4" + "@babel/helper-function-name" "^7.7.4" + "@babel/helper-optimise-call-expression" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" @@ -635,6 +883,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-computed-properties@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz#e856c1628d3238ffe12d668eb42559f79a81910d" + integrity sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-destructuring@7.5.0", "@babel/plugin-transform-destructuring@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz#f6c09fdfe3f94516ff074fe877db7bc9ef05855a" @@ -649,6 +904,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-destructuring@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz#2b713729e5054a1135097b6a67da1b6fe8789267" + integrity sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3" @@ -658,6 +920,14 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" +"@babel/plugin-transform-dotall-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz#f7ccda61118c5b7a2599a72d5e3210884a021e96" + integrity sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-duplicate-keys@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853" @@ -665,6 +935,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-duplicate-keys@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz#3d21731a42e3f598a73835299dd0169c3b90ac91" + integrity sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-exponentiation-operator@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008" @@ -673,6 +950,14 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-exponentiation-operator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz#dd30c0191e3a1ba19bcc7e389bdfddc0729d5db9" + integrity sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-flow-strip-types@7.4.4", "@babel/plugin-transform-flow-strip-types@^7.0.0": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.4.tgz#d267a081f49a8705fc9146de0768c6b58dccd8f7" @@ -688,6 +973,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-for-of@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz#248800e3a5e507b1f103d8b4ca998e77c63932bc" + integrity sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-function-name@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad" @@ -696,6 +988,14 @@ "@babel/helper-function-name" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-function-name@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz#75a6d3303d50db638ff8b5385d12451c865025b1" + integrity sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g== + dependencies: + "@babel/helper-function-name" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-literals@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1" @@ -703,6 +1003,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz#27fe87d2b5017a2a5a34d1c41a6b9f6a6262643e" + integrity sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-member-expression-literals@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d" @@ -710,6 +1017,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-member-expression-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz#aee127f2f3339fc34ce5e3055d7ffbf7aa26f19a" + integrity sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-modules-amd@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91" @@ -719,6 +1033,15 @@ "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" +"@babel/plugin-transform-modules-amd@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz#276b3845ca2b228f2995e453adc2e6f54d72fb71" + integrity sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ== + dependencies: + "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + babel-plugin-dynamic-import-node "^2.3.0" + "@babel/plugin-transform-modules-commonjs@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz#425127e6045231360858eeaa47a71d75eded7a74" @@ -739,6 +1062,16 @@ "@babel/helper-simple-access" "^7.1.0" babel-plugin-dynamic-import-node "^2.3.0" +"@babel/plugin-transform-modules-commonjs@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz#bee4386e550446343dd52a571eda47851ff857a3" + integrity sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA== + dependencies: + "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-simple-access" "^7.7.4" + babel-plugin-dynamic-import-node "^2.3.0" + "@babel/plugin-transform-modules-systemjs@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249" @@ -748,6 +1081,15 @@ "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" +"@babel/plugin-transform-modules-systemjs@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz#cd98152339d3e763dfe838b7d4273edaf520bb30" + integrity sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw== + dependencies: + "@babel/helper-hoist-variables" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + babel-plugin-dynamic-import-node "^2.3.0" + "@babel/plugin-transform-modules-umd@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae" @@ -756,6 +1098,14 @@ "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-modules-umd@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz#1027c355a118de0aae9fee00ad7813c584d9061f" + integrity sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw== + dependencies: + "@babel/helper-module-transforms" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-named-capturing-groups-regex@^7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz#9d269fd28a370258199b4294736813a60bbdd106" @@ -770,6 +1120,13 @@ dependencies: regexp-tree "^0.1.13" +"@babel/plugin-transform-named-capturing-groups-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz#fb3bcc4ee4198e7385805007373d6b6f42c98220" + integrity sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + "@babel/plugin-transform-new-target@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5" @@ -777,6 +1134,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-new-target@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz#4a0753d2d60639437be07b592a9e58ee00720167" + integrity sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-object-super@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9" @@ -785,6 +1149,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-replace-supers" "^7.5.5" +"@babel/plugin-transform-object-super@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz#48488937a2d586c0148451bf51af9d7dda567262" + integrity sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.7.4" + "@babel/plugin-transform-parameters@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16" @@ -794,6 +1166,15 @@ "@babel/helper-get-function-arity" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-parameters@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz#da4555c97f39b51ac089d31c7380f03bca4075ce" + integrity sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw== + dependencies: + "@babel/helper-call-delegate" "^7.7.4" + "@babel/helper-get-function-arity" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-property-literals@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905" @@ -801,6 +1182,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-property-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz#2388d6505ef89b266103f450f9167e6bd73f98c2" + integrity sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-constant-elements@^7.0.0", "@babel/plugin-transform-react-constant-elements@^7.2.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.5.0.tgz#4d6ae4033bc38f8a65dfca2b6235c44522a422fc" @@ -809,6 +1197,14 @@ "@babel/helper-annotate-as-pure" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-react-constant-elements@^7.6.3": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.7.4.tgz#499cf732a21ffd62cc4b0016e27c3906097f8982" + integrity sha512-U6XkHZ8RnmeEb8jBUOpeo6oFka5RhLgxAVvK4/fBbwoYlsHQYLb8I37ymTPDVsrWjqb94+hueuWQA/1OAA4rAQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-display-name@7.2.0", "@babel/plugin-transform-react-display-name@^7.0.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz#ebfaed87834ce8dc4279609a4f0c324c156e3eb0" @@ -880,6 +1276,13 @@ dependencies: regenerator-transform "^0.14.0" +"@babel/plugin-transform-regenerator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz#d18eac0312a70152d7d914cbed2dc3999601cfc0" + integrity sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw== + dependencies: + regenerator-transform "^0.14.0" + "@babel/plugin-transform-reserved-words@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634" @@ -887,6 +1290,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-reserved-words@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz#6a7cf123ad175bb5c69aec8f6f0770387ed3f1eb" + integrity sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-runtime@7.5.5", "@babel/plugin-transform-runtime@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.5.tgz#a6331afbfc59189d2135b2e09474457a8e3d28bc" @@ -904,6 +1314,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-shorthand-properties@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz#74a0a9b2f6d67a684c6fbfd5f0458eb7ba99891e" + integrity sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-spread@^7.2.0": version "7.2.2" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406" @@ -911,6 +1328,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz#aa673b356fe6b7e70d69b6e33a17fef641008578" + integrity sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-sticky-regex@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" @@ -919,6 +1343,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" +"@babel/plugin-transform-sticky-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz#ffb68c05090c30732076b1285dc1401b404a123c" + integrity sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + "@babel/plugin-transform-template-literals@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0" @@ -927,6 +1359,14 @@ "@babel/helper-annotate-as-pure" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-template-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz#1eb6411736dd3fe87dbd20cc6668e5121c17d604" + integrity sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-typeof-symbol@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2" @@ -934,6 +1374,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-typeof-symbol@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz#3174626214f2d6de322882e498a38e8371b2140e" + integrity sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-typescript@^7.3.2": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.5.5.tgz#6d862766f09b2da1cb1f7d505fe2aedab6b7d4b8" @@ -961,6 +1408,14 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" +"@babel/plugin-transform-unicode-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz#a3c0f65b117c4c81c5b6484f2a5e7b95346b83ae" + integrity sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/preset-env@7.5.5", "@babel/preset-env@^7.4.3", "@babel/preset-env@^7.4.5", "@babel/preset-env@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.5.5.tgz#bc470b53acaa48df4b8db24a570d6da1fef53c9a" @@ -1073,6 +1528,63 @@ js-levenshtein "^1.1.3" semver "^5.5.0" +"@babel/preset-env@^7.7.1": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8" + integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.7.4" + "@babel/plugin-proposal-dynamic-import" "^7.7.4" + "@babel/plugin-proposal-json-strings" "^7.7.4" + "@babel/plugin-proposal-object-rest-spread" "^7.7.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.7.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.7.4" + "@babel/plugin-syntax-async-generators" "^7.7.4" + "@babel/plugin-syntax-dynamic-import" "^7.7.4" + "@babel/plugin-syntax-json-strings" "^7.7.4" + "@babel/plugin-syntax-object-rest-spread" "^7.7.4" + "@babel/plugin-syntax-optional-catch-binding" "^7.7.4" + "@babel/plugin-syntax-top-level-await" "^7.7.4" + "@babel/plugin-transform-arrow-functions" "^7.7.4" + "@babel/plugin-transform-async-to-generator" "^7.7.4" + "@babel/plugin-transform-block-scoped-functions" "^7.7.4" + "@babel/plugin-transform-block-scoping" "^7.7.4" + "@babel/plugin-transform-classes" "^7.7.4" + "@babel/plugin-transform-computed-properties" "^7.7.4" + "@babel/plugin-transform-destructuring" "^7.7.4" + "@babel/plugin-transform-dotall-regex" "^7.7.4" + "@babel/plugin-transform-duplicate-keys" "^7.7.4" + "@babel/plugin-transform-exponentiation-operator" "^7.7.4" + "@babel/plugin-transform-for-of" "^7.7.4" + "@babel/plugin-transform-function-name" "^7.7.4" + "@babel/plugin-transform-literals" "^7.7.4" + "@babel/plugin-transform-member-expression-literals" "^7.7.4" + "@babel/plugin-transform-modules-amd" "^7.7.4" + "@babel/plugin-transform-modules-commonjs" "^7.7.4" + "@babel/plugin-transform-modules-systemjs" "^7.7.4" + "@babel/plugin-transform-modules-umd" "^7.7.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.4" + "@babel/plugin-transform-new-target" "^7.7.4" + "@babel/plugin-transform-object-super" "^7.7.4" + "@babel/plugin-transform-parameters" "^7.7.4" + "@babel/plugin-transform-property-literals" "^7.7.4" + "@babel/plugin-transform-regenerator" "^7.7.4" + "@babel/plugin-transform-reserved-words" "^7.7.4" + "@babel/plugin-transform-shorthand-properties" "^7.7.4" + "@babel/plugin-transform-spread" "^7.7.4" + "@babel/plugin-transform-sticky-regex" "^7.7.4" + "@babel/plugin-transform-template-literals" "^7.7.4" + "@babel/plugin-transform-typeof-symbol" "^7.7.4" + "@babel/plugin-transform-unicode-regex" "^7.7.4" + "@babel/types" "^7.7.4" + browserslist "^4.6.0" + core-js-compat "^3.1.1" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.5.0" + "@babel/preset-flow@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2" @@ -1092,7 +1604,7 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" -"@babel/preset-react@^7.7.4": +"@babel/preset-react@^7.7.0", "@babel/preset-react@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.7.4.tgz#3fe2ea698d8fb536d8e7881a592c3c1ee8bf5707" integrity sha512-j+vZtg0/8pQr1H8wKoaJyGL2IEk3rG/GIvua7Sec7meXVIvGycihlGMx5xcU00kqCJbwzHs18xTu3YfREOqQ+g== @@ -1175,6 +1687,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.7.4": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.7.tgz#194769ca8d6d7790ec23605af9ee3e42a0aa79cf" + integrity sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -1434,10 +1953,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@18.2.0": - version "18.2.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.2.0.tgz#db9c2af87b2bdd0af7a39802d31507139b543482" - integrity sha512-wZXFBzETsaMTw6VuqFu168NKiwXMI5MVT7SdTIRufCBrT6uhUpbx43ZiDCcg372RHXosunQaiD5eVm3aodO+Jg== +"@elastic/eui@18.2.1": + version "18.2.1" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.2.1.tgz#6ce6d0bd1d0541052d21f2918305524d71e91678" + integrity sha512-6C5tnWJTlBB++475i0vRoCsnz4JaYznb4zMNFLc+z5GY3vA3/E3AXTjmmBwybEicCCi3h1SnpJxZsgMakiZwRA== dependencies: "@types/chroma-js" "^1.4.3" "@types/lodash" "^4.14.116" @@ -2622,6 +3141,26 @@ react-inspector "^3.0.2" uuid "^3.3.2" +"@storybook/addon-actions@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.8.tgz#f63c6e1afb59e94ca1ebc776b7cad9b815e7419e" + integrity sha512-hadk+UaU6upOW0g447RfLRrnXRgE2rjRVk5sT8mVxBMj032NnwUd7ie/BZwy1yg5B8oFtpkgQYwqhPtoO2xBaQ== + dependencies: + "@storybook/addons" "5.2.8" + "@storybook/api" "5.2.8" + "@storybook/client-api" "5.2.8" + "@storybook/components" "5.2.8" + "@storybook/core-events" "5.2.8" + "@storybook/theming" "5.2.8" + core-js "^3.0.1" + fast-deep-equal "^2.0.1" + global "^4.3.2" + polished "^3.3.1" + prop-types "^15.7.2" + react "^16.8.3" + react-inspector "^3.0.2" + uuid "^3.3.2" + "@storybook/addon-console@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@storybook/addon-console/-/addon-console-1.2.1.tgz#f338401661b4636118b13839848061e996d4e104" @@ -2629,15 +3168,15 @@ dependencies: global "^4.3.2" -"@storybook/addon-info@^5.2.6": - version "5.2.6" - resolved "https://registry.yarnpkg.com/@storybook/addon-info/-/addon-info-5.2.6.tgz#25c8405ded9e20b1bf3b607d235601b087a73a5f" - integrity sha512-2ct91Zf14Be5HZ77szPpJK4Fvy4D/haQtziCfv9UKZXaBm7Rz9vKPuM3uCcfMywh1eMunhhluhJ/3bxmttHXAw== +"@storybook/addon-info@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/addon-info/-/addon-info-5.2.8.tgz#bf29741c21c16c85f4a7007606e8afa3eb77965c" + integrity sha512-iY8malDF6yayLfpiffMwDXOWeeoXdRbxse1f+kNHK4aVEUXLyh+uiogPhO8dzVDy8dQw1LP9C7xKZe2Dls59kw== dependencies: - "@storybook/addons" "5.2.6" - "@storybook/client-logger" "5.2.6" - "@storybook/components" "5.2.6" - "@storybook/theming" "5.2.6" + "@storybook/addons" "5.2.8" + "@storybook/client-logger" "5.2.8" + "@storybook/components" "5.2.8" + "@storybook/theming" "5.2.8" core-js "^3.0.1" global "^4.3.2" jsx-to-string "^1.4.0" @@ -2675,6 +3214,39 @@ react-lifecycles-compat "^3.0.4" react-select "^3.0.0" +"@storybook/addon-knobs@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.2.8.tgz#e0d03823969921a0da57a329376d03066dd749ee" + integrity sha512-5SAMJj+0pbhCiyNkKjkUxEbM9L/wrOE4HTvM7gvm902fULuKZklb3wV8iiUNRfIPCs6VhmmIhPzXICGjhW5xIg== + dependencies: + "@storybook/addons" "5.2.8" + "@storybook/api" "5.2.8" + "@storybook/client-api" "5.2.8" + "@storybook/components" "5.2.8" + "@storybook/core-events" "5.2.8" + "@storybook/theming" "5.2.8" + "@types/react-color" "^3.0.1" + copy-to-clipboard "^3.0.8" + core-js "^3.0.1" + escape-html "^1.0.3" + fast-deep-equal "^2.0.1" + global "^4.3.2" + lodash "^4.17.15" + prop-types "^15.7.2" + qs "^6.6.0" + react-color "^2.17.0" + react-lifecycles-compat "^3.0.4" + react-select "^3.0.0" + +"@storybook/addon-options@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-5.2.8.tgz#579c7e1f331303382541fa77c1c26242d7d6b79d" + integrity sha512-w7b6c+K5kv6AnQW1tnGSuNEQ8Ek2kFZ4anTaMYiGpoa1nQJjDrvS6R13GWHgxGACFpOtcBPMxTEX6YKAxiOgaA== + dependencies: + "@storybook/addons" "5.2.8" + core-js "^3.0.1" + util-deprecate "^1.0.2" + "@storybook/addon-storyshots@^5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/addon-storyshots/-/addon-storyshots-5.2.6.tgz#cc94f256cb28e2769a3dd472af420f8e0fcc306f" @@ -2690,6 +3262,21 @@ regenerator-runtime "^0.12.1" ts-dedent "^1.1.0" +"@storybook/addon-storyshots@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/addon-storyshots/-/addon-storyshots-5.2.8.tgz#1878d0cc7490941cc4006bd3bd96bfd06005e1e3" + integrity sha512-izUQTwzt1I0TdtYn3jv6yFIaW7H48gPW+nehisVXOA+0b0f+IySnC63+q3YcCQcC9cmZ5xxzZNJ1XycrsyVm0A== + dependencies: + "@jest/transform" "^24.9.0" + "@storybook/addons" "5.2.8" + core-js "^3.0.1" + glob "^7.1.3" + global "^4.3.2" + jest-specific-snapshot "^2.0.0" + read-pkg-up "^6.0.0" + regenerator-runtime "^0.12.1" + ts-dedent "^1.1.0" + "@storybook/addons@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.6.tgz#c1278137acb3502e068b0b0d07a8371c607e9c02" @@ -2703,6 +3290,19 @@ global "^4.3.2" util-deprecate "^1.0.2" +"@storybook/addons@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.8.tgz#f8bf8bd555b7a69fb1e9a52ab8cdb96384d931ff" + integrity sha512-yAo1N5z/45bNIQP8SD+HVTr7X898bYAtz1EZBrQ6zD8bGamzA2Br06rOLL9xXw29eQhsaVnPlqgDwCS1sTC7aQ== + dependencies: + "@storybook/api" "5.2.8" + "@storybook/channels" "5.2.8" + "@storybook/client-logger" "5.2.8" + "@storybook/core-events" "5.2.8" + core-js "^3.0.1" + global "^4.3.2" + util-deprecate "^1.0.2" + "@storybook/api@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.6.tgz#43d3c20b90e585e6c94b36e29845d39704ae2135" @@ -2726,6 +3326,29 @@ telejson "^3.0.2" util-deprecate "^1.0.2" +"@storybook/api@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.8.tgz#21f03df8041114eb929bd10b570a17f266568b7f" + integrity sha512-rFrPtTFDIPQoicLwq1AVsOvZNTUKnjD1w/NX1kKcyuWLL9BcOkU3YNLBlliGBg2JX/yS+fJKMyKk4NMzNBCZCg== + dependencies: + "@storybook/channels" "5.2.8" + "@storybook/client-logger" "5.2.8" + "@storybook/core-events" "5.2.8" + "@storybook/router" "5.2.8" + "@storybook/theming" "5.2.8" + core-js "^3.0.1" + fast-deep-equal "^2.0.1" + global "^4.3.2" + lodash "^4.17.15" + memoizerific "^1.11.3" + prop-types "^15.6.2" + react "^16.8.3" + semver "^6.0.0" + shallow-equal "^1.1.0" + store2 "^2.7.1" + telejson "^3.0.2" + util-deprecate "^1.0.2" + "@storybook/channel-postmessage@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.6.tgz#60aaef0e80300c9812a571ca3ce0f28e2c404f04" @@ -2737,6 +3360,17 @@ global "^4.3.2" telejson "^3.0.2" +"@storybook/channel-postmessage@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.8.tgz#7a84869ce0fc270c3b5dcd7fa4ed798b6055816f" + integrity sha512-RS3iDW1kpfODN+kBq3youn+KtLqHslZ4m7mTlOL80BUHKb4YkrA1lVkzpy1kVMWBU523pyDVQUVXr+M8y3iVug== + dependencies: + "@storybook/channels" "5.2.8" + "@storybook/client-logger" "5.2.8" + core-js "^3.0.1" + global "^4.3.2" + telejson "^3.0.2" + "@storybook/channels@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.6.tgz#e2837508864dc4d5b5e03f078886f0ce113762ea" @@ -2744,6 +3378,13 @@ dependencies: core-js "^3.0.1" +"@storybook/channels@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.8.tgz#79a99ad85dcacb688073c22340c5b7d16b801202" + integrity sha512-mFwQec27QSrqcl+IH0xA+4jfoEqC4m1G99LBHt/aTDjLZXclX1A470WqeZCp7Gx4OALpaPEVTaaaKPbiKz4C6w== + dependencies: + core-js "^3.0.1" + "@storybook/client-api@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.6.tgz#5760cb4302d82ce9210a63f3f55b1e05f04759c1" @@ -2765,6 +3406,28 @@ qs "^6.6.0" util-deprecate "^1.0.2" +"@storybook/client-api@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.8.tgz#1de791f7888442287f848e5f544eb883c5edc0da" + integrity sha512-OCKhZ+2sS3ot0ZV48nD79BWVzvvdMjUFYl0073ps5q+1+TLic1AlNmH0Sb5/9NrYXNV86v3VrM2jUbGsKe1qyw== + dependencies: + "@storybook/addons" "5.2.8" + "@storybook/channel-postmessage" "5.2.8" + "@storybook/channels" "5.2.8" + "@storybook/client-logger" "5.2.8" + "@storybook/core-events" "5.2.8" + "@storybook/router" "5.2.8" + common-tags "^1.8.0" + core-js "^3.0.1" + eventemitter3 "^4.0.0" + global "^4.3.2" + is-plain-object "^3.0.0" + lodash "^4.17.15" + memoizerific "^1.11.3" + qs "^6.6.0" + stable "^0.1.8" + util-deprecate "^1.0.2" + "@storybook/client-logger@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.6.tgz#cfc4536e9b724b086f7509c2bb34c221016713c9" @@ -2772,6 +3435,13 @@ dependencies: core-js "^3.0.1" +"@storybook/client-logger@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.8.tgz#5affe2f9dbbee374721fd2e8729116f5ac39c779" + integrity sha512-+oVSEJdeh7TQ1Bhanb3mCr7fc3Bug3+K79abZ28J45Ub5x4L/ZVClj1xMgUsJs30BZ5FB8vhdgH6TQb0NSxR4A== + dependencies: + core-js "^3.0.1" + "@storybook/components@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.6.tgz#cddb60227720aea7cae34fe782d0370bcdbd4005" @@ -2797,6 +3467,31 @@ react-textarea-autosize "^7.1.0" simplebar-react "^1.0.0-alpha.6" +"@storybook/components@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.8.tgz#f5d4a06ba4ba8c700b2d962deae182105b72fb99" + integrity sha512-h9l/LAMaj+emUCOyY/+ETy/S3P0npwQU280J88uL4O9XJALJ72EKfyttBCvMLvpM50E+fAPeDzuYn0t5qzGGxg== + dependencies: + "@storybook/client-logger" "5.2.8" + "@storybook/theming" "5.2.8" + "@types/react-syntax-highlighter" "10.1.0" + "@types/react-textarea-autosize" "^4.3.3" + core-js "^3.0.1" + global "^4.3.2" + markdown-to-jsx "^6.9.1" + memoizerific "^1.11.3" + polished "^3.3.1" + popper.js "^1.14.7" + prop-types "^15.7.2" + react "^16.8.3" + react-dom "^16.8.3" + react-focus-lock "^1.18.3" + react-helmet-async "^1.0.2" + react-popper-tooltip "^2.8.3" + react-syntax-highlighter "^8.0.1" + react-textarea-autosize "^7.1.0" + simplebar-react "^1.0.0-alpha.6" + "@storybook/core-events@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.6.tgz#34c9aae256e7e5f4a565b81f1e77dda8bccc6752" @@ -2804,6 +3499,13 @@ dependencies: core-js "^3.0.1" +"@storybook/core-events@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.8.tgz#93fc458ea0820ff1409d268b0fe51abba200f5a4" + integrity sha512-NkQKC5doO/YL9gsO61bqaxgveKktkiJWZ3XyyhL1ZebgnO9wTlrU+i9b5aX73Myk1oxbicQw9KcwDGYk0qFuNQ== + dependencies: + core-js "^3.0.1" + "@storybook/core@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.6.tgz#60c092607158d7d28db59f7e67da4f7e12703fb2" @@ -2878,6 +3580,80 @@ webpack-dev-middleware "^3.7.0" webpack-hot-middleware "^2.25.0" +"@storybook/core@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.8.tgz#3f6ddbacc705c1893deb15582c3a0a1ecd882cd1" + integrity sha512-P1Xx4setLBESPgS5KgL7Jskf5Q6fRa3ApwPt+ocjDoSDGCvsV7cUEpAp09U65u+89e5K4nQxvaZouhknFQBc1A== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.7.0" + "@babel/plugin-proposal-object-rest-spread" "^7.6.2" + "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/plugin-transform-react-constant-elements" "^7.6.3" + "@babel/preset-env" "^7.7.1" + "@storybook/addons" "5.2.8" + "@storybook/channel-postmessage" "5.2.8" + "@storybook/client-api" "5.2.8" + "@storybook/client-logger" "5.2.8" + "@storybook/core-events" "5.2.8" + "@storybook/node-logger" "5.2.8" + "@storybook/router" "5.2.8" + "@storybook/theming" "5.2.8" + "@storybook/ui" "5.2.8" + airbnb-js-shims "^1 || ^2" + ansi-to-html "^0.6.11" + autoprefixer "^9.4.9" + babel-plugin-add-react-displayname "^0.0.5" + babel-plugin-emotion "^10.0.14" + babel-plugin-macros "^2.4.5" + babel-preset-minify "^0.5.0 || 0.6.0-alpha.5" + boxen "^3.0.0" + case-sensitive-paths-webpack-plugin "^2.2.0" + chalk "^2.4.2" + cli-table3 "0.5.1" + commander "^2.19.0" + common-tags "^1.8.0" + core-js "^3.0.1" + corejs-upgrade-webpack-plugin "^2.2.0" + css-loader "^3.0.0" + detect-port "^1.3.0" + dotenv-webpack "^1.7.0" + ejs "^2.6.1" + express "^4.17.0" + file-loader "^3.0.1" + file-system-cache "^1.0.5" + find-cache-dir "^3.0.0" + fs-extra "^8.0.1" + global "^4.3.2" + html-webpack-plugin "^4.0.0-beta.2" + inquirer "^6.2.0" + interpret "^1.2.0" + ip "^1.1.5" + json5 "^2.1.0" + lazy-universal-dotenv "^3.0.1" + node-fetch "^2.6.0" + open "^6.1.0" + pnp-webpack-plugin "1.4.3" + postcss-flexbugs-fixes "^4.1.0" + postcss-loader "^3.0.0" + pretty-hrtime "^1.0.3" + qs "^6.6.0" + raw-loader "^2.0.0" + react-dev-utils "^9.0.0" + regenerator-runtime "^0.12.1" + resolve "^1.11.0" + resolve-from "^5.0.0" + semver "^6.0.0" + serve-favicon "^2.5.0" + shelljs "^0.8.3" + style-loader "^0.23.1" + terser-webpack-plugin "^1.2.4" + unfetch "^4.1.0" + url-loader "^2.0.1" + util-deprecate "^1.0.2" + webpack "^4.33.0" + webpack-dev-middleware "^3.7.0" + webpack-hot-middleware "^2.25.0" + "@storybook/node-logger@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.6.tgz#e353aff14375bef9e922c217a0afb50f93e2ceb1" @@ -2889,6 +3665,17 @@ pretty-hrtime "^1.0.3" regenerator-runtime "^0.12.1" +"@storybook/node-logger@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.8.tgz#4a3df21d731014d54b9ca53d5b9a72dd350bb075" + integrity sha512-3TK5mx6VWbfJO+WUrqwPhTbTQ4qESTnwJY/02xPzOhvuC6tIG1QOxzi+Rq6rFlwxTpUuWh6iyDYnGIqFFQywkA== + dependencies: + chalk "^2.4.2" + core-js "^3.0.1" + npmlog "^4.1.2" + pretty-hrtime "^1.0.3" + regenerator-runtime "^0.12.1" + "@storybook/react@^5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.6.tgz#e61c0ed184add9e715191649ddb995eead756a90" @@ -2917,6 +3704,34 @@ semver "^6.0.0" webpack "^4.33.0" +"@storybook/react@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.8.tgz#8d44c2d34caa1d7d748ec1fc9cf0fe2a88b001f9" + integrity sha512-T1DoWpSz33vaGx85Dh7q2KYetg7dQyiYhuOnZm2WxZTFZOw1jP62si53JGFp0PKxnT6iOBLHo3v2QkRkjt2mdQ== + dependencies: + "@babel/plugin-transform-react-constant-elements" "^7.6.3" + "@babel/preset-flow" "^7.0.0" + "@babel/preset-react" "^7.7.0" + "@storybook/addons" "5.2.8" + "@storybook/core" "5.2.8" + "@storybook/node-logger" "5.2.8" + "@svgr/webpack" "^4.0.3" + "@types/webpack-env" "^1.13.7" + babel-plugin-add-react-displayname "^0.0.5" + babel-plugin-named-asset-import "^0.3.1" + babel-plugin-react-docgen "^3.0.0" + babel-preset-react-app "^9.0.0" + common-tags "^1.8.0" + core-js "^3.0.1" + global "^4.3.2" + lodash "^4.17.15" + mini-css-extract-plugin "^0.7.0" + prop-types "^15.7.2" + react-dev-utils "^9.0.0" + regenerator-runtime "^0.12.1" + semver "^6.0.0" + webpack "^4.33.0" + "@storybook/router@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.6.tgz#5180d3785501699283c6c3717986c877f84fead5" @@ -2930,6 +3745,19 @@ memoizerific "^1.11.3" qs "^6.6.0" +"@storybook/router@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.8.tgz#d7de2d401701857c033e28560c30e16512f7f72f" + integrity sha512-wnbyKESUMyv9fwo9W+n4Fev/jXylB8whpjtHrOttjguUOYX1zGSHdwNI66voPetbtVLxUeHyJteJwdyRDSirJg== + dependencies: + "@reach/router" "^1.2.1" + "@types/reach__router" "^1.2.3" + core-js "^3.0.1" + global "^4.3.2" + lodash "^4.17.15" + memoizerific "^1.11.3" + qs "^6.6.0" + "@storybook/theming@5.2.6", "@storybook/theming@^5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.6.tgz#e04170b3e53dcfc791b2381c8a39192ae88cd291" @@ -2948,6 +3776,24 @@ prop-types "^15.7.2" resolve-from "^5.0.0" +"@storybook/theming@5.2.8", "@storybook/theming@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.8.tgz#a4c9e0e9a5789c1aa71e4fcb7a8ee86efe3dadcf" + integrity sha512-rGb66GkXb0jNJMH8UQ3Ru4FL+m1x0+UdxM8a8HSE/qb1GMv2qOwjVETfAL6nVL9u6ZmrtbhHoero4f6xDwZdRg== + dependencies: + "@emotion/core" "^10.0.14" + "@emotion/styled" "^10.0.14" + "@storybook/client-logger" "5.2.8" + common-tags "^1.8.0" + core-js "^3.0.1" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.14" + global "^4.3.2" + memoizerific "^1.11.3" + polished "^3.3.1" + prop-types "^15.7.2" + resolve-from "^5.0.0" + "@storybook/ui@5.2.6": version "5.2.6" resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.6.tgz#33df2f2e03d9cf81dc52928a0dc4db280ee8f56a" @@ -2987,6 +3833,45 @@ telejson "^3.0.2" util-deprecate "^1.0.2" +"@storybook/ui@5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.8.tgz#da8afca9eb29a40ef3ddc6a9f6e76d7a3344f2ef" + integrity sha512-7t1ARBfylhEsLmGsZBUCj1Wf1oAgCDDrf7fi+Fhdg5Rr16CMoBbe24Gv/mPYv01/pUDhGodxzltKGX5x0Hto2w== + dependencies: + "@storybook/addons" "5.2.8" + "@storybook/api" "5.2.8" + "@storybook/channels" "5.2.8" + "@storybook/client-logger" "5.2.8" + "@storybook/components" "5.2.8" + "@storybook/core-events" "5.2.8" + "@storybook/router" "5.2.8" + "@storybook/theming" "5.2.8" + copy-to-clipboard "^3.0.8" + core-js "^3.0.1" + core-js-pure "^3.0.1" + emotion-theming "^10.0.14" + fast-deep-equal "^2.0.1" + fuse.js "^3.4.4" + global "^4.3.2" + lodash "^4.17.15" + markdown-to-jsx "^6.9.3" + memoizerific "^1.11.3" + polished "^3.3.1" + prop-types "^15.7.2" + qs "^6.6.0" + react "^16.8.3" + react-dom "^16.8.3" + react-draggable "^4.0.3" + react-helmet-async "^1.0.2" + react-hotkeys "2.0.0-pre4" + react-sizeme "^2.6.7" + regenerator-runtime "^0.13.2" + resolve-from "^5.0.0" + semver "^6.0.0" + store2 "^2.7.1" + telejson "^3.0.2" + util-deprecate "^1.0.2" + "@svgr/babel-plugin-add-jsx-attribute@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" @@ -3513,6 +4398,13 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.0.tgz#cbb49815a5e1129d5f23836a98d65d93822409af" integrity sha512-dxdRrUov2HVTbSRFX+7xwUPlbGYVEZK6PrSqClg2QPos3PNe0bCajkDDkDeeC1znjSH03KOEqVbXpnJuWa2wgQ== +"@types/flot@^0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@types/flot/-/flot-0.0.31.tgz#0daca37c6c855b69a0a7e2e37dd0f84b3db8c8c1" + integrity sha512-X+RcMQCqPlQo8zPT6cUFTd/PoYBShMQlHUeOXf05jWlfYnvLuRmluB9z+2EsOKFgUzqzZve5brx+gnFxBaHEUw== + dependencies: + "@types/jquery" "*" + "@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" @@ -3716,7 +4608,7 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" integrity sha512-JxZ0NP8NuB0BJOXi1KvAA6rySLTPmhOy4n2gzSFq/IFM3LNFm0h+2Vn/bPPgEYlWqzS2NPeLgKqfm75baX+Hog== -"@types/jquery@^3.3.31": +"@types/jquery@*", "@types/jquery@^3.3.31": version "3.3.31" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg== @@ -4787,6 +5679,11 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-dynamic-import@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" + integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== + acorn-globals@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" @@ -4861,6 +5758,11 @@ acorn@^6.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== +acorn@^6.0.5: + version "6.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" + integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== + acorn@^6.2.1: version "6.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" @@ -7322,6 +8224,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.0.3: + version "5.4.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115" + integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + buffer@^5.1.0, buffer@^5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" @@ -7385,7 +8295,7 @@ cac@^4.3.4: string-width "^2.1.1" text-table "^0.2.0" -cacache@^11.3.3: +cacache@^11.3.2, cacache@^11.3.3: version "11.3.3" resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc" integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA== @@ -8014,7 +8924,7 @@ chroma-js@^2.0.4: dependencies: cross-env "^6.0.3" -chrome-trace-event@^1.0.2: +chrome-trace-event@^1.0.0, chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== @@ -8930,6 +9840,24 @@ copy-to-clipboard@^3.2.0: dependencies: toggle-selection "^1.0.6" +copy-webpack-plugin@5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.3.tgz#2179e3c8fd69f13afe74da338896f1f01a875b5c" + integrity sha512-PlZRs9CUMnAVylZq+vg2Juew662jWtwOXOqH4lbQD9ZFhRG9R7tVStOgHt21CBGVq7k5yIJaz8TXDLSjV+Lj8Q== + dependencies: + cacache "^11.3.2" + find-cache-dir "^2.1.0" + glob-parent "^3.1.0" + globby "^7.1.1" + is-glob "^4.0.1" + loader-utils "^1.2.3" + minimatch "^3.0.4" + normalize-path "^3.0.0" + p-limit "^2.2.0" + schema-utils "^1.0.0" + serialize-javascript "^1.7.0" + webpack-log "^2.0.0" + copy-webpack-plugin@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655" @@ -9145,6 +10073,14 @@ create-react-context@^0.2.1: fbjs "^0.8.0" gud "^1.0.0" +create-react-context@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" + integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== + dependencies: + gud "^1.0.0" + warning "^4.0.3" + cronstrue@^1.51.0: version "1.51.0" resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-1.51.0.tgz#7a63153d61d940344049037628da38a60784c8e2" @@ -9382,6 +10318,15 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" +css-to-react-native@^2.0.3: + version "2.3.2" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.3.2.tgz#e75e2f8f7aa385b4c3611c52b074b70a002f2e7d" + integrity sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^3.3.0" + css-to-react-native@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" @@ -10066,6 +11011,18 @@ deep-equal@^1.0.0, deep-equal@^1.0.1, deep-equal@~1.0.1: resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= +deep-equal@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + deep-extend@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -11309,7 +12266,7 @@ es6-promise-pool@^2.5.0: resolved "https://registry.yarnpkg.com/es6-promise-pool/-/es6-promise-pool-2.5.0.tgz#147c612b36b47f105027f9d2bf54a598a99d9ccb" integrity sha1-FHxhKza0fxBQJ/nSv1SlmKmdnMs= -es6-promise@^4.0.3, es6-promise@~4.2.4: +es6-promise@^4.0.3: version "4.2.4" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ== @@ -11650,7 +12607,7 @@ eslint-rule-composer@^0.3.0: resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== -eslint-scope@^4.0.3: +eslint-scope@^4.0.0, eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== @@ -11934,6 +12891,19 @@ execa@0.10.0, execa@^0.10.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@1.0.0, execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + execa@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-0.1.1.tgz#b09c2a9309bc0ef0501479472db3180f8d4c3edd" @@ -11981,19 +12951,6 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/execa/-/execa-3.2.0.tgz#18326b79c7ab7fbd6610fd900c1b9e95fa48f90a" @@ -12371,17 +13328,7 @@ fast-equals@^2.0.0: resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.0.tgz#bef2c423af3939f2c54310df54c57e64cd2adefc" integrity sha512-u6RBd8cSiLLxAiC04wVsLV6GBFDOXcTCgWkd3wEoFXgidPSoAJENqC9m7Jb2vewSvjBIfXV6icKeh3GTKfIaXA== -fast-glob@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.0.4.tgz#a4b9f49e36175f5ef1a3456f580226a6e7abcc9e" - integrity sha512-JAh0y6ScChRmATdQIsN416LK+bAFiGczD9A4zWBMPcTgkpj9SEOC7DEzpfbqoDKzieZw40dIAKx3PofGxukFqw== - dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - glob-parent "3.1.0" - merge2 "1.2.1" - micromatch "3.1.5" - -fast-glob@^2.2.6: +fast-glob@2.2.7, fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== @@ -12393,6 +13340,16 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.0.4.tgz#a4b9f49e36175f5ef1a3456f580226a6e7abcc9e" + integrity sha512-JAh0y6ScChRmATdQIsN416LK+bAFiGczD9A4zWBMPcTgkpj9SEOC7DEzpfbqoDKzieZw40dIAKx3PofGxukFqw== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + glob-parent "3.1.0" + merge2 "1.2.1" + micromatch "3.1.5" + fast-glob@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.0.4.tgz#d484a41005cb6faeb399b951fd1bd70ddaebb602" @@ -13706,7 +14663,7 @@ glob-to-regexp@^0.4.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.0.tgz#49bd677b1671022bd10921c3788f23cdebf9c7e6" integrity sha512-fyPCII4vn9Gvjq2U/oDAfP433aiE64cyP/CJjRJcpVGjqqNdioUYn9+r0cSzT1XPwmGAHuTT7iv+rQT8u/YHKQ== -glob-watcher@^5.0.3: +glob-watcher@5.0.3, glob-watcher@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== @@ -15899,10 +16856,10 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== -io-ts@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.0.1.tgz#1261c12f915c2f48d16393a36966636b48a45aa1" - integrity sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ== +io-ts@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.0.5.tgz#e6e3db9df8b047f9cbd6b69e7d2ad3e6437a0b13" + integrity sha512-pL7uUptryanI5Glv+GUv7xh+aLBjxGEDmLwmEYNSx0yOD3djK0Nw5Bt0N6BAkv9LadOUU7QKpRsLcqnTh3UlLA== ip-regex@^2.1.0: version "2.1.0" @@ -15978,6 +16935,11 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -17161,13 +18123,20 @@ jest-snapshot@^24.1.0, jest-snapshot@^24.9.0: pretty-format "^24.9.0" semver "^6.2.0" -jest-specific-snapshot@^2.0.0: +jest-specific-snapshot@2.0.0, jest-specific-snapshot@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-2.0.0.tgz#425fe524b25df154aa39f97fa6fe9726faaac273" integrity sha512-aXaNqBg/svwEpY5iQEzEHc5I85cUBKgfeVka9KmpznxLnatpjiqjr7QLb/BYNYlsrZjZzgRHTjQJ+Svx+dbdvg== dependencies: jest-snapshot "^24.1.0" +jest-styled-components@6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-6.3.1.tgz#fa21a89bfe8c20081c7c083cbaed2200854b60e3" + integrity sha512-zie3ajvJbwlbHCAq8/Bv5jdbcYCz0ZMRNNX6adL7wSRpkCVPQtiJigv1140JN1ZOJIODPn8VKrjeFCN+jlPa7w== + dependencies: + css "^2.2.4" + jest-styled-components@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-7.0.0.tgz#3e1b7dcd077800ea5191e1cc95b314917a2ed668" @@ -18316,7 +19285,7 @@ load-source-map@^1.0.0: semver "^5.3.0" source-map "^0.5.6" -loader-runner@^2.3.1, loader-runner@^2.4.0: +loader-runner@^2.3.0, loader-runner@^2.3.1, loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== @@ -19242,7 +20211,7 @@ memory-fs@^0.2.0: resolved "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" integrity sha1-8rslNovBIeORwlIN6Slpyu4KApA= -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -19346,7 +20315,7 @@ microevent.ts@~0.1.1: resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== -micromatch@3.1.10, micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: +micromatch@3.1.10, micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -19510,20 +20479,20 @@ mini-create-react-context@^0.3.0: gud "^1.0.0" tiny-warning "^1.0.2" -mini-css-extract-plugin@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1" - integrity sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw== +mini-css-extract-plugin@0.7.0, mini-css-extract-plugin@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz#5ba8290fbb4179a43dd27cca444ba150bee743a0" + integrity sha512-RQIw6+7utTYn8DBGsf/LpRgZCJMpZt+kuawJ/fju0KiOL6nAaTBNmCJwS7HtwSCXfS47gCkmtBFS7HdsquhdxQ== dependencies: loader-utils "^1.1.0" normalize-url "1.9.1" schema-utils "^1.0.0" webpack-sources "^1.1.0" -mini-css-extract-plugin@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz#5ba8290fbb4179a43dd27cca444ba150bee743a0" - integrity sha512-RQIw6+7utTYn8DBGsf/LpRgZCJMpZt+kuawJ/fju0KiOL6nAaTBNmCJwS7HtwSCXfS47gCkmtBFS7HdsquhdxQ== +mini-css-extract-plugin@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1" + integrity sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw== dependencies: loader-utils "^1.1.0" normalize-url "1.9.1" @@ -20199,11 +21168,6 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" -node-ensure@^0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" - integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc= - node-environment-flags@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" @@ -20323,7 +21287,7 @@ node-jose@1.1.0: util "^0.11.0" vm-browserify "0.0.4" -node-libs-browser@^2.2.1: +node-libs-browser@^2.0.0, node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== @@ -20483,6 +21447,11 @@ normalize-package-data@^2.5.0: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-path@3.0.0, normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -20490,11 +21459,6 @@ normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" @@ -21801,21 +22765,6 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pdf-image@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pdf-image/-/pdf-image-2.0.0.tgz#f134296876c3d5aacb6bb5805ad15d0a46775fd5" - integrity sha512-RyB8f/Khw8wZt2xXGWtQu/5ZyKl4pzNSi9/dnYNJOuGnxRiZvmUVmz6xmG6Xts53k8ZZaOBjDwoT4jjAVzIoPQ== - dependencies: - es6-promise "~4.2.4" - -pdfjs-dist@^2.0.943: - version "2.0.943" - resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.0.943.tgz#32fb9a2d863df5a1d89521a0b3cd900c16e7edde" - integrity sha512-iLhNcm4XceTHRaSU5o22ZGCm4YpuW5+rf4+BJFH/feBhMQLbCGBry+Jet8Q419QDI4qgARaIQzXuiNrsNWS8Yw== - dependencies: - node-ensure "^0.0.0" - worker-loader "^2.0.0" - pdfkit@>=0.8.1, pdfkit@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/pdfkit/-/pdfkit-0.11.0.tgz#9cdb2fc42bd2913587fe3ddf48cc5bbb3c36f7de" @@ -22436,7 +23385,7 @@ prop-types@15.6.1: loose-envify "^1.3.1" object-assign "^4.1.1" -prop-types@15.7.2, prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@15.7.2, prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -23175,6 +24124,15 @@ react-dev-utils@^9.0.0: strip-ansi "5.2.0" text-table "0.2.0" +react-docgen-typescript-loader@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/react-docgen-typescript-loader/-/react-docgen-typescript-loader-3.1.0.tgz#09cacf872617c97f946ee920d2239f51d543be41" + integrity sha512-gY+b7RkRPty5ZN4NMQ+jwx9MzTVuIj6LJCwdWRAi1+nrHJfH2gMMytQfxFdzQ7BlgD4COWnSE8Ixtl2L62kCRw== + dependencies: + "@webpack-contrib/schema-utils" "^1.0.0-beta.0" + loader-utils "^1.2.3" + react-docgen-typescript "^1.12.3" + react-docgen-typescript-loader@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/react-docgen-typescript-loader/-/react-docgen-typescript-loader-3.1.1.tgz#c1992538524fb9e45246d6c1314ddcfbf26e9d08" @@ -23350,6 +24308,11 @@ react-is@^16.10.2, react-is@^16.9.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa" integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw== +react-is@^16.3.1: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" + integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== + react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" @@ -23457,6 +24420,14 @@ react-onclickoutside@^6.7.1: resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.8.0.tgz#9f91b5b3ed59f4d9e43fd71620dc200773a4d569" integrity sha512-5Q4Rn7QLEoh7WIe66KFvYIpWJ49GeHoygP1/EtJyZjXKgrWH19Tf0Ty3lWyQzrEEDyLOwUvvmBFSE3dcDdvagA== +react-popper-tooltip@^2.10.1: + version "2.10.1" + resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-2.10.1.tgz#e10875f31916297c694d64a677d6f8fa0a48b4d1" + integrity sha512-cib8bKiyYcrIlHo9zXx81G0XvARfL8Jt+xum709MFCgQa3HTqTi4au3iJ9tm7vi7WU7ngnqbpWkMinBOtwo+IQ== + dependencies: + "@babel/runtime" "^7.7.4" + react-popper "^1.3.6" + react-popper-tooltip@^2.8.3: version "2.8.3" resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-2.8.3.tgz#1c63e7473a96362bd93be6c94fa404470a265197" @@ -23485,6 +24456,19 @@ react-popper@^1.3.3: typed-styles "^0.0.7" warning "^4.0.2" +react-popper@^1.3.6: + version "1.3.7" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324" + integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww== + dependencies: + "@babel/runtime" "^7.1.2" + create-react-context "^0.3.0" + deep-equal "^1.1.1" + popper.js "^1.14.4" + prop-types "^15.6.1" + typed-styles "^0.0.7" + warning "^4.0.2" + react-portal@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-3.2.0.tgz#4224e19b2b05d5cbe730a7ba0e34ec7585de0043" @@ -23709,6 +24693,14 @@ react-textarea-autosize@^7.1.0: "@babel/runtime" "^7.1.2" prop-types "^15.6.0" +react-textarea-autosize@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda" + integrity sha512-uH3ORCsCa3C6LHxExExhF4jHoXYCQwE5oECmrRsunlspaDAbS4mGKNlWZqjLfInWtFQcf0o1n1jC/NGXFdUBCg== + dependencies: + "@babel/runtime" "^7.1.2" + prop-types "^15.6.0" + react-tiny-virtual-list@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/react-tiny-virtual-list/-/react-tiny-virtual-list-2.2.0.tgz#eafb6fcf764e4ed41150ff9752cdaad8b35edf4a" @@ -24233,6 +25225,13 @@ regenerate-unicode-properties@^8.0.2: dependencies: regenerate "^1.4.0" +regenerate-unicode-properties@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" + integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== + dependencies: + regenerate "^1.4.0" + regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" @@ -24329,6 +25328,18 @@ regexpu-core@^4.5.4: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.1.0" +regexpu-core@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" + integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.1.0" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.1.0" + registry-auth-token@^3.0.1, registry-auth-token@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" @@ -25167,6 +26178,13 @@ rxjs-marbles@^5.0.3: dependencies: fast-equals "^2.0.0" +rxjs@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" + integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg== + dependencies: + tslib "^1.9.0" + rxjs@^5.0.0-beta.11, rxjs@^5.5.2: version "5.5.12" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" @@ -25356,7 +26374,7 @@ schema-utils@^0.3.0: dependencies: ajv "^5.0.0" -schema-utils@^0.4.0, schema-utils@^0.4.5: +schema-utils@^0.4.5: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== @@ -25582,7 +26600,7 @@ sentence-case@^2.1.0: no-case "^2.2.0" upper-case-first "^1.1.2" -serialize-javascript@^1.7.0, serialize-javascript@^2.1.0, serialize-javascript@^2.1.1: +serialize-javascript@^1.7.0, serialize-javascript@^2.1.0, serialize-javascript@^2.1.1, serialize-javascript@^2.1.2: version "2.1.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.1.tgz#952907a04a3e3a75af7f73d92d15e233862048b2" integrity sha512-MPLPRpD4FNqWq9tTIjYG5LesFouDhdyH0EPY3gVK4DRD5+g4aDqdNSzLIwceulo3Yj+PL1bPh6laE5+H6LTcrQ== @@ -25851,6 +26869,14 @@ simplebar-react@^1.0.0-alpha.6: prop-types "^15.6.1" simplebar "^4.2.0" +simplebar-react@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/simplebar-react/-/simplebar-react-2.1.0.tgz#57d524f4253579d81ac30db00acf7886b17bf826" + integrity sha512-UIMFPNkn6o57v058vPOiYbnbpc1CUZwPKLmQaDMvEJdgm+btZ2umFA6meXfiqFEQUjDE6Vq4ePnL7Fr6nzJd8w== + dependencies: + prop-types "^15.6.1" + simplebar "^5.1.0" + simplebar@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/simplebar/-/simplebar-4.2.0.tgz#97e5c1c85d05cc04f8c92939e4da71dd087e325c" @@ -25863,6 +26889,18 @@ simplebar@^4.2.0: lodash.throttle "^4.1.1" resize-observer-polyfill "^1.5.1" +simplebar@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/simplebar/-/simplebar-5.1.0.tgz#15437ace314ec888accd7d8f24ada672e9bb2717" + integrity sha512-bdi1SleK1YOSnfeUjo5UQXt/79zNjsCJVEfzrm6photmGi2aU6x0l7rX4KAGcrtj5AwsWPBVXgDyYAqbbpnuRg== + dependencies: + can-use-dom "^0.1.0" + core-js "^3.0.1" + lodash.debounce "^4.0.8" + lodash.memoize "^4.1.2" + lodash.throttle "^4.1.1" + resize-observer-polyfill "^1.5.1" + simplicial-complex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/simplicial-complex/-/simplicial-complex-1.0.0.tgz#6c33a4ed69fcd4d91b7bcadd3b30b63683eae241" @@ -26935,6 +27973,21 @@ style-loader@0.23.1, style-loader@^0.23.1: loader-utils "^1.1.0" schema-utils "^1.0.0" +styled-components@^3: + version "3.4.10" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.4.10.tgz#9a654c50ea2b516c36ade57ddcfa296bf85c96e1" + integrity sha512-TA8ip8LoILgmSAFd3r326pKtXytUUGu5YWuqZcOQVwVVwB6XqUMn4MHW2IuYJ/HAD81jLrdQed8YWfLSG1LX4Q== + dependencies: + buffer "^5.0.3" + css-to-react-native "^2.0.3" + fbjs "^0.8.16" + hoist-non-react-statics "^2.5.0" + prop-types "^15.5.4" + react-is "^16.3.1" + stylis "^3.5.0" + stylis-rule-sheet "^0.0.10" + supports-color "^3.2.3" + styled-components@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.0.0.tgz#fc59932c9b574571fa3cd462c862af1956114ff2" @@ -26951,11 +28004,21 @@ styled-components@^5.0.0: shallowequal "^1.1.0" supports-color "^5.5.0" +stylis-rule-sheet@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" + integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== + stylis@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== +stylis@^3.5.0: + version "3.5.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" + integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== + stylus-lookup@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-3.0.2.tgz#c9eca3ff799691020f30b382260a67355fefdddd" @@ -27064,7 +28127,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^3.1.0, supports-color@^3.1.2: +supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= @@ -27400,6 +28463,21 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" +terser-webpack-plugin@^1.1.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^2.1.2" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + terser-webpack-plugin@^1.2.4, terser-webpack-plugin@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" @@ -29950,14 +31028,14 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" -warning@^4.0.2: +warning@^4.0.2, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== dependencies: loose-envify "^1.0.0" -watchpack@^1.6.0: +watchpack@^1.5.0, watchpack@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== @@ -30117,7 +31195,7 @@ webpack-sources@^1.1.0: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: +webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -30125,6 +31203,36 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" +webpack@4.34.0: + version "4.34.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.34.0.tgz#a4c30129482f7b4ece4c0842002dedf2b56fab58" + integrity sha512-ry2IQy1wJjOefLe1uJLzn5tG/DdIKzQqNlIAd2L84kcaADqNvQDTBlo8UcCNyDaT5FiaB+16jhAkb63YeG3H8Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + acorn "^6.0.5" + acorn-dynamic-import "^4.0.0" + ajv "^6.1.0" + ajv-keywords "^3.1.0" + chrome-trace-event "^1.0.0" + enhanced-resolve "^4.1.0" + eslint-scope "^4.0.0" + json-parse-better-errors "^1.0.2" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + micromatch "^3.1.8" + mkdirp "~0.5.0" + neo-async "^2.5.0" + node-libs-browser "^2.0.0" + schema-utils "^1.0.0" + tapable "^1.1.0" + terser-webpack-plugin "^1.1.0" + watchpack "^1.5.0" + webpack-sources "^1.3.0" + webpack@4.41.0, webpack@^4.33.0, webpack@^4.38.0, webpack@^4.41.0: version "4.41.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b" @@ -30394,14 +31502,6 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" -worker-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac" - integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw== - dependencies: - loader-utils "^1.0.0" - schema-utils "^0.4.0" - worker-rpc@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"