diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3142e0ff97749..1137fb99f81a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,6 +8,16 @@ /src/plugins/share/ @elastic/kibana-app /src/legacy/server/url_shortening/ @elastic/kibana-app /src/legacy/server/sample_data/ @elastic/kibana-app +/src/legacy/core_plugins/kibana/public/dashboard/ @elastic/kibana-app +/src/legacy/core_plugins/kibana/public/discover/ @elastic/kibana-app +/src/legacy/core_plugins/kibana/public/visualize/ @elastic/kibana-app +/src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app +/src/legacy/core_plugins/kibana/public/home/ @elastic/kibana-app +/src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app +/src/plugins/home/ @elastic/kibana-app +/src/plugins/kibana_legacy/ @elastic/kibana-app +/src/plugins/timelion/ @elastic/kibana-app +/src/plugins/dev_tools/ @elastic/kibana-app # App Architecture /src/plugins/data/ @elastic/kibana-app-arch @@ -57,6 +67,13 @@ /x-pack/test/functional/services/transform_ui/ @elastic/ml-ui /x-pack/test/functional/services/transform.ts @elastic/ml-ui +# Maps +/x-pack/legacy/plugins/maps/ @elastic/kibana-gis +/x-pack/test/api_integration/apis/maps/ @elastic/kibana-gis +/x-pack/test/functional/apps/maps/ @elastic/kibana-gis +/x-pack/test/functional/es_archives/maps/ @elastic/kibana-gis +/x-pack/test/visual_regression/tests/maps/index.js @elastic/kibana-gis + # Operations /src/dev/ @elastic/kibana-operations /src/setup_node_env/ @elastic/kibana-operations @@ -109,6 +126,9 @@ /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 # Design **/*.scss @elastic/kibana-design diff --git a/.github/workflows/pr-project-assigner.yml b/.github/workflows/pr-project-assigner.yml index aea8a9cad6b1f..59123731dce66 100644 --- a/.github/workflows/pr-project-assigner.yml +++ b/.github/workflows/pr-project-assigner.yml @@ -11,5 +11,11 @@ jobs: uses: elastic/github-actions/project-assigner@v1.0.0 id: project_assigner with: - issue-mappings: '[{"label": "Team:AppAch", "projectName": "kibana-app-arch", "columnId": 6173897}]' - ghToken: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + issue-mappings: | + [ + { "label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173897 }, + { "label": "Feature:Lens", "projectName": "Lens", "columnId": 6219362 }, + { "label": "Team:Platform", "projectName": "kibana-platform", "columnId": 5514360 }, + {"label": "Team:Canvas", "projectName": "canvas", "columnId": 6187580} + ] + ghToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/project-assigner.yml b/.github/workflows/project-assigner.yml index c7f17993249eb..aec3bf88f0ee2 100644 --- a/.github/workflows/project-assigner.yml +++ b/.github/workflows/project-assigner.yml @@ -11,7 +11,7 @@ jobs: uses: elastic/github-actions/project-assigner@v1.0.0 id: project_assigner with: - issue-mappings: '[{"label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173895}]' + issue-mappings: '[{"label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173895}, {"label": "Feature:Lens", "projectName": "Lens", "columnId": 6219363}, {"label": "Team:Canvas", "projectName": "canvas", "columnId": 6187593}]' ghToken: ${{ secrets.GITHUB_TOKEN }} - + diff --git a/.gitignore b/.gitignore index e7391a5c292d0..02b20da297fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ disabledPlugins webpackstats.json /config/* !/config/kibana.yml -!/config/apm.js coverage selenium .babel_register_cache.json diff --git a/.i18nrc.json b/.i18nrc.json index ddde50b62a8f3..4bc0f773ee8b5 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -11,6 +11,7 @@ "embeddableApi": "src/plugins/embeddable", "embeddableExamples": "examples/embeddable_examples", "share": "src/plugins/share", + "home": "src/plugins/home", "esUi": "src/plugins/es_ui_shared", "devTools": "src/plugins/dev_tools", "expressions": "src/plugins/expressions", @@ -21,7 +22,7 @@ "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", - "management": "src/legacy/core_plugins/management", + "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", "kibana_utils": "src/plugins/kibana_utils", diff --git a/.node-version b/.node-version index 95abd2ac49910..06c9b9d306348 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -10.15.2 +10.18.0 diff --git a/.nvmrc b/.nvmrc index 95abd2ac49910..06c9b9d306348 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -10.15.2 +10.18.0 diff --git a/config/apm.js b/config/apm.js deleted file mode 100644 index 0cfcd759f163b..0000000000000 --- a/config/apm.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. - */ - -/** - * DO NOT EDIT THIS FILE! - * - * This file contains the configuration for the Elastic APM instrumentaion of - * Kibana itself and is only intented to be used during development of Kibana. - * - * Instrumentation is turned off by default. Once activated it will send APM - * data to an Elasticsearch cluster accessible by Elastic employees. - * - * To modify the configuration, either use environment variables, or create a - * file named `config/apm.dev.js`, which exports a config object as described - * in the docs. - * - * For an overview over the available configuration files, see: - * https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html - * - * For general information about Elastic APM, see: - * https://www.elastic.co/guide/en/apm/get-started/current/index.html - */ - -const { readFileSync } = require('fs'); -const { join } = require('path'); -const { execSync } = require('child_process'); -const merge = require('lodash.merge'); - -module.exports = merge( - { - active: false, - serverUrl: 'https://f1542b814f674090afd914960583265f.apm.us-central1.gcp.cloud.es.io:443', - // The secretToken below is intended to be hardcoded in this file even though - // it makes it public. This is not a security/privacy issue. Normally we'd - // instead disable the need for a secretToken in the APM Server config where - // the data is transmitted to, but due to how it's being hosted, it's easier, - // for now, to simply leave it in. - secretToken: 'R0Gjg46pE9K9wGestd', - globalLabels: {}, - centralConfig: false, - logUncaughtExceptions: true, - }, - devConfig() -); - -const rev = gitRev(); -if (rev !== null) module.exports.globalLabels.git_rev = rev; - -try { - const filename = join(__dirname, '..', 'data', 'uuid'); - module.exports.globalLabels.kibana_uuid = readFileSync(filename, 'utf-8'); -} catch (e) {} // eslint-disable-line no-empty - -function gitRev() { - try { - return execSync('git rev-parse --short HEAD', { - encoding: 'utf-8', - stdio: ['ignore', 'pipe', 'ignore'], - }).trim(); - } catch (e) { - return null; - } -} - -function devConfig() { - try { - return require('./apm.dev'); // eslint-disable-line import/no-unresolved - } catch (e) { - return {}; - } -} diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index 1f064c1cad3fd..942882f8c4dfb 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -1,9 +1,7 @@ [[advanced-queries]] === Advanced queries -When querying, you're simply searching and selecting data from fields in Elasticsearch documents. -It may be helpful to view some of your documents in {kibana-ref}/discover.html[Discover] to better understand how APM data is stored in Elasticsearch. - +When querying in the APM app, you're simply searching and selecting data from fields in Elasticsearch documents. Queries entered into the query bar are also added as parameters to the URL, so it's easy to share a specific query or view with others. @@ -13,11 +11,48 @@ In the screenshot below, you can begin to see some of the transaction fields ava image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] [float] -==== Example queries +==== Example APM app queries * Exclude response times slower than 2000 ms: `transaction.duration.us > 2000000` * Filter by response status code: `context.response.status_code >= 400` * Filter by single user ID: `context.user.id : 12` -* View _all_ transactions for an endpoint, instead of just a sample - `processor.event: "transaction" AND transaction.name: ""` TIP: Read the {kibana-ref}/kuery-query.html[Kibana Query Language Enhancements] documentation to learn more about the capabilities of the {kib} query language. + +[float] +[[discover-advanced-queries]] +=== Querying in the Discover app + +It may also be helpful to view your APM data in the {kibana-ref}/discover.html[Discover app]. +Querying documents in Discover works the same way as querying in the APM app, +and all of the example queries listed above can also be used in the Discover app. + +[float] +==== Example Discover app query + +One example where you may want to make use of the Discover app, +is for viewing _all_ transactions for an endpoint, instead of just a sample. + +TIP: Starting in v7.6, you can view 10 samples per bucket in the APM app, instead of just one. + +Use the APM app to find a transaction name and time bucket that you're interested in learning more about. +Then, switch to the Discover app and make a search: + +["source","sh"] +----- +processor.event: "transaction" AND transaction.name: "" and transaction.duration.us > 13000 and transaction.duration.us < 14000` +----- + +In this example, we're interested in viewing all of the `APIRestController#customers` transactions +that took between 13 and 14 milliseconds. Here's what Discover returns: + +[role="screenshot"] +image::apm/images/advanced-discover.png[View all transactions in bucket] + +You can now explore the data until you find a specific transaction that you're interested in. +Copy that transaction's `transaction.id`, and paste it into the APM app to view the data in the context of the APM app: + +[role="screenshot"] +image::apm/images/specific-transaction-search.png[View specific transaction in apm app] +[role="screenshot"] +image::apm/images/specific-transaction.png[View specific transaction in apm app] diff --git a/docs/apm/images/advanced-discover.png b/docs/apm/images/advanced-discover.png new file mode 100644 index 0000000000000..56ba58b2c1d41 Binary files /dev/null and b/docs/apm/images/advanced-discover.png differ diff --git a/docs/apm/images/specific-transaction-search.png b/docs/apm/images/specific-transaction-search.png new file mode 100644 index 0000000000000..4ed548f015713 Binary files /dev/null and b/docs/apm/images/specific-transaction-search.png differ diff --git a/docs/apm/images/specific-transaction.png b/docs/apm/images/specific-transaction.png new file mode 100644 index 0000000000000..9911dbd879f41 Binary files /dev/null and b/docs/apm/images/specific-transaction.png differ diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index c5c6f116ee34e..dc605a47de383 100644 --- a/docs/canvas/canvas-elements.asciidoc +++ b/docs/canvas/canvas-elements.asciidoc @@ -20,24 +20,24 @@ When you add elements to your workpad, you can: [[add-canvas-element]] === Add elements to your workpad -Choose the elements to display on your workpad, then familiarize yourself with the element using the preconfigured demo data. +Choose the elements to display on your workpad, then familiarize yourself with the element using the preconfigured demo data. By default, every element you add to a workpad uses demo data until you change the data source. The demo data includes a small sample data set that you can use to experiment with your element. . Click *Add element*. -. In the *Elements* window, select the element you want to use. +. In the *Elements* window, select the element you want to use. + [role="screenshot"] image::images/canvas-element-select.gif[Canvas elements] -. Play around with the default settings and see what the element can do. +. Play around with the default settings and see what the element can do. -TIP: Want to use a different element? You can delete the element by selecting it, clicking the *Element options* icon in the top right corner, then selecting *Delete*. +TIP: Want to use a different element? You can delete the element by selecting it, clicking the *Element options* icon in the top right, then selecting *Delete*. [float] [[connect-element-data]] === Connect the element to your data -When you are ready to move on from the demo data, connect the element to your own data. +When you have finished using the demo data, connect the element to a data source. . Make sure that the element is selected, then select *Data*. @@ -45,55 +45,51 @@ When you are ready to move on from the demo data, connect the element to your ow [float] [[elasticsearch-sql-data-source]] -==== Connect to Elasticsearch SQL +==== Connect to {es} SQL -Access your data in Elasticsearch using the Elasticsearch SQL syntax. +Access your data in {es} using SQL syntax. For information about SQL syntax, refer to {ref}/sql-spec.html[SQL language]. -Unfamiliar with writing Elasticsearch SQL queries? For more information, refer to {ref}/sql-spec.html[SQL language]. +. Click *{es} SQL*. -. Click *Elasticsearch SQL*. +. In the *{es} SQL query* box, enter your query, then *Preview* it. -. In the *Elasticearch SQL query* box, enter your query, then *Preview* it. - -. If everything looks correct, *Save* it. +. If everything looks correct, *Save* it. [float] [[elasticsearch-raw-doc-data-source]] -==== Connect to Elasticsearch raw data +==== Connect to {es} raw data -Use the Lucene query syntax to use your raw data in Elasticsearch. +Access your raw data in {es} without the use of aggregations. Use {es} raw data when you have low volume datasets, or to plot exact, non-aggregated values. -For for more information about the Lucene query string sytax, refer to <>. +To use targeted queries, you can enter a query using the <>. -. Click *Elasticsearch raw documents*. +. Click *{es} raw documents*. -. In the *Index* field, enter the index pattern that you want to display. +. In the *Index* field, enter the index pattern that you want to display. . From the *Fields* dropdown, select the associated fields you want to display. . To sort the data, select an option from the *Sort Field* and *Sort Order* dropdowns. -. For more targeted queries, enter a *Query* using the Lucene query string syntax. +. For more targeted queries, enter a *Query* using the Lucene query string syntax. -. *Preview* the query. +. *Preview* the query. -. If your query looks correct, *Save* it. +. If your query looks correct, *Save* it. [float] [[timelion-data-source]] ==== Connect to Timelion -Use <> queries to use your time series data. +Access your time series data using <> queries. To use Timelion queries, you can enter a query using the <>. . Click *Timelion*. -. Enter a *Query* using the Lucene query string syntax. -+ -For for more information about the Lucene query string syntax, refer to <>. +. Enter a *Query* using the Lucene query string syntax. . Enter the *Interval*, then *Preview* the query. -. If your query looks correct, *Save* it. +. If your query looks correct, *Save* it. [float] [[configure-display-options]] @@ -109,7 +105,7 @@ When you connect your element to a data source, the element often appears as a w . Click *Display* -. Change the display options for the element. +. Change the display options for the element. [float] [[element-display-container]] @@ -122,7 +118,7 @@ Further define the appearance of the element container and border. . Expand *Container style*. . Change the *Appearance* and *Border* options. - + [float] [[apply-element-styles]] ==== Apply a set of styles @@ -155,7 +151,7 @@ Increase or decrease how often your data refreshes on your workpad. [role="screenshot"] image::images/canvas-refresh-interval.png[Element data refresh interval] -TIP: To manually refresh the data, click the *Refresh data* icon. +TIP: To manually refresh the data, click the *Refresh data* icon. [float] [[organize-element]] @@ -223,7 +219,7 @@ Change the order of how the elements are displayed on your workpad. . Select an element. -. In the top right corder, click the *Element options* icon. +. In the top right corder, click the *Element options* icon. . Select *Order*, then select the order that you want the element to appear. @@ -262,7 +258,7 @@ When you have run out of room on your workpad page, add more pages. . Click *Page 1*, then click *+*. -. On the *Page* editor panel on the right, select the page transition from the *Transition* dropdown. +. On the *Page* editor panel on the right, select the page transition from the *Transition* dropdown. + [role="screenshot"] image::images/canvas-add-pages.gif[Add pages] diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.md b/docs/development/core/public/kibana-plugin-public.httpservicebase.md deleted file mode 100644 index 9ea77c95b343e..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.md +++ /dev/null @@ -1,37 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) - -## HttpServiceBase interface - - -Signature: - -```typescript -export interface HttpServiceBase -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [anonymousPaths](./kibana-plugin-public.httpservicebase.anonymouspaths.md) | IAnonymousPaths | APIs for denoting certain paths for not requiring authentication | -| [basePath](./kibana-plugin-public.httpservicebase.basepath.md) | IBasePath | APIs for manipulating the basePath on URL segments. | -| [delete](./kibana-plugin-public.httpservicebase.delete.md) | HttpHandler | Makes an HTTP request with the DELETE method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | -| [fetch](./kibana-plugin-public.httpservicebase.fetch.md) | HttpHandler | Makes an HTTP request. Defaults to a GET request unless overriden. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | -| [get](./kibana-plugin-public.httpservicebase.get.md) | HttpHandler | Makes an HTTP request with the GET method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | -| [head](./kibana-plugin-public.httpservicebase.head.md) | HttpHandler | Makes an HTTP request with the HEAD method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | -| [options](./kibana-plugin-public.httpservicebase.options.md) | HttpHandler | Makes an HTTP request with the OPTIONS method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | -| [patch](./kibana-plugin-public.httpservicebase.patch.md) | HttpHandler | Makes an HTTP request with the PATCH method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | -| [post](./kibana-plugin-public.httpservicebase.post.md) | HttpHandler | Makes an HTTP request with the POST method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | -| [put](./kibana-plugin-public.httpservicebase.put.md) | HttpHandler | Makes an HTTP request with the PUT method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | - -## Methods - -| Method | Description | -| --- | --- | -| [addLoadingCount(countSource$)](./kibana-plugin-public.httpservicebase.addloadingcount.md) | Adds a new source of loading counts. Used to show the global loading indicator when sum of all observed counts are more than 0. | -| [getLoadingCount$()](./kibana-plugin-public.httpservicebase.getloadingcount_.md) | Get the sum of all loading count sources as a single Observable. | -| [intercept(interceptor)](./kibana-plugin-public.httpservicebase.intercept.md) | Adds a new [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) to the global HTTP client. | -| [removeAllInterceptors()](./kibana-plugin-public.httpservicebase.removeallinterceptors.md) | Removes all configured interceptors. | - diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.removeallinterceptors.md b/docs/development/core/public/kibana-plugin-public.httpservicebase.removeallinterceptors.md deleted file mode 100644 index 0432ec29a22b6..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.removeallinterceptors.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [removeAllInterceptors](./kibana-plugin-public.httpservicebase.removeallinterceptors.md) - -## HttpServiceBase.removeAllInterceptors() method - -Removes all configured interceptors. - -Signature: - -```typescript -removeAllInterceptors(): void; -``` -Returns: - -`void` - diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.addloadingcount.md b/docs/development/core/public/kibana-plugin-public.httpsetup.addloadingcountsource.md similarity index 62% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.addloadingcount.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.addloadingcountsource.md index e984fea48625d..a2fe66bb55c77 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.addloadingcount.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.addloadingcountsource.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [addLoadingCount](./kibana-plugin-public.httpservicebase.addloadingcount.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [addLoadingCountSource](./kibana-plugin-public.httpsetup.addloadingcountsource.md) -## HttpServiceBase.addLoadingCount() method +## HttpSetup.addLoadingCountSource() method Adds a new source of loading counts. Used to show the global loading indicator when sum of all observed counts are more than 0. Signature: ```typescript -addLoadingCount(countSource$: Observable): void; +addLoadingCountSource(countSource$: Observable): void; ``` ## Parameters diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md b/docs/development/core/public/kibana-plugin-public.httpsetup.anonymouspaths.md similarity index 57% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.anonymouspaths.md index e94757c5eb031..a9268ca1d8ed6 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.anonymouspaths.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [anonymousPaths](./kibana-plugin-public.httpservicebase.anonymouspaths.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [anonymousPaths](./kibana-plugin-public.httpsetup.anonymouspaths.md) -## HttpServiceBase.anonymousPaths property +## HttpSetup.anonymousPaths property APIs for denoting certain paths for not requiring authentication diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md b/docs/development/core/public/kibana-plugin-public.httpsetup.basepath.md similarity index 57% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.basepath.md index 6c5f690a5c607..6b0726dc8ef2b 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.basepath.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [basePath](./kibana-plugin-public.httpservicebase.basepath.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [basePath](./kibana-plugin-public.httpsetup.basepath.md) -## HttpServiceBase.basePath property +## HttpSetup.basePath property APIs for manipulating the basePath on URL segments. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.delete.md b/docs/development/core/public/kibana-plugin-public.httpsetup.delete.md similarity index 63% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.delete.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.delete.md index 73022ef4f2946..565f0eb336d4f 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.delete.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.delete.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [delete](./kibana-plugin-public.httpservicebase.delete.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [delete](./kibana-plugin-public.httpsetup.delete.md) -## HttpServiceBase.delete property +## HttpSetup.delete property Makes an HTTP request with the DELETE method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.fetch.md b/docs/development/core/public/kibana-plugin-public.httpsetup.fetch.md similarity index 64% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.fetch.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.fetch.md index 3a1ae4892a3dd..2d6447363fa9b 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.fetch.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.fetch.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [fetch](./kibana-plugin-public.httpservicebase.fetch.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [fetch](./kibana-plugin-public.httpsetup.fetch.md) -## HttpServiceBase.fetch property +## HttpSetup.fetch property Makes an HTTP request. Defaults to a GET request unless overriden. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.get.md b/docs/development/core/public/kibana-plugin-public.httpsetup.get.md similarity index 63% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.get.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.get.md index a61b3dd140e50..0c484e33e9b58 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.get.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.get.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [get](./kibana-plugin-public.httpservicebase.get.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [get](./kibana-plugin-public.httpsetup.get.md) -## HttpServiceBase.get property +## HttpSetup.get property Makes an HTTP request with the GET method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.getloadingcount_.md b/docs/development/core/public/kibana-plugin-public.httpsetup.getloadingcount_.md similarity index 59% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.getloadingcount_.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.getloadingcount_.md index 0b2129330cd01..628b62b2ffc27 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.getloadingcount_.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.getloadingcount_.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [getLoadingCount$](./kibana-plugin-public.httpservicebase.getloadingcount_.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [getLoadingCount$](./kibana-plugin-public.httpsetup.getloadingcount_.md) -## HttpServiceBase.getLoadingCount$() method +## HttpSetup.getLoadingCount$() method Get the sum of all loading count sources as a single Observable. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.head.md b/docs/development/core/public/kibana-plugin-public.httpsetup.head.md similarity index 63% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.head.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.head.md index 4624d95f03bf3..e4d49c843e572 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.head.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.head.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [head](./kibana-plugin-public.httpservicebase.head.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [head](./kibana-plugin-public.httpsetup.head.md) -## HttpServiceBase.head property +## HttpSetup.head property Makes an HTTP request with the HEAD method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.intercept.md b/docs/development/core/public/kibana-plugin-public.httpsetup.intercept.md similarity index 72% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.intercept.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.intercept.md index 8cf5bf813df09..1bda0c6166e65 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.intercept.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.intercept.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [intercept](./kibana-plugin-public.httpservicebase.intercept.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [intercept](./kibana-plugin-public.httpsetup.intercept.md) -## HttpServiceBase.intercept() method +## HttpSetup.intercept() method Adds a new [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) to the global HTTP client. diff --git a/docs/development/core/public/kibana-plugin-public.httpsetup.md b/docs/development/core/public/kibana-plugin-public.httpsetup.md index 7ef037ea7abd1..8a14d26c57ca3 100644 --- a/docs/development/core/public/kibana-plugin-public.httpsetup.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.md @@ -2,12 +2,35 @@ [Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) -## HttpSetup type +## HttpSetup interface -See [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) Signature: ```typescript -export declare type HttpSetup = HttpServiceBase; +export interface HttpSetup ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [anonymousPaths](./kibana-plugin-public.httpsetup.anonymouspaths.md) | IAnonymousPaths | APIs for denoting certain paths for not requiring authentication | +| [basePath](./kibana-plugin-public.httpsetup.basepath.md) | IBasePath | APIs for manipulating the basePath on URL segments. | +| [delete](./kibana-plugin-public.httpsetup.delete.md) | HttpHandler | Makes an HTTP request with the DELETE method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | +| [fetch](./kibana-plugin-public.httpsetup.fetch.md) | HttpHandler | Makes an HTTP request. Defaults to a GET request unless overriden. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | +| [get](./kibana-plugin-public.httpsetup.get.md) | HttpHandler | Makes an HTTP request with the GET method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | +| [head](./kibana-plugin-public.httpsetup.head.md) | HttpHandler | Makes an HTTP request with the HEAD method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | +| [options](./kibana-plugin-public.httpsetup.options.md) | HttpHandler | Makes an HTTP request with the OPTIONS method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | +| [patch](./kibana-plugin-public.httpsetup.patch.md) | HttpHandler | Makes an HTTP request with the PATCH method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | +| [post](./kibana-plugin-public.httpsetup.post.md) | HttpHandler | Makes an HTTP request with the POST method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | +| [put](./kibana-plugin-public.httpsetup.put.md) | HttpHandler | Makes an HTTP request with the PUT method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | + +## Methods + +| Method | Description | +| --- | --- | +| [addLoadingCountSource(countSource$)](./kibana-plugin-public.httpsetup.addloadingcountsource.md) | Adds a new source of loading counts. Used to show the global loading indicator when sum of all observed counts are more than 0. | +| [getLoadingCount$()](./kibana-plugin-public.httpsetup.getloadingcount_.md) | Get the sum of all loading count sources as a single Observable. | +| [intercept(interceptor)](./kibana-plugin-public.httpsetup.intercept.md) | Adds a new [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) to the global HTTP client. | + diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.options.md b/docs/development/core/public/kibana-plugin-public.httpsetup.options.md similarity index 62% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.options.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.options.md index 0820beb2752f2..4ea5be8826bff 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.options.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.options.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [options](./kibana-plugin-public.httpservicebase.options.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [options](./kibana-plugin-public.httpsetup.options.md) -## HttpServiceBase.options property +## HttpSetup.options property Makes an HTTP request with the OPTIONS method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.patch.md b/docs/development/core/public/kibana-plugin-public.httpsetup.patch.md similarity index 63% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.patch.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.patch.md index 00e1ffc0e16bf..ef1d50005b012 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.patch.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.patch.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [patch](./kibana-plugin-public.httpservicebase.patch.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [patch](./kibana-plugin-public.httpsetup.patch.md) -## HttpServiceBase.patch property +## HttpSetup.patch property Makes an HTTP request with the PATCH method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.post.md b/docs/development/core/public/kibana-plugin-public.httpsetup.post.md similarity index 63% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.post.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.post.md index 3771a7c910895..1c19c35ac3038 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.post.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.post.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [post](./kibana-plugin-public.httpservicebase.post.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [post](./kibana-plugin-public.httpsetup.post.md) -## HttpServiceBase.post property +## HttpSetup.post property Makes an HTTP request with the POST method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.put.md b/docs/development/core/public/kibana-plugin-public.httpsetup.put.md similarity index 63% rename from docs/development/core/public/kibana-plugin-public.httpservicebase.put.md rename to docs/development/core/public/kibana-plugin-public.httpsetup.put.md index 6e43aafa916bc..e5243d8c80dae 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.put.md +++ b/docs/development/core/public/kibana-plugin-public.httpsetup.put.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [put](./kibana-plugin-public.httpservicebase.put.md) +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpSetup](./kibana-plugin-public.httpsetup.md) > [put](./kibana-plugin-public.httpsetup.put.md) -## HttpServiceBase.put property +## HttpSetup.put property Makes an HTTP request with the PUT method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. diff --git a/docs/development/core/public/kibana-plugin-public.httpstart.md b/docs/development/core/public/kibana-plugin-public.httpstart.md index bb9247c63897a..9abf319acf00d 100644 --- a/docs/development/core/public/kibana-plugin-public.httpstart.md +++ b/docs/development/core/public/kibana-plugin-public.httpstart.md @@ -4,10 +4,10 @@ ## HttpStart type -See [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) +See [HttpSetup](./kibana-plugin-public.httpsetup.md) Signature: ```typescript -export declare type HttpStart = HttpServiceBase; +export declare type HttpStart = HttpSetup; ``` diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 2c43f36ede09e..e2c2866b57b6b 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -56,7 +56,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | | | [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md). | | [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)s. | -| [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | | +| [HttpSetup](./kibana-plugin-public.httpsetup.md) | | | [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | | [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | | [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | @@ -118,8 +118,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | | [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | | [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | -| [HttpSetup](./kibana-plugin-public.httpsetup.md) | See [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | -| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | +| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpSetup](./kibana-plugin-public.httpsetup.md) | | [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | | [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md). | | [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. | diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md index e523717a37ac8..167c69d5fe329 100644 --- a/docs/development/core/server/kibana-plugin-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-server.corestart.md @@ -18,4 +18,5 @@ export interface CoreStart | --- | --- | --- | | [capabilities](./kibana-plugin-server.corestart.capabilities.md) | CapabilitiesStart | [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | | [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | +| [uiSettings](./kibana-plugin-server.corestart.uisettings.md) | UiSettingsServiceStart | [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) | diff --git a/docs/development/core/server/kibana-plugin-server.corestart.uisettings.md b/docs/development/core/server/kibana-plugin-server.corestart.uisettings.md new file mode 100644 index 0000000000000..323e929f2918e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.corestart.uisettings.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [uiSettings](./kibana-plugin-server.corestart.uisettings.md) + +## CoreStart.uiSettings property + +[UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) + +Signature: + +```typescript +uiSettings: UiSettingsServiceStart; +``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md b/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md index 2367420068064..ff71f13466cf8 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md @@ -9,5 +9,5 @@ Wrap a router handler to catch and converts legacy boom errors to proper custom Signature: ```typescript -handleLegacyErrors:

(handler: RequestHandler) => RequestHandler; +handleLegacyErrors: (handler: RequestHandler) => RequestHandler; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.md b/docs/development/core/server/kibana-plugin-server.irouter.md index 73e96191e02e7..a6536d2ed6763 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.md @@ -18,7 +18,7 @@ export interface IRouter | --- | --- | --- | | [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar<'delete'> | Register a route handler for DELETE request. | | [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar<'get'> | Register a route handler for GET request. | -| [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | +| [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <P, Q, B>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | | [patch](./kibana-plugin-server.irouter.patch.md) | RouteRegistrar<'patch'> | Register a route handler for PATCH request. | | [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar<'post'> | Register a route handler for POST request. | | [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar<'put'> | Register a route handler for PUT request. | diff --git a/docs/development/core/server/kibana-plugin-server.logger.get.md b/docs/development/core/server/kibana-plugin-server.logger.get.md new file mode 100644 index 0000000000000..b4a2d8a124260 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.logger.get.md @@ -0,0 +1,33 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [Logger](./kibana-plugin-server.logger.md) > [get](./kibana-plugin-server.logger.get.md) + +## Logger.get() method + +Returns a new [Logger](./kibana-plugin-server.logger.md) instance extending the current logger context. + +Signature: + +```typescript +get(...childContextPaths: string[]): Logger; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| childContextPaths | string[] | | + +Returns: + +`Logger` + +## Example + + +```typescript +const logger = loggerFactory.get('plugin', 'service'); // 'plugin.service' context +const subLogger = logger.get('feature'); // 'plugin.service.feature' context + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index ea5ca6502b076..9c8aafb158bfd 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -21,6 +21,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | +| [RouteValidationError](./kibana-plugin-server.routevalidationerror.md) | Error to return when the validation is not successful. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | | [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | | [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | @@ -90,11 +91,13 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | | [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | | [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | -| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | +| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | | [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | | [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | -| [RouteSchemas](./kibana-plugin-server.routeschemas.md) | RouteSchemas contains the schemas for validating the different parts of a request. | +| [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) | Validation result factory to be used in the custom validation function to return the valid data or validation errorsSee [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md). | +| [RouteValidatorConfig](./kibana-plugin-server.routevalidatorconfig.md) | The configuration object to the RouteValidator class. Set params, query and/or body to specify the validation logic to follow for that property. | +| [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) | Additional options for the RouteValidator class to modify its default behaviour. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | @@ -137,6 +140,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | | [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | +| [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) | | | [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | APIs to access the application's instance uuid. | @@ -199,6 +203,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | | [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | | [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | +| [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) | The custom validation function if @kbn/config-schema is not a valid solution for your specific plugin requirements. | +| [RouteValidationSpec](./kibana-plugin-server.routevalidationspec.md) | Allowed property validation options: either @kbn/config-schema validations or custom validation functionsSee [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) for custom validation. | +| [RouteValidatorFullConfig](./kibana-plugin-server.routevalidatorfullconfig.md) | Route validations config and options merged into one object | | [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | diff --git a/docs/development/core/server/kibana-plugin-server.requesthandler.md b/docs/development/core/server/kibana-plugin-server.requesthandler.md index 79abfd4293e9f..9fc183ffc334b 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandler.md @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han Signature: ```typescript -export declare type RequestHandler

| Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export declare type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => IKibanaResponse | Promise>; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md index c9fc80596efa9..d9b781e1e550e 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md @@ -6,7 +6,7 @@ Plugin specific context passed to a route handler. -Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request +Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request Signature: diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.md b/docs/development/core/server/kibana-plugin-server.routeconfig.md index 1970b23c7ec09..4beb12f0d056e 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.md @@ -9,7 +9,7 @@ Route specific configuration. Signature: ```typescript -export interface RouteConfig

| Type, Method extends RouteMethod> +export interface RouteConfig ``` ## Properties @@ -18,5 +18,5 @@ export interface RouteConfig

RouteConfigOptions<Method> | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | | [path](./kibana-plugin-server.routeconfig.path.md) | string | The endpoint \_within\_ the router path to register the route. | -| [validate](./kibana-plugin-server.routeconfig.validate.md) | RouteSchemas<P, Q, B> | false | A schema created with @kbn/config-schema that every request will be validated against. | +| [validate](./kibana-plugin-server.routeconfig.validate.md) | RouteValidatorFullConfig<P, Q, B> | false | A schema created with @kbn/config-schema that every request will be validated against. | diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md b/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md index e1ec743ae71cc..23a72fc3c68b3 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md @@ -9,7 +9,7 @@ A schema created with `@kbn/config-schema` that every request will be validated Signature: ```typescript -validate: RouteSchemas | false; +validate: RouteValidatorFullConfig | false; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-server.routeregistrar.md b/docs/development/core/server/kibana-plugin-server.routeregistrar.md index 0f5f49636fdd5..901d260fee21d 100644 --- a/docs/development/core/server/kibana-plugin-server.routeregistrar.md +++ b/docs/development/core/server/kibana-plugin-server.routeregistrar.md @@ -9,5 +9,5 @@ Route handler common definition Signature: ```typescript -export declare type RouteRegistrar =

| Type>(route: RouteConfig, handler: RequestHandler) => void; +export declare type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.body.md b/docs/development/core/server/kibana-plugin-server.routeschemas.body.md deleted file mode 100644 index 78a9d25c25d9d..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.routeschemas.body.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [body](./kibana-plugin-server.routeschemas.body.md) - -## RouteSchemas.body property - -Signature: - -```typescript -body?: B; -``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.md b/docs/development/core/server/kibana-plugin-server.routeschemas.md deleted file mode 100644 index 77b980551a8ff..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.routeschemas.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) - -## RouteSchemas interface - -RouteSchemas contains the schemas for validating the different parts of a request. - -Signature: - -```typescript -export interface RouteSchemas

| Type> -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [body](./kibana-plugin-server.routeschemas.body.md) | B | | -| [params](./kibana-plugin-server.routeschemas.params.md) | P | | -| [query](./kibana-plugin-server.routeschemas.query.md) | Q | | - diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.params.md b/docs/development/core/server/kibana-plugin-server.routeschemas.params.md deleted file mode 100644 index 3dbf9fed94dc0..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.routeschemas.params.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [params](./kibana-plugin-server.routeschemas.params.md) - -## RouteSchemas.params property - -Signature: - -```typescript -params?: P; -``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.query.md b/docs/development/core/server/kibana-plugin-server.routeschemas.query.md deleted file mode 100644 index 5be5830cb4bc8..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.routeschemas.query.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [query](./kibana-plugin-server.routeschemas.query.md) - -## RouteSchemas.query property - -Signature: - -```typescript -query?: Q; -``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationerror._constructor_.md b/docs/development/core/server/kibana-plugin-server.routevalidationerror._constructor_.md new file mode 100644 index 0000000000000..551e13faaf154 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidationerror._constructor_.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationError](./kibana-plugin-server.routevalidationerror.md) > [(constructor)](./kibana-plugin-server.routevalidationerror._constructor_.md) + +## RouteValidationError.(constructor) + +Constructs a new instance of the `RouteValidationError` class + +Signature: + +```typescript +constructor(error: Error | string, path?: string[]); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| error | Error | string | | +| path | string[] | | + diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationerror.md b/docs/development/core/server/kibana-plugin-server.routevalidationerror.md new file mode 100644 index 0000000000000..71bd72dca2eab --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidationerror.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationError](./kibana-plugin-server.routevalidationerror.md) + +## RouteValidationError class + +Error to return when the validation is not successful. + +Signature: + +```typescript +export declare class RouteValidationError extends SchemaTypeError +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(error, path)](./kibana-plugin-server.routevalidationerror._constructor_.md) | | Constructs a new instance of the RouteValidationError class | + diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationfunction.md b/docs/development/core/server/kibana-plugin-server.routevalidationfunction.md new file mode 100644 index 0000000000000..34fa096aaae78 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidationfunction.md @@ -0,0 +1,42 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) + +## RouteValidationFunction type + +The custom validation function if @kbn/config-schema is not a valid solution for your specific plugin requirements. + +Signature: + +```typescript +export declare type RouteValidationFunction = (data: any, validationResult: RouteValidationResultFactory) => { + value: T; + error?: never; +} | { + value?: never; + error: RouteValidationError; +}; +``` + +## Example + +The validation should look something like: + +```typescript +interface MyExpectedBody { + bar: string; + baz: number; +} + +const myBodyValidation: RouteValidationFunction = (data, validationResult) => { + const { ok, badRequest } = validationResult; + const { bar, baz } = data || {}; + if (typeof bar === 'string' && typeof baz === 'number') { + return ok({ bar, baz }); + } else { + return badRequest('Wrong payload', ['body']); + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.badrequest.md b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.badrequest.md new file mode 100644 index 0000000000000..36ea6103fb352 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.badrequest.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) > [badRequest](./kibana-plugin-server.routevalidationresultfactory.badrequest.md) + +## RouteValidationResultFactory.badRequest property + +Signature: + +```typescript +badRequest: (error: Error | string, path?: string[]) => { + error: RouteValidationError; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.md b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.md new file mode 100644 index 0000000000000..5f44b490e9a17 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) + +## RouteValidationResultFactory interface + +Validation result factory to be used in the custom validation function to return the valid data or validation errors + +See [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md). + +Signature: + +```typescript +export interface RouteValidationResultFactory +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [badRequest](./kibana-plugin-server.routevalidationresultfactory.badrequest.md) | (error: Error | string, path?: string[]) => {
error: RouteValidationError;
} | | +| [ok](./kibana-plugin-server.routevalidationresultfactory.ok.md) | <T>(value: T) => {
value: T;
} | | + diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.ok.md b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.ok.md new file mode 100644 index 0000000000000..eca6a31bd547f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.ok.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) > [ok](./kibana-plugin-server.routevalidationresultfactory.ok.md) + +## RouteValidationResultFactory.ok property + +Signature: + +```typescript +ok: (value: T) => { + value: T; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationspec.md b/docs/development/core/server/kibana-plugin-server.routevalidationspec.md new file mode 100644 index 0000000000000..f5fc06544043f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidationspec.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationSpec](./kibana-plugin-server.routevalidationspec.md) + +## RouteValidationSpec type + +Allowed property validation options: either @kbn/config-schema validations or custom validation functions + +See [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) for custom validation. + +Signature: + +```typescript +export declare type RouteValidationSpec = ObjectType | Type | RouteValidationFunction; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.body.md b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.body.md new file mode 100644 index 0000000000000..8b5d2c0413087 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.body.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorConfig](./kibana-plugin-server.routevalidatorconfig.md) > [body](./kibana-plugin-server.routevalidatorconfig.body.md) + +## RouteValidatorConfig.body property + +Validation logic for the body payload + +Signature: + +```typescript +body?: RouteValidationSpec; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.md b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.md new file mode 100644 index 0000000000000..4637da7741d80 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorConfig](./kibana-plugin-server.routevalidatorconfig.md) + +## RouteValidatorConfig interface + +The configuration object to the RouteValidator class. Set `params`, `query` and/or `body` to specify the validation logic to follow for that property. + +Signature: + +```typescript +export interface RouteValidatorConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [body](./kibana-plugin-server.routevalidatorconfig.body.md) | RouteValidationSpec<B> | Validation logic for the body payload | +| [params](./kibana-plugin-server.routevalidatorconfig.params.md) | RouteValidationSpec<P> | Validation logic for the URL params | +| [query](./kibana-plugin-server.routevalidatorconfig.query.md) | RouteValidationSpec<Q> | Validation logic for the Query params | + diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.params.md b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.params.md new file mode 100644 index 0000000000000..11de25ff3b19f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.params.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorConfig](./kibana-plugin-server.routevalidatorconfig.md) > [params](./kibana-plugin-server.routevalidatorconfig.params.md) + +## RouteValidatorConfig.params property + +Validation logic for the URL params + +Signature: + +```typescript +params?: RouteValidationSpec

; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.query.md b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.query.md new file mode 100644 index 0000000000000..510325c2dfff7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidatorconfig.query.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorConfig](./kibana-plugin-server.routevalidatorconfig.md) > [query](./kibana-plugin-server.routevalidatorconfig.query.md) + +## RouteValidatorConfig.query property + +Validation logic for the Query params + +Signature: + +```typescript +query?: RouteValidationSpec; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatorfullconfig.md b/docs/development/core/server/kibana-plugin-server.routevalidatorfullconfig.md new file mode 100644 index 0000000000000..0f3785b954a3a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidatorfullconfig.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorFullConfig](./kibana-plugin-server.routevalidatorfullconfig.md) + +## RouteValidatorFullConfig type + +Route validations config and options merged into one object + +Signature: + +```typescript +export declare type RouteValidatorFullConfig = RouteValidatorConfig & RouteValidatorOptions; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.md b/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.md new file mode 100644 index 0000000000000..00b029d9928e3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) + +## RouteValidatorOptions interface + +Additional options for the RouteValidator class to modify its default behaviour. + +Signature: + +```typescript +export interface RouteValidatorOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [unsafe](./kibana-plugin-server.routevalidatoroptions.unsafe.md) | {
params?: boolean;
query?: boolean;
body?: boolean;
} | Set the unsafe config to avoid running some additional internal \*safe\* validations on top of your custom validation | + diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.unsafe.md b/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.unsafe.md new file mode 100644 index 0000000000000..0406a372c4e9d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.unsafe.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) > [unsafe](./kibana-plugin-server.routevalidatoroptions.unsafe.md) + +## RouteValidatorOptions.unsafe property + +Set the `unsafe` config to avoid running some additional internal \*safe\* validations on top of your custom validation + +Signature: + +```typescript +unsafe?: { + params?: boolean; + query?: boolean; + body?: boolean; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md index 8091a7cec44aa..0047b5275408e 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md @@ -24,5 +24,17 @@ register(settings: Record): void; ## Example -setup(core: CoreSetup){ core.uiSettings.register(\[{ foo: { name: i18n.translate('my foo settings'), value: true, description: 'add some awesomeness', }, }\]); } + +```ts +setup(core: CoreSetup){ + core.uiSettings.register([{ + foo: { + name: i18n.translate('my foo settings'), + value: true, + description: 'add some awesomeness', + }, + }]); +} + +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsservicestart.asscopedtoclient.md b/docs/development/core/server/kibana-plugin-server.uisettingsservicestart.asscopedtoclient.md new file mode 100644 index 0000000000000..072dd39faa084 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsservicestart.asscopedtoclient.md @@ -0,0 +1,37 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) > [asScopedToClient](./kibana-plugin-server.uisettingsservicestart.asscopedtoclient.md) + +## UiSettingsServiceStart.asScopedToClient() method + +Creates a [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) with provided \*scoped\* saved objects client. + +This should only be used in the specific case where the client needs to be accessed from outside of the scope of a [RequestHandler](./kibana-plugin-server.requesthandler.md). + +Signature: + +```typescript +asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| savedObjectsClient | SavedObjectsClientContract | | + +Returns: + +`IUiSettingsClient` + +## Example + + +```ts +start(core: CoreStart) { + const soClient = core.savedObjects.getScopedClient(arbitraryRequest); + const uiSettingsClient = core.uiSettings.asScopedToClient(soClient); +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsservicestart.md b/docs/development/core/server/kibana-plugin-server.uisettingsservicestart.md new file mode 100644 index 0000000000000..ee3563552275a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsservicestart.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) + +## UiSettingsServiceStart interface + + +Signature: + +```typescript +export interface UiSettingsServiceStart +``` + +## Methods + +| Method | Description | +| --- | --- | +| [asScopedToClient(savedObjectsClient)](./kibana-plugin-server.uisettingsservicestart.asscopedtoclient.md) | Creates a [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) with provided \*scoped\* saved objects client.This should only be used in the specific case where the client needs to be accessed from outside of the scope of a [RequestHandler](./kibana-plugin-server.requesthandler.md). | + diff --git a/docs/discover/context.asciidoc b/docs/discover/context.asciidoc index 9049109d6124d..c402a734a16fa 100644 --- a/docs/discover/context.asciidoc +++ b/docs/discover/context.asciidoc @@ -1,90 +1,66 @@ [[document-context]] -== Viewing Document Context +== Viewing a document in context -For certain applications it can be useful to inspect a window of documents -surrounding a specific event. The context view enables you to do just that for -<> that are configured to contain time-based events. +Once you've narrowed your search to a specific event, +you might want to inspect the documents that occurred +immediately before and after the event. With the Context view, +you can do just that for index patterns that contain time-based events. -To show the context surrounding an anchor document, click the *Expand* button -image:images/ExpandButton.jpg[Expand Button] to the left of the document's -table entry and then click the *View surrounding documents* link. +To open the Context view, click the expand icon (<) in the document table, and then click +*View surrounding documents.* -image::images/Expanded-Document.png[Expanded Document] -{nbsp} +The documents are sorted +by the time field specified in the index pattern and displayed using the +same set of columns as the *Discover* view from which the context was opened. +The anchor document is highlighted in blue. -The context view displays a number of documents before and after the anchor -document. The anchor document itself is highlighted in blue. The view is sorted -by the time field specified in the index pattern configuration and uses the -same set of columns as the Discover view the context was opened from. If there -are multiple documents with the same time field value, the internal document -order is used as a secondary sorting criterion by default. - -[NOTE] --- -The field used for tiebreaking in case of equal time field values can be -configured using the advanced setting `context:tieBreakerFields` in -< Advanced Settings*>>, which defaults to the -`_doc` field. The value of this setting can be a comma-separated list of field -names, which will be checked in sequence for suitability when a context is -about to be displayed. The first suitable field is then used as the tiebreaking -field. A field is suitable if the field exists and is sortable in the index -pattern the context is based on. - -While not required, it is recommended to only -use fields which have {ref}/doc-values.html[doc values] enabled to achieve -good performance and avoid unnecessary {ref}/modules-fielddata.html[field -data] usage. Common examples for suitable fields include log line numbers, -monotonically increasing counters and high-precision timestamps. --- +[role="screenshot"] image::images/Discover-ContextView.png[Context View] -NOTE: The number of documents displayed by default can be configured -via the `context:defaultSize` setting in < -Advanced Settings*>>. - [float] -[[change-context-size]] -=== Changing the Context Size - -You can change the number documents displayed before and after the anchor -document independently. - -To increase the number of displayed documents that are newer than the anchor -document, click the *Load 5 more* button above the document list or enter the -desired number into the input box right of the button. - -image::images/Discover-ContextView-SizePicker-Newer.png[] -{nbsp} - -To increase the number of displayed documents that are older than the anchor -document, click the *Load 5 more* button below the document list or enter the -desired number into the input box right of the button. +[[filter-context]] +=== Filter the context -image::images/Discover-ContextView-SizePicker-Older.png[] -{nbsp} +The +filters you applied in *Discover* are carried over to the Context view. Pinned filters remain active, while normal +filters are copied in a disabled state. You can re-enable these filters to +refine your context view. -NOTE: The default number of documents loaded with each button click can be -configured via the `context:step` setting in < -Advanced Settings*>>. +If the Context view contains a large number of documents not related to the event under +investigation, you can use filters to restrict the documents to +display. [float] -[[filter-context]] -=== Filtering the Context - -Depending on how the documents are partitioned into index patterns, the context -view might contain a large number of documents not related to the event under -investigation. In order to adapt the focus of the context view to the task at -hand, you can use filters to restrict the documents considered by Kibana for -display in the context view. - -When switching from the discover view to the context view, the previously -applied filters are carried over. Pinned filters remain active while normal -filters are copied in a disabled state. You can selectively re-enabled them to -refine your context view. +[[change-context-size]] +=== Change the number of surrounding documents -New filters can be added via the *Add a filter* link in the filter bar, by -clicking the filter icons appearing when hovering a field, or by expanding -documents and clicking the filter icons in the table. +By default, the five newest and oldest +documents are listed. To increase the number of documents that surround the anchor document, +click *Load*. Five documents are added with each click. -image::images/Discover-ContextView-FilterMontage.png[] +[float] +[[configure-context-ContextView]] +=== Configure the context view + +To configure the Context view, use these settings in <>. + +[horizontal] +`context:defaultSize`:: The number of documents to display by default. +`context:step`:: The default number of documents to load with each button click. +`context:tieBreakerFields`:: The field to use for tiebreaking in case of equal time field values. +The default is the +`_doc` field. ++ +You can enter a comma-separated list of field +names, which is checked in sequence for suitability when a context is +displayed. The first suitable field is used as the tiebreaking +field. A field is suitable if the field exists and is sortable in the index +pattern the context is based on. ++ +Although not required, it is recommended to only +use fields that have {ref}/doc-values.html[doc values] enabled to achieve +good performance and avoid unnecessary {ref}/modules-fielddata.html[field +data] usage. Common examples for suitable fields include log line numbers, +monotonically increasing counters and high-precision timestamps. diff --git a/docs/discover/document-data.asciidoc b/docs/discover/document-data.asciidoc index dc6a45dc5ad7e..b45a31065aa9a 100644 --- a/docs/discover/document-data.asciidoc +++ b/docs/discover/document-data.asciidoc @@ -1,69 +1,55 @@ [[document-data]] -== Viewing Document Data +== Viewing document data -When you submit a search query, the 500 most recent documents that match the query -are listed in the Documents table. You can configure the number of documents shown -in the table by setting the `discover:sampleSize` property in <>. By default, the table shows the localized version of the time -field configured for the selected <> and the document `_source`. You can -<> from the Fields list. -You can <> by any indexed field that's included -in the table. - -To view a document's field data, click the *Expand* button -image:images/ExpandButton.jpg[Expand Button] to the left of the document's table -entry. - -image::images/Expanded-Document.png[] - -To view the original JSON document (pretty-printed), click the *JSON* tab. - -To view the document data as a separate page, click the *View single document* -link. You can bookmark and share this link to provide direct access to a -particular document. - -To display or hide a field's column in the Documents table, click the -image:images/add-column-button.png[Add Column] *Toggle column in table* button. - -To collapse the document details, click the *Collapse* button -image:images/CollapseButton.jpg[Collapse Button]. +When you submit a search query in *Discover*, the most recent documents that match the query +are listed in the documents table. +By default, the table includes columns for +the time field and the document `_source`, which shows all fields and values in the document. [float] [[sorting]] -=== Sorting the Document List -You can sort the documents in the Documents table by the values in any indexed -field. If a time field is configured for the current index pattern, the -documents are sorted in reverse chronological order by default. - -To change the sort order, hover over the name of the field you want to sort by -and click the sort button. Click again to reverse the sort order. +=== Modify the document table + +Use the following commands to +tailor the documents table to suit your needs. + +[horizontal] +Add a field column:: +Hover over the list of *Available fields* and then click *add* next to each field you want include as a column in the table. +The first field you add replaces the `_source` column. +Change sort order:: By default, columns are sorted by the values in the field. +If a time field is configured for the current index pattern, +the documents are sorted in reverse chronological order. ++ +To change the sort order, hover over the column +and click image:images/sort-icon.png[]. +The first click sorts by ascending order, the second click sorts by descending order, and the third +click removes the field from the sorted fields. + +Move a field column:: Hover over the column header and click the move left (<<) or move right icon (>>). +Remove a field column :: Hover over the list of *Specified fields* +and then click *remove*. +Or, use the (x) control in the column header. [float] -[[adding-columns]] -=== Adding Field Columns to the Documents Table -By default, the Documents table shows the localized version of the time field -that's configured for the selected index pattern and the document `_source`. -You can add fields to the table from the Fields list or from a document's -field data. - -To add a field column from the Fields list, hover over the field and click its -*add* button. +=== Drill down into field-level details +To view the document data in either table or JSON format, click the expand icon (>). +The expanded view provides these options for viewing your document: -To add a field column from a document's field data, expand the document -and click the field's -image:images/add-column-button.png[Add Column] *Toggle column in table* button. +* View the events that surround your document. +For example, you might want to see the 10 documents that occurred +immediately before and after your event. -Added field columns replace the `_source` column in the Documents table. The added -fields are also added to the *Selected Fields* list. +* View the document data as a separate page. You can bookmark and +share the link for direct access to a particular document. -To rearrange the field columns, hover over the header of the column you want to move -and click the *Move left* or *Move right* button. +[role="screenshot"] +image::images/Expanded-Document.png[] -image:images/Discover-MoveColumn.jpg[Move Column] [float] -[[removing-columns]] -=== Removing Field Columns from the Documents Table -To remove a field column from the Documents table, hover over the header of the -column you want to remove and click the *Remove* button -image:images/RemoveFieldButton.jpg[Remove Field Button]. \ No newline at end of file +=== Configure the number of documents to show + +By default, the documents table includes the 500 most recent documents that +match the query. To change this number, set the `discover:sampleSize` property in <>. diff --git a/docs/discover/field-filter.asciidoc b/docs/discover/field-filter.asciidoc index 5646fe079401e..49bb6078cdc58 100644 --- a/docs/discover/field-filter.asciidoc +++ b/docs/discover/field-filter.asciidoc @@ -1,127 +1,132 @@ [[field-filter]] -== Filtering by Field -You can filter the search results to display only those documents that contain -a particular value in a field. You can also create negative filters that -exclude documents that contain the specified field value. +== Filtering by field -You add field filters from the Fields list, the Documents table, or by manually -adding a filter. In addition to creating positive and negative filters, the -Documents table enables you to filter on whether or not a field is present. The -applied filters are shown below the Query bar. Negative filters are shown in red. +*Discover* offers +various types of filters, so you can restrict your documents to the exact data you want. +For example, you might look at the results for a +particular period of time. Or, you might include—or exclude— +all HTTP redirects that come from a specific IP and port. -To add a filter from the Fields list: +[float] +=== Add a filter + +A quick way to add a filter is from the fields list. -. Click the name of the field you want to filter on. This displays the top -five values for that field. +. Click the field to filter on. ++ +You'll see the number of documents that contain +the field, the top 5 values for the field, and the percentage of documents +that contain each value. + [role="screenshot"] image::images/filter-field.png[height=317] -. To add a positive filter, click the *Positive Filter* button -image:images/PositiveFilter.jpg[Positive Filter]. -This includes only those documents that contain that value in the field. -. To add a negative filter, click the *Negative Filter* button -image:images/NegativeFilter.jpg[Negative Filter]. -This excludes documents that contain that value in the field. - -To add a filter from the Documents table: - -. Expand a document in the Documents table by clicking the *Expand* button -image:images/ExpandButton.jpg[Expand Button] to the left of the document's -table entry. + +. Use the image:images/PositiveFilter.jpg[Positive Filter] icon to +show only documents that contain that value, +or image:images/NegativeFilter.jpg[Negative Filter] to exclude all documents with that value. + -image::images/Expanded-Document.png[] -. To add a positive filter, click the *Positive Filter* button -image:images/PositiveFilter.jpg[Positive Filter Button] to the right of the -field name. This includes only those documents that contain that value in the -field. -. To add a negative filter, click the *Negative Filter* button -image:images/NegativeFilter.jpg[Negative Filter Button] to the right of the -field name. This excludes documents that contain that value in the field. -. To filter on whether or not documents contain the field, click the -*Exists* button image:images/ExistsButton.jpg[Exists Button] to the right of the -field name. This includes only those documents that contain the field. - -To manually add a filter: - -. Click *Add Filter*. A popup will be displayed for you to create the filter. - -. Choose a field to filter by. This list of fields will include fields from the -index pattern you are currently querying against. +If there is no data to display, you might need to set a <>. +You can choose a time from the quick filter or choose your +own using absolute or relative times. + +. Try also these filtering options: ++ +* To limit the field +list to a particular data type, click *Filter by type*. +You can also filter for whether that type is +aggregatable or searchable. + -image::images/add_filter_field.png[] -. Choose an operation for your filter. +* To filter for whether a field is present, expand the document in +the document table, hover over the field, and click the *Filter for field present* icon. + +[float] +=== Filter by condition + +You can filter using advanced criteria, +such as if a value is equal to or in between certain values. + +. Click *Add Filter*. + +. Select a field. + +. Select an operation for your filter: + -image::images/add_filter_operator.png[] -The following operators can be selected: [horizontal] -`is`:: Filter where the value for the field matches the given value. -`is not`:: Filter where the value for the field does not match the given value. -`is one of`:: Filter where the value for the field matches one of the specified values. -`is not one of`:: Filter where the value for the field does not match any of the specified values. -`is between`:: Filter where the value for the field is in the given range. -`is not between`:: Filter where the value for the field is not in the given range. -`exists`:: Filter where any value is present for the field. -`does not exist`:: Filter where no value is present for the field. -. Choose the value(s) for your filter. Values from your indices may be suggested -as selections if you are filtering against an aggregatable field. +`is`:: The value for the field matches the given value. +`is not`:: The value for the field does not match the given value. +`is one of`:: The field matches one of the specified values. +`is not one of`:: The value for the field does not match any of the specified values. +`is between`:: The value for the field is in the given range. +`is not between`:: The value for the field is not in the given range. +`exists`:: Any value is present for the field. +`does not exist`:: No value is present for the field. +. Choose values for your filter. + -image::images/add_filter_value.png[] -. (Optional) Specify a label for the filter. If you specify a label, it will be -displayed below the query bar instead of the filter definition. -. Click *Save*. The filter will be applied to your search and be displayed below -the query bar. +Values from your indices may be suggested +as selections if you are filtering against an aggregatable field. +. (Optional) Specify a label for the filter. + +. Click *Save* to apply the filter to your search. ++ NOTE: If you are experiencing long-running queries as a result of the value suggestions, you can -turn off the suggestions by setting the advanced setting, `filterEditor:suggestValues`, to `false`. +turn off the suggestions by setting `filterEditor:suggestValues` to `false` +in <>. [float] [[filter-pinning]] -=== Managing Filters +=== Edit, disable, and delete filters + +To modify a filter, click its tag, and then select one of the following actions. + +*Pin across all apps*:: +Persist the filter +when you switch contexts in Kibana. For example, you can pin a filter +in *Discover* and it remains in place when you switch to *Visualize*. +A filter is based on a particular index field—if the indices being +searched do not contain the field in a pinned filter, it has no effect. -To modify a filter, click on it and click one of the action buttons. +*Edit filter*:: +Edit the +filter definition and label. -image::images/filter-allbuttons.png[] +*Exclude results*:: +Switch from a positive +filter to a negative filter, and vice versa. -  +*Temporarily disable*:: +Disable the filter without +removing it. Click again to reenable the filter. + +*Delete*:: +Delete the filter. + +To apply an action to all filters, +click the *Actions* icon, and then select the action. -Pin across all apps :: Pinned filters -persist when you switch contexts in Kibana. For example, you can pin a filter -in Discover and it remains in place when you switch to Visualize. -Note that a filter is based on a particular index field--if the indices being -searched don't contain the field in a pinned filter, it has no effect. -Edit Filter :: <> definition. Enables you to manually update the filter and -specify a label for the filter. -Exclude results :: Switch from a positive -filter to a negative filter and vice-versa. -Temporarily disable :: Disable the filter without -removing it. Click again to reenable the filter. Diagonal stripes indicate -that a filter is disabled. -Remove Filter :: Remove the filter. -To apply a filter action to all of the applied filters, -click *Actions* and select the action. [float] [[filter-edit]] -=== Editing a Filter -You can edit a filter by changing the field, operator, or value associated -with the filter (see the Add Filter section above), or by directly modifying -the filter query that is performed to filter your search results. This -enables you to create more complex filters that are based on multiple fields. - -. To edit the filter query, first click the edit button for the filter, then -click *Edit Query DSL*. -+ -image::images/edit_filter_query.png[] -. You can then edit the query for the filter. -+ -image::images/edit_filter_query_json.png[] +=== Modify the filter query -For example, you could use a -{ref}/query-dsl-bool-query.html[bool query] to create a filter for the -sample log data that displays the hits that originated from Canada or China that resulted in a 404 error: +You can directly modify +the query that filters your search results. This enables you +to create more complex filters using multiple fields. +. Click the filter tag, and then select *Edit > Edit Query DSL*. + +. Edit the query for the filter. ++ +//// +image::images/edit_filter_query_json.png[] ++ +//// +For example, if you are using the sample log data, you can use the +{ref}/query-dsl-bool-query.html[bool query] to create a filter +that displays the hits that originated from Canada or China that resulted in a 404 error: ++ ========== [source,json] { diff --git a/docs/discover/images/move-icon.png b/docs/discover/images/move-icon.png new file mode 100644 index 0000000000000..3fa1f9e2f1a59 Binary files /dev/null and b/docs/discover/images/move-icon.png differ diff --git a/docs/discover/images/sort-icon.png b/docs/discover/images/sort-icon.png new file mode 100644 index 0000000000000..7dd3719ec361b Binary files /dev/null and b/docs/discover/images/sort-icon.png differ diff --git a/docs/discover/viewing-field-stats.asciidoc b/docs/discover/viewing-field-stats.asciidoc index 96a26c78596e2..3631aba73fb20 100644 --- a/docs/discover/viewing-field-stats.asciidoc +++ b/docs/discover/viewing-field-stats.asciidoc @@ -1,14 +1,14 @@ [[viewing-field-stats]] == Viewing Field Data Statistics -From the Fields list, you can see how many of the documents in the Documents +From the fields list, you can see how many of the documents in the documents table contain a particular field, what the top 5 values are, and what percentage of documents contain each value. -Data can be visualized in various ways. The quick visualize can only be -applied to aggregatable fields. The keyword fields can be visualized and -they are available in the side bar if we uncheck "Hide missing fields". +You can visualize data in various ways. You can only apply the quick visualize +to aggregatable fields. You can visualize the keyword fields, and +they are available in the side bar if you uncheck "Hide missing fields". -To view field data statistics, click the name of a field in the Fields list. +To view field data statistics, click the name of a field in the fields list. -image:images/filter-field.png[Field Statistics,height=317] \ No newline at end of file +image:images/filter-field.png[Field Statistics,height=317] diff --git a/docs/images/Discover-ContextView-SizePicker-Newer.png b/docs/images/Discover-ContextView-SizePicker-Newer.png deleted file mode 100644 index 852cb22c1f27c..0000000000000 Binary files a/docs/images/Discover-ContextView-SizePicker-Newer.png and /dev/null differ diff --git a/docs/images/Discover-ContextView-SizePicker-Older.png b/docs/images/Discover-ContextView-SizePicker-Older.png deleted file mode 100644 index 38cd9acd1bee0..0000000000000 Binary files a/docs/images/Discover-ContextView-SizePicker-Older.png and /dev/null differ diff --git a/docs/images/Discover-ContextView.png b/docs/images/Discover-ContextView.png index 11d4a59c26e55..5c2de602f6b41 100644 Binary files a/docs/images/Discover-ContextView.png and b/docs/images/Discover-ContextView.png differ diff --git a/docs/images/Discover-Start.png b/docs/images/Discover-Start.png index 680aa3767bdf0..27e7a2c728597 100644 Binary files a/docs/images/Discover-Start.png and b/docs/images/Discover-Start.png differ diff --git a/docs/images/Expanded-Document.png b/docs/images/Expanded-Document.png index ad2f0db1a7ff9..4fa255e79a8ad 100644 Binary files a/docs/images/Expanded-Document.png and b/docs/images/Expanded-Document.png differ diff --git a/docs/limitations.asciidoc b/docs/limitations.asciidoc index 0b26a3cdcf71a..9bcba3b65d660 100644 --- a/docs/limitations.asciidoc +++ b/docs/limitations.asciidoc @@ -12,7 +12,7 @@ These {stack} features also have limitations that affect {kib}: * {ref}/watcher-limitations.html[Alerting] -* {stack-ov}/ml-limitations.html[Machine learning] +* {ml-docs}/ml-limitations.html[Machine learning] * {ref}/security-limitations.html[Security] -- diff --git a/docs/maps/vector-style-properties.asciidoc b/docs/maps/vector-style-properties.asciidoc index f51632218add1..5656a7f04d0e3 100644 --- a/docs/maps/vector-style-properties.asciidoc +++ b/docs/maps/vector-style-properties.asciidoc @@ -8,32 +8,52 @@ Point, polygon, and line features support different styling properties. [[point-style-properties]] ==== Point style properties +You can add text labels to your Point features by configuring label style properties. + +[cols="2*"] +|=== +|*Label* +|Specifies label content. +|*Label color* +|The text color. +|*Label size* +|The size of the text font, in pixels. +|=== + You can symbolize Point features as *Circle markers* or *Icons*. Use *Circle marker* to symbolize Points as circles. -*Fill color*:: The fill color of the point features. - -*Border color*:: The border color of the point features. - -*Border width*:: The border width of the point features. - -*Symbol size*:: The radius of the symbol size, in pixels. +[cols="2*"] +|=== +|*Border color* +|The border color of the point features. +|*Border width* +|The border width of the point features. +|*Fill color* +|The fill color of the point features. +|*Symbol size* +|The radius of the symbol size, in pixels. +|=== Use *Icon* to symbolize Points as icons. -*Fill color*:: The fill color of the point features. - -*Border color*:: The border color of the point features. - -*Border width*:: The border width of the point features. +[cols="2*"] +|=== +|*Border color* +|The border color of the point features. +|*Border width* +|The border width of the point features. +|*Fill color* +|The fill color of the point features. +|*Symbol orientation* +|The symbol orientation rotating the icon clockwise. +|*Symbol size* +|The radius of the symbol size, in pixels. +|=== -*Symbol orientation*:: The symbol orientation rotating the icon clockwise. - -*Symbol size*:: The radius of the symbol size, in pixels. -+ Available icons -+ + [role="screenshot"] image::maps/images/maki-icons.png[] @@ -42,17 +62,25 @@ image::maps/images/maki-icons.png[] [[polygon-style-properties]] ==== Polygon style properties -*Fill color*:: The fill color of the polygon features. - -*Border color*:: The border color of the polygon features. - -*Border width*:: The border width of the polygon features. +[cols="2*"] +|=== +|*Border color* +|The border color of the polygon features. +|*Border width* +|The border width of the polygon features. +|*Fill color* +|The fill color of the polygon features. +|=== [float] [[line-style-properties]] ==== Line style properties -*Border color*:: The color of the line features. - -*Border width*:: The width of the line features. +[cols="2*"] +|=== +|*Border color* +|The color of the line features. +|*Border width* +|The width of the line features. +|=== diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index f3e7273adedee..8fd7b0490e194 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -5,7 +5,7 @@ base image is https://hub.docker.com/_/centos/[centos:7]. A list of all published Docker images and tags is available at https://www.docker.elastic.co[www.docker.elastic.co]. The source code is in -https://github.com/elastic/kibana-docker/tree/{branch}[GitHub]. +https://github.com/elastic/dockerfiles/tree/{branch}/kibana[GitHub]. These images are free to use under the Elastic license. They contain open source and free commercial features and access to paid commercial features. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index fa583918703f3..36d6b0a6e473a 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -3,15 +3,100 @@ [partintro] -- -*Discover* enables you to explore your data with {kib}'s data discovery functions. -You have access to every document in every index that matches the selected <>. -You can submit search queries, filter the search results, and view document data. -You can also see the number of documents that match the search query and get field value statistics. -If a time field is configured for the selected index pattern, the distribution of -documents over time is displayed in a histogram at the top of the page. +When you know what your data includes, you can create visualizations +that best display that data and build better dashboards. +*Discover* enables you to explore your data, find +hidden insights and relationships, and get answers to your questions. + +With *Discover*, you can: + +* Access every document in every index that matches your selected index pattern +* Search your data and filter the search results +* Get field-level details about the documents that match your search +* View the events that occurred just before and after a document [role="screenshot"] image::images/Discover-Start.png[Discover] + + +[float] +=== Set up your index pattern + +The first thing to do in *Discover* is to select an <>, which +defines the data you want to explore and visualize. The current index pattern is in the upper left. +If you haven't yet created an index pattern, you can add a <>, +which has a pre-built index pattern. + +[float] +=== Set a time filter + +By default, *Discover* shows data for the last 15 minutes. +If you have a time-based index, and no data displays, +you might need to increase the time range. Using the <> in the upper right, +you can specify a common or recently-used time range, a relative time +from now, or an absolute time range. + +[float] +=== Search your data + +Now that you have your data and set the time span, you can start asking your questions. +You can search your data using the <>, +which offers a simplified query syntax. +For example, if +you search for `day_of_week : Friday`, you'll get a list of all documents +in which `day_of_week` is set to `Friday`. If you prefer +<>, you can access it from the KQL menu. + +[float] +=== Filter your search results + +Next, you'll want narrow your search results to a more manageable data set. +When you click on a name in the field list, you'll see +the top five values for the field, the number of documents that contain the field, +and the percentage of documents that contain each value. From this view, you can +use the (+) magnifier icon to quickly find all +documents that have that value, or (-) to exclude all +documents with that value. For more filter options, see <>. + +[role="screenshot"] +image::images/filter-field.png[height=317] + + +[float] +=== Add and remove fields + +The sortable documents table +lists the documents that match your search. +By default, the table includes columns for the time field and the document `_source`. +To zero in on a specific field, click *add* next to the field name in the left sidebar. +For example, if you add the `currency`, `customer_last_name`, and `day_of_week` fields, +the document table includes columns for those three fields. + +[float] +=== Examine document contents + +From the documents table, you can expand a document to +examine its field data in either table or JSON format. +The table view provides yet another filtering option—filtering for whether the field +is present. See <> for details. + +[float] +=== View a document in context + +Suppose you're troubleshooting your data, and you've narrowed down your results to a single document. +Now you want to to see the events that occurred just before and after the +document that you are looking at. You can do that by expanding the document and +clicking <>. + +[float] +=== Save and share your search + +Finally, its time to save and share your data. You can export your data as a CSV file +or create a direct link to share. The *Save* and *Share* actions are in the menu bar. + + + + -- include::{kib-repo-dir}/discover/set-time-filter.asciidoc[] diff --git a/docs/user/ml/index.asciidoc b/docs/user/ml/index.asciidoc index a2c23aad98d5b..cca0dc5e4530f 100644 --- a/docs/user/ml/index.asciidoc +++ b/docs/user/ml/index.asciidoc @@ -50,8 +50,8 @@ pane: image::user/ml/images/ml-job-management.jpg[Job Management] You can use the *Settings* pane to create and edit -{stack-ov}/ml-calendars.html[calendars] and the filters that are used in -{stack-ov}/ml-rules.html[custom rules]: +{ml-docs}/ml-calendars.html[calendars] and the filters that are used in +{ml-docs}/ml-rules.html[custom rules]: [role="screenshot"] image::user/ml/images/ml-settings.jpg[Calendar Management] @@ -73,7 +73,7 @@ image::user/ml/images/ml-annotations-list.jpg[Single Metric Viewer with annotati In some circumstances, annotations are also added automatically. For example, if the {anomaly-job} detects that there is missing data, it annotates the affected time period. For more information, see -{stack-ov}/ml-delayed-data-detection.html[Handling delayed data]. The +{ml-docs}/ml-delayed-data-detection.html[Handling delayed data]. The *Job Management* pane shows the full list of annotations for each job. NOTE: The {kib} {ml-features} use pop-ups. You must configure your web @@ -82,7 +82,7 @@ browser so that it does not block pop-up windows or create an exception for your For more information about the {anomaly-detect} feature, see https://www.elastic.co/what-is/elastic-stack-machine-learning[{ml-cap} in the {stack}] -and {stack-ov}/xpack-ml.html[{ml-cap} {anomaly-detect}]. +and {ml-docs}/xpack-ml.html[{ml-cap} {anomaly-detect}]. [[xpack-ml-dfanalytics]] == {dfanalytics-cap} @@ -99,4 +99,4 @@ in {kib}. For example: image::user/ml/images/outliers.jpg[{oldetection-cap} results in {kib}] For more information about the {dfanalytics} feature, see -{stack-ov}/ml-dfanalytics.html[{ml-cap} {dfanalytics}]. \ No newline at end of file +{ml-docs}/ml-dfanalytics.html[{ml-cap} {dfanalytics}]. \ No newline at end of file diff --git a/docs/user/reporting/reporting-troubleshooting.asciidoc b/docs/user/reporting/reporting-troubleshooting.asciidoc index 92464c24b45ea..ca7fa6abcc9d9 100644 --- a/docs/user/reporting/reporting-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-troubleshooting.asciidoc @@ -17,6 +17,7 @@ dependencies for Chromium. Make sure Kibana server OS has the appropriate packages installed for the distribution. If you are using CentOS/RHEL systems, install the following packages: + * `ipa-gothic-fonts` * `xorg-x11-fonts-100dpi` * `xorg-x11-fonts-75dpi` @@ -28,6 +29,7 @@ If you are using CentOS/RHEL systems, install the following packages: * `freetype` If you are using Ubuntu/Debian systems, install the following packages: + * `fonts-liberation` * `libfontconfig1` @@ -105,9 +107,10 @@ has its own command-line method to generate its own debug logs, which can someti caused by Kibana or Chromium. See more at https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/README.md#debugging-tips Using Puppeteer's debug method when launching Kibana would look like: -> Enable verbose logging - internal DevTools protocol traffic will be logged via the debug module under the puppeteer namespace. -> ``` -> env DEBUG="puppeteer:*" ./bin/kibana -> ``` +``` +env DEBUG="puppeteer:*" ./bin/kibana +``` +The internal DevTools protocol traffic will be logged via the `debug` module under the `puppeteer` namespace. + The Puppeteer logs are very verbose and could possibly contain sensitive information. Handle the generated output with care. diff --git a/docs/user/reporting/watch-example.asciidoc b/docs/user/reporting/watch-example.asciidoc index 4c769c85975c4..627e31017230c 100644 --- a/docs/user/reporting/watch-example.asciidoc +++ b/docs/user/reporting/watch-example.asciidoc @@ -56,7 +56,16 @@ report from the Kibana UI. //For more information, see <>. //<>. -NOTE: Reporting is integrated with Watcher only as an email attachment type. +[NOTE] +==== +Reporting is integrated with Watcher only as an email attachment type. + +The report Generation URL might contain date-math expressions +that cause the watch to fail with a `parse_exception`. +Remove curly braces `{` `}` from date-math expressions and +URL-encode characters to avoid this. +For example: `...(range:(%27@timestamp%27:(gte:now-15m%2Fd,lte:now%2Fd))))...` For more information about configuring watches, see {ref}/how-watcher-works.html[How Watcher works]. +==== diff --git a/package.json b/package.json index 9ac3c89e14c8e..1cec7aaacbbc9 100644 --- a/package.json +++ b/package.json @@ -110,12 +110,13 @@ ] }, "dependencies": { - "@babel/core": "^7.7.5", - "@babel/register": "^7.7.4", + "@babel/core": "^7.5.5", + "@babel/register": "^7.7.0", + "@elastic/apm-rum": "^4.6.0", "@elastic/charts": "^14.0.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "17.0.0", + "@elastic/eui": "17.1.2", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -152,12 +153,12 @@ "cache-loader": "^4.1.0", "chalk": "^2.4.2", "check-disk-space": "^2.1.0", - "chokidar": "3.3.0", + "chokidar": "3.2.1", "color": "1.0.3", "commander": "3.0.2", "compare-versions": "3.5.1", - "core-js": "^3.5.0", - "css-loader": "3.3.2", + "core-js": "^3.2.1", + "css-loader": "2.1.1", "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", "d3-cloud": "1.2.5", @@ -167,12 +168,12 @@ "elasticsearch": "^16.5.0", "elasticsearch-browser": "^16.5.0", "encode-uri-query": "1.0.1", - "execa": "^3.4.0", + "execa": "^3.2.0", "expiry-js": "0.1.7", "fast-deep-equal": "^3.1.1", - "file-loader": "5.0.2", + "file-loader": "4.2.0", "font-awesome": "4.7.0", - "getos": "^3.1.1", + "getos": "^3.1.0", "glob": "^7.1.2", "glob-all": "^3.1.0", "globby": "^8.0.1", @@ -221,7 +222,7 @@ "proxy-from-env": "1.0.0", "pug": "^2.0.4", "querystring-browser": "1.0.4", - "raw-loader": "4.0.0", + "raw-loader": "3.1.0", "react": "^16.12.0", "react-color": "^2.13.8", "react-dom": "^16.12.0", @@ -248,10 +249,10 @@ "script-loader": "0.7.2", "semver": "^5.5.0", "style-it": "^2.1.3", - "style-loader": "1.0.1", + "style-loader": "0.23.1", "symbol-observable": "^1.2.0", "tar": "4.4.13", - "terser-webpack-plugin": "^2.3.0", + "terser-webpack-plugin": "^2.1.2", "thread-loader": "^2.1.3", "tinygradient": "0.4.3", "tinymath": "1.2.1", @@ -259,25 +260,25 @@ "tslib": "^1.9.3", "type-detect": "^4.0.8", "ui-select": "0.19.8", - "url-loader": "3.0.0", + "url-loader": "2.2.0", "uuid": "3.3.2", - "val-loader": "^2.0.2", + "val-loader": "^1.1.1", "validate-npm-package-name": "2.2.2", "vega-lib": "4.3.0", "vega-lite": "^2.6.0", "vega-schema-url-parser": "1.0.0", "vega-tooltip": "^0.12.0", "vision": "^5.3.3", - "webpack": "4.41.2", + "webpack": "4.41.0", "webpack-merge": "4.2.2", "whatwg-fetch": "^3.0.0", "wrapper-webpack-plugin": "^2.1.0", "yauzl": "2.10.0" }, "devDependencies": { - "@babel/parser": "^7.7.5", - "@babel/plugin-syntax-dynamic-import": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/parser": "^7.5.5", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/types": "^7.5.5", "@elastic/elasticsearch": "^7.4.0", "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.2", @@ -298,7 +299,7 @@ "@testing-library/react-hooks": "^3.2.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", - "@types/babel__core": "^7.1.3", + "@types/babel__core": "^7.1.2", "@types/bluebird": "^3.1.1", "@types/boom": "^7.2.0", "@types/chance": "^1.0.0", @@ -310,7 +311,7 @@ "@types/delete-empty": "^2.0.0", "@types/elasticsearch": "^5.0.33", "@types/enzyme": "^3.9.0", - "@types/eslint": "^6.1.3", + "@types/eslint": "^6.1.2", "@types/fetch-mock": "^7.3.1", "@types/getopts": "^2.0.1", "@types/glob": "^7.1.1", @@ -356,7 +357,7 @@ "@types/sinon": "^7.0.13", "@types/strip-ansi": "^3.0.0", "@types/styled-components": "^4.4.1", - "@types/supertest": "^2.0.8", + "@types/supertest": "^2.0.5", "@types/supertest-as-promised": "^2.0.38", "@types/testing-library__react": "^9.1.2", "@types/testing-library__react-hooks": "^3.1.0", @@ -364,8 +365,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.10.0", - "@typescript-eslint/parser": "^2.10.0", + "@typescript-eslint/eslint-plugin": "^2.12.0", + "@typescript-eslint/parser": "^2.12.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -385,25 +386,25 @@ "enzyme-adapter-react-16": "^1.15.1", "enzyme-adapter-utils": "^1.12.1", "enzyme-to-json": "^3.4.3", - "eslint": "^6.7.2", - "eslint-config-prettier": "^6.7.0", + "eslint": "^6.5.1", + "eslint-config-prettier": "^6.4.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "^1.3.0", - "eslint-plugin-cypress": "^2.8.0", - "eslint-plugin-import": "^2.19.1", + "eslint-plugin-cypress": "^2.7.0", + "eslint-plugin-import": "^2.18.2", "eslint-plugin-jest": "^22.19.0", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-mocha": "^6.2.0", "eslint-plugin-no-unsanitized": "^3.0.2", "eslint-plugin-node": "^10.0.0", "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-prettier": "^3.1.1", "eslint-plugin-react": "^7.16.0", "eslint-plugin-react-hooks": "^2.1.2", "exit-hook": "^2.2.0", "faker": "1.1.0", "fetch-mock": "^7.3.9", - "geckodriver": "^1.19.1", + "geckodriver": "^1.19.0", "getopts": "^2.2.4", "grunt": "1.0.4", "grunt-available-tasks": "^0.6.3", @@ -422,7 +423,7 @@ "jest": "^24.9.0", "jest-cli": "^24.9.0", "jest-raw-loader": "^1.0.1", - "jimp": "0.9.3", + "jimp": "0.8.4", "json5": "^1.0.1", "karma": "3.1.4", "karma-chrome-launcher": "2.2.0", @@ -453,10 +454,10 @@ "regenerate": "^1.4.0", "sass-lint": "^1.12.1", "selenium-webdriver": "^4.0.0-alpha.5", - "simple-git": "1.129.0", + "simple-git": "1.116.0", "sinon": "^7.4.2", "strip-ansi": "^3.0.1", - "supertest": "^4.0.2", + "supertest": "^3.1.0", "supertest-as-promised": "^4.0.2", "tree-kill": "^1.2.1", "typescript": "3.7.2", @@ -467,7 +468,7 @@ "zlib": "^1.0.5" }, "engines": { - "node": "10.15.2", + "node": "10.18.0", "yarn": "^1.21.1" } } diff --git a/packages/elastic-datemath/package.json b/packages/elastic-datemath/package.json index e41744311e3be..57873d28d372d 100644 --- a/packages/elastic-datemath/package.json +++ b/packages/elastic-datemath/package.json @@ -11,8 +11,8 @@ "kbn:watch": "yarn build --watch" }, "devDependencies": { - "@babel/cli": "^7.7.5", - "@babel/preset-env": "^7.7.6", + "@babel/cli": "^7.5.5", + "@babel/preset-env": "^7.5.5", "babel-plugin-add-module-exports": "^1.0.2", "moment": "^2.24.0" }, diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 2e72066fcbc68..04602d196a7f3 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,14 +15,14 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.10.0", - "@typescript-eslint/parser": "^2.10.0", + "@typescript-eslint/eslint-plugin": "^2.12.0", + "@typescript-eslint/parser": "^2.12.0", "babel-eslint": "^10.0.3", - "eslint": "^6.7.2", + "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "^1.3.0", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-import": "^2.19.1", + "eslint-plugin-import": "^2.18.2", "eslint-plugin-jest": "^22.19.0", "eslint-plugin-mocha": "^6.2.0", "eslint-plugin-no-unsanitized": "^3.0.2", diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json index 73f19690d4c7b..9eefa16aaca01 100644 --- a/packages/kbn-analytics/package.json +++ b/packages/kbn-analytics/package.json @@ -14,7 +14,7 @@ "kbn:watch": "node scripts/build --source-maps --watch" }, "devDependencies": { - "@babel/cli": "^7.7.5", + "@babel/cli": "^7.5.5", "@kbn/dev-utils": "1.0.0", "@kbn/babel-preset": "1.0.0", "typescript": "3.7.2" diff --git a/packages/kbn-analytics/scripts/build.js b/packages/kbn-analytics/scripts/build.js index b7fbe629246ec..bb28c1460c9c2 100644 --- a/packages/kbn-analytics/scripts/build.js +++ b/packages/kbn-analytics/scripts/build.js @@ -55,7 +55,9 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE + ? [] + : ['--source-maps', 'inline']), ], wait: true, env: { diff --git a/packages/kbn-babel-code-parser/package.json b/packages/kbn-babel-code-parser/package.json index 8c7d51da791a5..a9d373d33ab38 100755 --- a/packages/kbn-babel-code-parser/package.json +++ b/packages/kbn-babel-code-parser/package.json @@ -15,12 +15,12 @@ "kbn:watch": "yarn build --watch" }, "devDependencies": { - "@babel/cli": "^7.7.5" + "@babel/cli": "^7.5.5" }, "dependencies": { "@kbn/babel-preset": "1.0.0", - "@babel/parser": "^7.7.5", - "@babel/traverse": "^7.7.4", + "@babel/parser": "^7.5.5", + "@babel/traverse": "^7.5.5", "lodash": "^4.17.15" } } diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index d617c287b4f89..0acafbae59afd 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -4,17 +4,17 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.7.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.7.4", - "@babel/plugin-proposal-optional-chaining": "^7.7.5", - "@babel/plugin-syntax-dynamic-import": "^7.7.4", - "@babel/plugin-transform-modules-commonjs": "^7.7.5", - "@babel/preset-env": "^7.7.6", + "@babel/plugin-proposal-class-properties": "^7.5.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", + "@babel/plugin-proposal-optional-chaining": "^7.6.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.5.0", + "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.7.4", "@babel/preset-typescript": "^7.7.4", "babel-plugin-add-module-exports": "^1.0.2", - "babel-plugin-filter-imports": "^4.0.0", + "babel-plugin-filter-imports": "^3.0.0", "babel-plugin-styled-components": "^1.10.6", - "babel-plugin-transform-define": "^2.0.0" + "babel-plugin-transform-define": "^1.3.1" } } diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index 56b3096433c24..fc3e3c541846a 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -59,6 +59,7 @@ import { export { ObjectType, TypeOf, Type }; export { ByteSizeValue } from './byte_size_value'; +export { SchemaTypeError, ValidationError } from './errors'; function any(options?: TypeOptions) { return new AnyType(options); diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 89b358c5b0b3e..09753afeb120f 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -12,7 +12,7 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "execa": "^3.4.0", + "execa": "^3.2.0", "exit-hook": "^2.2.0", "getopts": "^2.2.5", "moment": "^2.24.0", diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index adc81e3a6cc36..cb501dab3ddb7 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -11,11 +11,11 @@ "chalk": "^2.4.2", "dedent": "^0.7.0", "del": "^5.1.0", - "execa": "^3.4.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "glob": "^7.1.2", "node-fetch": "^2.6.0", - "simple-git": "^1.129.0", + "simple-git": "^1.91.0", "tar-fs": "^1.16.3", "tree-kill": "^1.2.1", "yauzl": "^2.10.0" diff --git a/packages/kbn-eslint-import-resolver-kibana/package.json b/packages/kbn-eslint-import-resolver-kibana/package.json index e6d185a9b5c3a..9fae27011767e 100755 --- a/packages/kbn-eslint-import-resolver-kibana/package.json +++ b/packages/kbn-eslint-import-resolver-kibana/package.json @@ -12,10 +12,10 @@ "dependencies": { "debug": "^2.6.9", "eslint-import-resolver-node": "0.3.2", - "eslint-import-resolver-webpack": "0.12.0", + "eslint-import-resolver-webpack": "0.11.1", "glob-all": "^3.1.0", "lru-cache": "^4.1.5", "resolve": "^1.7.1", - "webpack": "^4.41.2" + "webpack": "^4.41.0" } } diff --git a/packages/kbn-eslint-plugin-eslint/package.json b/packages/kbn-eslint-plugin-eslint/package.json index 70d6b0f017470..badcf13187caf 100644 --- a/packages/kbn-eslint-plugin-eslint/package.json +++ b/packages/kbn-eslint-plugin-eslint/package.json @@ -4,12 +4,12 @@ "private": true, "license": "Apache-2.0", "peerDependencies": { - "eslint": "6.7.2", + "eslint": "6.5.1", "babel-eslint": "^10.0.3" }, "dependencies": { "micromatch": "3.1.10", "dedent": "^0.7.0", - "eslint-module-utils": "2.5.0" + "eslint-module-utils": "2.4.1" } } diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index a3f5009f30dba..bbc5126da1dce 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -12,8 +12,8 @@ "kbn:watch": "node scripts/build --watch --source-maps" }, "devDependencies": { - "@babel/cli": "^7.7.5", - "@babel/core": "^7.7.5", + "@babel/cli": "^7.5.5", + "@babel/core": "^7.5.5", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@types/intl-relativeformat": "^2.1.0", diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index ccdddc87dbc18..0764451c74575 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -55,7 +55,9 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE + ? [] + : ['--source-maps', 'inline']), ], wait: true, env: { diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index 26b8026ed381c..27ef70d871856 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -9,29 +9,29 @@ "kbn:watch": "node scripts/build --dev --watch" }, "dependencies": { - "@babel/runtime": "^7.7.6", + "@babel/runtime": "^7.5.5", "@kbn/i18n": "1.0.0", "lodash": "npm:@elastic/lodash@3.10.1-kibana3", "lodash.clone": "^4.5.0", "uuid": "3.3.2" }, "devDependencies": { - "@babel/cli": "^7.7.5", - "@babel/core": "^7.7.5", - "@babel/plugin-transform-runtime": "^7.7.6", + "@babel/cli": "^7.5.5", + "@babel/core": "^7.5.5", + "@babel/plugin-transform-runtime": "^7.5.5", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "babel-loader": "^8.0.6", - "copy-webpack-plugin": "^5.1.1", - "css-loader": "3.3.2", + "copy-webpack-plugin": "^5.0.4", + "css-loader": "2.1.1", "del": "^5.1.0", "getopts": "^2.2.4", "pegjs": "0.10.0", - "sass-loader": "^8.0.0", - "style-loader": "1.0.1", + "sass-loader": "^7.3.1", + "style-loader": "0.23.1", "supports-color": "^7.0.0", - "url-loader": "3.0.0", - "webpack": "4.41.2", - "webpack-cli": "^3.3.10" + "url-loader": "2.2.0", + "webpack": "4.41.0", + "webpack-cli": "^3.3.9" } } diff --git a/packages/kbn-plugin-generator/package.json b/packages/kbn-plugin-generator/package.json index 8dafa36199417..ac98a0e675fb1 100644 --- a/packages/kbn-plugin-generator/package.json +++ b/packages/kbn-plugin-generator/package.json @@ -6,7 +6,7 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "execa": "^3.4.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index 68454a9ce8c1d..68af0aa791c8e 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -13,11 +13,11 @@ "@kbn/babel-preset": "1.0.0" }, "dependencies": { - "@babel/core": "^7.7.5", + "@babel/core": "^7.5.5", "argv-split": "^2.0.1", "commander": "^3.0.0", "del": "^5.1.0", - "execa": "^3.4.0", + "execa": "^3.2.0", "globby": "^8.0.1", "gulp-babel": "^8.0.0", "gulp-rename": "1.4.0", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index a5638b50629ec..aea85c13d7f32 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -8023,7 +8023,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _scripts__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(120); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -19088,7 +19088,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -19198,8 +19198,8 @@ const handleArgs = (file, args, options = {}) => { reject: true, cleanup: true, all: false, - windowsHide: true, - ...options + ...options, + windowsHide: true }; options.env = getEnv(options); @@ -23296,10 +23296,11 @@ const CleanCommand = { const originalCwd = process.cwd(); try { - for (const { - pattern, - cwd - } of toDelete) { + for (const _ref of toDelete) { + const { + pattern, + cwd + } = _ref; process.chdir(cwd); const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); @@ -45552,7 +45553,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(485); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(697); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(692); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -45732,8 +45733,8 @@ const EventEmitter = __webpack_require__(46); const path = __webpack_require__(16); const arrify = __webpack_require__(487); const globby = __webpack_require__(488); -const cpFile = __webpack_require__(687); -const CpyError = __webpack_require__(695); +const cpFile = __webpack_require__(682); +const CpyError = __webpack_require__(690); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -45862,8 +45863,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(489); const glob = __webpack_require__(37); const fastGlob = __webpack_require__(491); -const dirGlob = __webpack_require__(680); -const gitignore = __webpack_require__(683); +const dirGlob = __webpack_require__(675); +const gitignore = __webpack_require__(678); const DEFAULT_FILTER = () => false; @@ -46114,11 +46115,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(493); var taskManager = __webpack_require__(494); -var reader_async_1 = __webpack_require__(651); -var reader_stream_1 = __webpack_require__(675); -var reader_sync_1 = __webpack_require__(676); -var arrayUtils = __webpack_require__(678); -var streamUtils = __webpack_require__(679); +var reader_async_1 = __webpack_require__(646); +var reader_stream_1 = __webpack_require__(670); +var reader_sync_1 = __webpack_require__(671); +var arrayUtils = __webpack_require__(673); +var streamUtils = __webpack_require__(674); /** * Synchronous API. */ @@ -46758,9 +46759,9 @@ var extend = __webpack_require__(612); */ var compilers = __webpack_require__(615); -var parsers = __webpack_require__(647); -var cache = __webpack_require__(648); -var utils = __webpack_require__(649); +var parsers = __webpack_require__(642); +var cache = __webpack_require__(643); +var utils = __webpack_require__(644); var MAX_LENGTH = 1024 * 64; /** @@ -65299,9 +65300,9 @@ var toRegex = __webpack_require__(502); */ var compilers = __webpack_require__(632); -var parsers = __webpack_require__(643); -var Extglob = __webpack_require__(646); -var utils = __webpack_require__(645); +var parsers = __webpack_require__(638); +var Extglob = __webpack_require__(641); +var utils = __webpack_require__(640); var MAX_LENGTH = 1024 * 64; /** @@ -65811,7 +65812,7 @@ var parsers = __webpack_require__(636); * Module dependencies */ -var debug = __webpack_require__(638)('expand-brackets'); +var debug = __webpack_require__(574)('expand-brackets'); var extend = __webpack_require__(511); var Snapdragon = __webpack_require__(541); var toRegex = __webpack_require__(502); @@ -66405,839 +66406,12 @@ exports.createRegex = function(pattern, include) { /* 638 */ /***/ (function(module, exports, __webpack_require__) { -/** - * Detect Electron renderer process, which is node, but we should - * treat as a browser. - */ - -if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(639); -} else { - module.exports = __webpack_require__(642); -} - - -/***/ }), -/* 639 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(640); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = 'undefined' != typeof chrome - && 'undefined' != typeof chrome.storage - ? chrome.storage.local - : localstorage(); - -/** - * Colors. - */ - -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { - return true; - } - - // is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - try { - return JSON.stringify(v); - } catch (err) { - return '[UnexpectedJSONParseError]: ' + err.message; - } -}; - - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - var useColors = this.useColors; - - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - - if (!useColors) return; - - var c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit') - - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); -} - -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ - -function log() { - // this hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return 'object' === typeof console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; - } - } catch(e) {} -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ - -exports.enable(load()); - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - return window.localStorage; - } catch (e) {} -} - - -/***/ }), -/* 640 */ -/***/ (function(module, exports, __webpack_require__) { - - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = __webpack_require__(641); - -/** - * The currently active debug mode names, and names to skip. - */ - -exports.names = []; -exports.skips = []; - -/** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - -exports.formatters = {}; - -/** - * Previous log timestamp. - */ - -var prevTime; - -/** - * Select a color. - * @param {String} namespace - * @return {Number} - * @api private - */ - -function selectColor(namespace) { - var hash = 0, i; - - for (i in namespace) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return exports.colors[Math.abs(hash) % exports.colors.length]; -} - -/** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - -function createDebug(namespace) { - - function debug() { - // disabled? - if (!debug.enabled) return; - - var self = debug; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // turn the `arguments` into a proper Array - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - - args[0] = exports.coerce(args[0]); - - if ('string' !== typeof args[0]) { - // anything else let's inspect with %O - args.unshift('%O'); - } - - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); - - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // apply env-specific formatting (colors, etc.) - exports.formatArgs.call(self, args); - - var logFn = debug.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.enabled = exports.enabled(namespace); - debug.useColors = exports.useColors(); - debug.color = selectColor(namespace); - - // env-specific initialization logic for debug instances - if ('function' === typeof exports.init) { - exports.init(debug); - } - - return debug; -} - -/** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - -function enable(namespaces) { - exports.save(namespaces); - - exports.names = []; - exports.skips = []; - - var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - var len = split.length; - - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } -} - -/** - * Disable debug output. - * - * @api public - */ - -function disable() { - exports.enable(''); -} - -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - -function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; -} - -/** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} - - -/***/ }), -/* 641 */ -/***/ (function(module, exports) { - -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isNaN(val) === false) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtShort(ms) { - if (ms >= d) { - return Math.round(ms / d) + 'd'; - } - if (ms >= h) { - return Math.round(ms / h) + 'h'; - } - if (ms >= m) { - return Math.round(ms / m) + 'm'; - } - if (ms >= s) { - return Math.round(ms / s) + 's'; - } - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - return plural(ms, d, 'day') || - plural(ms, h, 'hour') || - plural(ms, m, 'minute') || - plural(ms, s, 'second') || - ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, n, name) { - if (ms < n) { - return; - } - if (ms < n * 1.5) { - return Math.floor(ms / n) + ' ' + name; - } - return Math.ceil(ms / n) + ' ' + name + 's'; -} - - -/***/ }), -/* 642 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Module dependencies. - */ - -var tty = __webpack_require__(579); -var util = __webpack_require__(29); - -/** - * This is the Node.js implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(640); -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ - -exports.inspectOpts = Object.keys(process.env).filter(function (key) { - return /^debug_/i.test(key); -}).reduce(function (obj, key) { - // camel-case - var prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); - - // coerce string value into JS value - var val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) val = true; - else if (/^(no|off|false|disabled)$/i.test(val)) val = false; - else if (val === 'null') val = null; - else val = Number(val); - - obj[prop] = val; - return obj; -}, {}); - -/** - * The file descriptor to write the `debug()` calls to. - * Set the `DEBUG_FD` env variable to override with another value. i.e.: - * - * $ DEBUG_FD=3 node script.js 3>debug.log - */ - -var fd = parseInt(process.env.DEBUG_FD, 10) || 2; - -if (1 !== fd && 2 !== fd) { - util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() -} - -var stream = 1 === fd ? process.stdout : - 2 === fd ? process.stderr : - createWritableStdioStream(fd); - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - return 'colors' in exports.inspectOpts - ? Boolean(exports.inspectOpts.colors) - : tty.isatty(fd); -} - -/** - * Map %o to `util.inspect()`, all on a single line. - */ - -exports.formatters.o = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n').map(function(str) { - return str.trim() - }).join(' '); -}; - -/** - * Map %o to `util.inspect()`, allowing multiple lines if needed. - */ - -exports.formatters.O = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); -}; - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs(args) { - var name = this.namespace; - var useColors = this.useColors; - - if (useColors) { - var c = this.color; - var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; - - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); - } else { - args[0] = new Date().toUTCString() - + ' ' + name + ' ' + args[0]; - } -} - -/** - * Invokes `util.format()` with the specified arguments and writes to `stream`. - */ - -function log() { - return stream.write(util.format.apply(util, arguments) + '\n'); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - if (null == namespaces) { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } else { - process.env.DEBUG = namespaces; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Copied from `node/src/node.js`. - * - * XXX: It's lame that node doesn't expose this API out-of-the-box. It also - * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. - */ - -function createWritableStdioStream (fd) { - var stream; - var tty_wrap = process.binding('tty_wrap'); - - // Note stream._type is used for test-module-load-list.js - - switch (tty_wrap.guessHandleType(fd)) { - case 'TTY': - stream = new tty.WriteStream(fd); - stream._type = 'tty'; - - // Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - case 'FILE': - var fs = __webpack_require__(23); - stream = new fs.SyncWriteStream(fd, { autoClose: false }); - stream._type = 'fs'; - break; - - case 'PIPE': - case 'TCP': - var net = __webpack_require__(580); - stream = new net.Socket({ - fd: fd, - readable: false, - writable: true - }); - - // FIXME Should probably have an option in net.Socket to create a - // stream from an existing fd which is writable only. But for now - // we'll just add this hack and set the `readable` member to false. - // Test: ./node test/fixtures/echo.js < /etc/passwd - stream.readable = false; - stream.read = null; - stream._type = 'pipe'; - - // FIXME Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - default: - // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); - } - - // For supporting legacy API we put the FD here. - stream.fd = fd; - - stream._isStdio = true; - - return stream; -} - -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ - -function init (debug) { - debug.inspectOpts = {}; - - var keys = Object.keys(exports.inspectOpts); - for (var i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} - -/** - * Enable namespaces listed in `process.env.DEBUG` initially. - */ - -exports.enable(load()); - - -/***/ }), -/* 643 */ -/***/ (function(module, exports, __webpack_require__) { - "use strict"; var brackets = __webpack_require__(633); -var define = __webpack_require__(644); -var utils = __webpack_require__(645); +var define = __webpack_require__(639); +var utils = __webpack_require__(640); /** * Characters to use in text regex (we want to "not" match @@ -67392,7 +66566,7 @@ module.exports = parsers; /***/ }), -/* 644 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67430,7 +66604,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 645 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67506,7 +66680,7 @@ utils.createRegex = function(str) { /***/ }), -/* 646 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67517,7 +66691,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(541); -var define = __webpack_require__(644); +var define = __webpack_require__(639); var extend = __webpack_require__(511); /** @@ -67525,7 +66699,7 @@ var extend = __webpack_require__(511); */ var compilers = __webpack_require__(632); -var parsers = __webpack_require__(643); +var parsers = __webpack_require__(638); /** * Customize Snapdragon parser and renderer @@ -67591,7 +66765,7 @@ module.exports = Extglob; /***/ }), -/* 647 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67681,14 +66855,14 @@ function textRegex(pattern) { /***/ }), -/* 648 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(624))(); /***/ }), -/* 649 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67706,7 +66880,7 @@ utils.define = __webpack_require__(611); utils.diff = __webpack_require__(628); utils.extend = __webpack_require__(612); utils.pick = __webpack_require__(629); -utils.typeOf = __webpack_require__(650); +utils.typeOf = __webpack_require__(645); utils.unique = __webpack_require__(514); /** @@ -68004,7 +67178,7 @@ utils.unixify = function(options) { /***/ }), -/* 650 */ +/* 645 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -68139,7 +67313,7 @@ function isBuffer(val) { /***/ }), -/* 651 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68158,9 +67332,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(652); -var reader_1 = __webpack_require__(665); -var fs_stream_1 = __webpack_require__(669); +var readdir = __webpack_require__(647); +var reader_1 = __webpack_require__(660); +var fs_stream_1 = __webpack_require__(664); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -68221,15 +67395,15 @@ exports.default = ReaderAsync; /***/ }), -/* 652 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(653); -const readdirAsync = __webpack_require__(661); -const readdirStream = __webpack_require__(664); +const readdirSync = __webpack_require__(648); +const readdirAsync = __webpack_require__(656); +const readdirStream = __webpack_require__(659); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -68313,7 +67487,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 653 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68321,11 +67495,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(654); +const DirectoryReader = __webpack_require__(649); let syncFacade = { - fs: __webpack_require__(659), - forEach: __webpack_require__(660), + fs: __webpack_require__(654), + forEach: __webpack_require__(655), sync: true }; @@ -68354,7 +67528,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 654 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68363,9 +67537,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(28).Readable; const EventEmitter = __webpack_require__(46).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(655); -const stat = __webpack_require__(657); -const call = __webpack_require__(658); +const normalizeOptions = __webpack_require__(650); +const stat = __webpack_require__(652); +const call = __webpack_require__(653); /** * Asynchronously reads the contents of a directory and streams the results @@ -68741,14 +67915,14 @@ module.exports = DirectoryReader; /***/ }), -/* 655 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(656); +const globToRegExp = __webpack_require__(651); module.exports = normalizeOptions; @@ -68925,7 +68099,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 656 */ +/* 651 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -69062,13 +68236,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 657 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(658); +const call = __webpack_require__(653); module.exports = stat; @@ -69143,7 +68317,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 658 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69204,14 +68378,14 @@ function callOnce (fn) { /***/ }), -/* 659 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(658); +const call = __webpack_require__(653); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -69275,7 +68449,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 660 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69304,7 +68478,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 661 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69312,12 +68486,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(662); -const DirectoryReader = __webpack_require__(654); +const maybe = __webpack_require__(657); +const DirectoryReader = __webpack_require__(649); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(663), + forEach: __webpack_require__(658), async: true }; @@ -69359,7 +68533,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 662 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69386,7 +68560,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 663 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69422,7 +68596,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 664 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69430,11 +68604,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(654); +const DirectoryReader = __webpack_require__(649); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(663), + forEach: __webpack_require__(658), async: true }; @@ -69454,16 +68628,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 665 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(666); -var entry_1 = __webpack_require__(668); -var pathUtil = __webpack_require__(667); +var deep_1 = __webpack_require__(661); +var entry_1 = __webpack_require__(663); +var pathUtil = __webpack_require__(662); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -69529,13 +68703,13 @@ exports.default = Reader; /***/ }), -/* 666 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(667); +var pathUtils = __webpack_require__(662); var patternUtils = __webpack_require__(495); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -69619,7 +68793,7 @@ exports.default = DeepFilter; /***/ }), -/* 667 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69650,13 +68824,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 668 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(667); +var pathUtils = __webpack_require__(662); var patternUtils = __webpack_require__(495); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -69742,7 +68916,7 @@ exports.default = EntryFilter; /***/ }), -/* 669 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69762,8 +68936,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var fsStat = __webpack_require__(670); -var fs_1 = __webpack_require__(674); +var fsStat = __webpack_require__(665); +var fs_1 = __webpack_require__(669); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -69813,14 +68987,14 @@ exports.default = FileSystemStream; /***/ }), -/* 670 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(671); -const statProvider = __webpack_require__(673); +const optionsManager = __webpack_require__(666); +const statProvider = __webpack_require__(668); /** * Asynchronous API. */ @@ -69851,13 +69025,13 @@ exports.statSync = statSync; /***/ }), -/* 671 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(672); +const fsAdapter = __webpack_require__(667); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -69870,7 +69044,7 @@ exports.prepare = prepare; /***/ }), -/* 672 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69893,7 +69067,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 673 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69945,7 +69119,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 674 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69976,7 +69150,7 @@ exports.default = FileSystem; /***/ }), -/* 675 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69996,9 +69170,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var readdir = __webpack_require__(652); -var reader_1 = __webpack_require__(665); -var fs_stream_1 = __webpack_require__(669); +var readdir = __webpack_require__(647); +var reader_1 = __webpack_require__(660); +var fs_stream_1 = __webpack_require__(664); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -70066,7 +69240,7 @@ exports.default = ReaderStream; /***/ }), -/* 676 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70085,9 +69259,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(652); -var reader_1 = __webpack_require__(665); -var fs_sync_1 = __webpack_require__(677); +var readdir = __webpack_require__(647); +var reader_1 = __webpack_require__(660); +var fs_sync_1 = __webpack_require__(672); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -70147,7 +69321,7 @@ exports.default = ReaderSync; /***/ }), -/* 677 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70166,8 +69340,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(670); -var fs_1 = __webpack_require__(674); +var fsStat = __webpack_require__(665); +var fs_1 = __webpack_require__(669); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -70213,7 +69387,7 @@ exports.default = FileSystemSync; /***/ }), -/* 678 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70229,7 +69403,7 @@ exports.flatten = flatten; /***/ }), -/* 679 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70250,13 +69424,13 @@ exports.merge = merge; /***/ }), -/* 680 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(681); +const pathType = __webpack_require__(676); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -70322,13 +69496,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 681 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(682); +const pify = __webpack_require__(677); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -70371,7 +69545,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 682 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70462,7 +69636,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 683 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70470,9 +69644,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(491); -const gitIgnore = __webpack_require__(684); -const pify = __webpack_require__(685); -const slash = __webpack_require__(686); +const gitIgnore = __webpack_require__(679); +const pify = __webpack_require__(680); +const slash = __webpack_require__(681); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -70570,7 +69744,7 @@ module.exports.sync = options => { /***/ }), -/* 684 */ +/* 679 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -71039,7 +70213,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 685 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71114,7 +70288,7 @@ module.exports = (input, options) => { /***/ }), -/* 686 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71132,17 +70306,17 @@ module.exports = input => { /***/ }), -/* 687 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(688); -const CpFileError = __webpack_require__(690); -const fs = __webpack_require__(692); -const ProgressEmitter = __webpack_require__(694); +const {Buffer} = __webpack_require__(683); +const CpFileError = __webpack_require__(685); +const fs = __webpack_require__(687); +const ProgressEmitter = __webpack_require__(689); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -71296,11 +70470,11 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 688 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ -var buffer = __webpack_require__(689) +var buffer = __webpack_require__(684) var Buffer = buffer.Buffer // alternative to using Object.keys for old browsers @@ -71364,18 +70538,18 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 689 */ +/* 684 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 690 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(691); +const NestedError = __webpack_require__(686); class CpFileError extends NestedError { constructor(message, nested) { @@ -71389,7 +70563,7 @@ module.exports = CpFileError; /***/ }), -/* 691 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(44); @@ -71443,15 +70617,15 @@ module.exports = NestedError; /***/ }), -/* 692 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(115); -const pify = __webpack_require__(693); -const CpFileError = __webpack_require__(690); +const pify = __webpack_require__(688); +const CpFileError = __webpack_require__(685); const fsP = pify(fs); @@ -71596,7 +70770,7 @@ if (fs.copyFileSync) { /***/ }), -/* 693 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71671,7 +70845,7 @@ module.exports = (input, options) => { /***/ }), -/* 694 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71712,12 +70886,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 695 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(696); +const NestedError = __webpack_require__(691); class CpyError extends NestedError { constructor(message, nested) { @@ -71731,7 +70905,7 @@ module.exports = CpyError; /***/ }), -/* 696 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -71787,7 +70961,7 @@ module.exports = NestedError; /***/ }), -/* 697 */ +/* 692 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index 92b495a111b61..ead454410a8b3 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -10,10 +10,10 @@ "prettier": "prettier --write './src/**/*.ts'" }, "devDependencies": { - "@babel/core": "^7.7.5", - "@babel/plugin-proposal-class-properties": "^7.7.4", - "@babel/plugin-proposal-object-rest-spread": "^7.7.4", - "@babel/preset-env": "^7.7.6", + "@babel/core": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@babel/preset-env": "^7.5.5", "@babel/preset-typescript": "^7.7.4", "@types/cmd-shim": "^2.0.0", "@types/cpy": "^5.1.0", @@ -40,7 +40,7 @@ "cpy": "^7.3.0", "dedent": "^0.7.0", "del": "^5.1.0", - "execa": "^3.4.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "glob": "^7.1.2", "globby": "^8.0.1", @@ -60,8 +60,8 @@ "tempy": "^0.3.0", "typescript": "3.7.2", "unlazy-loader": "^0.1.3", - "webpack": "^4.41.2", - "webpack-cli": "^3.3.10", + "webpack": "^4.41.0", + "webpack-cli": "^3.3.9", "wrap-ansi": "^3.0.1", "write-pkg": "^4.0.0" }, diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index ac35c583ec747..0cc54fa2a64c4 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -10,7 +10,7 @@ "kbn:watch": "yarn build --watch" }, "devDependencies": { - "@babel/cli": "^7.7.5", + "@babel/cli": "^7.5.5", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@types/parse-link-header": "^1.0.0", diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts index 7ae46ef6fac1e..b1f57cee8c23c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts @@ -25,7 +25,7 @@ it('collects metadata for the current test', async () => { const failureMetadata = new FailureMetadata(lifecycle); const test1 = {}; - await lifecycle.beforeEachTest.trigger(test1); + await lifecycle.beforeEachRunnable.trigger(test1); failureMetadata.add({ foo: 'bar' }); expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` @@ -35,7 +35,7 @@ it('collects metadata for the current test', async () => { `); const test2 = {}; - await lifecycle.beforeEachTest.trigger(test2); + await lifecycle.beforeEachRunnable.trigger(test2); failureMetadata.add({ test: 2 }); expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` @@ -55,7 +55,7 @@ it('adds messages to the messages state', () => { const failureMetadata = new FailureMetadata(lifecycle); const test1 = {}; - lifecycle.beforeEachTest.trigger(test1); + lifecycle.beforeEachRunnable.trigger(test1); failureMetadata.addMessages(['foo', 'bar']); failureMetadata.addMessages(['baz']); diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts index 9dc58d5b0b21f..be033e063fb9d 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts @@ -29,7 +29,7 @@ interface Metadata { export class FailureMetadata { // mocha's global types mean we can't import Mocha or it will override the global jest types.............. - private currentTest?: any; + private currentRunnable?: any; private readonly allMetadata = new Map(); constructor(lifecycle: Lifecycle) { @@ -39,18 +39,18 @@ export class FailureMetadata { ); } - lifecycle.beforeEachTest.add(test => { - this.currentTest = test; + lifecycle.beforeEachRunnable.add(runnable => { + this.currentRunnable = runnable; }); } add(metadata: Metadata | ((current: Metadata) => Metadata)) { - if (!this.currentTest) { - throw new Error('no current test to associate metadata with'); + if (!this.currentRunnable) { + throw new Error('no current runnable to associate metadata with'); } - const current = this.allMetadata.get(this.currentTest); - this.allMetadata.set(this.currentTest, { + const current = this.allMetadata.get(this.currentRunnable); + this.allMetadata.set(this.currentRunnable, { ...current, ...(typeof metadata === 'function' ? metadata(current || {}) : metadata), }); @@ -98,7 +98,7 @@ export class FailureMetadata { return screenshot; } - get(test: any) { - return this.allMetadata.get(test); + get(runnable: any) { + return this.allMetadata.get(runnable); } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts index 7f78bc28c6d3d..95843ae4dcff2 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts @@ -22,11 +22,13 @@ import { LifecyclePhase } from './lifecycle_phase'; // mocha's global types mean we can't import Mocha or it will override the global jest types.............. type ItsASuite = any; type ItsATest = any; +type ItsARunnable = any; export class Lifecycle { public readonly beforeTests = new LifecyclePhase<[]>({ singular: true, }); + public readonly beforeEachRunnable = new LifecyclePhase<[ItsARunnable]>(); public readonly beforeTestSuite = new LifecyclePhase<[ItsASuite]>(); public readonly beforeEachTest = new LifecyclePhase<[ItsATest]>(); public readonly afterTestSuite = new LifecyclePhase<[ItsASuite]>(); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js index 4eb45229c2234..64fc51a04aac9 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js @@ -19,7 +19,7 @@ import { createAssignmentProxy } from './assignment_proxy'; import { wrapFunction } from './wrap_function'; -import { wrapRunnableArgsWithErrorHandler } from './wrap_runnable_args'; +import { wrapRunnableArgs } from './wrap_runnable_args'; export function decorateMochaUi(lifecycle, context) { // incremented at the start of each suite, decremented after @@ -93,7 +93,7 @@ export function decorateMochaUi(lifecycle, context) { function wrapTestFunction(name, fn) { return wrapNonSuiteFunction( name, - wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { + wrapRunnableArgs(fn, lifecycle, async (err, test) => { await lifecycle.testFailure.trigger(err, test); }) ); @@ -111,7 +111,7 @@ export function decorateMochaUi(lifecycle, context) { function wrapTestHookFunction(name, fn) { return wrapNonSuiteFunction( name, - wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { + wrapRunnableArgs(fn, lifecycle, async (err, test) => { await lifecycle.testHookFailure.trigger(err, test); }) ); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/wrap_runnable_args.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/wrap_runnable_args.js index 5ee21e81e83cc..d312ad8079dc1 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/wrap_runnable_args.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/wrap_runnable_args.js @@ -23,28 +23,24 @@ import { wrapFunction, wrapAsyncFunction } from './wrap_function'; * Wraps a "runnable" defining function (it(), beforeEach(), etc.) * so that any "runnable" arguments passed to it are wrapped and will * trigger a lifecycle event if they throw an error. - * - * @param {Function} fn - * @param {String} eventName - * @return {Function} */ -export function wrapRunnableArgsWithErrorHandler(fn, handler) { +export function wrapRunnableArgs(fn, lifecycle, handler) { return wrapFunction(fn, { before(target, thisArg, argumentsList) { for (let i = 0; i < argumentsList.length; i++) { if (typeof argumentsList[i] === 'function') { - argumentsList[i] = wrapRunnableError(argumentsList[i], handler); + argumentsList[i] = wrapAsyncFunction(argumentsList[i], { + async before(target, thisArg) { + await lifecycle.beforeEachRunnable.trigger(thisArg); + }, + + async handleError(target, thisArg, argumentsList, err) { + await handler(err, thisArg.test); + throw err; + }, + }); } } }, }); } - -function wrapRunnableError(runnable, handler) { - return wrapAsyncFunction(runnable, { - async handleError(target, thisArg, argumentsList, err) { - await handler(err, thisArg.test); - throw err; - }, - }); -} diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index e3a46811684b4..b4d9d3dfee03f 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -30,18 +30,18 @@ "enzyme-adapter-react-16": "^1.9.1" }, "devDependencies": { - "@babel/core": "^7.7.5", + "@babel/core": "^7.5.5", "@elastic/eui": "0.0.55", "@kbn/babel-preset": "1.0.0", "autoprefixer": "9.6.1", "babel-loader": "^8.0.6", "brace": "0.11.1", "chalk": "^2.4.2", - "chokidar": "3.3.0", - "core-js": "^3.5.0", - "css-loader": "^3.3.2", + "chokidar": "3.2.1", + "core-js": "^3.2.1", + "css-loader": "^2.1.1", "expose-loader": "^0.7.5", - "file-loader": "^5.0.2", + "file-loader": "^4.2.0", "grunt": "1.0.4", "grunt-babel": "^8.0.0", "grunt-contrib-clean": "^1.1.0", @@ -56,7 +56,7 @@ "node-sass": "^4.9.4", "postcss": "^7.0.5", "postcss-loader": "^3.0.0", - "raw-loader": "^4.0.0", + "raw-loader": "^3.1.0", "react-dom": "^16.12.0", "react-redux": "^5.1.2", "react-router": "^3.2.0", @@ -64,11 +64,11 @@ "redux": "3.7.2", "redux-thunk": "2.2.0", "regenerator-runtime": "^0.13.3", - "sass-loader": "^8.0.0", + "sass-loader": "^7.3.1", "sinon": "^7.4.2", - "style-loader": "^1.0.1", - "webpack": "^4.41.2", - "webpack-dev-server": "^3.9.0", + "style-loader": "^0.23.1", + "webpack": "^4.41.0", + "webpack-dev-server": "^3.8.2", "yeoman-generator": "1.1.1", "yo": "2.0.6" } diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 495b5fb374b43..36bbc8cc82873 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -18,6 +18,7 @@ */ import { PromiseType } from 'utility-types'; +export { $Values, Required, Optional, Class } from 'utility-types'; /** * Returns wrapped type of a promise. diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json index a79d08677020b..a999eb41eb781 100644 --- a/packages/kbn-utility-types/package.json +++ b/packages/kbn-utility-types/package.json @@ -13,7 +13,7 @@ "clean": "del target" }, "dependencies": { - "utility-types": "^3.7.0" + "utility-types": "^3.10.0" }, "devDependencies": { "del-cli": "^3.0.0", diff --git a/rfcs/text/0007_lifecycle_unblocked.md b/rfcs/text/0007_lifecycle_unblocked.md new file mode 100644 index 0000000000000..cb978d3dcd7ba --- /dev/null +++ b/rfcs/text/0007_lifecycle_unblocked.md @@ -0,0 +1,374 @@ +- Start Date: 2019-09-11 +- RFC PR: (leave this empty) +- Kibana Issue: (leave this empty) + +## Table of contents +- [Summary](#summary) +- [Motivation](#motivation) +- [Detailed design](#detailed-design) + - [

  1. Synchronous lifecycle methods
](#ollisynchronous-lifecycle-methodsliol) + - [
  1. Synchronous Context Provider functions
](#ol-start2lisynchronous-context-provider-functionsliol) + - [
  1. Core should not expose API's as observables
](#ol-start3licore-should-not-expose-apis-as-observablesliol) + - [
  1. Complete example code
](#ol-start4licomplete-example-codeliol) + - [
  1. Core should expose a status signal for Core services & plugins
](#ol-start5licore-should-expose-a-status-signal-for-core-services-amp-pluginsliol) +- [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) + - [
  1. Introduce a lifecycle/context provider timeout
](#olliintroduce-a-lifecyclecontext-provider-timeoutliol) + - [
  1. Treat anything that blocks Kibana from starting up as a bug
](#ol-start2litreat-anything-that-blocks-kibana-from-starting-up-as-a-bugliol) +- [Adoption strategy](#adoption-strategy) +- [How we teach this](#how-we-teach-this) +- [Unresolved questions](#unresolved-questions) +- [Footnotes](#footnotes) + +# Summary + +Prevent plugin lifecycle methods from blocking Kibana startup by making the +following changes: +1. Synchronous lifecycle methods +2. Synchronous context provider functions +3. Core should not expose API's as observables + +# Motivation +Plugin lifecycle methods and context provider functions are async +(promise-returning) functions. Core runs these functions in series and waits +for each plugin's lifecycle/context provider function to resolve before +calling the next. This allows plugins to depend on the API's returned from +other plugins. + +With the current design, a single lifecycle method that blocks will block all +of Kibana from starting up. Similarly, a blocking context provider will block +all the handlers that depend on that context. Plugins (including legacy +plugins) rely heavily on this blocking behaviour to ensure that all conditions +required for their plugin's operation are met before their plugin is started +and exposes it's API's. This means a single plugin with a network error that +isn't retried or a dependency on an external host that is down, could block +all of Kibana from starting up. + +We should make it impossible for a single plugin lifecycle function to stall +all of kibana. + +# Detailed design + +### 1. Synchronous lifecycle methods +Lifecycle methods are synchronous functions, they can perform async operations +but Core doesn't wait for these to complete. This guarantees that no plugin +lifecycle function can block other plugins or core from starting up [1]. + +Core will still expose special API's that are able block the setup lifecycle +such as registering Saved Object migrations, but this will be limited to +operations where the risk of blocking all of kibana starting up is limited. + +### 2. Synchronous Context Provider functions +Making context provider functions synchronous guarantees that a context +handler will never be blocked by registered context providers. They can expose +async API's which could potentially have blocking behaviour. + +```ts +export type IContextProvider< + THandler extends HandlerFunction, + TContextName extends keyof HandlerContextType +> = ( + context: Partial>, + ...rest: HandlerParameters +) => + | HandlerContextType[TContextName]; +``` + +### 3. Core should not expose API's as observables +All Core API's should be reactive: when internal state changes, their behaviour +should change accordingly. But, exposing these internal state changes as part +of the API contract leaks internal implementation details consumers can't do +anything useful with and don't care about. + +For example: Core currently exposes `core.elasticsearch.adminClient$`, an +Observable which emits a pre-configured elasticsearch client every time there's +a configuration change. This includes changes to the logging configuration and +might in the future include updating the authentication headers sent to +elasticsearch https://github.com/elastic/kibana/issues/19829. As a plugin +author who wants to make search requests against elasticsearch I shouldn't +have to care about, react to, or keep track of, how many times the underlying +configuration has changed. I want to use the `callAsInternalUser` method and I +expect Core to use the most up to date configuration to send this request. + +> Note: It would not be desirable for Core to dynamically load all +> configuration changes. Changing the Elasticsearch `hosts` could mean Kibana +> is pointing to a completely new Elasticsearch cluster. Since this is a risky +> change to make and would likely require core and almost all plugins to +> completely re-initialize, it's safer to require a complete Kibana restart. + +This does not mean we should remove all observables from Core's API's. When an +API consumer is interested in the *state changes itself* it absolutely makes +sense to expose this as an Observable. Good examples of this is exposing +plugin config as this is state that changes over time to which a plugin should +directly react to. + +This is important in the context of synchronous lifecycle methods and context +handlers since exposing convenient API's become very ugly: + +*(3.1): exposing Observable-based API's through the route handler context:* +```ts +// Before: Using an async context provider +coreSetup.http.registerRouteHandlerContext(coreId, 'core', async (context, req) => { + const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); + const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); + return { + elasticsearch: { + adminClient: adminClient.asScoped(req), + dataClient: dataClient.asScoped(req), + }, + }; +}); + +// After: Using a synchronous context provider +coreSetup.http.registerRouteHandlerContext(coreId, 'core', async (context, req) => { + return { + elasticsearch: { + // (3.1.1) We can expose a convenient API by doing a lot of work + adminClient: () => { + callAsInternalUser: async (...args) => { + const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); + return adminClient.asScoped(req).callAsinternalUser(args); + }, + callAsCurrentUser: async (...args) => { + adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); + return adminClient.asScoped(req).callAsCurrentUser(args); + } + }, + // (3.1.2) Or a lazy approach which perpetuates the problem to consumers: + dataClient: async () => { + const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); + return dataClient.asScoped(req); + }, + }, + }; +}); +``` + +### 4. Complete example code +*(4.1) Doing async operations in a plugin's setup lifecycle* +```ts +export class Plugin { + public setup(core: CoreSetup) { + // Async setup is possible and any operations involving async API's + // will still block until these API's are ready, (savedObjects find only + // resolves once the elasticsearch client has established a connection to + // the cluster). The difference is that these details are now internal to + // the API. + (async () => { + const docs = await core.savedObjects.client.find({...}); + ... + await core.savedObjects.client.update(...); + })(); + } +} +``` + +*(4.2) Exposing an API from a plugin's setup lifecycle* +```ts +export class Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + private async initSavedConfig(core: CoreSetup) { + // Note: pulling a config value here means our code isn't reactive to + // changes, but this is equivalent to doing it in an async setup lifecycle. + const config = await this.initializerContext.config + .create>() + .pipe(first()) + .toPromise(); + try { + const savedConfig = await core.savedObjects.internalRepository.get({...}); + return Object.assign({}, config, savedConfig); + } catch (e) { + if (SavedObjectErrorHelpers.isNotFoundError(e)) { + return await core.savedObjects.internalRepository.create(config, {...}); + } + } + } + public setup(core: CoreSetup) { + // savedConfigPromise resolves with the same kind of "setup state" that a + // plugin would have constructed in an async setup lifecycle. + const savedConfigPromise = initSavedConfig(core); + return { + ping: async () => { + const savedConfig = await savedConfigPromise; + if (config.allowPing === false || savedConfig.allowPing === false) { + throw new Error('ping() has been disabled'); + } + // Note: the elasticsearch client no longer exposes an adminClient$ + // observable, improving the ergonomics of consuming the API. + return await core.elasticsearch.adminClient.callAsInternalUser('ping', ...); + } + }; + } +} +``` + +*(4.3) Exposing an observable free Elasticsearch API from the route context* +```ts +coreSetup.http.registerRouteHandlerContext(coreId, 'core', async (context, req) => { + return { + elasticsearch: { + adminClient: coreSetup.elasticsearch.adminClient.asScoped(req), + dataClient: coreSetup.elasticsearch.adminClient.asScoped(req), + }, + }; +}); +``` + +### 5. Core should expose a status signal for Core services & plugins +Core should expose a global mechanism for core services and plugins to signal +their status. This is equivalent to the legacy status API +`kibana.Plugin.status` which allowed plugins to set their status to e.g. 'red' +or 'green'. The exact design of this API is outside of the scope of this RFC. + +What is important, is that there is a global mechanism to signal status +changes which Core then makes visible to system administrators in the Kibana +logs and the `/status` HTTP API. Plugins should be able to inspect and +subscribe to status changes from any of their dependencies. + +This will provide an obvious mechanism for plugins to signal that the +conditions which are required for this plugin to operate are not currently +present and manual intervention might be required. Status changes can happen +in both setup and start lifecycles e.g.: + - [setup] a required remote host is down + - [start] a remote host which was up during setup, started returning + connection timeout errors. + +# Drawbacks +Not being able to block on a lifecycle method means plugins can no longer be +certain that all setup is "complete" before they expose their API's or reach +the start lifecycle. + +A plugin might want to poll an external host to ensure that the host is up in +its setup lifecycle before making network requests to this host in it's start +lifecycle. + +Even if Kibana was using a valid, but incorrect configuration for the remote +host, with synchronous lifecycles Kibana would still start up. Although the +status API and logs would indicate a problem, these might not be monitored +leading to the error only being discovered once someone tries to use it's +functionality. This is an acceptable drawback because it buys us isolation. +Some problems might go unnoticed, but no single plugin should affect the +availability of all other plugins. + +In effect, the plugin is polling the world to construct a snapshot +of state which drives future behaviour. Modeling this with lifecycle functions +is insufficient since it assumes that any state constructed in the setup +lifecycle is static and won't and can't be changed in the future. + +For example: a plugin's setup lifecycle might poll for the existence of a +custom Elasticsearch index and if it doesn't exist, create it. Should there be +an Elasticsearch restore which deletes the index, the plugin wouldn't be able +to gracefully recover by simply running it's setup lifecycle a second time. + +The once-off nature of lifecycle methods are incompatible with the real-world +dynamic conditions under which plugins run. Not being able to block a +lifecycle method is, therefore, only a drawback when plugins are authored under +the false illusion of stability. + +# Alternatives +## 1. Introduce a lifecycle/context provider timeout +Lifecycle methods and context providers would timeout after X seconds and any +API's they expose would not be available if the timeout had been reached. + +Drawbacks: +1. A blocking setup lifecycle makes it easy for plugin authors to fall into + the trap of assuming that their plugin's behaviour can continue to operate + based on the snapshot of conditions present during setup. + +2. For lifecycle methods: there would be no way to recover from a timeout, + once a timeout had been reached the API will remain unavailable. + + Context providers have the benefit of being re-created for each handler + call, so a single timeout would not permanently disable the API. + +3. Plugins have less control over their behaviour. When an upstream server + becomes unavailable, a plugin might prefer to keep retrying the request + indefinitely or only timeout after more than X seconds. It also isn't able + to expose detailed error information to downstream consumers such as + specifying which host or service is unavailable. + +4. (minor) Introduces an additional failure condition that needs to be handled. + Consumers should handle the API not being available in setup, as well as, + error responses from the API itself. Since remote hosts like Elasticsearch + could go down even after a successful setup, this effectively means API + consumers have to handle the same error condition in two places. + +## 2. Treat anything that blocks Kibana from starting up as a bug +Keep the existing New Platform blocking behaviour, but through strong +conventions and developer awareness minimize the risk of plugins blocking +Kibana's startup indefinetely. By logging detailed diagnostic info on any +plugins that appear to be blocking startup, we can aid system administrators +to recover a blocked Kibana. + +A parallel can be drawn between Kibana's async plugin initialization and the TC39 +proposal for [top-level await](https://github.com/tc39/proposal-top-level-await). +> enables modules to act as big async functions: With top-level await, +> ECMAScript Modules (ESM) can await resources, causing other modules who +> import them to wait before they start evaluating their body + +They believe the benefits outweigh the risk of modules blocking loading since: + - [developer education should result in correct usage](https://github.com/tc39/proposal-top-level-await#will-top-level-await-cause-developers-to-make-their-code-block-longer-than-it-should) + - [there are existing unavoidable ways in which modules could block loading such as infinite loops or recursion](https://github.com/tc39/proposal-top-level-await#does-top-level-await-increase-the-risk-of-deadlocks) + + +Drawbacks: +1. A blocking setup lifecycle makes it easy for plugin authors to fall into + the trap of assuming that their plugin's behaviour can continue to operate + based on the snapshot of conditions present during setup. +2. This opens up the potential for a bug in Elastic or third-party plugins to + effectively "break" kibana. Instead of a single plugin being disabled all + of kibana would be down requiring manual intervention by a system + administrator. + +# Adoption strategy +Although the eventual goal is to have sync-only lifecycles / providers, we +will start by deprecating async behaviour and implementing a 30s timeout as +per alternative (1). This will immediately lower the impact of plugin bugs +while at the same time enabling a more incremental rollout and the flexibility +to discover use cases that would require adopting Core API's to support sync +lifecycles / providers. + +Adoption and implementation should be handled as follows: + - Adopt Core API’s to make sync lifecycles easier (3) + - Update migration guide and other documentation examples. + - Deprecate async lifecycles / context providers with a warning. Add a + timeout of 30s after which a plugin and it's dependencies will be disabled. + - Refactor existing plugin lifecycles which are easily converted to sync + - Future: remove async timeout lifecycles / context providers + +The following New Platform plugins or shims currently rely on async lifecycle +functions and will be impacted: +1. [region_map](https://github.com/elastic/kibana/blob/6039709929caf0090a4130b8235f3a53bd04ed84/src/legacy/core_plugins/region_map/public/plugin.ts#L68) +2. [tile_map](https://github.com/elastic/kibana/blob/6039709929caf0090a4130b8235f3a53bd04ed84/src/legacy/core_plugins/tile_map/public/plugin.ts#L62) +3. [vis_type_table](https://github.com/elastic/kibana/blob/6039709929caf0090a4130b8235f3a53bd04ed84/src/legacy/core_plugins/vis_type_table/public/plugin.ts#L61) +4. [vis_type_vega](https://github.com/elastic/kibana/blob/6039709929caf0090a4130b8235f3a53bd04ed84/src/legacy/core_plugins/vis_type_vega/public/plugin.ts#L59) +5. [timelion](https://github.com/elastic/kibana/blob/9d69b72a5f200e58220231035b19da852fc6b0a5/src/plugins/timelion/server/plugin.ts#L40) +6. [code](https://github.com/elastic/kibana/blob/5049b460b47d4ae3432e1d9219263bb4be441392/x-pack/legacy/plugins/code/server/plugin.ts#L129-L149) +7. [spaces](https://github.com/elastic/kibana/blob/096c7ee51136327f778845c636d7c4f1188e5db2/x-pack/legacy/plugins/spaces/server/new_platform/plugin.ts#L95) +8. [licensing](https://github.com/elastic/kibana/blob/4667c46caef26f8f47714504879197708debae32/x-pack/plugins/licensing/server/plugin.ts) +9. [security](https://github.com/elastic/kibana/blob/0f2324e44566ce2cf083d89082841e57d2db6ef6/x-pack/plugins/security/server/plugin.ts#L96) + +# How we teach this + +Async Plugin lifecycle methods and async context provider functions have been +deprecated. In the future all lifecycle methods will by sync only. Plugins +should treat the setup lifecycle as a place in time to register functionality +with core or other plugins' API's and not as a mechanism to kick off and wait +for any initialization that's required for the plugin to be able to run. + +# Unresolved questions +1. ~~Are the drawbacks worth the benefits or can we live with Kibana potentially +being blocked for the sake of convenient async lifecycle stages?~~ + +2. Should core provide conventions or patterns for plugins to construct a + snapshot of state and reactively updating this state and the behaviour it + drives as the state of the world changes? + +3. Do plugins ever need to read config values and pass these as parameters to + Core API’s? If so we would have to expose synchronous config values to + support sync lifecycles. + +# Footnotes +[1] Synchronous lifecycles can still be blocked by e.g. an infine for loop, +but this would always be unintentional behaviour in contrast to intentional +async behaviour like blocking until an external service becomes available. diff --git a/src/apm.js b/src/apm.js index cea6f8fc072aa..e3f4d84d9b523 100644 --- a/src/apm.js +++ b/src/apm.js @@ -17,21 +17,81 @@ * under the License. */ -const { existsSync } = require('fs'); const { join } = require('path'); -const { name, version } = require('../package.json'); +const { readFileSync } = require('fs'); +const { execSync } = require('child_process'); +const merge = require('lodash.merge'); +const { name, version, build } = require('../package.json'); -module.exports = function(serviceName = name) { - if (process.env.kbnWorkerType === 'optmzr') return; +const ROOT_DIR = join(__dirname, '..'); + +function gitRev() { + try { + return execSync('git rev-parse --short HEAD', { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + } catch (e) { + return null; + } +} + +function devConfig() { + try { + const apmDevConfigPath = join(ROOT_DIR, 'config', 'apm.dev.js'); + return require(apmDevConfigPath); // eslint-disable-line import/no-dynamic-require + } catch (e) { + return {}; + } +} + +const apmConfig = merge( + { + active: false, + serverUrl: 'https://f1542b814f674090afd914960583265f.apm.us-central1.gcp.cloud.es.io:443', + // The secretToken below is intended to be hardcoded in this file even though + // it makes it public. This is not a security/privacy issue. Normally we'd + // instead disable the need for a secretToken in the APM Server config where + // the data is transmitted to, but due to how it's being hosted, it's easier, + // for now, to simply leave it in. + secretToken: 'R0Gjg46pE9K9wGestd', + globalLabels: {}, + breakdownMetrics: true, + centralConfig: false, + logUncaughtExceptions: true, + }, + devConfig() +); + +try { + const filename = join(ROOT_DIR, 'data', 'uuid'); + apmConfig.globalLabels.kibana_uuid = readFileSync(filename, 'utf-8'); +} catch (e) {} // eslint-disable-line no-empty - const conf = { - serviceName: `${serviceName}-${version.replace(/\./g, '_')}`, +const rev = gitRev(); +if (rev !== null) apmConfig.globalLabels.git_rev = rev; + +function getConfig(serviceName) { + return { + ...apmConfig, + ...{ + serviceName: `${serviceName}-${version.replace(/\./g, '_')}`, + }, }; +} + +/** + * Flag to disable APM RUM support on all kibana builds by default + */ +const isKibanaDistributable = Boolean(build && build.distributable === true); - const configFile = join(__dirname, '..', 'config', 'apm.js'); +module.exports = function(serviceName = name) { + if (process.env.kbnWorkerType === 'optmzr') return; - if (existsSync(configFile)) conf.configFile = configFile; - else conf.active = false; + const conf = getConfig(serviceName); require('elastic-apm-node').start(conf); }; + +module.exports.getConfig = getConfig; +module.exports.isKibanaDistributable = isKibanaDistributable; diff --git a/src/cli/cluster/__mocks__/cluster.js b/src/cli/cluster/cluster.mock.ts similarity index 85% rename from src/cli/cluster/__mocks__/cluster.js rename to src/cli/cluster/cluster.mock.ts index d653771136ae6..332f8aad53ba1 100644 --- a/src/cli/cluster/__mocks__/cluster.js +++ b/src/cli/cluster/cluster.mock.ts @@ -18,12 +18,15 @@ */ /* eslint-env jest */ +// eslint-disable-next-line max-classes-per-file import EventEmitter from 'events'; import { assign, random } from 'lodash'; import { delay } from 'bluebird'; class MockClusterFork extends EventEmitter { - constructor(cluster) { + public exitCode = 0; + + constructor(cluster: MockCluster) { super(); let dead = true; @@ -49,9 +52,9 @@ class MockClusterFork extends EventEmitter { send: jest.fn(), }); - jest.spyOn(this, 'on'); - jest.spyOn(this, 'off'); - jest.spyOn(this, 'emit'); + jest.spyOn(this as EventEmitter, 'on'); + jest.spyOn(this as EventEmitter, 'off'); + jest.spyOn(this as EventEmitter, 'emit'); (async () => { await wait(); @@ -61,11 +64,7 @@ class MockClusterFork extends EventEmitter { } } -class MockCluster extends EventEmitter { +export class MockCluster extends EventEmitter { fork = jest.fn(() => new MockClusterFork(this)); setupMaster = jest.fn(); } - -export function mockCluster() { - return new MockCluster(); -} diff --git a/src/cli/cluster/cluster_manager.test.mocks.ts b/src/cli/cluster/cluster_manager.test.mocks.ts new file mode 100644 index 0000000000000..53984fd12cbf1 --- /dev/null +++ b/src/cli/cluster/cluster_manager.test.mocks.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 { MockCluster } from './cluster.mock'; +export const mockCluster = new MockCluster(); +jest.mock('cluster', () => mockCluster); diff --git a/src/cli/cluster/cluster_manager.test.js b/src/cli/cluster/cluster_manager.test.ts similarity index 84% rename from src/cli/cluster/cluster_manager.test.js rename to src/cli/cluster/cluster_manager.test.ts index be8a096db9a66..bd37e854e1691 100644 --- a/src/cli/cluster/cluster_manager.test.js +++ b/src/cli/cluster/cluster_manager.test.ts @@ -17,8 +17,7 @@ * under the License. */ -import { mockCluster } from './__mocks__/cluster'; -jest.mock('cluster', () => mockCluster()); +import { mockCluster } from './cluster_manager.test.mocks'; jest.mock('readline', () => ({ createInterface: jest.fn(() => ({ on: jest.fn(), @@ -27,15 +26,14 @@ jest.mock('readline', () => ({ })), })); -import cluster from 'cluster'; import { sample } from 'lodash'; -import ClusterManager from './cluster_manager'; -import Worker from './worker'; +import { ClusterManager } from './cluster_manager'; +import { Worker } from './worker'; describe('CLI cluster manager', () => { beforeEach(() => { - cluster.fork.mockImplementation(() => { + mockCluster.fork.mockImplementation(() => { return { process: { kill: jest.fn(), @@ -44,16 +42,16 @@ describe('CLI cluster manager', () => { off: jest.fn(), on: jest.fn(), send: jest.fn(), - }; + } as any; }); }); afterEach(() => { - cluster.fork.mockReset(); + mockCluster.fork.mockReset(); }); test('has two workers', () => { - const manager = ClusterManager.create({}); + const manager = new ClusterManager({}, {} as any); expect(manager.workers).toHaveLength(2); for (const worker of manager.workers) expect(worker).toBeInstanceOf(Worker); @@ -63,7 +61,7 @@ describe('CLI cluster manager', () => { }); test('delivers broadcast messages to other workers', () => { - const manager = ClusterManager.create({}); + const manager = new ClusterManager({}, {} as any); for (const worker of manager.workers) { Worker.prototype.start.call(worker); // bypass the debounced start method @@ -76,10 +74,10 @@ describe('CLI cluster manager', () => { messenger.emit('broadcast', football); for (const worker of manager.workers) { if (worker === messenger) { - expect(worker.fork.send).not.toHaveBeenCalled(); + expect(worker.fork!.send).not.toHaveBeenCalled(); } else { - expect(worker.fork.send).toHaveBeenCalledTimes(1); - expect(worker.fork.send).toHaveBeenCalledWith(football); + expect(worker.fork!.send).toHaveBeenCalledTimes(1); + expect(worker.fork!.send).toHaveBeenCalledWith(football); } } }); @@ -88,7 +86,7 @@ describe('CLI cluster manager', () => { test('correctly configures `BasePathProxy`.', async () => { const basePathProxyMock = { start: jest.fn() }; - ClusterManager.create({}, {}, basePathProxyMock); + new ClusterManager({}, {} as any, basePathProxyMock as any); expect(basePathProxyMock.start).toHaveBeenCalledWith({ shouldRedirectFromOldBasePath: expect.any(Function), @@ -97,13 +95,13 @@ describe('CLI cluster manager', () => { }); describe('proxy is configured with the correct `shouldRedirectFromOldBasePath` and `blockUntil` functions.', () => { - let clusterManager; - let shouldRedirectFromOldBasePath; - let blockUntil; + let clusterManager: ClusterManager; + let shouldRedirectFromOldBasePath: (path: string) => boolean; + let blockUntil: () => Promise; beforeEach(async () => { const basePathProxyMock = { start: jest.fn() }; - clusterManager = ClusterManager.create({}, {}, basePathProxyMock); + clusterManager = new ClusterManager({}, {} as any, basePathProxyMock as any); jest.spyOn(clusterManager.server, 'on'); jest.spyOn(clusterManager.server, 'off'); @@ -146,7 +144,7 @@ describe('CLI cluster manager', () => { expect(clusterManager.server.on).toHaveBeenCalledTimes(2); expect(clusterManager.server.on).toHaveBeenCalledWith('crashed', expect.any(Function)); - const [, [eventName, onCrashed]] = clusterManager.server.on.mock.calls; + const [, [eventName, onCrashed]] = (clusterManager.server.on as jest.Mock).mock.calls; // Check event name to make sure we call the right callback, // in Jest 23 we could use `toHaveBeenNthCalledWith` instead. expect(eventName).toBe('crashed'); @@ -164,7 +162,7 @@ describe('CLI cluster manager', () => { expect(clusterManager.server.on).toHaveBeenCalledTimes(2); expect(clusterManager.server.on).toHaveBeenCalledWith('listening', expect.any(Function)); - const [[eventName, onListening]] = clusterManager.server.on.mock.calls; + const [[eventName, onListening]] = (clusterManager.server.on as jest.Mock).mock.calls; // Check event name to make sure we call the right callback, // in Jest 23 we could use `toHaveBeenNthCalledWith` instead. expect(eventName).toBe('listening'); diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.ts similarity index 83% rename from src/cli/cluster/cluster_manager.js rename to src/cli/cluster/cluster_manager.ts index cd1b3a0dadfc6..d97f7485fb4d2 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/cli/cluster/cluster_manager.ts @@ -20,26 +20,38 @@ import { resolve } from 'path'; import { format as formatUrl } from 'url'; import opn from 'opn'; - import { debounce, invoke, bindAll, once, uniq } from 'lodash'; import * as Rx from 'rxjs'; import { first, mapTo, filter, map, take } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/dev-utils'; +import { FSWatcher } from 'chokidar'; + +import { LegacyConfig } from '../../core/server/legacy/config'; +import { BasePathProxyServer } from '../../core/server/http'; +// @ts-ignore import Log from '../log'; -import Worker from './worker'; -import { Config } from '../../legacy/server/config/config'; +import { Worker } from './worker'; process.env.kbnWorkerType = 'managr'; -export default class ClusterManager { - static create(opts, settings = {}, basePathProxy) { - return new ClusterManager(opts, Config.withDefaultSchema(settings), basePathProxy); - } - - constructor(opts, config, basePathProxy) { +export class ClusterManager { + public optimizer: Worker; + public server: Worker; + public workers: Worker[]; + + private watcher: FSWatcher | null = null; + private basePathProxy: BasePathProxyServer | undefined; + private log: any; + private addedCount = 0; + private inReplMode: boolean; + + constructor( + opts: Record, + config: LegacyConfig, + basePathProxy?: BasePathProxyServer + ) { this.log = new Log(opts.quiet, opts.silent); - this.addedCount = 0; this.inReplMode = !!opts.repl; this.basePathProxy = basePathProxy; @@ -79,7 +91,7 @@ export default class ClusterManager { worker.on('broadcast', msg => { this.workers.forEach(to => { if (to !== worker && to.online) { - to.fork.send(msg); + to.fork!.send(msg); } }); }); @@ -90,10 +102,10 @@ export default class ClusterManager { // and all workers. This is only used by LogRotator service // when the cluster mode is enabled this.server.on('reloadLoggingConfigFromServerWorker', () => { - process.emit('message', { reloadLoggingConfig: true }); + process.emit('message' as any, { reloadLoggingConfig: true } as any); this.workers.forEach(worker => { - worker.fork.send({ reloadLoggingConfig: true }); + worker.fork!.send({ reloadLoggingConfig: true }); }); }); @@ -111,9 +123,9 @@ export default class ClusterManager { } if (opts.watch) { - const pluginPaths = config.get('plugins.paths'); + const pluginPaths = config.get('plugins.paths'); const scanDirs = [ - ...config.get('plugins.scanDirs'), + ...config.get('plugins.scanDirs'), resolve(REPO_ROOT, 'src/plugins'), resolve(REPO_ROOT, 'x-pack/plugins'), ]; @@ -131,7 +143,7 @@ export default class ClusterManager { resolve(path, 'scripts'), resolve(path, 'docs') ), - [] + [] as string[] ); this.setupWatching(extraPaths, pluginInternalDirsIgnore); @@ -149,7 +161,7 @@ export default class ClusterManager { } } - setupOpen(openUrl) { + setupOpen(openUrl: string) { const serverListening$ = Rx.merge( Rx.fromEvent(this.server, 'listening').pipe(mapTo(true)), Rx.fromEvent(this.server, 'fork:exit').pipe(mapTo(false)), @@ -157,7 +169,7 @@ export default class ClusterManager { ); const optimizeSuccess$ = Rx.fromEvent(this.optimizer, 'optimizeStatus').pipe( - map(msg => !!msg.success) + map((msg: any) => !!msg.success) ); Rx.combineLatest(serverListening$, optimizeSuccess$) @@ -169,8 +181,10 @@ export default class ClusterManager { .then(() => opn(openUrl)); } - setupWatching(extraPaths, pluginInternalDirsIgnore) { + setupWatching(extraPaths: string[], pluginInternalDirsIgnore: string[]) { + // eslint-disable-next-line @typescript-eslint/no-var-requires const chokidar = require('chokidar'); + // eslint-disable-next-line @typescript-eslint/no-var-requires const { fromRoot } = require('../../core/server/utils'); const watchPaths = [ @@ -204,7 +218,7 @@ export default class ClusterManager { ...ignorePaths, 'plugins/java_languageserver', ], - }); + }) as FSWatcher; this.watcher.on('add', this.onWatcherAdd); this.watcher.on('error', this.onWatcherError); @@ -213,8 +227,8 @@ export default class ClusterManager { 'ready', once(() => { // start sending changes to workers - this.watcher.removeListener('add', this.onWatcherAdd); - this.watcher.on('all', this.onWatcherChange); + this.watcher!.removeListener('add', this.onWatcherAdd); + this.watcher!.on('all', this.onWatcherChange); this.log.good('watching for changes', `(${this.addedCount} files)`); this.startCluster(); @@ -229,6 +243,7 @@ export default class ClusterManager { if (this.inReplMode) { return; } + // eslint-disable-next-line @typescript-eslint/no-var-requires const readline = require('readline'); const rl = readline.createInterface(process.stdin, process.stdout); @@ -263,16 +278,16 @@ export default class ClusterManager { this.addedCount += 1; } - onWatcherChange(e, path) { + onWatcherChange(e: any, path: string) { invoke(this.workers, 'onChange', path); } - onWatcherError(err) { + onWatcherError(err: any) { this.log.bad('failed to watch files!\n', err.stack); process.exit(1); // eslint-disable-line no-process-exit } - shouldRedirectFromOldBasePath(path) { + shouldRedirectFromOldBasePath(path: string) { // strip `s/{id}` prefix when checking for need to redirect if (path.startsWith('s/')) { path = path diff --git a/src/cli/cluster/worker.test.js b/src/cli/cluster/worker.test.ts similarity index 80% rename from src/cli/cluster/worker.test.js rename to src/cli/cluster/worker.test.ts index b43cc123abcbb..4f9337681e083 100644 --- a/src/cli/cluster/worker.test.js +++ b/src/cli/cluster/worker.test.ts @@ -17,22 +17,20 @@ * under the License. */ -import { mockCluster } from './__mocks__/cluster'; -jest.mock('cluster', () => mockCluster()); +import { mockCluster } from './cluster_manager.test.mocks'; -import cluster from 'cluster'; - -import Worker from './worker'; +import { Worker, ClusterWorker } from './worker'; +// @ts-ignore import Log from '../log'; -const workersToShutdown = []; +const workersToShutdown: Worker[] = []; -function assertListenerAdded(emitter, event) { +function assertListenerAdded(emitter: NodeJS.EventEmitter, event: any) { expect(emitter.on).toHaveBeenCalledWith(event, expect.any(Function)); } -function assertListenerRemoved(emitter, event) { - const [, onEventListener] = emitter.on.mock.calls.find(([eventName]) => { +function assertListenerRemoved(emitter: NodeJS.EventEmitter, event: any) { + const [, onEventListener] = (emitter.on as jest.Mock).mock.calls.find(([eventName]) => { return eventName === event; }); @@ -44,6 +42,7 @@ function setup(opts = {}) { log: new Log(false, true), ...opts, baseArgv: [], + type: 'test', }); workersToShutdown.push(worker); @@ -53,7 +52,7 @@ function setup(opts = {}) { describe('CLI cluster manager', () => { afterEach(async () => { while (workersToShutdown.length > 0) { - const worker = workersToShutdown.pop(); + const worker = workersToShutdown.pop() as Worker; // If `fork` exists we should set `exitCode` to the non-zero value to // prevent worker from auto restart. if (worker.fork) { @@ -63,14 +62,14 @@ describe('CLI cluster manager', () => { await worker.shutdown(); } - cluster.fork.mockClear(); + mockCluster.fork.mockClear(); }); describe('#onChange', () => { describe('opts.watch = true', () => { test('restarts the fork', () => { const worker = setup({ watch: true }); - jest.spyOn(worker, 'start').mockImplementation(() => {}); + jest.spyOn(worker, 'start').mockResolvedValue(); worker.onChange('/some/path'); expect(worker.changes).toEqual(['/some/path']); expect(worker.start).toHaveBeenCalledTimes(1); @@ -80,7 +79,7 @@ describe('CLI cluster manager', () => { describe('opts.watch = false', () => { test('does not restart the fork', () => { const worker = setup({ watch: false }); - jest.spyOn(worker, 'start').mockImplementation(() => {}); + jest.spyOn(worker, 'start').mockResolvedValue(); worker.onChange('/some/path'); expect(worker.changes).toEqual([]); expect(worker.start).not.toHaveBeenCalled(); @@ -94,13 +93,13 @@ describe('CLI cluster manager', () => { const worker = setup(); await worker.start(); expect(worker).toHaveProperty('online', true); - const fork = worker.fork; - expect(fork.process.kill).not.toHaveBeenCalled(); + const fork = worker.fork as ClusterWorker; + expect(fork!.process.kill).not.toHaveBeenCalled(); assertListenerAdded(fork, 'message'); assertListenerAdded(fork, 'online'); assertListenerAdded(fork, 'disconnect'); await worker.shutdown(); - expect(fork.process.kill).toHaveBeenCalledTimes(1); + expect(fork!.process.kill).toHaveBeenCalledTimes(1); assertListenerRemoved(fork, 'message'); assertListenerRemoved(fork, 'online'); assertListenerRemoved(fork, 'disconnect'); @@ -120,7 +119,7 @@ describe('CLI cluster manager', () => { test(`is bound to fork's message event`, async () => { const worker = setup(); await worker.start(); - expect(worker.fork.on).toHaveBeenCalledWith('message', expect.any(Function)); + expect(worker.fork!.on).toHaveBeenCalledWith('message', expect.any(Function)); }); }); @@ -138,8 +137,8 @@ describe('CLI cluster manager', () => { test('calls #onMessage with message parts', () => { const worker = setup(); jest.spyOn(worker, 'onMessage').mockImplementation(() => {}); - worker.parseIncomingMessage([10, 100, 1000, 10000]); - expect(worker.onMessage).toHaveBeenCalledWith(10, 100, 1000, 10000); + worker.parseIncomingMessage(['event', 'some-data']); + expect(worker.onMessage).toHaveBeenCalledWith('event', 'some-data'); }); }); }); @@ -149,7 +148,7 @@ describe('CLI cluster manager', () => { test('emits the data to be broadcasted', () => { const worker = setup(); const data = {}; - jest.spyOn(worker, 'emit').mockImplementation(() => {}); + jest.spyOn(worker, 'emit').mockImplementation(() => true); worker.onMessage('WORKER_BROADCAST', data); expect(worker.emit).toHaveBeenCalledWith('broadcast', data); }); @@ -158,7 +157,7 @@ describe('CLI cluster manager', () => { describe('when sent WORKER_LISTENING message', () => { test('sets the listening flag and emits the listening event', () => { const worker = setup(); - jest.spyOn(worker, 'emit').mockImplementation(() => {}); + jest.spyOn(worker, 'emit').mockImplementation(() => true); expect(worker).toHaveProperty('listening', false); worker.onMessage('WORKER_LISTENING'); expect(worker).toHaveProperty('listening', true); @@ -170,8 +169,6 @@ describe('CLI cluster manager', () => { test('does nothing', () => { const worker = setup(); worker.onMessage('asdlfkajsdfahsdfiohuasdofihsdoif'); - worker.onMessage({}); - worker.onMessage(23049283094); }); }); }); @@ -185,7 +182,7 @@ describe('CLI cluster manager', () => { await worker.start(); - expect(cluster.fork).toHaveBeenCalledTimes(1); + expect(mockCluster.fork).toHaveBeenCalledTimes(1); expect(worker.on).toHaveBeenCalledWith('fork:online', expect.any(Function)); }); @@ -193,12 +190,12 @@ describe('CLI cluster manager', () => { const worker = setup(); jest.spyOn(process, 'on'); - jest.spyOn(cluster, 'on'); + jest.spyOn(mockCluster, 'on'); await worker.start(); - expect(cluster.on).toHaveBeenCalledTimes(1); - expect(cluster.on).toHaveBeenCalledWith('exit', expect.any(Function)); + expect(mockCluster.on).toHaveBeenCalledTimes(1); + expect(mockCluster.on).toHaveBeenCalledWith('exit', expect.any(Function)); expect(process.on).toHaveBeenCalledTimes(1); expect(process.on).toHaveBeenCalledWith('exit', expect.any(Function)); }); diff --git a/src/cli/cluster/worker.js b/src/cli/cluster/worker.ts similarity index 75% rename from src/cli/cluster/worker.js rename to src/cli/cluster/worker.ts index 2250075f20a60..fb87f1a87654c 100644 --- a/src/cli/cluster/worker.js +++ b/src/cli/cluster/worker.ts @@ -21,25 +21,57 @@ import _ from 'lodash'; import cluster from 'cluster'; import { EventEmitter } from 'events'; -import { BinderFor } from '../../legacy/utils'; +import { BinderFor } from '../../legacy/utils/binder_for'; import { fromRoot } from '../../core/server/utils'; const cliPath = fromRoot('src/cli'); const baseArgs = _.difference(process.argv.slice(2), ['--no-watch']); const baseArgv = [process.execPath, cliPath].concat(baseArgs); +export type ClusterWorker = cluster.Worker & { + killed: boolean; + exitCode?: number; +}; + cluster.setupMaster({ exec: cliPath, silent: false, }); -const dead = fork => { +const dead = (fork: ClusterWorker) => { return fork.isDead() || fork.killed; }; -export default class Worker extends EventEmitter { - constructor(opts) { - opts = opts || {}; +interface WorkerOptions { + type: string; + log: any; // src/cli/log.js + argv?: string[]; + title?: string; + watch?: boolean; + baseArgv?: string[]; +} + +export class Worker extends EventEmitter { + private readonly clusterBinder: BinderFor; + private readonly processBinder: BinderFor; + + private type: string; + private title: string; + private log: any; + private forkBinder: BinderFor | null = null; + private startCount: number; + private watch: boolean; + private env: Record; + + public fork: ClusterWorker | null = null; + public changes: string[]; + + // status flags + public online = false; // the fork can accept messages + public listening = false; // the fork is listening for connections + public crashed = false; // the fork crashed + + constructor(opts: WorkerOptions) { super(); this.log = opts.log; @@ -48,15 +80,9 @@ export default class Worker extends EventEmitter { this.watch = opts.watch !== false; this.startCount = 0; - // status flags - this.online = false; // the fork can accept messages - this.listening = false; // the fork is listening for connections - this.crashed = false; // the fork crashed - this.changes = []; - this.forkBinder = null; // defined when the fork is - this.clusterBinder = new BinderFor(cluster); + this.clusterBinder = new BinderFor(cluster as any); // lack the 'off' method this.processBinder = new BinderFor(process); this.env = { @@ -66,7 +92,7 @@ export default class Worker extends EventEmitter { }; } - onExit(fork, code) { + onExit(fork: ClusterWorker, code: number) { if (this.fork !== fork) return; // we have our fork's exit, so stop listening for others @@ -91,7 +117,7 @@ export default class Worker extends EventEmitter { } } - onChange(path) { + onChange(path: string) { if (!this.watch) return; this.changes.push(path); this.start(); @@ -104,7 +130,7 @@ export default class Worker extends EventEmitter { this.fork.killed = true; // stop listening to the fork, it's just going to die - this.forkBinder.destroy(); + this.forkBinder!.destroy(); // we don't need to react to process.exit anymore this.processBinder.destroy(); @@ -114,12 +140,14 @@ export default class Worker extends EventEmitter { } } - parseIncomingMessage(msg) { - if (!Array.isArray(msg)) return; - this.onMessage(...msg); + parseIncomingMessage(msg: any) { + if (!Array.isArray(msg)) { + return; + } + this.onMessage(msg[0], msg[1]); } - onMessage(type, data) { + onMessage(type: string, data?: any) { switch (type) { case 'WORKER_BROADCAST': this.emit('broadcast', data); @@ -170,16 +198,16 @@ export default class Worker extends EventEmitter { this.log.warn(`restarting ${this.title}...`); } - this.fork = cluster.fork(this.env); + this.fork = cluster.fork(this.env) as ClusterWorker; this.forkBinder = new BinderFor(this.fork); // when the fork sends a message, comes online, or loses its connection, then react - this.forkBinder.on('message', msg => this.parseIncomingMessage(msg)); + this.forkBinder.on('message', (msg: any) => this.parseIncomingMessage(msg)); this.forkBinder.on('online', () => this.onOnline()); this.forkBinder.on('disconnect', () => this.onDisconnect()); // when the cluster says a fork has exited, check if it is ours - this.clusterBinder.on('exit', (fork, code) => this.onExit(fork, code)); + this.clusterBinder.on('exit', (fork: ClusterWorker, code: number) => this.onExit(fork, code)); // when the process exits, make sure we kill our workers this.processBinder.on('exit', () => this.shutdown()); diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 5bb22579d123e..1c78de966c46f 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -46,6 +46,8 @@ - [How to](#how-to) - [Configure plugin](#configure-plugin) - [Handle plugin configuration deprecations](#handle-plugin-config-deprecations) + - [Use scoped services](#use-scoped-services) + - [Declare a custom scoped service](#declare-a-custom-scoped-service) - [Mock new platform services in tests](#mock-new-platform-services-in-tests) - [Writing mocks for your plugin](#writing-mocks-for-your-plugin) - [Using mocks in your tests](#using-mocks-in-your-tests) @@ -1190,22 +1192,23 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | | `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | -| `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client | -| `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client | -| `xpackMainPlugin.info.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | | +| `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | +| `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | | `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactory`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | | | `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | | | `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | | | `server.savedObjects.getScopedSavedObjectsClient` | [`core.savedObjects.getScopedClient`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | | | `request.getSavedObjectsClient` | [`context.core.savedObjects.client`](/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md) | | +| `request.getUiSettingsService` | [`context.uiSettings.client`](/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md) | | | `kibana.Plugin.deprecations` | [Handle plugin configuration deprecations](#handle-plugin-config-deprecations) and [`PluginConfigDescriptor.deprecations`](docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md) | Deprecations from New Platform are not applied to legacy configuration | _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-server.coresetup.md)_ ##### Plugin services -| Legacy Platform | New Platform | Notes | -| ------------------------------------------- | ------------------------------------------------------------------------------ | ----- | -| `server.plugins.xpack_main.registerFeature` | [`plugins.features.registerFeature`](x-pack/plugins/features/server/plugin.ts) | | +| Legacy Platform | New Platform | Notes | +| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----- | +| `server.plugins.xpack_main.registerFeature` | [`plugins.features.registerFeature`](x-pack/plugins/features/server/plugin.ts) | | +| `server.plugins.xpack_main.feature(pluginID).registerLicenseCheckResultsGenerator` | [`x-pack licensing plugin`](/x-pack/plugins/licensing/README.md) | | #### UI Exports @@ -1399,7 +1402,7 @@ export const config: PluginConfigDescriptor = { deprecations: ({ rename, unused }) => [ rename('oldProperty', 'newProperty'), unused('someUnusedProperty'), - ] + ] }; ``` @@ -1413,7 +1416,7 @@ export const config: PluginConfigDescriptor = { deprecations: ({ renameFromRoot, unusedFromRoot }) => [ renameFromRoot('oldplugin.property', 'myplugin.property'), unusedFromRoot('oldplugin.deprecated'), - ] + ] }; ``` @@ -1421,6 +1424,68 @@ Note that deprecations registered in new platform's plugins are not applied to t During migration, if you still need the deprecations to be effective in the legacy plugin, you need to declare them in both plugin definitions. +### Use scoped services +Whenever Kibana needs to get access to data saved in elasticsearch, it should perform a check whether an end-user has access to the data. +In the legacy platform, Kibana requires to bind elasticsearch related API with an incoming request to access elasticsearch service on behalf of a user. +```js + async function handler(req, res) { + const dataCluster = server.plugins.elasticsearch.getCluster('data'); + const data = await dataCluster.callWithRequest(req, 'ping'); + } +``` + +The new platform introduced [a handler interface](/rfcs/text/0003_handler_interface.md) on the server-side to perform that association internally. Core services, that require impersonation with an incoming request, are +exposed via `context` argument of [the request handler interface.](/docs/development/core/server/kibana-plugin-server.requesthandler.md) +The above example looks in the new platform as +```js + async function handler(context, req, res) { + const data = await context.core.elasticsearch.adminClient.callAsInternalUser('ping') + } +``` + +The [request handler context](/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md) exposed the next scoped **core** services: +| Legacy Platform | New Platform | +| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------| +| `request.getSavedObjectsClient` | [`context.savedObjects.client`](/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md) | +| `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | +| `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | +| `request.getUiSettingsService` | [`context.uiSettings.client`](/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md) | + +#### Declare a custom scoped service +Plugins can extend the handler context with custom API that will be available to the plugin itself and all dependent plugins. +For example, the plugin creates a custom elasticsearch client and want to use it via the request handler context: + +```ts +import { CoreSetup, IScopedClusterClient } from 'kibana/server'; + +export interface MyPluginContext { + client: IScopedClusterClient; +} + +// extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file +declare module 'src/core/server' { + interface RequestHandlerContext { + myPlugin?: MyPluginContext; + } +} + +class Plugin { + setup(core: CoreSetup) { + const client = core.elasticsearch.createClient('myClient'); + core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => { + return { client: client.asScoped(req) }; + }); + + router.get( + { path: '/api/my-plugin/', validate }, + async (context, req, res) => { + const data = await context.myPlugin.client.callAsCurrentUser('endpoint'); + ... + } + ); + } +``` + ### Mock new platform services in tests #### Writing mocks for your plugin diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index abc4c144356e8..2a9dca96062dc 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -211,7 +211,7 @@ export class CoreSystem { const injectedMetadata = await this.injectedMetadata.start(); const uiSettings = await this.uiSettings.start(); const docLinks = await this.docLinks.start({ injectedMetadata }); - const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); + const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup! }); const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); const application = await this.application.start({ http, injectedMetadata }); diff --git a/src/core/public/http/__snapshots__/http_service.test.ts.snap b/src/core/public/http/__snapshots__/http_service.test.ts.snap deleted file mode 100644 index 3d0309476365d..0000000000000 --- a/src/core/public/http/__snapshots__/http_service.test.ts.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`addLoadingCount() adds a fatal error if source observable emits a negative number 1`] = ` -Array [ - Array [ - [Error: Observables passed to loadingCount.add() must only emit positive numbers], - ], -] -`; - -exports[`addLoadingCount() adds a fatal error if source observables emit an error 1`] = ` -Array [ - Array [ - [Error: foo bar], - ], -] -`; - -exports[`getLoadingCount$() emits 0 initially, the right count when sources emit their own count, and ends with zero 1`] = ` -Array [ - 0, - 100, - 110, - 111, - 11, - 21, - 20, - 0, -] -`; - -exports[`getLoadingCount$() only emits when loading count changes 1`] = ` -Array [ - 0, - 1, - 0, -] -`; diff --git a/src/core/public/http/anonymous_paths.test.ts b/src/core/public/http/anonymous_paths.test.ts deleted file mode 100644 index bf9212f625f1e..0000000000000 --- a/src/core/public/http/anonymous_paths.test.ts +++ /dev/null @@ -1,107 +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 { AnonymousPaths } from './anonymous_paths'; -import { BasePath } from './base_path_service'; - -describe('#register', () => { - it(`allows paths that don't start with /`, () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('bar'); - }); - - it(`allows paths that end with '/'`, () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar/'); - }); -}); - -describe('#isAnonymous', () => { - it('returns true for registered paths', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar'); - expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); - }); - - it('returns true for paths registered with a trailing slash, but call "isAnonymous" with no trailing slash', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar/'); - expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); - }); - - it('returns true for paths registered without a trailing slash, but call "isAnonymous" with a trailing slash', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar'); - expect(anonymousPaths.isAnonymous('/foo/bar/')).toBe(true); - }); - - it('returns true for paths registered without a starting slash', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('bar'); - expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); - }); - - it('returns true for paths registered with a starting slash', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar'); - expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); - }); - - it('when there is no basePath and calling "isAnonymous" without a starting slash, returns true for paths registered with a starting slash', () => { - const basePath = new BasePath('/'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar'); - expect(anonymousPaths.isAnonymous('bar')).toBe(true); - }); - - it('when there is no basePath and calling "isAnonymous" with a starting slash, returns true for paths registered with a starting slash', () => { - const basePath = new BasePath('/'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar'); - expect(anonymousPaths.isAnonymous('/bar')).toBe(true); - }); - - it('returns true for paths whose capitalization is different', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/BAR'); - expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); - }); - - it('returns false for other paths', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar'); - expect(anonymousPaths.isAnonymous('/foo/foo')).toBe(false); - }); - - it('returns false for sub-paths of registered paths', () => { - const basePath = new BasePath('/foo'); - const anonymousPaths = new AnonymousPaths(basePath); - anonymousPaths.register('/bar'); - expect(anonymousPaths.isAnonymous('/foo/bar/baz')).toBe(false); - }); -}); diff --git a/src/core/public/http/anonymous_paths.ts b/src/core/public/http/anonymous_paths.ts deleted file mode 100644 index 300c4d64df353..0000000000000 --- a/src/core/public/http/anonymous_paths.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IAnonymousPaths, IBasePath } from 'src/core/public'; - -export class AnonymousPaths implements IAnonymousPaths { - private readonly paths = new Set(); - - constructor(private basePath: IBasePath) {} - - public isAnonymous(path: string): boolean { - const pathWithoutBasePath = this.basePath.remove(path); - return this.paths.has(this.normalizePath(pathWithoutBasePath)); - } - - public register(path: string) { - this.paths.add(this.normalizePath(path)); - } - - private normalizePath(path: string) { - // always lower-case it - let normalized = path.toLowerCase(); - - // remove the slash from the end - if (normalized.endsWith('/')) { - normalized = normalized.slice(0, normalized.length - 1); - } - - // put a slash at the start - if (!normalized.startsWith('/')) { - normalized = `/${normalized}`; - } - - // it's normalized!!! - return normalized; - } -} diff --git a/src/core/public/http/anonymous_paths_service.test.ts b/src/core/public/http/anonymous_paths_service.test.ts new file mode 100644 index 0000000000000..515715d9a613d --- /dev/null +++ b/src/core/public/http/anonymous_paths_service.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { AnonymousPathsService } from './anonymous_paths_service'; +import { BasePath } from './base_path'; + +describe('#setup()', () => { + describe('#register', () => { + it(`allows paths that don't start with /`, () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('bar'); + }); + + it(`allows paths that end with '/'`, () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar/'); + }); + }); + + describe('#isAnonymous', () => { + it('returns true for registered paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered with a trailing slash, but call "isAnonymous" with no trailing slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar/'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered without a trailing slash, but call "isAnonymous" with a trailing slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar/')).toBe(true); + }); + + it('returns true for paths registered without a starting slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('when there is no basePath and calling "isAnonymous" without a starting slash, returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('bar')).toBe(true); + }); + + it('when there is no basePath and calling "isAnonymous" with a starting slash, returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/bar')).toBe(true); + }); + + it('returns true for paths whose capitalization is different', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/BAR'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns false for other paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/foo')).toBe(false); + }); + + it('returns false for sub-paths of registered paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPathsService().setup({ basePath }); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar/baz')).toBe(false); + }); + }); +}); diff --git a/src/core/public/http/anonymous_paths_service.ts b/src/core/public/http/anonymous_paths_service.ts new file mode 100644 index 0000000000000..ee9b3578c0270 --- /dev/null +++ b/src/core/public/http/anonymous_paths_service.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 { IAnonymousPaths, IBasePath } from 'src/core/public'; +import { CoreService } from '../../types'; + +interface Deps { + basePath: IBasePath; +} + +export class AnonymousPathsService implements CoreService { + private readonly paths = new Set(); + + public setup({ basePath }: Deps) { + return { + isAnonymous: (path: string): boolean => { + const pathWithoutBasePath = basePath.remove(path); + return this.paths.has(normalizePath(pathWithoutBasePath)); + }, + + register: (path: string) => { + this.paths.add(normalizePath(path)); + }, + + normalizePath, + }; + } + + public start(deps: Deps) { + return this.setup(deps); + } + + public stop() {} +} + +const normalizePath = (path: string) => { + // always lower-case it + let normalized = path.toLowerCase(); + + // remove the slash from the end + if (normalized.endsWith('/')) { + normalized = normalized.slice(0, normalized.length - 1); + } + + // put a slash at the start + if (!normalized.startsWith('/')) { + normalized = `/${normalized}`; + } + + // it's normalized!!! + return normalized; +}; diff --git a/src/core/public/http/base_path_service.test.ts b/src/core/public/http/base_path.test.ts similarity index 98% rename from src/core/public/http/base_path_service.test.ts rename to src/core/public/http/base_path.test.ts index 65403c906e614..63b7fa61cee84 100644 --- a/src/core/public/http/base_path_service.test.ts +++ b/src/core/public/http/base_path.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { BasePath } from './base_path_service'; +import { BasePath } from './base_path'; describe('BasePath', () => { describe('#get()', () => { diff --git a/src/core/public/http/base_path_service.ts b/src/core/public/http/base_path.ts similarity index 100% rename from src/core/public/http/base_path_service.ts rename to src/core/public/http/base_path.ts diff --git a/src/core/public/http/fetch.test.ts b/src/core/public/http/fetch.test.ts new file mode 100644 index 0000000000000..adb3d696a962f --- /dev/null +++ b/src/core/public/http/fetch.test.ts @@ -0,0 +1,569 @@ +/* + * 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. + */ + +// @ts-ignore +import fetchMock from 'fetch-mock/es5/client'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { Fetch } from './fetch'; +import { BasePath } from './base_path'; +import { IHttpResponse } from './types'; + +function delay(duration: number) { + return new Promise(r => setTimeout(r, duration)); +} + +describe('Fetch', () => { + const fetchInstance = new Fetch({ + basePath: new BasePath('http://localhost/myBase'), + kibanaVersion: 'VERSION', + }); + afterEach(() => { + fetchMock.restore(); + }); + + describe('http requests', () => { + it('should use supplied request method', async () => { + fetchMock.post('*', {}); + await fetchInstance.fetch('/my/path', { method: 'POST' }); + + expect(fetchMock.lastOptions()!.method).toBe('POST'); + }); + + it('should use supplied Content-Type', async () => { + fetchMock.get('*', {}); + await fetchInstance.fetch('/my/path', { headers: { 'Content-Type': 'CustomContentType' } }); + + expect(fetchMock.lastOptions()!.headers).toMatchObject({ + 'content-type': 'CustomContentType', + }); + }); + + it('should use supplied pathname and querystring', async () => { + fetchMock.get('*', {}); + await fetchInstance.fetch('/my/path', { query: { a: 'b' } }); + + expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path?a=b'); + }); + + it('should use supplied headers', async () => { + fetchMock.get('*', {}); + await fetchInstance.fetch('/my/path', { + headers: { myHeader: 'foo' }, + }); + + expect(fetchMock.lastOptions()!.headers).toEqual({ + 'content-type': 'application/json', + 'kbn-version': 'VERSION', + myheader: 'foo', + }); + }); + + it('should return response', async () => { + fetchMock.get('*', { foo: 'bar' }); + const json = await fetchInstance.fetch('/my/path'); + expect(json).toEqual({ foo: 'bar' }); + }); + + it('should prepend url with basepath by default', async () => { + fetchMock.get('*', {}); + await fetchInstance.fetch('/my/path'); + expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path'); + }); + + it('should not prepend url with basepath when disabled', async () => { + fetchMock.get('*', {}); + await fetchInstance.fetch('my/path', { prependBasePath: false }); + expect(fetchMock.lastUrl()).toBe('/my/path'); + }); + + it('should not include undefined query params', async () => { + fetchMock.get('*', {}); + await fetchInstance.fetch('/my/path', { query: { a: undefined } }); + expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path'); + }); + + it('should make request with defaults', async () => { + fetchMock.get('*', {}); + await fetchInstance.fetch('/my/path'); + + const lastCall = fetchMock.lastCall(); + + expect(lastCall!.request.credentials).toBe('same-origin'); + expect(lastCall![1]).toMatchObject({ + method: 'GET', + headers: { + 'content-type': 'application/json', + 'kbn-version': 'VERSION', + }, + }); + }); + + it('should expose detailed response object when asResponse = true', async () => { + fetchMock.get('*', { foo: 'bar' }); + + const response = await fetchInstance.fetch('/my/path', { asResponse: true }); + + expect(response.request).toBeInstanceOf(Request); + expect(response.response).toBeInstanceOf(Response); + expect(response.body).toEqual({ foo: 'bar' }); + }); + + it('should reject on network error', async () => { + expect.assertions(1); + fetchMock.get('*', { status: 500 }); + + await expect(fetchInstance.fetch('/my/path')).rejects.toThrow(/Internal Server Error/); + }); + + it('should contain error message when throwing response', async () => { + fetchMock.get('*', { status: 404, body: { foo: 'bar' } }); + + await expect(fetchInstance.fetch('/my/path')).rejects.toMatchObject({ + message: 'Not Found', + body: { + foo: 'bar', + }, + response: { + status: 404, + url: 'http://localhost/myBase/my/path', + }, + }); + }); + + it('should support get() helper', async () => { + fetchMock.get('*', {}); + await fetchInstance.get('/my/path', { method: 'POST' }); + + expect(fetchMock.lastOptions()!.method).toBe('GET'); + }); + + it('should support head() helper', async () => { + fetchMock.head('*', {}); + await fetchInstance.head('/my/path', { method: 'GET' }); + + expect(fetchMock.lastOptions()!.method).toBe('HEAD'); + }); + + it('should support post() helper', async () => { + fetchMock.post('*', {}); + await fetchInstance.post('/my/path', { method: 'GET', body: '{}' }); + + expect(fetchMock.lastOptions()!.method).toBe('POST'); + }); + + it('should support put() helper', async () => { + fetchMock.put('*', {}); + await fetchInstance.put('/my/path', { method: 'GET', body: '{}' }); + + expect(fetchMock.lastOptions()!.method).toBe('PUT'); + }); + + it('should support patch() helper', async () => { + fetchMock.patch('*', {}); + await fetchInstance.patch('/my/path', { method: 'GET', body: '{}' }); + + expect(fetchMock.lastOptions()!.method).toBe('PATCH'); + }); + + it('should support delete() helper', async () => { + fetchMock.delete('*', {}); + await fetchInstance.delete('/my/path', { method: 'GET' }); + + expect(fetchMock.lastOptions()!.method).toBe('DELETE'); + }); + + it('should support options() helper', async () => { + fetchMock.mock('*', { method: 'OPTIONS' }); + await fetchInstance.options('/my/path', { method: 'GET' }); + + expect(fetchMock.lastOptions()!.method).toBe('OPTIONS'); + }); + + it('should make requests for NDJSON content', async () => { + const content = readFileSync(join(__dirname, '_import_objects.ndjson'), { + encoding: 'utf-8', + }); + const body = new FormData(); + + body.append('file', content); + fetchMock.post('*', { + body: content, + headers: { 'Content-Type': 'application/ndjson' }, + }); + + const data = await fetchInstance.post('/my/path', { + body, + headers: { + 'Content-Type': undefined, + }, + }); + + expect(data).toBeInstanceOf(Blob); + + const ndjson = await new Response(data).text(); + + expect(ndjson).toEqual(content); + }); + }); + + describe('interception', () => { + beforeEach(async () => { + fetchMock.get('*', { foo: 'bar' }); + }); + + afterEach(() => { + fetchMock.restore(); + fetchInstance.removeAllInterceptors(); + }); + + it('should make request and receive response', async () => { + fetchInstance.intercept({}); + + const body = await fetchInstance.fetch('/my/path'); + + expect(fetchMock.called()).toBe(true); + expect(body).toEqual({ foo: 'bar' }); + }); + + it('should be able to manipulate request instance', async () => { + fetchInstance.intercept({ + request(request) { + request.headers.set('Content-Type', 'CustomContentType'); + }, + }); + fetchInstance.intercept({ + request(request) { + return new Request('/my/route', request); + }, + }); + + const body = await fetchInstance.fetch('/my/path'); + + expect(fetchMock.called()).toBe(true); + expect(body).toEqual({ foo: 'bar' }); + expect(fetchMock.lastOptions()!.headers).toMatchObject({ + 'content-type': 'CustomContentType', + }); + expect(fetchMock.lastUrl()).toBe('/my/route'); + }); + + it('should call interceptors in correct order', async () => { + const order: string[] = []; + + fetchInstance.intercept({ + request() { + order.push('Request 1'); + }, + response() { + order.push('Response 1'); + }, + }); + fetchInstance.intercept({ + request() { + order.push('Request 2'); + }, + response() { + order.push('Response 2'); + }, + }); + fetchInstance.intercept({ + request() { + order.push('Request 3'); + }, + response() { + order.push('Response 3'); + }, + }); + + const body = await fetchInstance.fetch('/my/path'); + + expect(fetchMock.called()).toBe(true); + expect(body).toEqual({ foo: 'bar' }); + expect(order).toEqual([ + 'Request 3', + 'Request 2', + 'Request 1', + 'Response 1', + 'Response 2', + 'Response 3', + ]); + }); + + it('should skip remaining interceptors when controller halts during request', async () => { + const usedSpy = jest.fn(); + const unusedSpy = jest.fn(); + + fetchInstance.intercept({ request: unusedSpy, response: unusedSpy }); + fetchInstance.intercept({ + request(request, controller) { + controller.halt(); + }, + response: unusedSpy, + }); + fetchInstance.intercept({ + request: usedSpy, + response: unusedSpy, + }); + + fetchInstance.fetch('/my/path').then(unusedSpy, unusedSpy); + await delay(1000); + + expect(unusedSpy).toHaveBeenCalledTimes(0); + expect(usedSpy).toHaveBeenCalledTimes(1); + expect(fetchMock.called()).toBe(false); + }); + + it('should skip remaining interceptors when controller halts during response', async () => { + const usedSpy = jest.fn(); + const unusedSpy = jest.fn(); + + fetchInstance.intercept({ + request: usedSpy, + response(response, controller) { + controller.halt(); + }, + }); + fetchInstance.intercept({ request: usedSpy, response: unusedSpy }); + fetchInstance.intercept({ request: usedSpy, response: unusedSpy }); + + fetchInstance.fetch('/my/path').then(unusedSpy, unusedSpy); + await delay(1000); + + expect(fetchMock.called()).toBe(true); + expect(usedSpy).toHaveBeenCalledTimes(3); + expect(unusedSpy).toHaveBeenCalledTimes(0); + }); + + it('should skip remaining interceptors when controller halts during responseError', async () => { + fetchMock.post('*', 401); + + const unusedSpy = jest.fn(); + + fetchInstance.intercept({ + responseError(response, controller) { + controller.halt(); + }, + }); + fetchInstance.intercept({ response: unusedSpy, responseError: unusedSpy }); + + fetchInstance.post('/my/path').then(unusedSpy, unusedSpy); + await delay(1000); + + expect(fetchMock.called()).toBe(true); + expect(unusedSpy).toHaveBeenCalledTimes(0); + }); + + it('should not fetch if exception occurs during request interception', async () => { + const usedSpy = jest.fn(); + const unusedSpy = jest.fn(); + + fetchInstance.intercept({ + request: unusedSpy, + requestError: usedSpy, + response: unusedSpy, + responseError: unusedSpy, + }); + fetchInstance.intercept({ + request() { + throw new Error('Interception Error'); + }, + response: unusedSpy, + responseError: unusedSpy, + }); + fetchInstance.intercept({ request: usedSpy, response: unusedSpy, responseError: unusedSpy }); + + await expect(fetchInstance.fetch('/my/path')).rejects.toThrow(/Interception Error/); + expect(fetchMock.called()).toBe(false); + expect(unusedSpy).toHaveBeenCalledTimes(0); + expect(usedSpy).toHaveBeenCalledTimes(2); + }); + + it('should succeed if request throws but caught by interceptor', async () => { + const usedSpy = jest.fn(); + const unusedSpy = jest.fn(); + + fetchInstance.intercept({ + request: unusedSpy, + requestError({ request }) { + return new Request('/my/route', request); + }, + response: usedSpy, + }); + fetchInstance.intercept({ + request() { + throw new Error('Interception Error'); + }, + response: usedSpy, + }); + fetchInstance.intercept({ request: usedSpy, response: usedSpy }); + + await expect(fetchInstance.fetch('/my/route')).resolves.toEqual({ foo: 'bar' }); + expect(fetchMock.called()).toBe(true); + expect(unusedSpy).toHaveBeenCalledTimes(0); + expect(usedSpy).toHaveBeenCalledTimes(4); + }); + + it('should accumulate request information', async () => { + const routes = ['alpha', 'beta', 'gamma']; + const createRequest = jest.fn( + (request: Request) => new Request(`/api/${routes.shift()}`, request) + ); + + fetchInstance.intercept({ + request: createRequest, + }); + fetchInstance.intercept({ + requestError(httpErrorRequest) { + return httpErrorRequest.request; + }, + }); + fetchInstance.intercept({ + request(request) { + throw new Error('Invalid'); + }, + }); + fetchInstance.intercept({ + request: createRequest, + }); + fetchInstance.intercept({ + request: createRequest, + }); + + await expect(fetchInstance.fetch('/my/route')).resolves.toEqual({ foo: 'bar' }); + expect(fetchMock.called()).toBe(true); + expect(routes.length).toBe(0); + expect(createRequest.mock.calls[0][0].url).toContain('/my/route'); + expect(createRequest.mock.calls[1][0].url).toContain('/api/alpha'); + expect(createRequest.mock.calls[2][0].url).toContain('/api/beta'); + expect(fetchMock.lastCall()!.request.url).toContain('/api/gamma'); + }); + + it('should accumulate response information', async () => { + const bodies = ['alpha', 'beta', 'gamma']; + const createResponse = jest.fn((httpResponse: IHttpResponse) => ({ + body: bodies.shift(), + })); + + fetchInstance.intercept({ + response: createResponse, + }); + fetchInstance.intercept({ + response: createResponse, + }); + fetchInstance.intercept({ + response(httpResponse) { + throw new Error('Invalid'); + }, + }); + fetchInstance.intercept({ + responseError({ error, ...httpResponse }) { + return httpResponse; + }, + }); + fetchInstance.intercept({ + response: createResponse, + }); + + await expect(fetchInstance.fetch('/my/route')).resolves.toEqual('gamma'); + expect(fetchMock.called()).toBe(true); + expect(bodies.length).toBe(0); + expect(createResponse.mock.calls[0][0].body).toEqual({ foo: 'bar' }); + expect(createResponse.mock.calls[1][0].body).toBe('alpha'); + expect(createResponse.mock.calls[2][0].body).toBe('beta'); + }); + + describe('request availability during interception', () => { + it('should be available to responseError when response throws', async () => { + let spiedRequest: Request | undefined; + + fetchInstance.intercept({ + response() { + throw new Error('Internal Server Error'); + }, + }); + fetchInstance.intercept({ + responseError({ request }) { + spiedRequest = request; + }, + }); + + await expect(fetchInstance.fetch('/my/path')).rejects.toThrow(); + expect(fetchMock.called()).toBe(true); + expect(spiedRequest).toBeDefined(); + }); + }); + + describe('response availability during interception', () => { + it('should be available to responseError when network request fails', async () => { + fetchMock.restore(); + fetchMock.get('*', { status: 500 }); + + let spiedResponse: Response | undefined; + + fetchInstance.intercept({ + responseError({ response }) { + spiedResponse = response; + }, + }); + + await expect(fetchInstance.fetch('/my/path')).rejects.toThrow(); + expect(spiedResponse).toBeDefined(); + }); + }); + + it('should actually halt request interceptors in reverse order', async () => { + const unusedSpy = jest.fn(); + + fetchInstance.intercept({ request: unusedSpy }); + fetchInstance.intercept({ + request(request, controller) { + controller.halt(); + }, + }); + + fetchInstance.fetch('/my/path'); + await delay(500); + + expect(unusedSpy).toHaveBeenCalledTimes(0); + }); + + it('should recover from failing request interception via request error interceptor', async () => { + const usedSpy = jest.fn(); + + fetchInstance.intercept({ + requestError(httpErrorRequest) { + return httpErrorRequest.request; + }, + response: usedSpy, + }); + + fetchInstance.intercept({ + request(request, controller) { + throw new Error('Request Error'); + }, + response: usedSpy, + }); + + await expect(fetchInstance.fetch('/my/path')).resolves.toEqual({ foo: 'bar' }); + expect(usedSpy).toHaveBeenCalledTimes(2); + }); + }); +}); diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts index 472b617cacd7f..b86f1f5c08029 100644 --- a/src/core/public/http/fetch.ts +++ b/src/core/public/http/fetch.ts @@ -35,20 +35,30 @@ interface Params { const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/; const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/; -export class FetchService { +export class Fetch { private readonly interceptors = new Set(); constructor(private readonly params: Params) {} public intercept(interceptor: HttpInterceptor) { this.interceptors.add(interceptor); - return () => this.interceptors.delete(interceptor); + return () => { + this.interceptors.delete(interceptor); + }; } public removeAllInterceptors() { this.interceptors.clear(); } + public readonly delete = this.shorthand('DELETE'); + public readonly get = this.shorthand('GET'); + public readonly head = this.shorthand('HEAD'); + public readonly options = this.shorthand('options'); + public readonly patch = this.shorthand('PATCH'); + public readonly post = this.shorthand('POST'); + public readonly put = this.shorthand('PUT'); + public fetch: HttpHandler = async ( path: string, options: HttpFetchOptions = {} @@ -152,4 +162,9 @@ export class FetchService { return new HttpResponse({ request, response, body }); } + + private shorthand(method: string) { + return (path: string, options: HttpFetchOptions = {}) => + this.fetch(path, { ...options, method }); + } } diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index 5887e7b3e96d0..1111fd39ec78e 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -20,7 +20,7 @@ import { HttpService } from './http_service'; import { HttpSetup } from './types'; import { BehaviorSubject } from 'rxjs'; -import { BasePath } from './base_path_service'; +import { BasePath } from './base_path'; export type HttpSetupMock = jest.Mocked & { basePath: BasePath; @@ -41,15 +41,13 @@ const createServiceMock = ({ basePath = '' } = {}): HttpSetupMock => ({ register: jest.fn(), isAnonymous: jest.fn(), }, - addLoadingCount: jest.fn(), + addLoadingCountSource: jest.fn(), getLoadingCount$: jest.fn().mockReturnValue(new BehaviorSubject(0)), - stop: jest.fn(), intercept: jest.fn(), - removeAllInterceptors: jest.fn(), }); const createMock = ({ basePath = '' } = {}) => { - const mocked: jest.Mocked> = { + const mocked: jest.Mocked> = { setup: jest.fn(), start: jest.fn(), stop: jest.fn(), diff --git a/src/core/public/http/http_service.test.mocks.ts b/src/core/public/http/http_service.test.mocks.ts new file mode 100644 index 0000000000000..e60dad0509699 --- /dev/null +++ b/src/core/public/http/http_service.test.mocks.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 { loadingCountServiceMock } from './loading_count_service.mock'; + +export const loadingServiceMock = loadingCountServiceMock.create(); +jest.doMock('./loading_count_service', () => ({ + LoadingCountService: jest.fn(() => loadingServiceMock), +})); diff --git a/src/core/public/http/http_service.test.ts b/src/core/public/http/http_service.test.ts index 09f3cca419e4d..f95d25d116976 100644 --- a/src/core/public/http/http_service.test.ts +++ b/src/core/public/http/http_service.test.ts @@ -17,692 +17,22 @@ * under the License. */ -import * as Rx from 'rxjs'; -import { toArray } from 'rxjs/operators'; // @ts-ignore import fetchMock from 'fetch-mock/es5/client'; -import { readFileSync } from 'fs'; -import { join } from 'path'; -import { setup, SetupTap } from '../../../test_utils/public/http_test_setup'; -import { IHttpResponse } from './types'; - -function delay(duration: number) { - return new Promise(r => setTimeout(r, duration)); -} - -const setupFakeBasePath: SetupTap = injectedMetadata => { - injectedMetadata.getBasePath.mockReturnValue('/foo/bar'); -}; - -describe('basePath.get()', () => { - it('returns an empty string if no basePath is injected', () => { - const { http } = setup(injectedMetadata => { - injectedMetadata.getBasePath.mockReturnValue(undefined as any); - }); - - expect(http.basePath.get()).toBe(''); - }); - - it('returns the injected basePath', () => { - const { http } = setup(setupFakeBasePath); - - expect(http.basePath.get()).toBe('/foo/bar'); - }); -}); - -describe('http requests', () => { - afterEach(() => { - fetchMock.restore(); - }); - - it('should use supplied request method', async () => { - const { http } = setup(); - - fetchMock.post('*', {}); - await http.fetch('/my/path', { method: 'POST' }); - - expect(fetchMock.lastOptions()!.method).toBe('POST'); - }); - - it('should use supplied Content-Type', async () => { - const { http } = setup(); - - fetchMock.get('*', {}); - await http.fetch('/my/path', { headers: { 'Content-Type': 'CustomContentType' } }); - - expect(fetchMock.lastOptions()!.headers).toMatchObject({ - 'content-type': 'CustomContentType', - }); - }); - - it('should use supplied pathname and querystring', async () => { - const { http } = setup(); - - fetchMock.get('*', {}); - await http.fetch('/my/path', { query: { a: 'b' } }); - - expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path?a=b'); - }); - - it('should use supplied headers', async () => { - const { http } = setup(); - - fetchMock.get('*', {}); - await http.fetch('/my/path', { - headers: { myHeader: 'foo' }, - }); - - expect(fetchMock.lastOptions()!.headers).toEqual({ - 'content-type': 'application/json', - 'kbn-version': 'kibanaVersion', - myheader: 'foo', - }); - }); - - it('should return response', async () => { - const { http } = setup(); - fetchMock.get('*', { foo: 'bar' }); - const json = await http.fetch('/my/path'); - expect(json).toEqual({ foo: 'bar' }); - }); - - it('should prepend url with basepath by default', async () => { - const { http } = setup(); - fetchMock.get('*', {}); - await http.fetch('/my/path'); - expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path'); - }); - - it('should not prepend url with basepath when disabled', async () => { - const { http } = setup(); - fetchMock.get('*', {}); - await http.fetch('my/path', { prependBasePath: false }); - expect(fetchMock.lastUrl()).toBe('/my/path'); - }); - - it('should not include undefined query params', async () => { - const { http } = setup(); - fetchMock.get('*', {}); - await http.fetch('/my/path', { query: { a: undefined } }); - expect(fetchMock.lastUrl()).toBe('http://localhost/myBase/my/path'); - }); - - it('should make request with defaults', async () => { - const { http } = setup(); - - fetchMock.get('*', {}); - await http.fetch('/my/path'); - - const lastCall = fetchMock.lastCall(); - - expect(lastCall!.request.credentials).toBe('same-origin'); - expect(lastCall![1]).toMatchObject({ - method: 'GET', - headers: { - 'content-type': 'application/json', - 'kbn-version': 'kibanaVersion', - }, - }); - }); - - it('should expose detailed response object when asResponse = true', async () => { - const { http } = setup(); - - fetchMock.get('*', { foo: 'bar' }); - - const response = await http.fetch('/my/path', { asResponse: true }); - - expect(response.request).toBeInstanceOf(Request); - expect(response.response).toBeInstanceOf(Response); - expect(response.body).toEqual({ foo: 'bar' }); - }); - - it('should reject on network error', async () => { - const { http } = setup(); - - expect.assertions(1); - fetchMock.get('*', { status: 500 }); - - await expect(http.fetch('/my/path')).rejects.toThrow(/Internal Server Error/); - }); - - it('should contain error message when throwing response', async () => { - const { http } = setup(); - - fetchMock.get('*', { status: 404, body: { foo: 'bar' } }); - - await expect(http.fetch('/my/path')).rejects.toMatchObject({ - message: 'Not Found', - body: { - foo: 'bar', - }, - response: { - status: 404, - url: 'http://localhost/myBase/my/path', - }, - }); - }); - - it('should support get() helper', async () => { - const { http } = setup(); - - fetchMock.get('*', {}); - await http.get('/my/path', { method: 'POST' }); - - expect(fetchMock.lastOptions()!.method).toBe('GET'); - }); - - it('should support head() helper', async () => { - const { http } = setup(); - - fetchMock.head('*', {}); - await http.head('/my/path', { method: 'GET' }); - - expect(fetchMock.lastOptions()!.method).toBe('HEAD'); - }); - - it('should support post() helper', async () => { - const { http } = setup(); - - fetchMock.post('*', {}); - await http.post('/my/path', { method: 'GET', body: '{}' }); - - expect(fetchMock.lastOptions()!.method).toBe('POST'); - }); - - it('should support put() helper', async () => { - const { http } = setup(); - - fetchMock.put('*', {}); - await http.put('/my/path', { method: 'GET', body: '{}' }); - - expect(fetchMock.lastOptions()!.method).toBe('PUT'); - }); - - it('should support patch() helper', async () => { - const { http } = setup(); - - fetchMock.patch('*', {}); - await http.patch('/my/path', { method: 'GET', body: '{}' }); - - expect(fetchMock.lastOptions()!.method).toBe('PATCH'); - }); - - it('should support delete() helper', async () => { - const { http } = setup(); - - fetchMock.delete('*', {}); - await http.delete('/my/path', { method: 'GET' }); - - expect(fetchMock.lastOptions()!.method).toBe('DELETE'); - }); - - it('should support options() helper', async () => { - const { http } = setup(); - - fetchMock.mock('*', { method: 'OPTIONS' }); - await http.options('/my/path', { method: 'GET' }); - - expect(fetchMock.lastOptions()!.method).toBe('OPTIONS'); - }); - - it('should make requests for NDJSON content', async () => { - const { http } = setup(); - const content = readFileSync(join(__dirname, '_import_objects.ndjson'), { encoding: 'utf-8' }); - const body = new FormData(); - - body.append('file', content); - fetchMock.post('*', { - body: content, - headers: { 'Content-Type': 'application/ndjson' }, - }); - - const data = await http.post('/my/path', { - body, - headers: { - 'Content-Type': undefined, - }, - }); - - expect(data).toBeInstanceOf(Blob); - - const ndjson = await new Response(data).text(); - - expect(ndjson).toEqual(content); - }); -}); - -describe('interception', () => { - const { http } = setup(); - - beforeEach(() => { - fetchMock.get('*', { foo: 'bar' }); - }); - - afterEach(() => { - fetchMock.restore(); - http.removeAllInterceptors(); - }); - - it('should make request and receive response', async () => { - http.intercept({}); - - const body = await http.fetch('/my/path'); - - expect(fetchMock.called()).toBe(true); - expect(body).toEqual({ foo: 'bar' }); - }); - - it('should be able to manipulate request instance', async () => { - http.intercept({ - request(request) { - request.headers.set('Content-Type', 'CustomContentType'); - }, - }); - http.intercept({ - request(request) { - return new Request('/my/route', request); - }, - }); - - const body = await http.fetch('/my/path'); - - expect(fetchMock.called()).toBe(true); - expect(body).toEqual({ foo: 'bar' }); - expect(fetchMock.lastOptions()!.headers).toMatchObject({ - 'content-type': 'CustomContentType', - }); - expect(fetchMock.lastUrl()).toBe('/my/route'); - }); - - it('should call interceptors in correct order', async () => { - const order: string[] = []; - - http.intercept({ - request() { - order.push('Request 1'); - }, - response() { - order.push('Response 1'); - }, - }); - http.intercept({ - request() { - order.push('Request 2'); - }, - response() { - order.push('Response 2'); - }, - }); - http.intercept({ - request() { - order.push('Request 3'); - }, - response() { - order.push('Response 3'); - }, - }); - - const body = await http.fetch('/my/path'); - - expect(fetchMock.called()).toBe(true); - expect(body).toEqual({ foo: 'bar' }); - expect(order).toEqual([ - 'Request 3', - 'Request 2', - 'Request 1', - 'Response 1', - 'Response 2', - 'Response 3', - ]); - }); - - it('should skip remaining interceptors when controller halts during request', async () => { - const usedSpy = jest.fn(); - const unusedSpy = jest.fn(); - - http.intercept({ request: unusedSpy, response: unusedSpy }); - http.intercept({ - request(request, controller) { - controller.halt(); - }, - response: unusedSpy, - }); - http.intercept({ - request: usedSpy, - response: unusedSpy, - }); - - http.fetch('/my/path').then(unusedSpy, unusedSpy); - await delay(1000); - - expect(unusedSpy).toHaveBeenCalledTimes(0); - expect(usedSpy).toHaveBeenCalledTimes(1); - expect(fetchMock.called()).toBe(false); - }); - - it('should skip remaining interceptors when controller halts during response', async () => { - const usedSpy = jest.fn(); - const unusedSpy = jest.fn(); - - http.intercept({ - request: usedSpy, - response(response, controller) { - controller.halt(); - }, - }); - http.intercept({ request: usedSpy, response: unusedSpy }); - http.intercept({ request: usedSpy, response: unusedSpy }); - - http.fetch('/my/path').then(unusedSpy, unusedSpy); - await delay(1000); - - expect(fetchMock.called()).toBe(true); - expect(usedSpy).toHaveBeenCalledTimes(3); - expect(unusedSpy).toHaveBeenCalledTimes(0); - }); - - it('should skip remaining interceptors when controller halts during responseError', async () => { - fetchMock.post('*', 401); - - const unusedSpy = jest.fn(); - - http.intercept({ - responseError(response, controller) { - controller.halt(); - }, - }); - http.intercept({ response: unusedSpy, responseError: unusedSpy }); - - http.post('/my/path').then(unusedSpy, unusedSpy); - await delay(1000); - - expect(fetchMock.called()).toBe(true); - expect(unusedSpy).toHaveBeenCalledTimes(0); - }); - - it('should not fetch if exception occurs during request interception', async () => { - const usedSpy = jest.fn(); - const unusedSpy = jest.fn(); - - http.intercept({ - request: unusedSpy, - requestError: usedSpy, - response: unusedSpy, - responseError: unusedSpy, - }); - http.intercept({ - request() { - throw new Error('Interception Error'); - }, - response: unusedSpy, - responseError: unusedSpy, - }); - http.intercept({ request: usedSpy, response: unusedSpy, responseError: unusedSpy }); - - await expect(http.fetch('/my/path')).rejects.toThrow(/Interception Error/); - expect(fetchMock.called()).toBe(false); - expect(unusedSpy).toHaveBeenCalledTimes(0); - expect(usedSpy).toHaveBeenCalledTimes(2); - }); - - it('should succeed if request throws but caught by interceptor', async () => { - const usedSpy = jest.fn(); - const unusedSpy = jest.fn(); - - http.intercept({ - request: unusedSpy, - requestError({ request }) { - return new Request('/my/route', request); - }, - response: usedSpy, - }); - http.intercept({ - request() { - throw new Error('Interception Error'); - }, - response: usedSpy, - }); - http.intercept({ request: usedSpy, response: usedSpy }); - - await expect(http.fetch('/my/route')).resolves.toEqual({ foo: 'bar' }); - expect(fetchMock.called()).toBe(true); - expect(unusedSpy).toHaveBeenCalledTimes(0); - expect(usedSpy).toHaveBeenCalledTimes(4); - }); - - it('should accumulate request information', async () => { - const routes = ['alpha', 'beta', 'gamma']; - const createRequest = jest.fn( - (request: Request) => new Request(`/api/${routes.shift()}`, request) - ); - - http.intercept({ - request: createRequest, - }); - http.intercept({ - requestError(httpErrorRequest) { - return httpErrorRequest.request; - }, - }); - http.intercept({ - request(request) { - throw new Error('Invalid'); - }, - }); - http.intercept({ - request: createRequest, - }); - http.intercept({ - request: createRequest, - }); - - await expect(http.fetch('/my/route')).resolves.toEqual({ foo: 'bar' }); - expect(fetchMock.called()).toBe(true); - expect(routes.length).toBe(0); - expect(createRequest.mock.calls[0][0].url).toContain('/my/route'); - expect(createRequest.mock.calls[1][0].url).toContain('/api/alpha'); - expect(createRequest.mock.calls[2][0].url).toContain('/api/beta'); - expect(fetchMock.lastCall()!.request.url).toContain('/api/gamma'); - }); - - it('should accumulate response information', async () => { - const bodies = ['alpha', 'beta', 'gamma']; - const createResponse = jest.fn((httpResponse: IHttpResponse) => ({ - body: bodies.shift(), - })); - - http.intercept({ - response: createResponse, - }); - http.intercept({ - response: createResponse, - }); - http.intercept({ - response(httpResponse) { - throw new Error('Invalid'); - }, - }); - http.intercept({ - responseError({ error, ...httpResponse }) { - return httpResponse; - }, - }); - http.intercept({ - response: createResponse, - }); - - await expect(http.fetch('/my/route')).resolves.toEqual('gamma'); - expect(fetchMock.called()).toBe(true); - expect(bodies.length).toBe(0); - expect(createResponse.mock.calls[0][0].body).toEqual({ foo: 'bar' }); - expect(createResponse.mock.calls[1][0].body).toBe('alpha'); - expect(createResponse.mock.calls[2][0].body).toBe('beta'); - }); - - describe('request availability during interception', () => { - it('should be available to responseError when response throws', async () => { - let spiedRequest: Request | undefined; - - http.intercept({ - response() { - throw new Error('Internal Server Error'); - }, - }); - http.intercept({ - responseError({ request }) { - spiedRequest = request; - }, - }); - - await expect(http.fetch('/my/path')).rejects.toThrow(); - expect(fetchMock.called()).toBe(true); - expect(spiedRequest).toBeDefined(); - }); - }); - - describe('response availability during interception', () => { - it('should be available to responseError when network request fails', async () => { - fetchMock.restore(); - fetchMock.get('*', { status: 500 }); - - let spiedResponse: Response | undefined; - - http.intercept({ - responseError({ response }) { - spiedResponse = response; - }, - }); - - await expect(http.fetch('/my/path')).rejects.toThrow(); - expect(spiedResponse).toBeDefined(); - }); - }); - - it('should actually halt request interceptors in reverse order', async () => { - const unusedSpy = jest.fn(); - - http.intercept({ request: unusedSpy }); - http.intercept({ - request(request, controller) { - controller.halt(); - }, - }); - - http.fetch('/my/path'); - await delay(500); - - expect(unusedSpy).toHaveBeenCalledTimes(0); - }); - - it('should recover from failing request interception via request error interceptor', async () => { - const usedSpy = jest.fn(); - - http.intercept({ - requestError(httpErrorRequest) { - return httpErrorRequest.request; - }, - response: usedSpy, - }); - - http.intercept({ - request(request, controller) { - throw new Error('Request Error'); - }, - response: usedSpy, - }); - - await expect(http.fetch('/my/path')).resolves.toEqual({ foo: 'bar' }); - expect(usedSpy).toHaveBeenCalledTimes(2); - }); -}); - -describe('addLoadingCount()', () => { - it('subscribes to passed in sources, unsubscribes on stop', () => { - const { httpService, http } = setup(); - - const unsubA = jest.fn(); - const subA = jest.fn().mockReturnValue(unsubA); - http.addLoadingCount(new Rx.Observable(subA)); - expect(subA).toHaveBeenCalledTimes(1); - expect(unsubA).not.toHaveBeenCalled(); - - const unsubB = jest.fn(); - const subB = jest.fn().mockReturnValue(unsubB); - http.addLoadingCount(new Rx.Observable(subB)); - expect(subB).toHaveBeenCalledTimes(1); - expect(unsubB).not.toHaveBeenCalled(); - +import { loadingServiceMock } from './http_service.test.mocks'; + +import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock'; +import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; +import { HttpService } from './http_service'; + +describe('#stop()', () => { + it('calls loadingCount.stop()', () => { + const injectedMetadata = injectedMetadataServiceMock.createSetupContract(); + const fatalErrors = fatalErrorsServiceMock.createSetupContract(); + const httpService = new HttpService(); + httpService.setup({ fatalErrors, injectedMetadata }); + httpService.start({ fatalErrors, injectedMetadata }); httpService.stop(); - - expect(subA).toHaveBeenCalledTimes(1); - expect(unsubA).toHaveBeenCalledTimes(1); - expect(subB).toHaveBeenCalledTimes(1); - expect(unsubB).toHaveBeenCalledTimes(1); - }); - - it('adds a fatal error if source observables emit an error', async () => { - const { http, fatalErrors } = setup(); - - http.addLoadingCount(Rx.throwError(new Error('foo bar'))); - expect(fatalErrors.add.mock.calls).toMatchSnapshot(); - }); - - it('adds a fatal error if source observable emits a negative number', async () => { - const { http, fatalErrors } = setup(); - - http.addLoadingCount(Rx.of(1, 2, 3, 4, -9)); - expect(fatalErrors.add.mock.calls).toMatchSnapshot(); - }); -}); - -describe('getLoadingCount$()', () => { - it('emits 0 initially, the right count when sources emit their own count, and ends with zero', async () => { - const { httpService, http } = setup(); - - const countA$ = new Rx.Subject(); - const countB$ = new Rx.Subject(); - const countC$ = new Rx.Subject(); - const promise = http - .getLoadingCount$() - .pipe(toArray()) - .toPromise(); - - http.addLoadingCount(countA$); - http.addLoadingCount(countB$); - http.addLoadingCount(countC$); - - countA$.next(100); - countB$.next(10); - countC$.next(1); - countA$.complete(); - countB$.next(20); - countC$.complete(); - countB$.next(0); - - httpService.stop(); - expect(await promise).toMatchSnapshot(); - }); - - it('only emits when loading count changes', async () => { - const { httpService, http } = setup(); - - const count$ = new Rx.Subject(); - const promise = http - .getLoadingCount$() - .pipe(toArray()) - .toPromise(); - - http.addLoadingCount(count$); - count$.next(0); - count$.next(0); - count$.next(0); - count$.next(0); - count$.next(0); - count$.next(1); - count$.next(1); - httpService.stop(); - - expect(await promise).toMatchSnapshot(); + expect(loadingServiceMock.stop).toHaveBeenCalled(); }); }); diff --git a/src/core/public/http/http_service.ts b/src/core/public/http/http_service.ts index 477bcd6152d44..567cdd310cbdf 100644 --- a/src/core/public/http/http_service.ts +++ b/src/core/public/http/http_service.ts @@ -17,32 +17,52 @@ * under the License. */ -import { HttpSetup, HttpStart, HttpServiceBase } from './types'; -import { setup } from './http_setup'; +import { HttpSetup, HttpStart } from './types'; import { InjectedMetadataSetup } from '../injected_metadata'; import { FatalErrorsSetup } from '../fatal_errors'; +import { BasePath } from './base_path'; +import { AnonymousPathsService } from './anonymous_paths_service'; +import { LoadingCountService } from './loading_count_service'; +import { Fetch } from './fetch'; +import { CoreService } from '../../types'; interface HttpDeps { injectedMetadata: InjectedMetadataSetup; - fatalErrors: FatalErrorsSetup | null; + fatalErrors: FatalErrorsSetup; } /** @internal */ -export class HttpService { - private service!: HttpServiceBase; +export class HttpService implements CoreService { + private readonly anonymousPaths = new AnonymousPathsService(); + private readonly loadingCount = new LoadingCountService(); - public setup(deps: HttpDeps): HttpSetup { - this.service = setup(deps.injectedMetadata, deps.fatalErrors); - return this.service; + public setup({ injectedMetadata, fatalErrors }: HttpDeps): HttpSetup { + const kibanaVersion = injectedMetadata.getKibanaVersion(); + const basePath = new BasePath(injectedMetadata.getBasePath()); + const fetchService = new Fetch({ basePath, kibanaVersion }); + const loadingCount = this.loadingCount.setup({ fatalErrors }); + + return { + basePath, + anonymousPaths: this.anonymousPaths.setup({ basePath }), + intercept: fetchService.intercept.bind(fetchService), + fetch: fetchService.fetch.bind(fetchService), + delete: fetchService.delete.bind(fetchService), + get: fetchService.get.bind(fetchService), + head: fetchService.head.bind(fetchService), + options: fetchService.options.bind(fetchService), + patch: fetchService.patch.bind(fetchService), + post: fetchService.post.bind(fetchService), + put: fetchService.put.bind(fetchService), + ...loadingCount, + }; } - public start(deps: HttpDeps): HttpStart { - return this.service || this.setup(deps); + public start(deps: HttpDeps) { + return this.setup(deps); } public stop() { - if (this.service) { - this.service.stop(); - } + this.loadingCount.stop(); } } diff --git a/src/core/public/http/http_setup.ts b/src/core/public/http/http_setup.ts deleted file mode 100644 index c63750849f13a..0000000000000 --- a/src/core/public/http/http_setup.ts +++ /dev/null @@ -1,123 +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 { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { - distinctUntilChanged, - endWith, - map, - pairwise, - startWith, - takeUntil, - tap, -} from 'rxjs/operators'; -import { InjectedMetadataSetup } from '../injected_metadata'; -import { FatalErrorsSetup } from '../fatal_errors'; -import { HttpFetchOptions, HttpServiceBase } from './types'; -import { HttpInterceptController } from './http_intercept_controller'; -import { HttpInterceptHaltError } from './http_intercept_halt_error'; -import { BasePath } from './base_path_service'; -import { AnonymousPaths } from './anonymous_paths'; -import { FetchService } from './fetch'; - -export function checkHalt(controller: HttpInterceptController, error?: Error) { - if (error instanceof HttpInterceptHaltError) { - throw error; - } else if (controller.halted) { - throw new HttpInterceptHaltError(); - } -} - -export const setup = ( - injectedMetadata: InjectedMetadataSetup, - fatalErrors: FatalErrorsSetup | null -): HttpServiceBase => { - const loadingCount$ = new BehaviorSubject(0); - const stop$ = new Subject(); - const kibanaVersion = injectedMetadata.getKibanaVersion(); - const basePath = new BasePath(injectedMetadata.getBasePath()); - const anonymousPaths = new AnonymousPaths(basePath); - - const fetchService = new FetchService({ basePath, kibanaVersion }); - - function shorthand(method: string) { - return (path: string, options: HttpFetchOptions = {}) => - fetchService.fetch(path, { ...options, method }); - } - - function stop() { - stop$.next(); - loadingCount$.complete(); - } - - function addLoadingCount(count$: Observable) { - count$ - .pipe( - distinctUntilChanged(), - - tap(count => { - if (count < 0) { - throw new Error( - 'Observables passed to loadingCount.add() must only emit positive numbers' - ); - } - }), - - // use takeUntil() so that we can finish each stream on stop() the same way we do when they complete, - // by removing the previous count from the total - takeUntil(stop$), - endWith(0), - startWith(0), - pairwise(), - map(([prev, next]) => next - prev) - ) - .subscribe({ - next: delta => { - loadingCount$.next(loadingCount$.getValue() + delta); - }, - error: error => { - if (fatalErrors) { - fatalErrors.add(error); - } - }, - }); - } - - function getLoadingCount$() { - return loadingCount$.pipe(distinctUntilChanged()); - } - - return { - stop, - basePath, - anonymousPaths, - intercept: fetchService.intercept.bind(fetchService), - removeAllInterceptors: fetchService.removeAllInterceptors.bind(fetchService), - fetch: fetchService.fetch.bind(fetchService), - delete: shorthand('DELETE'), - get: shorthand('GET'), - head: shorthand('HEAD'), - options: shorthand('OPTIONS'), - patch: shorthand('PATCH'), - post: shorthand('POST'), - put: shorthand('PUT'), - addLoadingCount, - getLoadingCount$, - }; -}; diff --git a/src/core/public/http/loading_count_service.mock.ts b/src/core/public/http/loading_count_service.mock.ts new file mode 100644 index 0000000000000..79928aa4b160d --- /dev/null +++ b/src/core/public/http/loading_count_service.mock.ts @@ -0,0 +1,50 @@ +/* + * 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 { LoadingCountSetup, LoadingCountService } from './loading_count_service'; +import { BehaviorSubject } from 'rxjs'; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + addLoadingCountSource: jest.fn(), + getLoadingCount$: jest.fn(), + }; + setupContract.getLoadingCount$.mockReturnValue(new BehaviorSubject(0)); + return setupContract; +}; + +type LoadingCountServiceContract = PublicMethodsOf; +const createServiceMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mocked.setup.mockReturnValue(createSetupContractMock()); + mocked.start.mockReturnValue(createSetupContractMock()); + + return mocked; +}; + +export const loadingCountServiceMock = { + create: createServiceMock, + createSetupContract: createSetupContractMock, + createStartContract: createSetupContractMock, +}; diff --git a/src/core/public/http/loading_count_service.test.ts b/src/core/public/http/loading_count_service.test.ts new file mode 100644 index 0000000000000..3ba4d315178cc --- /dev/null +++ b/src/core/public/http/loading_count_service.test.ts @@ -0,0 +1,152 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable, throwError, of, Subject } from 'rxjs'; +import { toArray } from 'rxjs/operators'; + +import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock'; +import { LoadingCountService } from './loading_count_service'; + +describe('LoadingCountService', () => { + const setup = () => { + const fatalErrors = fatalErrorsServiceMock.createSetupContract(); + const service = new LoadingCountService(); + const loadingCount = service.setup({ fatalErrors }); + return { fatalErrors, loadingCount, service }; + }; + + describe('addLoadingCountSource()', () => { + it('subscribes to passed in sources, unsubscribes on stop', () => { + const { service, loadingCount } = setup(); + + const unsubA = jest.fn(); + const subA = jest.fn().mockReturnValue(unsubA); + loadingCount.addLoadingCountSource(new Observable(subA)); + expect(subA).toHaveBeenCalledTimes(1); + expect(unsubA).not.toHaveBeenCalled(); + + const unsubB = jest.fn(); + const subB = jest.fn().mockReturnValue(unsubB); + loadingCount.addLoadingCountSource(new Observable(subB)); + expect(subB).toHaveBeenCalledTimes(1); + expect(unsubB).not.toHaveBeenCalled(); + + service.stop(); + + expect(subA).toHaveBeenCalledTimes(1); + expect(unsubA).toHaveBeenCalledTimes(1); + expect(subB).toHaveBeenCalledTimes(1); + expect(unsubB).toHaveBeenCalledTimes(1); + }); + + it('adds a fatal error if source observables emit an error', () => { + const { loadingCount, fatalErrors } = setup(); + + loadingCount.addLoadingCountSource(throwError(new Error('foo bar'))); + expect(fatalErrors.add.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + [Error: foo bar], + ], + ] + `); + }); + + it('adds a fatal error if source observable emits a negative number', () => { + const { loadingCount, fatalErrors } = setup(); + + loadingCount.addLoadingCountSource(of(1, 2, 3, 4, -9)); + expect(fatalErrors.add.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + [Error: Observables passed to loadingCount.add() must only emit positive numbers], + ], + ] + `); + }); + }); + + describe('getLoadingCount$()', () => { + it('emits 0 initially, the right count when sources emit their own count, and ends with zero', async () => { + const { service, loadingCount } = setup(); + + const countA$ = new Subject(); + const countB$ = new Subject(); + const countC$ = new Subject(); + const promise = loadingCount + .getLoadingCount$() + .pipe(toArray()) + .toPromise(); + + loadingCount.addLoadingCountSource(countA$); + loadingCount.addLoadingCountSource(countB$); + loadingCount.addLoadingCountSource(countC$); + + countA$.next(100); + countB$.next(10); + countC$.next(1); + countA$.complete(); + countB$.next(20); + countC$.complete(); + countB$.next(0); + + service.stop(); + expect(await promise).toMatchInlineSnapshot(` + Array [ + 0, + 100, + 110, + 111, + 11, + 21, + 20, + 0, + ] + `); + }); + + it('only emits when loading count changes', async () => { + const { service, loadingCount } = setup(); + + const count$ = new Subject(); + const promise = loadingCount + .getLoadingCount$() + .pipe(toArray()) + .toPromise(); + + loadingCount.addLoadingCountSource(count$); + count$.next(0); + count$.next(0); + count$.next(0); + count$.next(0); + count$.next(0); + count$.next(1); + count$.next(1); + service.stop(); + + expect(await promise).toMatchInlineSnapshot(` + Array [ + 0, + 1, + 0, + ] + `); + }); + }); +}); diff --git a/src/core/public/http/loading_count_service.ts b/src/core/public/http/loading_count_service.ts new file mode 100644 index 0000000000000..14b945e0801ca --- /dev/null +++ b/src/core/public/http/loading_count_service.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 { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { + distinctUntilChanged, + endWith, + map, + pairwise, + startWith, + takeUntil, + tap, +} from 'rxjs/operators'; +import { FatalErrorsSetup } from '../fatal_errors'; +import { CoreService } from '../../types'; + +/** @public */ +export interface LoadingCountSetup { + addLoadingCountSource(countSource$: Observable): void; + + getLoadingCount$(): Observable; +} + +/** + * See {@link LoadingCountSetup}. + * @public + */ +export type LoadingCountStart = LoadingCountSetup; + +/** @internal */ +export class LoadingCountService implements CoreService { + private readonly stop$ = new Subject(); + private readonly loadingCount$ = new BehaviorSubject(0); + + public setup({ fatalErrors }: { fatalErrors: FatalErrorsSetup }) { + return { + getLoadingCount$: () => this.loadingCount$.pipe(distinctUntilChanged()), + addLoadingCountSource: (count$: Observable) => { + count$ + .pipe( + distinctUntilChanged(), + + tap(count => { + if (count < 0) { + throw new Error( + 'Observables passed to loadingCount.add() must only emit positive numbers' + ); + } + }), + + // use takeUntil() so that we can finish each stream on stop() the same way we do when they complete, + // by removing the previous count from the total + takeUntil(this.stop$), + endWith(0), + startWith(0), + pairwise(), + map(([prev, next]) => next - prev) + ) + .subscribe({ + next: delta => { + this.loadingCount$.next(this.loadingCount$.getValue() + delta); + }, + error: error => fatalErrors.add(error), + }); + }, + }; + } + + public start({ fatalErrors }: { fatalErrors: FatalErrorsSetup }) { + return this.setup({ fatalErrors }); + } + + public stop() { + this.stop$.next(); + this.loadingCount$.complete(); + } +} diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 48385a72325db..27ffddc79cf65 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -20,10 +20,7 @@ import { Observable } from 'rxjs'; /** @public */ -export interface HttpServiceBase { - /** @internal */ - stop(): void; - +export interface HttpSetup { /** * APIs for manipulating the basePath on URL segments. */ @@ -41,11 +38,6 @@ export interface HttpServiceBase { */ intercept(interceptor: HttpInterceptor): () => void; - /** - * Removes all configured interceptors. - */ - removeAllInterceptors(): void; - /** Makes an HTTP request. Defaults to a GET request unless overriden. See {@link HttpHandler} for options. */ fetch: HttpHandler; /** Makes an HTTP request with the DELETE method. See {@link HttpHandler} for options. */ @@ -68,7 +60,7 @@ export interface HttpServiceBase { * more than 0. * @param countSource$ an Observable to subscribe to for loading count updates. */ - addLoadingCount(countSource$: Observable): void; + addLoadingCountSource(countSource$: Observable): void; /** * Get the sum of all loading count sources as a single Observable. @@ -76,6 +68,12 @@ export interface HttpServiceBase { getLoadingCount$(): Observable; } +/** + * See {@link HttpSetup} + * @public + */ +export type HttpStart = HttpSetup; + /** * APIs for manipulating the basePath on URL segments. * @public @@ -112,18 +110,6 @@ export interface IAnonymousPaths { register(path: string): void; } -/** - * See {@link HttpServiceBase} - * @public - */ -export type HttpSetup = HttpServiceBase; - -/** - * See {@link HttpServiceBase} - * @public - */ -export type HttpStart = HttpServiceBase; - /** @public */ export interface HttpHeadersInit { [name: string]: any; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index f83ca2564de8e..7488f9b973b71 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -121,7 +121,6 @@ export { } from './saved_objects'; export { - HttpServiceBase, HttpHeadersInit, HttpRequestInit, HttpFetchOptions, diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 7c0b69bf880e7..4753396157a09 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -89,6 +89,9 @@ export interface InjectedMetadataParams { user?: Record; }; }; + apm: { + [key: string]: unknown; + }; }; } diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap index 626c91b6a9668..9bd686776138f 100644 --- a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap +++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap @@ -31,7 +31,7 @@ Array [ ] `; -exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`; +exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`; exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` Array [ @@ -74,4 +74,4 @@ Array [ ] `; -exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap index 3928c54f90179..131ec836f5252 100644 --- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap +++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap @@ -29,7 +29,7 @@ Array [ ] `; -exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
Modal content
"`; +exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
Modal content
"`; exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` Array [ diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 83b4e67c1cb15..dfbb6b4a6fbf5 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -544,8 +544,8 @@ export interface HttpRequestInit { } // @public (undocumented) -export interface HttpServiceBase { - addLoadingCount(countSource$: Observable): void; +export interface HttpSetup { + addLoadingCountSource(countSource$: Observable): void; anonymousPaths: IAnonymousPaths; basePath: IBasePath; delete: HttpHandler; @@ -558,16 +558,10 @@ export interface HttpServiceBase { patch: HttpHandler; post: HttpHandler; put: HttpHandler; - removeAllInterceptors(): void; - // @internal (undocumented) - stop(): void; } // @public -export type HttpSetup = HttpServiceBase; - -// @public -export type HttpStart = HttpServiceBase; +export type HttpStart = HttpSetup; // @public export interface I18nStart { @@ -877,7 +871,7 @@ export interface SavedObjectsBulkUpdateOptions { // @public export class SavedObjectsClient { // @internal - constructor(http: HttpServiceBase); + constructor(http: HttpSetup); bulkCreate: (objects?: SavedObjectsBulkCreateObject[], options?: SavedObjectsBulkCreateOptions) => Promise>; bulkGet: (objects?: { id: string; diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index c71fe51956c28..dab98ee66cdb1 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -36,7 +36,7 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../legacy/ui/public/error_auto_create_index/error_auto_create_index'; import { SimpleSavedObject } from './simple_saved_object'; -import { HttpFetchOptions, HttpServiceBase } from '../http'; +import { HttpFetchOptions, HttpSetup } from '../http'; type SavedObjectsFindOptions = Omit; @@ -158,7 +158,7 @@ export type SavedObjectsClientContract = PublicMethodsOf; * @public */ export class SavedObjectsClient { - private http: HttpServiceBase; + private http: HttpSetup; private batchQueue: BatchQueueEntry[]; /** @@ -194,7 +194,7 @@ export class SavedObjectsClient { ); /** @internal */ - constructor(http: HttpServiceBase) { + constructor(http: HttpSetup) { this.http = http; this.batchQueue = []; } diff --git a/src/core/public/ui_settings/ui_settings_service.test.ts b/src/core/public/ui_settings/ui_settings_service.test.ts index afb68c4844901..2747a78d93fa6 100644 --- a/src/core/public/ui_settings/ui_settings_service.test.ts +++ b/src/core/public/ui_settings/ui_settings_service.test.ts @@ -38,7 +38,7 @@ describe('#stop', () => { it('stops the uiSettingsClient and uiSettingsApi', async () => { const service = new UiSettingsService(); let loadingCount$: Rx.Observable; - defaultDeps.http.addLoadingCount.mockImplementation(obs$ => (loadingCount$ = obs$)); + defaultDeps.http.addLoadingCountSource.mockImplementation(obs$ => (loadingCount$ = obs$)); const client = service.setup(defaultDeps); service.stop(); diff --git a/src/core/public/ui_settings/ui_settings_service.ts b/src/core/public/ui_settings/ui_settings_service.ts index 5a03cd1cfeedc..1e01d15fa337b 100644 --- a/src/core/public/ui_settings/ui_settings_service.ts +++ b/src/core/public/ui_settings/ui_settings_service.ts @@ -38,7 +38,7 @@ export class UiSettingsService { public setup({ http, injectedMetadata }: UiSettingsServiceDeps): IUiSettingsClient { this.uiSettingsApi = new UiSettingsApi(http); - http.addLoadingCount(this.uiSettingsApi.getLoadingCount$()); + http.addLoadingCountSource(this.uiSettingsApi.getLoadingCount$()); // TODO: Migrate away from legacyMetadata https://github.com/elastic/kibana/issues/22779 const legacyMetadata = injectedMetadata.getLegacyMetadata(); diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 8469a1d23a44b..ba742292e9e83 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -77,7 +77,7 @@ function createKibanaRequestMock({ body: schema.object({}, { allowUnknowns: true }), query: schema.object({}, { allowUnknowns: true }), } - ) as KibanaRequest, Readonly<{}>, Readonly<{}>>; + ); } type DeepPartial = T extends any[] diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 27d9f530050be..df357aeaf2731 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -27,10 +27,18 @@ import supertest from 'supertest'; import { ByteSizeValue, schema } from '@kbn/config-schema'; import { HttpConfig } from './http_config'; -import { Router } from './router'; +import { + Router, + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RouteValidationResultFactory, + RouteValidationFunction, +} from './router'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { HttpServer } from './http_server'; import { Readable } from 'stream'; +import { RequestHandlerContext } from 'kibana/server'; const cookieOptions = { name: 'sid', @@ -288,6 +296,229 @@ test('valid body', async () => { }); }); +test('valid body with validate function', async () => { + const router = new Router('/foo', logger, enhanceWithContext); + + router.post( + { + path: '/', + validate: { + body: ({ bar, baz } = {}, { ok, badRequest }) => { + if (typeof bar === 'string' && typeof baz === 'number') { + return ok({ bar, baz }); + } else { + return badRequest('Wrong payload', ['body']); + } + }, + }, + }, + (context, req, res) => { + return res.ok({ body: req.body }); + } + ); + + const { registerRouter, server: innerServer } = await server.setup(config); + registerRouter(router); + + await server.start(); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); +}); + +test('not inline validation - specifying params', async () => { + const router = new Router('/foo', logger, enhanceWithContext); + + const bodyValidation = ( + { bar, baz }: any = {}, + { ok, badRequest }: RouteValidationResultFactory + ) => { + if (typeof bar === 'string' && typeof baz === 'number') { + return ok({ bar, baz }); + } else { + return badRequest('Wrong payload', ['body']); + } + }; + + router.post( + { + path: '/', + validate: { + body: bodyValidation, + }, + }, + (context, req, res) => { + return res.ok({ body: req.body }); + } + ); + + const { registerRouter, server: innerServer } = await server.setup(config); + registerRouter(router); + + await server.start(); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); +}); + +test('not inline validation - specifying validation handler', async () => { + const router = new Router('/foo', logger, enhanceWithContext); + + const bodyValidation: RouteValidationFunction<{ bar: string; baz: number }> = ( + { bar, baz } = {}, + { ok, badRequest } + ) => { + if (typeof bar === 'string' && typeof baz === 'number') { + return ok({ bar, baz }); + } else { + return badRequest('Wrong payload', ['body']); + } + }; + + router.post( + { + path: '/', + validate: { + body: bodyValidation, + }, + }, + (context, req, res) => { + return res.ok({ body: req.body }); + } + ); + + const { registerRouter, server: innerServer } = await server.setup(config); + registerRouter(router); + + await server.start(); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); +}); + +// https://github.com/elastic/kibana/issues/47047 +test('not inline handler - KibanaRequest', async () => { + const router = new Router('/foo', logger, enhanceWithContext); + + const handler = ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ) => { + const body = { + bar: req.body.bar.toUpperCase(), + baz: req.body.baz.toString(), + }; + + return res.ok({ body }); + }; + + router.post( + { + path: '/', + validate: { + body: ({ bar, baz } = {}, { ok, badRequest }) => { + if (typeof bar === 'string' && typeof baz === 'number') { + return ok({ bar, baz }); + } else { + return badRequest('Wrong payload', ['body']); + } + }, + }, + }, + handler + ); + + const { registerRouter, server: innerServer } = await server.setup(config); + registerRouter(router); + + await server.start(); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'TEST', baz: '123' }); + }); +}); + +test('not inline handler - RequestHandler', async () => { + const router = new Router('/foo', logger, enhanceWithContext); + + const handler: RequestHandler = ( + context, + req, + res + ) => { + const body = { + bar: req.body.bar.toUpperCase(), + baz: req.body.baz.toString(), + }; + + return res.ok({ body }); + }; + + router.post( + { + path: '/', + validate: { + body: ({ bar, baz } = {}, { ok, badRequest }) => { + if (typeof bar === 'string' && typeof baz === 'number') { + return ok({ bar, baz }); + } else { + return badRequest('Wrong payload', ['body']); + } + }, + }, + }, + handler + ); + + const { registerRouter, server: innerServer } = await server.setup(config); + registerRouter(router); + + await server.start(); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'TEST', baz: '123' }); + }); +}); + test('invalid body', async () => { const router = new Router('/foo', logger, enhanceWithContext); diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 21de3945f1044..55ba813484268 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -47,10 +47,16 @@ export { RouteMethod, RouteRegistrar, RouteConfigOptions, - RouteSchemas, RouteConfigOptionsBody, RouteContentType, validBodyOutput, + RouteValidatorConfig, + RouteValidationSpec, + RouteValidationFunction, + RouteValidatorOptions, + RouteValidationError, + RouteValidatorFullConfig, + RouteValidationResultFactory, } from './router'; export { BasePathProxyServer } from './base_path_proxy_server'; export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth'; diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 6117190c57ba8..c3b9b20d84865 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -642,6 +642,116 @@ describe('Response factory', () => { }); }); + it('validate function in body', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/foo'); + + router.post( + { + path: '/', + validate: { + body: ({ bar, baz } = {}, { ok, badRequest }) => { + if (typeof bar === 'string' && typeof baz === 'number') { + return ok({ bar, baz }); + } else { + return badRequest('Wrong payload', ['body']); + } + }, + }, + }, + (context, req, res) => { + return res.ok({ body: req.body }); + } + ); + + await server.start(); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: '123', + }) + .expect(400) + .then(res => { + expect(res.body).toEqual({ + error: 'Bad Request', + message: '[request body.body]: Wrong payload', + statusCode: 400, + }); + }); + }); + + it('@kbn/config-schema validation in body', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/foo'); + + router.post( + { + path: '/', + validate: { + body: schema.object({ + bar: schema.string(), + baz: schema.number(), + }), + }, + }, + (context, req, res) => { + return res.ok({ body: req.body }); + } + ); + + await server.start(); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: '123', // Automatic casting happens + }) + .expect(200) + .then(res => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); + + await supertest(innerServer.listener) + .post('/foo/') + .send({ + bar: 'test', + baz: 'test', // Can't cast it into number + }) + .expect(400) + .then(res => { + expect(res.body).toEqual({ + error: 'Bad Request', + message: '[request body.baz]: expected value of type [number] but got [string]', + statusCode: 400, + }); + }); + }); + it('401 Unauthorized', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter('/'); diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index c4b4d3840d1b9..8f895753c38c3 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -18,19 +18,18 @@ */ import Boom from 'boom'; -import { ObjectType, TypeOf } from '@kbn/config-schema'; import { KibanaRequest } from './request'; import { KibanaResponseFactory } from './response'; import { RequestHandler } from './router'; import { RequestHandlerContext } from '../../../server'; import { RouteMethod } from './route'; -export const wrapErrors =

( +export const wrapErrors = ( handler: RequestHandler ): RequestHandler => { return async ( context: RequestHandlerContext, - request: KibanaRequest, TypeOf, TypeOf, RouteMethod>, + request: KibanaRequest, response: KibanaResponseFactory ) => { try { diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts index 35bfb3ba9c33a..084d30d694474 100644 --- a/src/core/server/http/router/index.ts +++ b/src/core/server/http/router/index.ts @@ -31,7 +31,6 @@ export { RouteMethod, RouteConfig, RouteConfigOptions, - RouteSchemas, RouteContentType, RouteConfigOptionsBody, validBodyOutput, @@ -55,3 +54,13 @@ export { } from './response'; export { IKibanaSocket } from './socket'; + +export { + RouteValidatorConfig, + RouteValidationSpec, + RouteValidationFunction, + RouteValidatorOptions, + RouteValidationError, + RouteValidatorFullConfig, + RouteValidationResultFactory, +} from './validator'; diff --git a/src/core/server/http/router/request.test.ts b/src/core/server/http/router/request.test.ts index ebb7ffa7a6fc9..51162a2c258e9 100644 --- a/src/core/server/http/router/request.test.ts +++ b/src/core/server/http/router/request.test.ts @@ -18,6 +18,7 @@ */ import { KibanaRequest } from './request'; import { httpServerMock } from '../http_server.mocks'; +import { schema } from '@kbn/config-schema'; describe('KibanaRequest', () => { describe('get all headers', () => { @@ -64,4 +65,56 @@ describe('KibanaRequest', () => { }); }); }); + + describe('RouteSchema type inferring', () => { + it('should work with config-schema', () => { + const body = Buffer.from('body!'); + const request = { + ...httpServerMock.createRawRequest({ + params: { id: 'params' }, + query: { search: 'query' }, + }), + payload: body, // Set outside because the mock is using `merge` by lodash and breaks the Buffer into arrays + } as any; + const kibanaRequest = KibanaRequest.from(request, { + params: schema.object({ id: schema.string() }), + query: schema.object({ search: schema.string() }), + body: schema.buffer(), + }); + expect(kibanaRequest.params).toStrictEqual({ id: 'params' }); + expect(kibanaRequest.params.id.toUpperCase()).toEqual('PARAMS'); // infers it's a string + expect(kibanaRequest.query).toStrictEqual({ search: 'query' }); + expect(kibanaRequest.query.search.toUpperCase()).toEqual('QUERY'); // infers it's a string + expect(kibanaRequest.body).toEqual(body); + expect(kibanaRequest.body.byteLength).toBeGreaterThan(0); // infers it's a buffer + }); + + it('should work with ValidationFunction', () => { + const body = Buffer.from('body!'); + const request = { + ...httpServerMock.createRawRequest({ + params: { id: 'params' }, + query: { search: 'query' }, + }), + payload: body, // Set outside because the mock is using `merge` by lodash and breaks the Buffer into arrays + } as any; + const kibanaRequest = KibanaRequest.from(request, { + params: schema.object({ id: schema.string() }), + query: schema.object({ search: schema.string() }), + body: (data, { ok, badRequest }) => { + if (Buffer.isBuffer(data)) { + return ok(data); + } else { + return badRequest('It should be a Buffer', []); + } + }, + }); + expect(kibanaRequest.params).toStrictEqual({ id: 'params' }); + expect(kibanaRequest.params.id.toUpperCase()).toEqual('PARAMS'); // infers it's a string + expect(kibanaRequest.query).toStrictEqual({ search: 'query' }); + expect(kibanaRequest.query.search.toUpperCase()).toEqual('QUERY'); // infers it's a string + expect(kibanaRequest.body).toEqual(body); + expect(kibanaRequest.body.byteLength).toBeGreaterThan(0); // infers it's a buffer + }); + }); }); diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index b132899910569..47b001700b015 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -20,13 +20,11 @@ import { Url } from 'url'; import { Request } from 'hapi'; -import { ObjectType, Type, TypeOf } from '@kbn/config-schema'; - -import { Stream } from 'stream'; import { deepFreeze, RecursiveReadonly } from '../../../utils'; import { Headers } from './headers'; -import { RouteMethod, RouteSchemas, RouteConfigOptions, validBodyOutput } from './route'; +import { RouteMethod, RouteConfigOptions, validBodyOutput } from './route'; import { KibanaSocket, IKibanaSocket } from './socket'; +import { RouteValidator, RouteValidatorFullConfig } from './validator'; const requestSymbol = Symbol('request'); @@ -70,12 +68,13 @@ export class KibanaRequest< * instance of a KibanaRequest. * @internal */ - public static from< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type - >(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders: boolean = true) { - const requestParts = KibanaRequest.validate(req, routeSchemas); + public static from( + req: Request, + routeSchemas: RouteValidator | RouteValidatorFullConfig = {}, + withoutSecretHeaders: boolean = true + ) { + const routeValidator = RouteValidator.from(routeSchemas); + const requestParts = KibanaRequest.validate(req, routeValidator); return new KibanaRequest( req, requestParts.params, @@ -91,40 +90,17 @@ export class KibanaRequest< * received in the route handler. * @internal */ - private static validate< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type - >( + private static validate( req: Request, - routeSchemas: RouteSchemas | undefined + routeValidator: RouteValidator ): { - params: TypeOf

; - query: TypeOf; - body: TypeOf; + params: P; + query: Q; + body: B; } { - if (routeSchemas === undefined) { - return { - body: {}, - params: {}, - query: {}, - }; - } - - const params = - routeSchemas.params === undefined - ? {} - : routeSchemas.params.validate(req.params, {}, 'request params'); - - const query = - routeSchemas.query === undefined - ? {} - : routeSchemas.query.validate(req.query, {}, 'request query'); - - const body = - routeSchemas.body === undefined - ? {} - : routeSchemas.body.validate(req.payload, {}, 'request body'); + const params = routeValidator.getParams(req.params, 'request params'); + const query = routeValidator.getQuery(req.query, 'request query'); + const body = routeValidator.getBody(req.payload, 'request body'); return { query, params, body }; } diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index 129cf4c922ffd..4439a80b1eac7 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -17,8 +17,7 @@ * under the License. */ -import { ObjectType, Type } from '@kbn/config-schema'; -import { Stream } from 'stream'; +import { RouteValidatorFullConfig } from './validator'; /** * The set of common HTTP methods supported by Kibana routing. @@ -124,12 +123,7 @@ export interface RouteConfigOptions { * Route specific configuration. * @public */ -export interface RouteConfig< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type, - Method extends RouteMethod -> { +export interface RouteConfig { /** * The endpoint _within_ the router path to register the route. * @@ -201,25 +195,10 @@ export interface RouteConfig< * }); * ``` */ - validate: RouteSchemas | false; + validate: RouteValidatorFullConfig | false; /** * Additional route options {@link RouteConfigOptions}. */ options?: RouteConfigOptions; } - -/** - * RouteSchemas contains the schemas for validating the different parts of a - * request. - * @public - */ -export interface RouteSchemas< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type -> { - params?: P; - query?: Q; - body?: B; -} diff --git a/src/core/server/http/router/router.test.ts b/src/core/server/http/router/router.test.ts index f5469a95b5106..a936da6a40a9f 100644 --- a/src/core/server/http/router/router.test.ts +++ b/src/core/server/http/router/router.test.ts @@ -20,6 +20,7 @@ import { Router } from './router'; import { loggingServiceMock } from '../../logging/logging_service.mock'; import { schema } from '@kbn/config-schema'; + const logger = loggingServiceMock.create().get(); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); @@ -38,12 +39,15 @@ describe('Router', () => { const router = new Router('', logger, enhanceWithContext); expect(() => router.get( - // we use 'any' because validate requires @kbn/config-schema usage - { path: '/', validate: { params: { validate: () => 'error' } } } as any, + // we use 'any' because validate requires valid Type or function usage + { + path: '/', + validate: { params: { validate: () => 'error' } } as any, + }, (context, req, res) => res.ok({}) ) ).toThrowErrorMatchingInlineSnapshot( - `"Expected a valid schema declared with '@kbn/config-schema' package at key: [params]."` + `"Expected a valid validation logic declared with '@kbn/config-schema' package or a RouteValidationFunction at key: [params]."` ); }); diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index 3bed8fe4186ac..bb56ee3727d1a 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -17,24 +17,18 @@ * under the License. */ -import { ObjectType, TypeOf, Type } from '@kbn/config-schema'; import { Request, ResponseObject, ResponseToolkit } from 'hapi'; import Boom from 'boom'; -import { Stream } from 'stream'; +import { Type } from '@kbn/config-schema'; import { Logger } from '../../logging'; import { KibanaRequest } from './request'; import { KibanaResponseFactory, kibanaResponseFactory, IKibanaResponse } from './response'; -import { - RouteConfig, - RouteConfigOptions, - RouteMethod, - RouteSchemas, - validBodyOutput, -} from './route'; +import { RouteConfig, RouteConfigOptions, RouteMethod, validBodyOutput } from './route'; import { HapiResponseAdapter } from './response_adapter'; import { RequestHandlerContext } from '../../../server'; import { wrapErrors } from './error_wrapper'; +import { RouteValidator } from './validator'; interface RouterRoute { method: RouteMethod; @@ -48,11 +42,7 @@ interface RouterRoute { * * @public */ -export type RouteRegistrar = < - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type ->( +export type RouteRegistrar = ( route: RouteConfig, handler: RequestHandler ) => void; @@ -108,9 +98,7 @@ export interface IRouter { * Wrap a router handler to catch and converts legacy boom errors to proper custom errors. * @param handler {@link RequestHandler} - a route handler to wrap */ - handleLegacyErrors:

( - handler: RequestHandler - ) => RequestHandler; + handleLegacyErrors: (handler: RequestHandler) => RequestHandler; /** * Returns all routes registered with this router. @@ -120,12 +108,9 @@ export interface IRouter { getRoutes: () => RouterRoute[]; } -export type ContextEnhancer< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType, - Method extends RouteMethod -> = (handler: RequestHandler) => RequestHandlerEnhanced; +export type ContextEnhancer = ( + handler: RequestHandler +) => RequestHandlerEnhanced; function getRouteFullPath(routerPath: string, routePath: string) { // If router's path ends with slash and route's path starts with slash, @@ -140,11 +125,10 @@ function getRouteFullPath(routerPath: string, routePath: string) { * @returns Route schemas if `validate` is specified on the route, otherwise * undefined. */ -function routeSchemasFromRouteConfig< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type ->(route: RouteConfig, routeMethod: RouteMethod) { +function routeSchemasFromRouteConfig( + route: RouteConfig, + routeMethod: RouteMethod +) { // The type doesn't allow `validate` to be undefined, but it can still // happen when it's used from JavaScript. if (route.validate === undefined) { @@ -155,15 +139,17 @@ function routeSchemasFromRouteConfig< if (route.validate !== false) { Object.entries(route.validate).forEach(([key, schema]) => { - if (!(schema instanceof Type)) { + if (!(schema instanceof Type || typeof schema === 'function')) { throw new Error( - `Expected a valid schema declared with '@kbn/config-schema' package at key: [${key}].` + `Expected a valid validation logic declared with '@kbn/config-schema' package or a RouteValidationFunction at key: [${key}].` ); } }); } - return route.validate ? route.validate : undefined; + if (route.validate) { + return RouteValidator.from(route.validate); + } } /** @@ -174,12 +160,7 @@ function routeSchemasFromRouteConfig< */ function validOptions( method: RouteMethod, - routeConfig: RouteConfig< - ObjectType, - ObjectType, - ObjectType | Type | Type, - typeof method - > + routeConfig: RouteConfig ) { const shouldNotHavePayload = ['head', 'get'].includes(method); const { options = {}, validate } = routeConfig; @@ -225,11 +206,7 @@ export class Router implements IRouter { private readonly log: Logger, private readonly enhanceWithContext: ContextEnhancer ) { - const buildMethod = (method: Method) => < - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type - >( + const buildMethod = (method: Method) => ( route: RouteConfig, handler: RequestHandler ) => { @@ -260,17 +237,11 @@ export class Router implements IRouter { return [...this.routes]; } - public handleLegacyErrors

( - handler: RequestHandler - ): RequestHandler { + public handleLegacyErrors(handler: RequestHandler): RequestHandler { return wrapErrors(handler); } - private async handle< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type - >({ + private async handle({ routeSchemas, request, responseToolkit, @@ -279,9 +250,9 @@ export class Router implements IRouter { request: Request; responseToolkit: ResponseToolkit; handler: RequestHandlerEnhanced; - routeSchemas?: RouteSchemas; + routeSchemas?: RouteValidator; }) { - let kibanaRequest: KibanaRequest, TypeOf, TypeOf, typeof request.method>; + let kibanaRequest: KibanaRequest; const hapiResponseAdapter = new HapiResponseAdapter(responseToolkit); try { kibanaRequest = KibanaRequest.from(request, routeSchemas); @@ -303,16 +274,14 @@ type WithoutHeadArgument = T extends (first: any, ...rest: infer Params) => i ? (...rest: Params) => Return : never; -type RequestHandlerEnhanced< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type, - Method extends RouteMethod -> = WithoutHeadArgument>; +type RequestHandlerEnhanced = WithoutHeadArgument< + RequestHandler +>; /** * A function executed when route path matched requested resource path. * Request handler is expected to return a result of one of {@link KibanaResponseFactory} functions. + * @param context {@link RequestHandlerContext} - the core context exposed for this request. * @param request {@link KibanaRequest} - object containing information about requested resource, * such as path, method, headers, parameters, query, body, etc. * @param response {@link KibanaResponseFactory} - a set of helper functions used to respond to a request. @@ -344,12 +313,12 @@ type RequestHandlerEnhanced< * @public */ export type RequestHandler< - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType | Type | Type, + P = unknown, + Q = unknown, + B = unknown, Method extends RouteMethod = any > = ( context: RequestHandlerContext, - request: KibanaRequest, TypeOf, TypeOf, Method>, + request: KibanaRequest, response: KibanaResponseFactory ) => IKibanaResponse | Promise>; diff --git a/src/core/server/http/router/validator/index.ts b/src/core/server/http/router/validator/index.ts new file mode 100644 index 0000000000000..edb116c40144a --- /dev/null +++ b/src/core/server/http/router/validator/index.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 { + RouteValidator, + RouteValidatorConfig, + RouteValidationSpec, + RouteValidationFunction, + RouteValidatorOptions, + RouteValidatorFullConfig, + RouteValidationResultFactory, +} from './validator'; +export { RouteValidationError } from './validator_error'; diff --git a/src/core/server/http/router/validator/validator.test.ts b/src/core/server/http/router/validator/validator.test.ts new file mode 100644 index 0000000000000..729eb1b60c10a --- /dev/null +++ b/src/core/server/http/router/validator/validator.test.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. + */ + +import { RouteValidationError, RouteValidator } from './'; +import { schema, Type } from '@kbn/config-schema'; + +describe('Router validator', () => { + it('should validate and infer the type from a function', () => { + const validator = RouteValidator.from({ + params: ({ foo }, validationResult) => { + if (typeof foo === 'string') { + return validationResult.ok({ foo }); + } + return validationResult.badRequest('Not a string', ['foo']); + }, + }); + expect(validator.getParams({ foo: 'bar' })).toStrictEqual({ foo: 'bar' }); + expect(validator.getParams({ foo: 'bar' }).foo.toUpperCase()).toBe('BAR'); // It knows it's a string! :) + expect(() => validator.getParams({ foo: 1 })).toThrowError('[foo]: Not a string'); + expect(() => validator.getParams({})).toThrowError('[foo]: Not a string'); + + expect(() => validator.getParams(undefined)).toThrowError( + "Cannot destructure property `foo` of 'undefined' or 'null'." + ); + expect(() => validator.getParams({}, 'myField')).toThrowError('[myField.foo]: Not a string'); + + expect(validator.getBody(undefined)).toStrictEqual({}); + expect(validator.getQuery(undefined)).toStrictEqual({}); + }); + + it('should validate and infer the type from a function that does not use the resolver', () => { + const validator = RouteValidator.from({ + params: data => { + if (typeof data.foo === 'string') { + return { value: { foo: data.foo as string } }; + } + return { error: new RouteValidationError('Not a string', ['foo']) }; + }, + }); + expect(validator.getParams({ foo: 'bar' })).toStrictEqual({ foo: 'bar' }); + expect(validator.getParams({ foo: 'bar' }).foo.toUpperCase()).toBe('BAR'); // It knows it's a string! :) + expect(() => validator.getParams({ foo: 1 })).toThrowError('[foo]: Not a string'); + expect(() => validator.getParams({})).toThrowError('[foo]: Not a string'); + + expect(() => validator.getParams(undefined)).toThrowError( + `Cannot read property 'foo' of undefined` + ); + expect(() => validator.getParams({}, 'myField')).toThrowError('[myField.foo]: Not a string'); + + expect(validator.getBody(undefined)).toStrictEqual({}); + expect(validator.getQuery(undefined)).toStrictEqual({}); + }); + + it('should validate and infer the type from a config-schema ObjectType', () => { + const schemaValidation = RouteValidator.from({ + params: schema.object({ + foo: schema.string(), + }), + }); + + expect(schemaValidation.getParams({ foo: 'bar' })).toStrictEqual({ foo: 'bar' }); + expect(schemaValidation.getParams({ foo: 'bar' }).foo.toUpperCase()).toBe('BAR'); // It knows it's a string! :) + expect(() => schemaValidation.getParams({ foo: 1 })).toThrowError( + '[foo]: expected value of type [string] but got [number]' + ); + expect(() => schemaValidation.getParams({})).toThrowError( + '[foo]: expected value of type [string] but got [undefined]' + ); + expect(() => schemaValidation.getParams(undefined)).toThrowError( + '[foo]: expected value of type [string] but got [undefined]' + ); + expect(() => schemaValidation.getParams({}, 'myField')).toThrowError( + '[myField.foo]: expected value of type [string] but got [undefined]' + ); + }); + + it('should validate and infer the type from a config-schema non-ObjectType', () => { + const schemaValidation = RouteValidator.from({ params: schema.buffer() }); + + const foo = Buffer.from('hi!'); + expect(schemaValidation.getParams(foo)).toStrictEqual(foo); + expect(schemaValidation.getParams(foo).byteLength).toBeGreaterThan(0); // It knows it's a buffer! :) + expect(() => schemaValidation.getParams({ foo: 1 })).toThrowError( + 'expected value of type [Buffer] but got [Object]' + ); + expect(() => schemaValidation.getParams({})).toThrowError( + 'expected value of type [Buffer] but got [Object]' + ); + expect(() => schemaValidation.getParams(undefined)).toThrowError( + `expected value of type [Buffer] but got [undefined]` + ); + expect(() => schemaValidation.getParams({}, 'myField')).toThrowError( + '[myField]: expected value of type [Buffer] but got [Object]' + ); + }); + + it('should catch the errors thrown by the validate function', () => { + const validator = RouteValidator.from({ + params: data => { + throw new Error('Something went terribly wrong'); + }, + }); + + expect(() => validator.getParams({ foo: 1 })).toThrowError('Something went terribly wrong'); + expect(() => validator.getParams({}, 'myField')).toThrowError( + '[myField]: Something went terribly wrong' + ); + }); + + it('should not accept invalid validation options', () => { + const wrongValidateSpec = RouteValidator.from({ + params: { validate: (data: T): T => data } as Type, + }); + + expect(() => wrongValidateSpec.getParams({ foo: 1 })).toThrowError( + 'The validation rule provided in the handler is not valid' + ); + }); +}); diff --git a/src/core/server/http/router/validator/validator.ts b/src/core/server/http/router/validator/validator.ts new file mode 100644 index 0000000000000..65c0a934e6ef0 --- /dev/null +++ b/src/core/server/http/router/validator/validator.ts @@ -0,0 +1,280 @@ +/* + * 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 { ValidationError, Type, schema, ObjectType } from '@kbn/config-schema'; +import { Stream } from 'stream'; +import { RouteValidationError } from './validator_error'; + +/** + * Validation result factory to be used in the custom validation function to return the valid data or validation errors + * + * See {@link RouteValidationFunction}. + * + * @public + */ +export interface RouteValidationResultFactory { + ok: (value: T) => { value: T }; + badRequest: (error: Error | string, path?: string[]) => { error: RouteValidationError }; +} + +/** + * The custom validation function if @kbn/config-schema is not a valid solution for your specific plugin requirements. + * + * @example + * + * The validation should look something like: + * ```typescript + * interface MyExpectedBody { + * bar: string; + * baz: number; + * } + * + * const myBodyValidation: RouteValidationFunction = (data, validationResult) => { + * const { ok, badRequest } = validationResult; + * const { bar, baz } = data || {}; + * if (typeof bar === 'string' && typeof baz === 'number') { + * return ok({ bar, baz }); + * } else { + * return badRequest('Wrong payload', ['body']); + * } + * } + * ``` + * + * @public + */ +export type RouteValidationFunction = ( + data: any, + validationResult: RouteValidationResultFactory +) => + | { + value: T; + error?: never; + } + | { + value?: never; + error: RouteValidationError; + }; + +/** + * Allowed property validation options: either @kbn/config-schema validations or custom validation functions + * + * See {@link RouteValidationFunction} for custom validation. + * + * @public + */ +export type RouteValidationSpec = ObjectType | Type | RouteValidationFunction; + +// Ugly as hell but we need this conditional typing to have proper type inference +type RouteValidationResultType | undefined> = NonNullable< + T extends RouteValidationFunction + ? ReturnType['value'] + : T extends Type + ? ReturnType + : undefined +>; + +/** + * The configuration object to the RouteValidator class. + * Set `params`, `query` and/or `body` to specify the validation logic to follow for that property. + * + * @public + */ +export interface RouteValidatorConfig { + /** + * Validation logic for the URL params + * @public + */ + params?: RouteValidationSpec

; + /** + * Validation logic for the Query params + * @public + */ + query?: RouteValidationSpec; + /** + * Validation logic for the body payload + * @public + */ + body?: RouteValidationSpec; +} + +/** + * Additional options for the RouteValidator class to modify its default behaviour. + * + * @public + */ +export interface RouteValidatorOptions { + /** + * Set the `unsafe` config to avoid running some additional internal *safe* validations on top of your custom validation + * @public + */ + unsafe?: { + params?: boolean; + query?: boolean; + body?: boolean; + }; +} + +/** + * Route validations config and options merged into one object + * @public + */ +export type RouteValidatorFullConfig = RouteValidatorConfig & + RouteValidatorOptions; + +/** + * Route validator class to define the validation logic for each new route. + * + * @internal + */ +export class RouteValidator

{ + public static from

( + opts: RouteValidator | RouteValidatorFullConfig + ) { + if (opts instanceof RouteValidator) { + return opts; + } + const { params, query, body, ...options } = opts; + return new RouteValidator({ params, query, body }, options); + } + + private static ResultFactory: RouteValidationResultFactory = { + ok: (value: T) => ({ value }), + badRequest: (error: Error | string, path?: string[]) => ({ + error: new RouteValidationError(error, path), + }), + }; + + private constructor( + private readonly config: RouteValidatorConfig, + private readonly options: RouteValidatorOptions = {} + ) {} + + /** + * Get validated URL params + * @internal + */ + public getParams(data: unknown, namespace?: string): Readonly

{ + return this.validate(this.config.params, this.options.unsafe?.params, data, namespace); + } + + /** + * Get validated query params + * @internal + */ + public getQuery(data: unknown, namespace?: string): Readonly { + return this.validate(this.config.query, this.options.unsafe?.query, data, namespace); + } + + /** + * Get validated body + * @internal + */ + public getBody(data: unknown, namespace?: string): Readonly { + return this.validate(this.config.body, this.options.unsafe?.body, data, namespace); + } + + /** + * Has body validation + * @internal + */ + public hasBody(): boolean { + return typeof this.config.body !== 'undefined'; + } + + private validate( + validationRule?: RouteValidationSpec, + unsafe?: boolean, + data?: unknown, + namespace?: string + ): RouteValidationResultType { + if (typeof validationRule === 'undefined') { + return {}; + } + let precheckedData = this.preValidateSchema(data).validate(data, {}, namespace); + + if (unsafe !== true) { + precheckedData = this.safetyPrechecks(precheckedData, namespace); + } + + const customCheckedData = this.customValidation(validationRule, precheckedData, namespace); + + if (unsafe === true) { + return customCheckedData; + } + + return this.safetyPostchecks(customCheckedData, namespace); + } + + private safetyPrechecks(data: T, namespace?: string): T { + // We can add any pre-validation safety logic in here + return data; + } + + private safetyPostchecks(data: T, namespace?: string): T { + // We can add any post-validation safety logic in here + return data; + } + + private customValidation( + validationRule: RouteValidationSpec, + data?: unknown, + namespace?: string + ): RouteValidationResultType { + if (validationRule instanceof Type) { + return validationRule.validate(data, {}, namespace); + } else if (typeof validationRule === 'function') { + return this.validateFunction(validationRule, data, namespace); + } else { + throw new ValidationError( + new RouteValidationError(`The validation rule provided in the handler is not valid`), + namespace + ); + } + } + + private validateFunction( + validateFn: RouteValidationFunction, + data: unknown, + namespace?: string + ): T { + let result: ReturnType; + try { + result = validateFn(data, RouteValidator.ResultFactory); + } catch (err) { + result = { error: new RouteValidationError(err) }; + } + + if (result.error) { + throw new ValidationError(result.error, namespace); + } + return result.value; + } + + private preValidateSchema(data: any) { + if (Buffer.isBuffer(data)) { + // if options.body.parse !== true + return schema.buffer(); + } else if (data instanceof Stream) { + // if options.body.output === 'stream' + return schema.stream(); + } else { + return schema.maybe(schema.nullable(schema.object({}, { allowUnknowns: true }))); + } + } +} diff --git a/src/core/server/http/router/validator/validator_error.ts b/src/core/server/http/router/validator/validator_error.ts new file mode 100644 index 0000000000000..d306db4ad1cf4 --- /dev/null +++ b/src/core/server/http/router/validator/validator_error.ts @@ -0,0 +1,34 @@ +/* + * 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 { SchemaTypeError } from '@kbn/config-schema'; + +/** + * Error to return when the validation is not successful. + * @public + */ +export class RouteValidationError extends SchemaTypeError { + constructor(error: Error | string, path: string[] = []) { + super(error, path); + + // Set the prototype explicitly, see: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, RouteValidationError.prototype); + } +} diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 2aaa8306e871f..878f854f2a517 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -43,7 +43,7 @@ import { ElasticsearchServiceSetup, IScopedClusterClient } from './elasticsearch import { HttpServiceSetup } from './http'; import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; import { ContextSetup } from './context'; -import { IUiSettingsClient, UiSettingsServiceSetup } from './ui_settings'; +import { IUiSettingsClient, UiSettingsServiceSetup, UiSettingsServiceStart } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; import { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './saved_objects'; import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; @@ -134,10 +134,16 @@ export { RouteRegistrar, RouteMethod, RouteConfigOptions, - RouteSchemas, RouteConfigOptionsBody, RouteContentType, validBodyOutput, + RouteValidatorConfig, + RouteValidationSpec, + RouteValidationFunction, + RouteValidatorOptions, + RouteValidatorFullConfig, + RouteValidationResultFactory, + RouteValidationError, SessionStorage, SessionStorageCookieOptions, SessionCookieValidationResult, @@ -204,6 +210,7 @@ export { UiSettingsParams, UiSettingsType, UiSettingsServiceSetup, + UiSettingsServiceStart, UserProvidedValues, } from './ui_settings'; @@ -234,6 +241,8 @@ export { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './legacy'; * data client which uses the credentials of the incoming request * - {@link ScopedClusterClient | elasticsearch.adminClient} - Elasticsearch * admin client which uses the credentials of the incoming request + * - {@link IUiSettingsClient | uiSettings.client} - uiSettings client + * which uses the credentials of the incoming request * * @public */ @@ -284,6 +293,8 @@ export interface CoreStart { capabilities: CapabilitiesStart; /** {@link SavedObjectsServiceStart} */ savedObjects: SavedObjectsServiceStart; + /** {@link UiSettingsServiceStart} */ + uiSettings: UiSettingsServiceStart; } export { diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 06cf848bff25a..52adaaccab4b7 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -19,7 +19,7 @@ import { InternalElasticsearchServiceSetup } from './elasticsearch'; import { InternalHttpServiceSetup } from './http'; -import { InternalUiSettingsServiceSetup } from './ui_settings'; +import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings'; import { ContextSetup } from './context'; import { InternalSavedObjectsServiceStart, @@ -45,4 +45,5 @@ export interface InternalCoreSetup { export interface InternalCoreStart { capabilities: CapabilitiesStart; savedObjects: InternalSavedObjectsServiceStart; + uiSettings: InternalUiSettingsServiceStart; } diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 17ec1e9756432..c652bb1c94887 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -27,7 +27,7 @@ import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks'; import { BehaviorSubject, throwError } from 'rxjs'; import { LegacyService, LegacyServiceSetupDeps, LegacyServiceStartDeps } from '.'; // @ts-ignore: implicit any for JS file -import MockClusterManager from '../../../cli/cluster/cluster_manager'; +import { ClusterManager as MockClusterManager } from '../../../cli/cluster/cluster_manager'; import KbnServer from '../../../legacy/server/kbn_server'; import { Config, Env, ObjectToConfigAdapter } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; @@ -98,6 +98,7 @@ beforeEach(() => { core: { capabilities: capabilitiesServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), plugins: { contracts: new Map() }, }, plugins: {}, @@ -354,9 +355,15 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); - const [[cliArgs, , basePathProxy]] = MockClusterManager.create.mock.calls; - expect(cliArgs.basePath).toBe(false); - expect(basePathProxy).not.toBeDefined(); + expect(MockClusterManager).toHaveBeenCalledTimes(1); + expect(MockClusterManager).toHaveBeenCalledWith( + expect.objectContaining({ silent: true, basePath: false }), + expect.objectContaining({ + get: expect.any(Function), + set: expect.any(Function), + }), + undefined + ); }); test('creates ClusterManager with base path proxy.', async () => { @@ -376,11 +383,15 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); - expect(MockClusterManager.create).toBeCalledTimes(1); - - const [[cliArgs, , basePathProxy]] = MockClusterManager.create.mock.calls; - expect(cliArgs.basePath).toEqual(true); - expect(basePathProxy).toBeInstanceOf(BasePathProxyServer); + expect(MockClusterManager).toHaveBeenCalledTimes(1); + expect(MockClusterManager).toHaveBeenCalledWith( + expect.objectContaining({ quiet: true, basePath: true }), + expect.objectContaining({ + get: expect.any(Function), + set: expect.any(Function), + }), + expect.any(BasePathProxyServer) + ); }); }); diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 1bba38433d7f4..2e8a467eff995 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -244,7 +244,7 @@ export class LegacyService implements CoreService { private async createClusterManager(config: LegacyConfig) { const basePathProxy$ = this.coreContext.env.cliArgs.basePath - ? combineLatest(this.devConfig$, this.httpConfig$).pipe( + ? combineLatest([this.devConfig$, this.httpConfig$]).pipe( first(), map( ([dev, http]) => @@ -253,7 +253,9 @@ export class LegacyService implements CoreService { ) : EMPTY; - require('../../../cli/cluster/cluster_manager').create( + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { ClusterManager } = require('../../../cli/cluster/cluster_manager'); + return new ClusterManager( this.coreContext.env.cliArgs, config, await basePathProxy$.toPromise() @@ -310,6 +312,7 @@ export class LegacyService implements CoreService { const coreStart: CoreStart = { capabilities: startDeps.core.capabilities, savedObjects: { getScopedClient: startDeps.core.savedObjects.getScopedClient }, + uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient }, }; // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 3a68b18409b0a..53849b040c413 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -121,6 +121,7 @@ function createCoreStartMock() { const mock: MockedKeys = { capabilities: capabilitiesServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), }; return mock; @@ -143,6 +144,7 @@ function createInternalCoreStartMock() { const startDeps: InternalCoreStart = { capabilities: capabilitiesServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), }; return startDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 04a7547fd3747..6e9a7967e9eca 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -200,6 +200,11 @@ export function createPluginStartContext( capabilities: { resolveCapabilities: deps.capabilities.resolveCapabilities, }, - savedObjects: { getScopedClient: deps.savedObjects.getScopedClient }, + savedObjects: { + getScopedClient: deps.savedObjects.getScopedClient, + }, + uiSettings: { + asScopedToClient: deps.uiSettings.asScopedToClient, + }, }; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 4e6493a17aea1..ef5368751c8f5 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -115,6 +115,7 @@ import { RenderSearchTemplateParams } from 'elasticsearch'; import { Request } from 'hapi'; import { ResponseObject } from 'hapi'; import { ResponseToolkit } from 'hapi'; +import { SchemaTypeError } from '@kbn/config-schema'; import { ScrollParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch'; import { SearchResponse } from 'elasticsearch'; @@ -574,6 +575,8 @@ export interface CoreStart { capabilities: CapabilitiesStart; // (undocumented) savedObjects: SavedObjectsServiceStart; + // (undocumented) + uiSettings: UiSettingsServiceStart; } // @public @@ -803,7 +806,7 @@ export interface IRouter { // // @internal getRoutes: () => RouterRoute[]; - handleLegacyErrors:

(handler: RequestHandler) => RequestHandler; + handleLegacyErrors: (handler: RequestHandler) => RequestHandler; patch: RouteRegistrar<'patch'>; post: RouteRegistrar<'post'>; put: RouteRegistrar<'put'>; @@ -839,8 +842,10 @@ export class KibanaRequest | Type>(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders?: boolean): KibanaRequest; + static from(req: Request, routeSchemas?: RouteValidator | RouteValidatorFullConfig, withoutSecretHeaders?: boolean): KibanaRequest; readonly headers: Headers; // (undocumented) readonly params: Params; @@ -1157,7 +1162,7 @@ export type RedirectResponseOptions = HttpResponseOptions & { }; // @public -export type RequestHandler

| Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => IKibanaResponse | Promise>; // @public export interface RequestHandlerContext { @@ -1199,10 +1204,10 @@ export type ResponseHeaders = { }; // @public -export interface RouteConfig

| Type, Method extends RouteMethod> { +export interface RouteConfig { options?: RouteConfigOptions; path: string; - validate: RouteSchemas | false; + validate: RouteValidatorFullConfig | false; } // @public @@ -1227,16 +1232,54 @@ export type RouteContentType = 'application/json' | 'application/*+json' | 'appl export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; // @public -export type RouteRegistrar =

| Type>(route: RouteConfig, handler: RequestHandler) => void; +export type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; // @public -export interface RouteSchemas

| Type> { - // (undocumented) - body?: B; +export class RouteValidationError extends SchemaTypeError { + constructor(error: Error | string, path?: string[]); +} + +// @public +export type RouteValidationFunction = (data: any, validationResult: RouteValidationResultFactory) => { + value: T; + error?: never; +} | { + value?: never; + error: RouteValidationError; +}; + +// @public +export interface RouteValidationResultFactory { // (undocumented) - params?: P; + badRequest: (error: Error | string, path?: string[]) => { + error: RouteValidationError; + }; // (undocumented) - query?: Q; + ok: (value: T) => { + value: T; + }; +} + +// @public +export type RouteValidationSpec = ObjectType | Type | RouteValidationFunction; + +// @public +export interface RouteValidatorConfig { + body?: RouteValidationSpec; + params?: RouteValidationSpec

; + query?: RouteValidationSpec; +} + +// @public +export type RouteValidatorFullConfig = RouteValidatorConfig & RouteValidatorOptions; + +// @public +export interface RouteValidatorOptions { + unsafe?: { + params?: boolean; + query?: boolean; + body?: boolean; + }; } // @public (undocumented) @@ -1822,6 +1865,11 @@ export interface UiSettingsServiceSetup { register(settings: Record): void; } +// @public (undocumented) +export interface UiSettingsServiceStart { + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +} + // @public export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index 59925f46543e7..67a878be86742 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -65,6 +65,12 @@ jest.doMock('./context/context_service', () => ({ ContextService: jest.fn(() => mockContextService), })); +import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; +export const mockUiSettingsService = uiSettingsServiceMock.create(); +jest.doMock('./ui_settings/ui_settings_service', () => ({ + UiSettingsService: jest.fn(() => mockUiSettingsService), +})); + export const mockEnsureValidConfiguration = jest.fn(); jest.doMock('./legacy/config/ensure_valid_configuration', () => ({ ensureValidConfiguration: mockEnsureValidConfiguration, diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index d593a6275fa4c..27dff1e208aaf 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -26,6 +26,7 @@ import { mockSavedObjectsService, mockContextService, mockEnsureValidConfiguration, + mockUiSettingsService, } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; @@ -57,6 +58,7 @@ test('sets up services on "setup"', async () => { expect(mockPluginsService.setup).not.toHaveBeenCalled(); expect(mockLegacyService.setup).not.toHaveBeenCalled(); expect(mockSavedObjectsService.setup).not.toHaveBeenCalled(); + expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); await server.setup(); @@ -65,6 +67,7 @@ test('sets up services on "setup"', async () => { expect(mockPluginsService.setup).toHaveBeenCalledTimes(1); expect(mockLegacyService.setup).toHaveBeenCalledTimes(1); expect(mockSavedObjectsService.setup).toHaveBeenCalledTimes(1); + expect(mockUiSettingsService.setup).toHaveBeenCalledTimes(1); }); test('injects legacy dependency to context#setup()', async () => { @@ -100,11 +103,14 @@ test('runs services on "start"', async () => { expect(mockHttpService.start).not.toHaveBeenCalled(); expect(mockLegacyService.start).not.toHaveBeenCalled(); expect(mockSavedObjectsService.start).not.toHaveBeenCalled(); + expect(mockUiSettingsService.start).not.toHaveBeenCalled(); + await server.start(); expect(mockHttpService.start).toHaveBeenCalledTimes(1); expect(mockLegacyService.start).toHaveBeenCalledTimes(1); expect(mockSavedObjectsService.start).toHaveBeenCalledTimes(1); + expect(mockUiSettingsService.start).toHaveBeenCalledTimes(1); }); test('does not fail on "setup" if there are unused paths detected', async () => { @@ -125,6 +131,7 @@ test('stops services on "stop"', async () => { expect(mockPluginsService.stop).not.toHaveBeenCalled(); expect(mockLegacyService.stop).not.toHaveBeenCalled(); expect(mockSavedObjectsService.stop).not.toHaveBeenCalled(); + expect(mockUiSettingsService.stop).not.toHaveBeenCalled(); await server.stop(); @@ -133,6 +140,7 @@ test('stops services on "stop"', async () => { expect(mockPluginsService.stop).toHaveBeenCalledTimes(1); expect(mockLegacyService.stop).toHaveBeenCalledTimes(1); expect(mockSavedObjectsService.stop).toHaveBeenCalledTimes(1); + expect(mockUiSettingsService.stop).toHaveBeenCalledTimes(1); }); test(`doesn't setup core services if config validation fails`, async () => { @@ -146,6 +154,7 @@ test(`doesn't setup core services if config validation fails`, async () => { expect(mockElasticsearchService.setup).not.toHaveBeenCalled(); expect(mockPluginsService.setup).not.toHaveBeenCalled(); expect(mockLegacyService.setup).not.toHaveBeenCalled(); + expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); }); test(`doesn't setup core services if legacy config validation fails`, async () => { @@ -164,4 +173,5 @@ test(`doesn't setup core services if legacy config validation fails`, async () = expect(mockPluginsService.setup).not.toHaveBeenCalled(); expect(mockLegacyService.setup).not.toHaveBeenCalled(); expect(mockSavedObjectsService.stop).not.toHaveBeenCalled(); + expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); }); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 89d99d6c4a4ec..5ca3800f3fb8f 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -158,14 +158,18 @@ export class Server { this.log.debug('starting server'); const savedObjectsStart = await this.savedObjects.start({}); const capabilitiesStart = this.capabilities.start(); + const uiSettingsStart = await this.uiSettings.start(); + const pluginsStart = await this.plugins.start({ capabilities: capabilitiesStart, savedObjects: savedObjectsStart, + uiSettings: uiSettingsStart, }); const coreStart = { capabilities: capabilitiesStart, savedObjects: savedObjectsStart, + uiSettings: uiSettingsStart, plugins: pluginsStart, }; await this.legacy.start({ @@ -186,6 +190,7 @@ export class Server { await this.savedObjects.stop(); await this.elasticsearch.stop(); await this.http.stop(); + await this.uiSettings.stop(); } private registerDefaultRoute(httpSetup: InternalHttpServiceSetup) { diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts index fd0a21bed4e12..f1185474c2160 100644 --- a/src/core/server/ui_settings/index.ts +++ b/src/core/server/ui_settings/index.ts @@ -24,9 +24,11 @@ export { UiSettingsService } from './ui_settings_service'; export { UiSettingsServiceSetup, + UiSettingsServiceStart, IUiSettingsClient, UiSettingsParams, InternalUiSettingsServiceSetup, + InternalUiSettingsServiceStart, UiSettingsType, UserProvidedValues, } from './types'; diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index 49d3d3b33392f..5e3f0a4fbb6bd 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -125,6 +125,7 @@ export interface UiSettingsServiceSetup { * @param settings * * @example + * ```ts * setup(core: CoreSetup){ * core.uiSettings.register([{ * foo: { @@ -134,6 +135,29 @@ export interface UiSettingsServiceSetup { * }, * }]); * } + * ``` */ register(settings: Record): void; } + +/** @public */ +export interface UiSettingsServiceStart { + /** + * Creates a {@link IUiSettingsClient} with provided *scoped* saved objects client. + * + * This should only be used in the specific case where the client needs to be accessed + * from outside of the scope of a {@link RequestHandler}. + * + * @example + * ```ts + * start(core: CoreStart) { + * const soClient = core.savedObjects.getScopedClient(arbitraryRequest); + * const uiSettingsClient = core.uiSettings.asScopedToClient(soClient); + * } + * ``` + */ + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +} + +/** @internal */ +export type InternalUiSettingsServiceStart = UiSettingsServiceStart; diff --git a/src/core/server/ui_settings/ui_settings_service.mock.ts b/src/core/server/ui_settings/ui_settings_service.mock.ts index bb21109a2f967..b850963a0bc1b 100644 --- a/src/core/server/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -17,7 +17,12 @@ * under the License. */ -import { IUiSettingsClient, InternalUiSettingsServiceSetup } from './types'; +import { + IUiSettingsClient, + InternalUiSettingsServiceSetup, + InternalUiSettingsServiceStart, +} from './types'; +import { UiSettingsService } from './ui_settings_service'; const createClientMock = () => { const mocked: jest.Mocked = { @@ -46,7 +51,31 @@ const createSetupMock = () => { return mocked; }; +const createStartMock = () => { + const mocked: jest.Mocked = { + asScopedToClient: jest.fn(), + }; + + mocked.asScopedToClient.mockReturnValue(createClientMock()); + + return mocked; +}; + +type UiSettingsServiceContract = PublicMethodsOf; +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + mocked.setup.mockResolvedValue(createSetupMock()); + mocked.start.mockResolvedValue(createStartMock()); + return mocked; +}; + export const uiSettingsServiceMock = { createSetupContract: createSetupMock, + createStartContract: createStartMock, createClient: createClientMock, + create: createMock, }; diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts index d7a085a220190..d908a91a39c70 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -114,4 +114,40 @@ describe('uiSettings', () => { }); }); }); + + describe('#start', () => { + describe('#asScopedToClient', () => { + it('passes saved object type "config" to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + await service.setup(setupDeps); + const start = await service.start(); + start.asScopedToClient(savedObjectsClient); + + expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].type).toBe('config'); + }); + + it('passes overrides to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + await service.setup(setupDeps); + const start = await service.start(); + start.asScopedToClient(savedObjectsClient); + expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toBe(overrides); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual(overrides); + }); + + it('passes a copy of set defaults to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + const setup = await service.setup(setupDeps); + setup.register(defaults); + const start = await service.start(); + start.asScopedToClient(savedObjectsClient); + + expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).toEqual(defaults); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).not.toBe(defaults); + }); + }); + }); }); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index 8458a80de4952..db08c3cad85a2 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -25,9 +25,13 @@ import { Logger } from '../logging'; import { SavedObjectsClientContract } from '../saved_objects/types'; import { InternalHttpServiceSetup } from '../http'; -import { UiSettingsConfigType } from './ui_settings_config'; +import { UiSettingsConfigType, config as uiConfigDefinition } from './ui_settings_config'; import { UiSettingsClient } from './ui_settings_client'; -import { InternalUiSettingsServiceSetup, UiSettingsParams } from './types'; +import { + InternalUiSettingsServiceSetup, + InternalUiSettingsServiceStart, + UiSettingsParams, +} from './types'; import { mapToObject } from '../../utils/'; import { registerRoutes } from './routes'; @@ -37,42 +41,52 @@ interface SetupDeps { } /** @internal */ -export class UiSettingsService implements CoreService { +export class UiSettingsService + implements CoreService { private readonly log: Logger; private readonly config$: Observable; private readonly uiSettingsDefaults = new Map(); + private overrides: Record = {}; constructor(private readonly coreContext: CoreContext) { this.log = coreContext.logger.get('ui-settings-service'); - this.config$ = coreContext.configService.atPath('uiSettings'); + this.config$ = coreContext.configService.atPath(uiConfigDefinition.path); } public async setup(deps: SetupDeps): Promise { registerRoutes(deps.http.createRouter('')); this.log.debug('Setting up ui settings service'); - const overrides = await this.getOverrides(deps); - const { version, buildNum } = this.coreContext.env.packageInfo; - + this.overrides = await this.getOverrides(deps); return { register: this.register.bind(this), - asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => { - return new UiSettingsClient({ - type: 'config', - id: version, - buildNum, - savedObjectsClient, - defaults: mapToObject(this.uiSettingsDefaults), - overrides, - log: this.log, - }); - }, + asScopedToClient: this.getScopedClientFactory(), }; } - public async start() {} + public async start(): Promise { + return { + asScopedToClient: this.getScopedClientFactory(), + }; + } public async stop() {} + private getScopedClientFactory(): ( + savedObjectsClient: SavedObjectsClientContract + ) => UiSettingsClient { + const { version, buildNum } = this.coreContext.env.packageInfo; + return (savedObjectsClient: SavedObjectsClientContract) => + new UiSettingsClient({ + type: 'config', + id: version, + buildNum, + savedObjectsClient, + defaults: mapToObject(this.uiSettingsDefaults), + overrides: this.overrides, + log: this.log, + }); + } + private register(settings: Record = {}) { Object.entries(settings).forEach(([key, value]) => { if (this.uiSettingsDefaults.has(key)) { @@ -93,7 +107,6 @@ export class UiSettingsService implements CoreService { - setup(...params: any[]): Promise; - start(...params: any[]): Promise; - stop(): Promise; + setup(...params: any[]): TSetup | Promise; + start(...params: any[]): TStart | Promise; + stop(): void | Promise; } diff --git a/src/dev/build/tasks/copy_source_task.js b/src/dev/build/tasks/copy_source_task.js index e5698c37ba16f..ee9dc159de47f 100644 --- a/src/dev/build/tasks/copy_source_task.js +++ b/src/dev/build/tasks/copy_source_task.js @@ -46,7 +46,6 @@ export const CopySourceTask = { 'typings/**', 'webpackShims/**', 'config/kibana.yml', - 'config/apm.js', 'tsconfig*.json', '.i18nrc.json', 'kibana.d.ts', diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.js b/src/dev/build/tasks/os_packages/create_os_package_tasks.js index 3a328971538db..0416dac0aad8c 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.js +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.js @@ -18,7 +18,7 @@ */ import { runFpm } from './run_fpm'; -import { runDockerGenerator } from './docker_generator'; +import { runDockerGenerator, runDockerGeneratorForUBI } from './docker_generator'; export const CreateDebPackageTask = { description: 'Creating deb package', @@ -45,6 +45,10 @@ export const CreateDockerPackageTask = { description: 'Creating docker package', async run(config, log, build) { + // Builds Docker targets for default and oss await runDockerGenerator(config, log, build); + + // Builds Docker target default with ubi7 base image + await runDockerGeneratorForUBI(config, log, build); }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js index 65b0ffdfa5e81..bbcb6dfeeb109 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js +++ b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.js @@ -22,9 +22,11 @@ import { compress, copyAll, mkdirp, write } from '../../../lib'; import { dockerfileTemplate } from './templates'; export async function bundleDockerFiles(config, log, build, scope) { - log.info(`Generating kibana${scope.imageFlavor} docker build context bundle`); + log.info( + `Generating kibana${scope.imageFlavor}${scope.ubiImageFlavor} docker build context bundle` + ); - const dockerFilesDirName = `kibana${scope.imageFlavor}-${scope.versionTag}-docker-build-context`; + const dockerFilesDirName = `kibana${scope.imageFlavor}${scope.ubiImageFlavor}-${scope.versionTag}-docker-build-context`; const dockerFilesBuildDir = resolve(scope.dockerBuildDir, dockerFilesDirName); const dockerFilesOutputDir = config.resolveFromTarget(`${dockerFilesDirName}.tar.gz`); diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.js b/src/dev/build/tasks/os_packages/docker_generator/run.js index fcd95357790e5..7424b6f01b82b 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.js +++ b/src/dev/build/tasks/os_packages/docker_generator/run.js @@ -29,20 +29,27 @@ const linkAsync = promisify(link); const unlinkAsync = promisify(unlink); const chmodAsync = promisify(chmod); -export async function runDockerGenerator(config, log, build) { +export async function runDockerGenerator(config, log, build, ubi = false) { + // UBI var config + const baseOSImage = ubi ? 'registry.access.redhat.com/ubi7/ubi-minimal:7.7' : 'centos:7'; + const ubiVersionTag = 'ubi7'; + const ubiImageFlavor = ubi ? `-${ubiVersionTag}` : ''; + + // General docker var config const license = build.isOss() ? 'ASL 2.0' : 'Elastic License'; const imageFlavor = build.isOss() ? '-oss' : ''; const imageTag = 'docker.elastic.co/kibana/kibana'; const versionTag = config.getBuildVersion(); const artifactTarball = `kibana${imageFlavor}-${versionTag}-linux-x86_64.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); + // That would produce oss, default and default-ubi7 const dockerBuildDir = config.resolveFromRepo( 'build', 'kibana-docker', - build.isOss() ? 'oss' : 'default' + build.isOss() ? `oss` : `default${ubiImageFlavor}` ); const dockerOutputDir = config.resolveFromTarget( - `kibana${imageFlavor}-${versionTag}-docker.tar.gz` + `kibana${imageFlavor}${ubiImageFlavor}-${versionTag}-docker.tar.gz` ); const scope = { artifactTarball, @@ -53,6 +60,8 @@ export async function runDockerGenerator(config, log, build) { imageTag, dockerBuildDir, dockerOutputDir, + baseOSImage, + ubiImageFlavor, }; // Verify if we have the needed kibana target in order @@ -103,3 +112,12 @@ export async function runDockerGenerator(config, log, build) { // Pack Dockerfiles and create a target for them await bundleDockerFiles(config, log, build, scope); } + +export async function runDockerGeneratorForUBI(config, log, build) { + // Only run ubi docker image build for default distribution + if (build.isOss()) { + return; + } + + await runDockerGenerator(config, log, build, true); +} diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js index 66f2fd65832e4..4e8dfe188b867 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.js @@ -19,21 +19,28 @@ import dedent from 'dedent'; -function generator({ imageTag, imageFlavor, versionTag, dockerOutputDir }) { +function generator({ + imageTag, + imageFlavor, + versionTag, + dockerOutputDir, + baseOSImage, + ubiImageFlavor, +}) { return dedent(` #!/usr/bin/env bash # # ** THIS IS AN AUTO-GENERATED FILE ** # set -euo pipefail - - docker pull centos:7 - - echo "Building: kibana${imageFlavor}-docker"; \\ - docker build -t ${imageTag}${imageFlavor}:${versionTag} -f Dockerfile . || exit 1; - docker save ${imageTag}${imageFlavor}:${versionTag} | gzip -c > ${dockerOutputDir} - + docker pull ${baseOSImage} + + echo "Building: kibana${imageFlavor}${ubiImageFlavor}-docker"; \\ + docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${versionTag} -f Dockerfile . || exit 1; + + docker save ${imageTag}${imageFlavor}${ubiImageFlavor}:${versionTag} | gzip -c > ${dockerOutputDir} + exit 0 `); } diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js index 5228eed84cd59..25ae71856c12d 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js @@ -19,7 +19,14 @@ import dedent from 'dedent'; -function generator({ artifactTarball, versionTag, license, usePublicArtifact }) { +function generator({ + artifactTarball, + versionTag, + license, + usePublicArtifact, + baseOSImage, + ubiImageFlavor, +}) { const copyArtifactTarballInsideDockerOptFolder = () => { if (usePublicArtifact) { return `RUN cd /opt && curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/${artifactTarball} && cd -`; @@ -28,6 +35,14 @@ function generator({ artifactTarball, versionTag, license, usePublicArtifact }) return `COPY ${artifactTarball} /opt`; }; + const packageManager = () => { + if (ubiImageFlavor) { + return 'microdnf'; + } + + return 'yum'; + }; + return dedent(` # # ** THIS IS AN AUTO-GENERATED FILE ** @@ -37,7 +52,9 @@ function generator({ artifactTarball, versionTag, license, usePublicArtifact }) # Build stage 0 # Extract Kibana and make various file manipulations. ################################################################################ - FROM centos:7 AS prep_files + FROM ${baseOSImage} AS prep_files + # Add tar and gzip + RUN ${packageManager()} update -y && ${packageManager()} install -y tar gzip && ${packageManager()} clean all ${copyArtifactTarballInsideDockerOptFolder()} RUN mkdir /usr/share/kibana WORKDIR /usr/share/kibana @@ -53,11 +70,11 @@ function generator({ artifactTarball, versionTag, license, usePublicArtifact }) # Build stage 1 # Copy prepared files from the previous stage and complete the image. ################################################################################ - FROM centos:7 + FROM ${baseOSImage} EXPOSE 5601 # Add Reporting dependencies. - RUN yum update -y && yum install -y fontconfig freetype && yum clean all + RUN ${packageManager()} update -y && ${packageManager()} install -y fontconfig freetype shadow-utils && ${packageManager()} clean all # Add an init process, check the checksum to make sure it's a match RUN curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64 diff --git a/src/docs/docs_repo.js b/src/docs/docs_repo.js index 63fcd2a6de5ec..51ae0d495b9ff 100644 --- a/src/docs/docs_repo.js +++ b/src/docs/docs_repo.js @@ -27,7 +27,7 @@ export function buildDocsScript(cmd) { export function buildDocsArgs(cmd) { const docsIndexFile = resolve(kibanaDir, 'docs', 'index.asciidoc'); - let args = ['--doc', docsIndexFile, '--chunk=1']; + let args = ['--doc', docsIndexFile, '--direct_html', '--chunk=1']; if (cmd.open) { args = [...args, '--open']; } diff --git a/src/fixtures/stubbed_logstash_index_pattern.js b/src/fixtures/stubbed_logstash_index_pattern.js index 22fbf0ab5a5f8..e20d1b5cd7717 100644 --- a/src/fixtures/stubbed_logstash_index_pattern.js +++ b/src/fixtures/stubbed_logstash_index_pattern.js @@ -21,7 +21,7 @@ import StubIndexPattern from 'test_utils/stub_index_pattern'; import stubbedLogstashFields from 'fixtures/logstash_fields'; import { getKbnFieldType } from '../plugins/data/common'; -import { mockUiSettings } from '../legacy/ui/public/new_platform/new_platform.karma_mock'; +import { npSetup } from '../legacy/ui/public/new_platform/new_platform.karma_mock'; export default function stubbedLogstashIndexPatternService() { const mockLogstashFields = stubbedLogstashFields(); @@ -41,13 +41,8 @@ export default function stubbedLogstashIndexPatternService() { }; }); - const indexPattern = new StubIndexPattern( - 'logstash-*', - cfg => cfg, - 'time', - fields, - mockUiSettings - ); + const indexPattern = new StubIndexPattern('logstash-*', cfg => cfg, 'time', fields, npSetup.core); + indexPattern.id = 'logstash-*'; indexPattern.isTimeNanosBased = () => false; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js index 801580f4e158c..b3999c76493c0 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js @@ -61,7 +61,6 @@ export function ScriptHighlightRules() { }, { token: 'script.keyword.operator', - regex: '\\?\\.|\\*\\.|=~|==~|!|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|->|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=|\\b(?:in|instanceof|new|typeof|void)', }, diff --git a/src/legacy/core_plugins/input_control_vis/index.ts b/src/legacy/core_plugins/input_control_vis/index.ts new file mode 100644 index 0000000000000..8f6178e26126b --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/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 inputControlVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'input_control_vis', + require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', '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(); + }, + } as Legacy.PluginSpecOptions); + +// eslint-disable-next-line import/no-default-export +export default inputControlVisPluginInitializer; diff --git a/src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap similarity index 73% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap index 809214f756713..632fe63e9e148 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap @@ -16,9 +16,33 @@ exports[`renders ControlsTab 1`] = ` "size": 5, "type": "terms", }, + "parent": "parent", "type": "list", } } + deps={ + Object { + "core": Object { + "getStartServices": [MockFunction], + "injectedMetadata": Object { + "getInjectedVar": [MockFunction], + }, + }, + "data": Object { + "query": Object { + "filterManager": Object { + "fieldName": "myField", + "getAppFilters": [MockFunction], + "getGlobalFilters": [MockFunction], + "getIndexPattern": [Function], + }, + "timefilter": Object { + "timefilter": Object {}, + }, + }, + }, + } + } getIndexPattern={[Function]} handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} @@ -49,9 +73,33 @@ exports[`renders ControlsTab 1`] = ` "options": Object { "step": 1, }, + "parent": "parent", "type": "range", } } + deps={ + Object { + "core": Object { + "getStartServices": [MockFunction], + "injectedMetadata": Object { + "getInjectedVar": [MockFunction], + }, + }, + "data": Object { + "query": Object { + "filterManager": Object { + "fieldName": "myField", + "getAppFilters": [MockFunction], + "getGlobalFilters": [MockFunction], + "getIndexPattern": [Function], + }, + "timefilter": Object { + "timefilter": Object {}, + }, + }, + }, + } + } getIndexPattern={[Function]} handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.tsx.snap index ff3d1ffc146e3..9bc8b1b9ac5cd 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.tsx.snap @@ -3,6 +3,7 @@ exports[`renders dynamic options should display disabled dynamic options with tooltip for non-string fields 1`] = ` { + return fields.find(({ name: n }) => n === name); +}; + +export const getDepsMock = (): InputControlVisDependencies => + ({ + core: { + getStartServices: jest.fn().mockReturnValue([ + null, + { + data: { + ui: { + IndexPatternSelect: () => (

) as any, + }, + indexPatterns: { + get: () => ({ + fields, + }), + }, + }, + }, + ]), + injectedMetadata: { + getInjectedVar: jest.fn().mockImplementation(key => { + switch (key) { + case 'autocompleteTimeout': + return 1000; + case 'autocompleteTerminateAfter': + return 100000; + default: + return ''; + } + }), + }, + }, + data: { + query: { + filterManager: { + fieldName: 'myField', + getIndexPattern: () => ({ + fields, + }), + getAppFilters: jest.fn().mockImplementation(() => []), + getGlobalFilters: jest.fn().mockImplementation(() => []), + }, + timefilter: { + timefilter: {}, + }, + }, + }, + } as any); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts similarity index 82% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts index c693bf100e265..638dd7170cb8d 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts @@ -17,7 +17,12 @@ * under the License. */ -export const getIndexPatternMock = () => { +import { IIndexPattern } from '../../../../../../../plugins/data/public'; + +/** + * Returns forced **Partial** IndexPattern for use in tests + */ +export const getIndexPatternMock = (): Promise => { return Promise.resolve({ id: 'mockIndexPattern', title: 'mockIndexPattern', @@ -26,5 +31,5 @@ export const getIndexPatternMock = () => { { name: 'textField', type: 'string', aggregatable: false }, { name: 'numberField', type: 'number', aggregatable: true }, ], - }); + } as IIndexPattern); }; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts new file mode 100644 index 0000000000000..9da47bedcc784 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.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 { SearchSource } from '../../../legacy_imports'; + +export const getSearchSourceMock = (esSearchResponse?: any): SearchSource => + jest.fn().mockImplementation(() => ({ + setParent: jest.fn(), + setField: jest.fn(), + fetch: jest.fn().mockResolvedValue( + esSearchResponse + ? esSearchResponse + : { + aggregations: { + termsAgg: { + buckets: [ + { + key: 'Zurich Airport', + doc_count: 691, + }, + { + key: 'Xi an Xianyang International Airport', + doc_count: 526, + }, + ], + }, + }, + } + ), + })); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts new file mode 100644 index 0000000000000..881412a7c56fd --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts @@ -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 { ShallowWrapper, ReactWrapper } from 'enzyme'; + +export const updateComponent = async ( + component: + | ShallowWrapper, React.Component<{}, {}, any>> + | ReactWrapper, React.Component<{}, {}, any>> +) => { + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); +}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx similarity index 72% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx index cc31b8d238dbe..dbac5d9d94371 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx @@ -17,13 +17,10 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { RangeControlEditor } from './range_control_editor'; -import { ListControlEditor } from './list_control_editor'; -import { getTitle } from '../../editor_utils'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import React, { PureComponent, ChangeEvent } from 'react'; +import { InjectedIntlProps } from 'react-intl'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { EuiAccordion, EuiButtonIcon, @@ -32,11 +29,45 @@ import { EuiFormRow, EuiPanel, EuiSpacer, + EuiSwitchEvent, } from '@elastic/eui'; -class ControlEditorUi extends Component { - changeLabel = evt => { - this.props.handleLabelChange(this.props.controlIndex, evt); +import { RangeControlEditor } from './range_control_editor'; +import { ListControlEditor } from './list_control_editor'; +import { getTitle, ControlParams, CONTROL_TYPES, ControlParamsOptions } from '../../editor_utils'; +import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { InputControlVisDependencies } from '../../plugin'; + +interface ControlEditorUiProps { + controlIndex: number; + controlParams: ControlParams; + handleLabelChange: (controlIndex: number, event: ChangeEvent) => 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: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: EuiSwitchEvent + ) => void; + handleNumberOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => void; + parentCandidates: Array<{ + value: string; + text: string; + }>; + handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + deps: InputControlVisDependencies; +} + +class ControlEditorUi extends PureComponent { + changeLabel = (event: ChangeEvent) => { + this.props.handleLabelChange(this.props.controlIndex, event); }; removeControl = () => { @@ -51,18 +82,18 @@ class ControlEditorUi extends Component { this.props.moveControl(this.props.controlIndex, 1); }; - changeIndexPattern = evt => { - this.props.handleIndexPatternChange(this.props.controlIndex, evt); + changeIndexPattern = (indexPatternId: string) => { + this.props.handleIndexPatternChange(this.props.controlIndex, indexPatternId); }; - changeFieldName = evt => { - this.props.handleFieldNameChange(this.props.controlIndex, evt); + changeFieldName = (fieldName: string) => { + this.props.handleFieldNameChange(this.props.controlIndex, fieldName); }; renderEditor() { let controlEditor = null; switch (this.props.controlParams.type) { - case 'list': + case CONTROL_TYPES.LIST: controlEditor = ( ); break; - case 'range': + case CONTROL_TYPES.RANGE: controlEditor = ( ); break; @@ -167,24 +200,4 @@ class ControlEditorUi extends Component { } } -ControlEditorUi.propTypes = { - controlIndex: PropTypes.number.isRequired, - controlParams: PropTypes.object.isRequired, - handleLabelChange: PropTypes.func.isRequired, - moveControl: PropTypes.func.isRequired, - handleRemoveControl: PropTypes.func.isRequired, - handleIndexPatternChange: PropTypes.func.isRequired, - handleFieldNameChange: PropTypes.func.isRequired, - getIndexPattern: PropTypes.func.isRequired, - handleCheckboxOptionChange: PropTypes.func.isRequired, - handleNumberOptionChange: PropTypes.func.isRequired, - parentCandidates: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - }) - ).isRequired, - handleParentChange: PropTypes.func.isRequired, -}; - export const ControlEditor = injectI18n(ControlEditorUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx similarity index 87% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index 28f435c27ea8f..4e7c9bafbf510 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -17,45 +17,36 @@ * under the License. */ -jest.mock('../../../../../core_plugins/data/public/legacy', () => ({ - indexPatterns: { - indexPatterns: { - get: jest.fn(), - }, - }, -})); - -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - ui: { - IndexPatternSelect: () => { - return
; - }, - }, - }, - }, - }, -})); - import React from 'react'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; +import { getDepsMock } from './__tests__/get_deps_mock'; import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; -import { ControlsTab } from './controls_tab'; +import { ControlsTab, ControlsTabUiProps } from './controls_tab'; const indexPatternsMock = { get: getIndexPatternMock, }; -let props; +let props: ControlsTabUiProps; beforeEach(() => { props = { + deps: getDepsMock(), vis: { API: { indexPatterns: indexPatternsMock, }, + type: { + name: 'test', + title: 'test', + visualization: null, + requestHandler: 'test', + responseHandler: 'test', + stage: 'beta', + requiresSearch: false, + hidden: false, + }, }, stateParams: { controls: [ @@ -71,6 +62,7 @@ beforeEach(() => { size: 5, order: 'desc', }, + parent: 'parent', }, { id: '2', @@ -81,10 +73,12 @@ beforeEach(() => { options: { step: 1, }, + parent: 'parent', }, ], }, setValue: jest.fn(), + intl: null as any, }; }); @@ -105,7 +99,7 @@ describe('behavior', () => { 'controls', expect.arrayContaining(props.stateParams.controls) ); - expect(props.setValue.mock.calls[0][1].length).toEqual(3); + expect((props.setValue as jest.Mock).mock.calls[0][1].length).toEqual(3); }); test('remove control button', () => { @@ -120,6 +114,7 @@ describe('behavior', () => { fieldName: 'numberField', label: '', type: 'range', + parent: 'parent', options: { step: 1, }, @@ -142,6 +137,7 @@ describe('behavior', () => { fieldName: 'numberField', label: '', type: 'range', + parent: 'parent', options: { step: 1, }, @@ -152,6 +148,7 @@ describe('behavior', () => { fieldName: 'keywordField', label: 'custom label', type: 'list', + parent: 'parent', options: { type: 'terms', multiselect: true, @@ -177,6 +174,7 @@ describe('behavior', () => { fieldName: 'numberField', label: '', type: 'range', + parent: 'parent', options: { step: 1, }, @@ -187,6 +185,7 @@ describe('behavior', () => { fieldName: 'keywordField', label: 'custom label', type: 'list', + parent: 'parent', options: { type: 'terms', multiselect: true, diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx similarity index 68% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx index 97036d7b0f5df..56381ef7d1570 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -17,14 +17,10 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { ControlEditor } from './control_editor'; -import { addControl, moveControl, newControl, removeControl, setControl } from '../../editor_utils'; -import { getLineageMap, getParentCandidates } from '../../lineage'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { npStart } from 'ui/new_platform'; +import React, { PureComponent, ChangeEvent } from 'react'; +import { InjectedIntlProps } from 'react-intl'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiFlexGroup, @@ -32,55 +28,97 @@ import { EuiFormRow, EuiPanel, EuiSelect, + EuiSwitchEvent, } from '@elastic/eui'; -class ControlsTabUi extends Component { +import { ControlEditor } from './control_editor'; +import { + addControl, + moveControl, + newControl, + removeControl, + setControl, + ControlParams, + CONTROL_TYPES, + ControlParamsOptions, +} from '../../editor_utils'; +import { getLineageMap, getParentCandidates } from '../../lineage'; +import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { VisOptionsProps } from '../../legacy_imports'; +import { InputControlVisDependencies } from '../../plugin'; + +interface ControlsTabUiState { + type: CONTROL_TYPES; +} + +interface ControlsTabUiParams { + controls: ControlParams[]; +} +type ControlsTabUiInjectedProps = InjectedIntlProps & + Pick, 'vis' | 'stateParams' | 'setValue'> & { + deps: InputControlVisDependencies; + }; + +export type ControlsTabUiProps = ControlsTabUiInjectedProps; + +class ControlsTabUi extends PureComponent { state = { - type: 'list', + type: CONTROL_TYPES.LIST, }; - getIndexPattern = async indexPatternId => { - return await npStart.plugins.data.indexPatterns.get(indexPatternId); + getIndexPattern = async (indexPatternId: string): Promise => { + const [, startDeps] = await this.props.deps.core.getStartServices(); + return await startDeps.data.indexPatterns.get(indexPatternId); }; - onChange = value => this.props.setValue('controls', value); + onChange = (value: ControlParams[]) => this.props.setValue('controls', value); - handleLabelChange = (controlIndex, evt) => { + handleLabelChange = (controlIndex: number, event: ChangeEvent) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.label = evt.target.value; + updatedControl.label = event.target.value; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleIndexPatternChange = (controlIndex, indexPatternId) => { + handleIndexPatternChange = (controlIndex: number, indexPatternId: string) => { const updatedControl = this.props.stateParams.controls[controlIndex]; updatedControl.indexPattern = indexPatternId; updatedControl.fieldName = ''; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleFieldNameChange = (controlIndex, fieldName) => { + handleFieldNameChange = (controlIndex: number, fieldName: string) => { const updatedControl = this.props.stateParams.controls[controlIndex]; updatedControl.fieldName = fieldName; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleCheckboxOptionChange = (controlIndex, optionName, evt) => { + handleCheckboxOptionChange = ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: EuiSwitchEvent + ) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.options[optionName] = evt.target.checked; + // @ts-ignore + updatedControl.options[optionName] = event.target.checked; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleNumberOptionChange = (controlIndex, optionName, evt) => { + handleNumberOptionChange = ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.options[optionName] = parseFloat(evt.target.value); + // @ts-ignore + updatedControl.options[optionName] = parseFloat(event.target.value); this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleRemoveControl = controlIndex => { + handleRemoveControl = (controlIndex: number) => { this.onChange(removeControl(this.props.stateParams.controls, controlIndex)); }; - moveControl = (controlIndex, direction) => { + moveControl = (controlIndex: number, direction: number) => { this.onChange(moveControl(this.props.stateParams.controls, controlIndex, direction)); }; @@ -88,9 +126,9 @@ class ControlsTabUi extends Component { this.onChange(addControl(this.props.stateParams.controls, newControl(this.state.type))); }; - handleParentChange = (controlIndex, evt) => { + handleParentChange = (controlIndex: number, event: ChangeEvent) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.parent = evt.target.value; + updatedControl.parent = event.target.value; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; @@ -117,6 +155,7 @@ class ControlsTabUi extends Component { handleNumberOptionChange={this.handleNumberOptionChange} parentCandidates={parentCandidates} handleParentChange={this.handleParentChange} + deps={this.props.deps} /> ); }); @@ -137,14 +176,14 @@ class ControlsTabUi extends Component { data-test-subj="selectControlType" options={[ { - value: 'range', + value: CONTROL_TYPES.RANGE, text: intl.formatMessage({ id: 'inputControl.editor.controlsTab.select.rangeDropDownOptionLabel', defaultMessage: 'Range slider', }), }, { - value: 'list', + value: CONTROL_TYPES.LIST, text: intl.formatMessage({ id: 'inputControl.editor.controlsTab.select.listDropDownOptionLabel', defaultMessage: 'Options list', @@ -152,7 +191,7 @@ class ControlsTabUi extends Component { }, ]} value={this.state.type} - onChange={evt => this.setState({ type: evt.target.value })} + onChange={event => this.setState({ type: event.target.value as CONTROL_TYPES })} aria-label={intl.formatMessage({ id: 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', defaultMessage: 'Select control type', @@ -186,9 +225,8 @@ class ControlsTabUi extends Component { } } -ControlsTabUi.propTypes = { - vis: PropTypes.object.isRequired, - setValue: PropTypes.func.isRequired, -}; - export const ControlsTab = injectI18n(ControlsTabUi); + +export const getControlsTab = (deps: InputControlVisDependencies) => ( + props: Omit +) => ; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.tsx similarity index 70% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.tsx index 456ff17a316a1..bde2f09ab0a47 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.tsx @@ -18,43 +18,59 @@ */ import _ from 'lodash'; -import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { InjectedIntlProps } from 'react-intl'; + import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; + +import { IIndexPattern, IFieldType } from '../../../../../../plugins/data/public'; + +interface FieldSelectUiState { + isLoading: boolean; + fields: Array>; + indexPatternId: string; +} + +export type FieldSelectUiProps = InjectedIntlProps & { + getIndexPattern: (indexPatternId: string) => Promise; + indexPatternId: string; + onChange: (value: any) => void; + fieldName?: string; + filterField?: (field: IFieldType) => boolean; + controlIndex: number; +}; -import { EuiFormRow, EuiComboBox } from '@elastic/eui'; +class FieldSelectUi extends Component { + private hasUnmounted: boolean; -class FieldSelectUi extends Component { - constructor(props) { + constructor(props: FieldSelectUiProps) { super(props); - this._hasUnmounted = false; + this.hasUnmounted = false; this.state = { isLoading: false, fields: [], indexPatternId: props.indexPatternId, }; - this.filterField = _.get(props, 'filterField', () => { - return true; - }); } componentWillUnmount() { - this._hasUnmounted = true; + this.hasUnmounted = true; } componentDidMount() { this.loadFields(this.state.indexPatternId); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: FieldSelectUiProps) { if (this.props.indexPatternId !== nextProps.indexPatternId) { - this.loadFields(nextProps.indexPatternId); + this.loadFields(nextProps.indexPatternId ?? ''); } } - loadFields = indexPatternId => { + loadFields = (indexPatternId: string) => { this.setState( { isLoading: true, @@ -65,12 +81,12 @@ class FieldSelectUi extends Component { ); }; - debouncedLoad = _.debounce(async indexPatternId => { + debouncedLoad = _.debounce(async (indexPatternId: string) => { if (!indexPatternId || indexPatternId.length === 0) { return; } - let indexPattern; + let indexPattern: IIndexPattern; try { indexPattern = await this.props.getIndexPattern(indexPatternId); } catch (err) { @@ -78,7 +94,7 @@ class FieldSelectUi extends Component { return; } - if (this._hasUnmounted) { + if (this.hasUnmounted) { return; } @@ -88,17 +104,15 @@ class FieldSelectUi extends Component { return; } - const fieldsByTypeMap = new Map(); - const fields = []; - indexPattern.fields.filter(this.filterField).forEach(field => { - if (fieldsByTypeMap.has(field.type)) { - const fieldsList = fieldsByTypeMap.get(field.type); + const fieldsByTypeMap = new Map(); + const fields: Array> = []; + indexPattern.fields + .filter(this.props.filterField ?? (() => true)) + .forEach((field: IFieldType) => { + const fieldsList = fieldsByTypeMap.get(field.type) ?? []; fieldsList.push(field.name); fieldsByTypeMap.set(field.type, fieldsList); - } else { - fieldsByTypeMap.set(field.type, [field.name]); - } - }); + }); fieldsByTypeMap.forEach((fieldsList, fieldType) => { fields.push({ @@ -117,11 +131,11 @@ class FieldSelectUi extends Component { this.setState({ isLoading: false, - fields: fields, + fields, }); }, 300); - onChange = selectedOptions => { + onChange = (selectedOptions: Array>) => { this.props.onChange(_.get(selectedOptions, '0.value')); }; @@ -165,13 +179,4 @@ class FieldSelectUi extends Component { } } -FieldSelectUi.propTypes = { - getIndexPattern: PropTypes.func.isRequired, - indexPatternId: PropTypes.string, - onChange: PropTypes.func.isRequired, - fieldName: PropTypes.string, - filterField: PropTypes.func, - controlIndex: PropTypes.number.isRequired, -}; - export const FieldSelect = injectI18n(FieldSelectUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.tsx similarity index 73% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.tsx index 7d7fbc0539de0..66fdbca64f053 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.tsx @@ -17,15 +17,20 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { ComponentType } from 'react'; import { injectI18n } from '@kbn/i18n/react'; import { EuiFormRow } from '@elastic/eui'; +import { InjectedIntlProps } from 'react-intl'; +import { IndexPatternSelect } from 'src/plugins/data/public'; -import { npStart } from 'ui/new_platform'; -const { IndexPatternSelect } = npStart.plugins.data.ui; +export type IndexPatternSelectFormRowUiProps = InjectedIntlProps & { + onChange: (opt: any) => void; + indexPatternId: string; + controlIndex: number; + IndexPatternSelect: ComponentType; +}; -function IndexPatternSelectFormRowUi(props) { +function IndexPatternSelectFormRowUi(props: IndexPatternSelectFormRowUiProps) { const { controlIndex, indexPatternId, intl, onChange } = props; const selectId = `indexPatternSelect-${controlIndex}`; @@ -37,7 +42,7 @@ function IndexPatternSelectFormRowUi(props) { defaultMessage: 'Index Pattern', })} > - ); } -IndexPatternSelectFormRowUi.propTypes = { - onChange: PropTypes.func.isRequired, - indexPatternId: PropTypes.string, - controlIndex: PropTypes.number.isRequired, -}; - export const IndexPatternSelectFormRow = injectI18n(IndexPatternSelectFormRowUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx similarity index 80% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx index 24b14943c8bb3..de0187f87212f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx @@ -17,31 +17,21 @@ * under the License. */ -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - ui: { - IndexPatternSelect: () => { - return
; - }, - }, - }, - }, - }, -})); - import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { getDepsMock } from './__tests__/get_deps_mock'; +import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; import { ListControlEditor } from './list_control_editor'; +import { ControlParams } from '../../editor_utils'; +import { updateComponent } from './__tests__/update_component'; -const controlParams = { +const controlParamsBase: ControlParams = { id: '1', indexPattern: 'indexPattern1', fieldName: 'keywordField', @@ -53,11 +43,13 @@ const controlParams = { dynamicOptions: false, size: 10, }, + parent: '', }; -let handleFieldNameChange; -let handleIndexPatternChange; -let handleCheckboxOptionChange; -let handleNumberOptionChange; +const deps = getDepsMock(); +let handleFieldNameChange: sinon.SinonSpy; +let handleIndexPatternChange: sinon.SinonSpy; +let handleCheckboxOptionChange: sinon.SinonSpy; +let handleNumberOptionChange: sinon.SinonSpy; beforeEach(() => { handleFieldNameChange = sinon.spy(); @@ -68,8 +60,9 @@ beforeEach(() => { describe('renders', () => { test('should not display any options until field is selected', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: '', type: 'list', @@ -79,9 +72,11 @@ describe('renders', () => { dynamicOptions: true, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); @@ -109,9 +101,10 @@ describe('renders', () => { ]; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); describe('dynamic options', () => { test('should display dynamic options for string fields', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: 'keywordField', type: 'list', @@ -142,9 +133,11 @@ describe('renders', () => { dynamicOptions: true, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); test('should display size field when dynamic options is disabled', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: 'keywordField', type: 'list', @@ -177,9 +168,11 @@ describe('renders', () => { dynamicOptions: false, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); test('should display disabled dynamic options with tooltip for non-string fields', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: 'numberField', type: 'list', @@ -212,9 +203,11 @@ describe('renders', () => { dynamicOptions: true, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); @@ -240,9 +230,10 @@ describe('renders', () => { test('handleCheckboxOptionChange - multiselect', async () => { const component = mountWithIntl( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); const checkbox = findTestSubject(component, 'listControlMultiselectInput'); checkbox.simulate('click'); @@ -268,10 +256,10 @@ test('handleCheckboxOptionChange - multiselect', async () => { handleCheckboxOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - // Synthetic `evt.target.checked` does not get altered by EuiSwitch, + sinon.match(event => { + // Synthetic `event.target.checked` does not get altered by EuiSwitch, // but its aria attribute is correctly updated - if (evt.target.getAttribute('aria-checked') === 'true') { + if (event.target.getAttribute('aria-checked') === 'true') { return true; } return false; @@ -282,9 +270,10 @@ test('handleCheckboxOptionChange - multiselect', async () => { test('handleNumberOptionChange - size', async () => { const component = mountWithIntl( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); const input = findTestSubject(component, 'listControlSizeInput'); input.simulate('change', { target: { value: 7 } }); @@ -310,8 +296,8 @@ test('handleNumberOptionChange - size', async () => { handleNumberOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - if (evt.target.value === 7) { + sinon.match(event => { + if (event.target.value === 7) { return true; } return false; @@ -322,9 +308,10 @@ test('handleNumberOptionChange - size', async () => { test('field name change', async () => { const component = shallowWithIntl( { /> ); - const update = async () => { - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - }; - // ensure that after async loading is complete the DynamicOptionsSwitch is not disabled expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(0); - await update(); + await updateComponent(component); expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(1); component.setProps({ controlParams: { - ...controlParams, + ...controlParamsBase, fieldName: 'numberField', }, }); @@ -361,20 +341,20 @@ test('field name change', async () => { expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=true]') ).toHaveLength(0); - await update(); + await updateComponent(component); expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=true]') ).toHaveLength(1); component.setProps({ - controlParams, + controlParams: controlParamsBase, }); // ensure that after async loading is complete the DynamicOptionsSwitch is not disabled again, because we switched to original "string" field expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(0); - await update(); + await updateComponent(component); expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(1); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx similarity index 70% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx index 2ee225475b0fe..ff74d30a6e1a8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx @@ -17,35 +17,90 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; +import React, { PureComponent, ChangeEvent, ComponentType } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFormRow, + EuiFieldNumber, + EuiSwitch, + EuiSelect, + EuiSelectProps, + EuiSwitchEvent, +} from '@elastic/eui'; + import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; import { FieldSelect } from './field_select'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { ControlParams, ControlParamsOptions } from '../../editor_utils'; +import { + IIndexPattern, + IFieldType, + IndexPatternSelect, +} from '../../../../../../plugins/data/public'; +import { InputControlVisDependencies } from '../../plugin'; -import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect } from '@elastic/eui'; +interface ListControlEditorState { + isLoadingFieldType: boolean; + isStringField: boolean; + prevFieldName: string; + IndexPatternSelect: ComponentType | null; +} -function filterField(field) { - return field.aggregatable && ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type); +interface ListControlEditorProps { + getIndexPattern: (indexPatternId: string) => Promise; + controlIndex: number; + controlParams: ControlParams; + handleFieldNameChange: (fieldName: string) => void; + handleIndexPatternChange: (indexPatternId: string) => void; + handleCheckboxOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: EuiSwitchEvent + ) => void; + handleNumberOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => void; + handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + parentCandidates: EuiSelectProps['options']; + deps: InputControlVisDependencies; } -export class ListControlEditor extends Component { - state = { +function filterField(field: IFieldType) { + return ( + Boolean(field.aggregatable) && + ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type) + ); +} + +export class ListControlEditor extends PureComponent< + ListControlEditorProps, + ListControlEditorState +> { + private isMounted: boolean = false; + + state: ListControlEditorState = { isLoadingFieldType: true, isStringField: false, prevFieldName: this.props.controlParams.fieldName, + IndexPatternSelect: null, }; componentDidMount() { - this._isMounted = true; + this.isMounted = true; this.loadIsStringField(); + this.getIndexPatternSelect(); } componentWillUnmount() { - this._isMounted = false; + this.isMounted = false; } - static getDerivedStateFromProps = (nextProps, prevState) => { + static getDerivedStateFromProps = ( + nextProps: ListControlEditorProps, + prevState: ListControlEditorState + ) => { const isNewFieldName = prevState.prevFieldName !== nextProps.controlParams.fieldName; if (!prevState.isLoadingFieldType && isNewFieldName) { return { @@ -63,13 +118,20 @@ export class ListControlEditor extends Component { } }; + async getIndexPatternSelect() { + const [, { data }] = await this.props.deps.core.getStartServices(); + this.setState({ + IndexPatternSelect: data.ui.IndexPatternSelect, + }); + } + loadIsStringField = async () => { if (!this.props.controlParams.indexPattern || !this.props.controlParams.fieldName) { this.setState({ isLoadingFieldType: false }); return; } - let indexPattern; + let indexPattern: IIndexPattern; try { indexPattern = await this.props.getIndexPattern(this.props.controlParams.indexPattern); } catch (err) { @@ -77,13 +139,13 @@ export class ListControlEditor extends Component { return; } - if (!this._isMounted) { + if (!this.isMounted) { return; } - const field = indexPattern.fields.find(field => { - return field.name === this.props.controlParams.fieldName; - }); + const field = (indexPattern.fields as IFieldType[]).find( + ({ name }) => name === this.props.controlParams.fieldName + ); if (!field) { return; } @@ -121,8 +183,8 @@ export class ListControlEditor extends Component { { - this.props.handleParentChange(this.props.controlIndex, evt); + onChange={event => { + this.props.handleParentChange(this.props.controlIndex, event); }} /> @@ -147,9 +209,9 @@ export class ListControlEditor extends Component { defaultMessage="Multiselect" /> } - checked={this.props.controlParams.options.multiselect} - onChange={evt => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', evt); + checked={this.props.controlParams.options.multiselect ?? true} + onChange={event => { + this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', event); }} data-test-subj="listControlMultiselectInput" /> @@ -180,9 +242,9 @@ export class ListControlEditor extends Component { defaultMessage="Dynamic Options" /> } - checked={this.props.controlParams.options.dynamicOptions} - onChange={evt => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', evt); + checked={this.props.controlParams.options.dynamicOptions ?? false} + onChange={event => { + this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', event); }} disabled={this.state.isStringField ? false : true} data-test-subj="listControlDynamicOptionsSwitch" @@ -212,8 +274,8 @@ export class ListControlEditor extends Component { { - this.props.handleNumberOptionChange(this.props.controlIndex, 'size', evt); + onChange={event => { + this.props.handleNumberOptionChange(this.props.controlIndex, 'size', event); }} data-test-subj="listControlSizeInput" /> @@ -225,12 +287,17 @@ export class ListControlEditor extends Component { }; render() { + if (this.state.IndexPatternSelect === null) { + return null; + } + return ( - + <> {this.renderOptions()} - + ); } } - -ListControlEditor.propTypes = { - getIndexPattern: PropTypes.func.isRequired, - controlIndex: PropTypes.number.isRequired, - controlParams: PropTypes.object.isRequired, - handleFieldNameChange: PropTypes.func.isRequired, - handleIndexPatternChange: PropTypes.func.isRequired, - handleCheckboxOptionChange: PropTypes.func.isRequired, - handleNumberOptionChange: PropTypes.func.isRequired, - parentCandidates: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - }) - ).isRequired, - handleParentChange: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx similarity index 93% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx index ef84d37ca8de5..36ec4d4446fd6 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx @@ -21,17 +21,19 @@ import React from 'react'; import { shallow } from 'enzyme'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { OptionsTab } from './options_tab'; +import { OptionsTab, OptionsTabProps } from './options_tab'; +import { Vis } from '../../legacy_imports'; describe('OptionsTab', () => { - let props; + let props: OptionsTabProps; beforeEach(() => { props = { - vis: {}, + vis: {} as Vis, stateParams: { updateFiltersOnChange: false, useTimeFilter: false, + pinFilters: false, }, setValue: jest.fn(), }; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx similarity index 74% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx index 236624b11118c..43f9e15302e51 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx @@ -17,24 +17,37 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { EuiForm, EuiFormRow, EuiSwitch } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiSwitchEvent } from '@elastic/eui'; + +import { VisOptionsProps } from '../../legacy_imports'; + +interface OptionsTabParams { + updateFiltersOnChange: boolean; + useTimeFilter: boolean; + pinFilters: boolean; +} +type OptionsTabInjectedProps = Pick< + VisOptionsProps, + 'vis' | 'setValue' | 'stateParams' +>; + +export type OptionsTabProps = OptionsTabInjectedProps; -export class OptionsTab extends Component { - handleUpdateFiltersChange = evt => { - this.props.setValue('updateFiltersOnChange', evt.target.checked); +export class OptionsTab extends PureComponent { + handleUpdateFiltersChange = (event: EuiSwitchEvent) => { + this.props.setValue('updateFiltersOnChange', event.target.checked); }; - handleUseTimeFilter = evt => { - this.props.setValue('useTimeFilter', evt.target.checked); + handleUseTimeFilter = (event: EuiSwitchEvent) => { + this.props.setValue('useTimeFilter', event.target.checked); }; - handlePinFilters = evt => { - this.props.setValue('pinFilters', evt.target.checked); + handlePinFilters = (event: EuiSwitchEvent) => { + this.props.setValue('pinFilters', event.target.checked); }; render() { @@ -85,8 +98,3 @@ export class OptionsTab extends Component { ); } } - -OptionsTab.propTypes = { - vis: PropTypes.object.isRequired, - setValue: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.js deleted file mode 100644 index 6e1754b28647f..0000000000000 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import PropTypes from 'prop-types'; -import React, { Fragment } from 'react'; -import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; -import { FieldSelect } from './field_select'; - -import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -function filterField(field) { - return field.type === 'number'; -} - -export function RangeControlEditor(props) { - const stepSizeId = `stepSize-${props.controlIndex}`; - const decimalPlacesId = `decimalPlaces-${props.controlIndex}`; - const handleDecimalPlacesChange = evt => { - props.handleNumberOptionChange(props.controlIndex, 'decimalPlaces', evt); - }; - const handleStepChange = evt => { - props.handleNumberOptionChange(props.controlIndex, 'step', evt); - }; - return ( - - - - - - - } - > - - - - - } - > - - - - ); -} - -RangeControlEditor.propTypes = { - getIndexPattern: PropTypes.func.isRequired, - controlIndex: PropTypes.number.isRequired, - controlParams: PropTypes.object.isRequired, - handleFieldNameChange: PropTypes.func.isRequired, - handleIndexPatternChange: PropTypes.func.isRequired, - handleNumberOptionChange: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx similarity index 71% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx index 145b18a42dc15..e7f9b6083890c 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx @@ -18,30 +18,20 @@ */ import React from 'react'; -import sinon from 'sinon'; import { shallow } from 'enzyme'; +import { SinonSpy, spy, assert, match } from 'sinon'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - ui: { - IndexPatternSelect: () => { - return
; - }, - }, - }, - }, - }, -})); - +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; import { RangeControlEditor } from './range_control_editor'; +import { ControlParams } from '../../editor_utils'; +import { getDepsMock } from './__tests__/get_deps_mock'; +import { updateComponent } from './__tests__/update_component'; -const controlParams = { +const controlParams: ControlParams = { id: '1', indexPattern: 'indexPattern1', fieldName: 'numberField', @@ -51,20 +41,23 @@ const controlParams = { decimalPlaces: 0, step: 1, }, + parent: '', }; -let handleFieldNameChange; -let handleIndexPatternChange; -let handleNumberOptionChange; +const deps = getDepsMock(); +let handleFieldNameChange: SinonSpy; +let handleIndexPatternChange: SinonSpy; +let handleNumberOptionChange: SinonSpy; beforeEach(() => { - handleFieldNameChange = sinon.spy(); - handleIndexPatternChange = sinon.spy(); - handleNumberOptionChange = sinon.spy(); + handleFieldNameChange = spy(); + handleIndexPatternChange = spy(); + handleNumberOptionChange = spy(); }); -test('renders RangeControlEditor', () => { +test('renders RangeControlEditor', async () => { const component = shallow( { handleNumberOptionChange={handleNumberOptionChange} /> ); + + await updateComponent(component); + expect(component).toMatchSnapshot(); // eslint-disable-line }); -test('handleNumberOptionChange - step', () => { +test('handleNumberOptionChange - step', async () => { const component = mountWithIntl( { handleNumberOptionChange={handleNumberOptionChange} /> ); + + await updateComponent(component); + findTestSubject(component, 'rangeControlSizeInput0').simulate('change', { target: { value: 0.5 }, }); - sinon.assert.notCalled(handleFieldNameChange); - sinon.assert.notCalled(handleIndexPatternChange); + assert.notCalled(handleFieldNameChange); + assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'step'; - sinon.assert.calledWith( + assert.calledWith( handleNumberOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - if (evt.target.value === 0.5) { + match(event => { + if (event.target.value === 0.5) { return true; } return false; @@ -107,9 +107,10 @@ test('handleNumberOptionChange - step', () => { ); }); -test('handleNumberOptionChange - decimalPlaces', () => { +test('handleNumberOptionChange - decimalPlaces', async () => { const component = mountWithIntl( { handleNumberOptionChange={handleNumberOptionChange} /> ); + + await updateComponent(component); + findTestSubject(component, 'rangeControlDecimalPlacesInput0').simulate('change', { target: { value: 2 }, }); - sinon.assert.notCalled(handleFieldNameChange); - sinon.assert.notCalled(handleIndexPatternChange); + assert.notCalled(handleFieldNameChange); + assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'decimalPlaces'; - sinon.assert.calledWith( + assert.calledWith( handleNumberOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - if (evt.target.value === 2) { + match(event => { + if (event.target.value === 2) { return true; } return false; 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 new file mode 100644 index 0000000000000..44477eafda6b1 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx @@ -0,0 +1,139 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { Component, Fragment, ChangeEvent, ComponentType } from 'react'; + +import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; +import { FieldSelect } from './field_select'; +import { ControlParams, ControlParamsOptions } from '../../editor_utils'; +import { + IIndexPattern, + IFieldType, + IndexPatternSelect, +} from '../../../../../../plugins/data/public'; +import { InputControlVisDependencies } from '../../plugin'; + +interface RangeControlEditorProps { + controlIndex: number; + controlParams: ControlParams; + getIndexPattern: (indexPatternId: string) => Promise; + handleFieldNameChange: (fieldName: string) => void; + handleIndexPatternChange: (indexPatternId: string) => void; + handleNumberOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => void; + deps: InputControlVisDependencies; +} + +interface RangeControlEditorState { + IndexPatternSelect: ComponentType | null; +} + +function filterField(field: IFieldType) { + return field.type === 'number'; +} + +export class RangeControlEditor extends Component< + RangeControlEditorProps, + RangeControlEditorState +> { + state: RangeControlEditorState = { + IndexPatternSelect: null, + }; + + componentDidMount() { + this.getIndexPatternSelect(); + } + + async getIndexPatternSelect() { + const [, { data }] = await this.props.deps.core.getStartServices(); + this.setState({ + IndexPatternSelect: data.ui.IndexPatternSelect, + }); + } + + render() { + const stepSizeId = `stepSize-${this.props.controlIndex}`; + const decimalPlacesId = `decimalPlaces-${this.props.controlIndex}`; + if (this.state.IndexPatternSelect === null) { + return null; + } + + return ( + + + + + + + } + > + { + this.props.handleNumberOptionChange(this.props.controlIndex, 'step', event); + }} + data-test-subj={`rangeControlSizeInput${this.props.controlIndex}`} + /> + + + + } + > + { + this.props.handleNumberOptionChange(this.props.controlIndex, 'decimalPlaces', event); + }} + data-test-subj={`rangeControlDecimalPlacesInput${this.props.controlIndex}`} + /> + + + ); + } +} diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.tsx.snap index 6437cb19aef97..ba183cc40b126 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.tsx.snap @@ -47,7 +47,6 @@ exports[`renders disabled control with tooltip 1`] = ` anchorClassName="eui-displayBlock" content="I am disabled for testing purposes" delay="regular" - placement="top" position="top" >
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap similarity index 99% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap index 841421474e7b1..5a76967c71fbb 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap @@ -18,7 +18,6 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = ` > diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.tsx similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.tsx diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.tsx similarity index 75% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.tsx index fdd4fcb6e26ae..29385582924e7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.tsx @@ -17,16 +17,24 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { ReactElement } from 'react'; import { EuiFormRow, EuiToolTip, EuiIcon } from '@elastic/eui'; -export function FormRow(props) { +export interface FormRowProps { + label: string; + warningMsg?: string; + id: string; + children: ReactElement; + controlIndex: number; + disableMsg?: string; +} + +export function FormRow(props: FormRowProps) { let control = props.children; if (props.disableMsg) { control = ( - + {control} ); @@ -49,12 +57,3 @@ export function FormRow(props) { ); } - -FormRow.propTypes = { - label: PropTypes.string.isRequired, - warningMsg: PropTypes.string, - id: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, - controlIndex: PropTypes.number.isRequired, - disableMsg: PropTypes.string, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx similarity index 87% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx index a835078ab4dc0..1712f024f5b7b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx @@ -21,11 +21,16 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { InputControlVis } from './input_control_vis'; +import { ListControl } from '../../control/list_control_factory'; +import { RangeControl } from '../../control/range_control_factory'; -const mockListControl = { +jest.mock('ui/new_platform'); + +const mockListControl: ListControl = { id: 'mock-list-control', isEnabled: () => { return true; @@ -38,11 +43,9 @@ const mockListControl = { label: 'list control', value: [], selectOptions: ['choice1', 'choice2'], - format: value => { - return value; - }, -}; -const mockRangeControl = { + format: (value: any) => value, +} as ListControl; +const mockRangeControl: RangeControl = { id: 'mock-range-control', isEnabled: () => { return true; @@ -56,16 +59,16 @@ const mockRangeControl = { value: { min: 0, max: 0 }, min: 0, max: 100, - format: value => { - return value; - }, -}; + format: (value: any) => value, +} as RangeControl; const updateFiltersOnChange = false; -let stageFilter; -let submitFilters; -let resetControls; -let clearControls; +const refreshControlMock = () => Promise.resolve(); + +let stageFilter: sinon.SinonSpy; +let submitFilters: sinon.SinonSpy; +let resetControls: sinon.SinonSpy; +let clearControls: sinon.SinonSpy; beforeEach(() => { stageFilter = sinon.spy(); @@ -89,7 +92,7 @@ test('Renders list control', () => { hasValues={() => { return false; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -110,7 +113,7 @@ test('Renders range control', () => { hasValues={() => { return false; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -131,7 +134,7 @@ test('Apply and Cancel change btns enabled when there are changes', () => { hasValues={() => { return false; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -152,7 +155,7 @@ test('Clear btns enabled when there are values', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -173,7 +176,7 @@ test('clearControls', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); findTestSubject(component, 'inputControlClearBtn').simulate('click'); @@ -198,7 +201,7 @@ test('submitFilters', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); findTestSubject(component, 'inputControlSubmitBtn').simulate('click'); @@ -223,7 +226,7 @@ test('resetControls', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); findTestSubject(component, 'inputControlCancelBtn').simulate('click'); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.tsx similarity index 60% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.tsx index 9e140155698f0..e2497287f35d0 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.tsx @@ -17,16 +17,37 @@ * under the License. */ -import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { RangeControl } from './range_control'; -import { ListControl } from './list_control'; import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; +import { CONTROL_TYPES } from '../../editor_utils'; +import { ListControl } from '../../control/list_control_factory'; +import { RangeControl } from '../../control/range_control_factory'; +import { ListControl as ListControlComponent } from '../vis/list_control'; +import { RangeControl as RangeControlComponent } from '../vis/range_control'; + +function isListControl(control: RangeControl | ListControl): control is ListControl { + return control.type === CONTROL_TYPES.LIST; +} + +function isRangeControl(control: RangeControl | ListControl): control is RangeControl { + return control.type === CONTROL_TYPES.RANGE; +} + +interface InputControlVisProps { + stageFilter: (controlIndex: number, newValue: any) => void; + submitFilters: () => void; + resetControls: () => void; + clearControls: () => void; + controls: Array; + updateFiltersOnChange?: boolean; + hasChanges: () => boolean; + hasValues: () => boolean; + refreshControl: (controlIndex: number, query: any) => Promise; +} -export class InputControlVis extends Component { - constructor(props) { +export class InputControlVis extends Component { + constructor(props: InputControlVisProps) { super(props); this.handleSubmit = this.handleSubmit.bind(this); @@ -49,39 +70,38 @@ export class InputControlVis extends Component { renderControls() { return this.props.controls.map((control, index) => { let controlComponent = null; - switch (control.type) { - case 'list': - controlComponent = ( - { - this.props.refreshControl(index, query); - }} - /> - ); - break; - case 'range': - controlComponent = ( - - ); - break; - default: - throw new Error(`Unhandled control type ${control.type}`); + + if (isListControl(control)) { + controlComponent = ( + { + this.props.refreshControl(index, query); + }} + /> + ); + } else if (isRangeControl(control)) { + controlComponent = ( + + ); + } else { + throw new Error(`Unhandled control type ${control!.type}`); } + return ( { +const formatOptionLabel = (value: any) => { return `${value} + formatting`; }; -let stageFilter; +let stageFilter: sinon.SinonSpy; beforeEach(() => { stageFilter = sinon.spy(); @@ -46,6 +46,7 @@ test('renders ListControl', () => { controlIndex={0} stageFilter={stageFilter} formatOptionLabel={formatOptionLabel} + intl={{} as any} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -56,11 +57,13 @@ test('disableMsg', () => { ); expect(component).toMatchSnapshot(); // eslint-disable-line diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.tsx similarity index 76% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.tsx index 7e92545e817e0..d62adfdce56b4 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.tsx @@ -17,46 +17,76 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import _ from 'lodash'; -import { FormRow } from './form_row'; import { injectI18n } from '@kbn/i18n/react'; +import { InjectedIntlProps } from 'react-intl'; import { EuiFieldText, EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormRow } from './form_row'; + +interface ListControlUiState { + isLoading: boolean; +} + +export type ListControlUiProps = InjectedIntlProps & { + id: string; + label: string; + selectedOptions: any[]; + options?: any[]; + formatOptionLabel: (option: any) => any; + disableMsg?: string; + multiselect?: boolean; + dynamicOptions?: boolean; + partialResults?: boolean; + controlIndex: number; + stageFilter: (controlIndex: number, value: any) => void; + fetchOptions?: (searchValue: string) => void; +}; + +class ListControlUi extends PureComponent { + static defaultProps = { + dynamicOptions: false, + multiselect: true, + selectedOptions: [], + options: [], + }; + + private isMounted: boolean = false; -class ListControlUi extends Component { state = { isLoading: false, }; componentDidMount = () => { - this._isMounted = true; + this.isMounted = true; }; componentWillUnmount = () => { - this._isMounted = false; + this.isMounted = false; }; - handleOnChange = selectedOptions => { + handleOnChange = (selectedOptions: any[]) => { const selectedValues = selectedOptions.map(({ value }) => { return value; }); this.props.stageFilter(this.props.controlIndex, selectedValues); }; - debouncedFetch = _.debounce(async searchValue => { - await this.props.fetchOptions(searchValue); + debouncedFetch = _.debounce(async (searchValue: string) => { + if (this.props.fetchOptions) { + await this.props.fetchOptions(searchValue); + } - if (this._isMounted) { + if (this.isMounted) { this.setState({ isLoading: false, }); } }, 300); - onSearchChange = searchValue => { + onSearchChange = (searchValue: string) => { this.setState( { isLoading: true, @@ -81,7 +111,7 @@ class ListControlUi extends Component { } const options = this.props.options - .map(option => { + ?.map(option => { return { label: this.props.formatOptionLabel(option).toString(), value: option, @@ -141,29 +171,4 @@ class ListControlUi extends Component { } } -ListControlUi.propTypes = { - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - selectedOptions: PropTypes.array.isRequired, - options: PropTypes.array, - formatOptionLabel: PropTypes.func.isRequired, - disableMsg: PropTypes.string, - multiselect: PropTypes.bool, - dynamicOptions: PropTypes.bool, - partialResults: PropTypes.bool, - controlIndex: PropTypes.number.isRequired, - stageFilter: PropTypes.func.isRequired, - fetchOptions: PropTypes.func, -}; - -ListControlUi.defaultProps = { - dynamicOptions: false, - multiselect: true, -}; - -ListControlUi.defaultProps = { - selectedOptions: [], - options: [], -}; - export const ListControl = injectI18n(ListControlUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.tsx similarity index 89% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.tsx index 8b72def2f2698..639616151a395 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.tsx @@ -21,8 +21,11 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { RangeControl, ceilWithPrecision, floorWithPrecision } from './range_control'; +import { RangeControl as RangeControlClass } from '../../control/range_control_factory'; -const control = { +jest.mock('ui/new_platform'); + +const control: RangeControlClass = { id: 'mock-range-control', isEnabled: () => { return true; @@ -39,7 +42,7 @@ const control = { hasValue: () => { return false; }, -}; +} as RangeControlClass; test('renders RangeControl', () => { const component = shallowWithIntl( @@ -49,7 +52,7 @@ test('renders RangeControl', () => { }); test('disabled', () => { - const disabledRangeControl = { + const disabledRangeControl: RangeControlClass = { id: 'mock-range-control', isEnabled: () => { return false; @@ -64,7 +67,7 @@ test('disabled', () => { hasValue: () => { return false; }, - }; + } as RangeControlClass; const component = shallowWithIntl( {}} /> ); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.tsx similarity index 72% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.tsx index ee3e3c8fe4788..cd3982afd9afd 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.tsx @@ -18,12 +18,17 @@ */ import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; + +import { ValidatedDualRange } from '../../legacy_imports'; import { FormRow } from './form_row'; -import { ValidatedDualRange } from 'ui/validated_range'; +import { RangeControl as RangeControlClass } from '../../control/range_control_factory'; -function roundWithPrecision(value, decimalPlaces, roundFunction) { +function roundWithPrecision( + value: number, + decimalPlaces: number, + roundFunction: (n: number) => number +) { if (decimalPlaces <= 0) { return roundFunction(value); } @@ -35,18 +40,29 @@ function roundWithPrecision(value, decimalPlaces, roundFunction) { return results; } -export function ceilWithPrecision(value, decimalPlaces) { +export function ceilWithPrecision(value: number, decimalPlaces: number) { return roundWithPrecision(value, decimalPlaces, Math.ceil); } -export function floorWithPrecision(value, decimalPlaces) { +export function floorWithPrecision(value: number, decimalPlaces: number) { return roundWithPrecision(value, decimalPlaces, Math.floor); } -export class RangeControl extends Component { - state = {}; +export interface RangeControlState { + value?: [string, string]; + prevValue?: [string, string]; +} + +export interface RangeControlProps { + control: RangeControlClass; + controlIndex: number; + stageFilter: (controlIndex: number, value: any) => void; +} + +export class RangeControl extends PureComponent { + state: RangeControlState = {}; - static getDerivedStateFromProps(nextProps, prevState) { + static getDerivedStateFromProps(nextProps: RangeControlProps, prevState: RangeControlState) { const nextValue = nextProps.control.hasValue() ? [nextProps.control.value.min, nextProps.control.value.max] : ['', '']; @@ -68,7 +84,7 @@ export class RangeControl extends Component { return null; } - onChangeComplete = _.debounce(value => { + onChangeComplete = _.debounce((value: [string, string]) => { const controlValue = { min: value[0], max: value[1], @@ -111,16 +127,10 @@ export class RangeControl extends Component { id={this.props.control.id} label={this.props.control.label} controlIndex={this.props.controlIndex} - disableMsg={this.props.control.isEnabled() ? null : this.props.control.disabledReason} + disableMsg={this.props.control.isEnabled() ? undefined : this.props.control.disabledReason} > {this.renderControl()} ); } } - -RangeControl.propTypes = { - control: PropTypes.object.isRequired, - controlIndex: PropTypes.number.isRequired, - stageFilter: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control.test.js b/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts similarity index 78% rename from src/legacy/core_plugins/input_control_vis/public/control/control.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/control.test.ts index aa9bed44d031d..e76b199a0262c 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts @@ -19,34 +19,50 @@ import expect from '@kbn/expect'; import { Control } from './control'; +import { ControlParams } from '../editor_utils'; +import { FilterManager as BaseFilterManager } from './filter_manager/filter_manager'; +import { SearchSource } from '../legacy_imports'; -function createControlParams(id, label) { +function createControlParams(id: string, label: string): ControlParams { return { - id: id, + id, options: {}, - label: label, - }; + label, + } as ControlParams; } -let valueFromFilterBar; -const mockFilterManager = { +let valueFromFilterBar: any; +const mockFilterManager: BaseFilterManager = { getValueFromFilterBar: () => { return valueFromFilterBar; }, - createFilter: value => { - return `mockKbnFilter:${value}`; + createFilter: (value: any) => { + return `mockKbnFilter:${value}` as any; }, getIndexPattern: () => { return 'mockIndexPattern'; }, -}; -const mockKbnApi = {}; +} as any; + +class ControlMock extends Control { + fetch() { + return Promise.resolve(); + } + + destroy() {} +} +const mockKbnApi: SearchSource = {} as SearchSource; describe('hasChanged', () => { - let control; + let control: ControlMock; beforeEach(() => { - control = new Control(createControlParams(3, 'control'), mockFilterManager, mockKbnApi); + control = new ControlMock( + createControlParams('3', 'control'), + mockFilterManager, + false, + mockKbnApi + ); }); afterEach(() => { @@ -70,23 +86,26 @@ describe('hasChanged', () => { }); describe('ancestors', () => { - let grandParentControl; - let parentControl; - let childControl; + let grandParentControl: any; + let parentControl: any; + let childControl: any; beforeEach(() => { - grandParentControl = new Control( - createControlParams(1, 'grandparent control'), + grandParentControl = new ControlMock( + createControlParams('1', 'grandparent control'), mockFilterManager, + false, mockKbnApi ); - parentControl = new Control( - createControlParams(2, 'parent control'), + parentControl = new ControlMock( + createControlParams('2', 'parent control'), mockFilterManager, + false, mockKbnApi ); - childControl = new Control( - createControlParams(3, 'child control'), + childControl = new ControlMock( + createControlParams('3', 'child control'), mockFilterManager, + false, mockKbnApi ); }); @@ -122,7 +141,7 @@ describe('ancestors', () => { }); describe('getAncestorValues', () => { - let lastAncestorValues; + let lastAncestorValues: any[]; beforeEach(() => { grandParentControl.set('myGrandParentValue'); parentControl.set('myParentValue'); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control.js b/src/legacy/core_plugins/input_control_vis/public/control/control.ts similarity index 66% rename from src/legacy/core_plugins/input_control_vis/public/control/control.js rename to src/legacy/core_plugins/input_control_vis/public/control/control.ts index 4035dc1adefe8..9dc03ecc23452 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/control.ts @@ -22,32 +22,53 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -export function noValuesDisableMsg(fieldName, indexPatternName) { +import { esFilters } from '../../../../../plugins/data/public'; +import { SearchSource as SearchSourceClass } from '../legacy_imports'; +import { ControlParams, ControlParamsOptions, CONTROL_TYPES } from '../editor_utils'; +import { RangeFilterManager } from './filter_manager/range_filter_manager'; +import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; +import { FilterManager as BaseFilterManager } from './filter_manager/filter_manager'; + +export function noValuesDisableMsg(fieldName: string, indexPatternName: string) { return i18n.translate('inputControl.control.noValuesDisableTooltip', { defaultMessage: 'Filtering occurs on the "{fieldName}" field, which doesn\'t exist on any documents in the "{indexPatternName}" \ index pattern. Choose a different field or index documents that contain values for this field.', - values: { fieldName: fieldName, indexPatternName: indexPatternName }, + values: { fieldName, indexPatternName }, }); } -export function noIndexPatternMsg(indexPatternId) { +export function noIndexPatternMsg(indexPatternId: string) { return i18n.translate('inputControl.control.noIndexPatternTooltip', { defaultMessage: 'Could not locate index-pattern id: {indexPatternId}.', values: { indexPatternId }, }); } -export class Control { - constructor(controlParams, filterManager, useTimeFilter, SearchSource) { +export abstract class Control { + private kbnFilter: esFilters.Filter | null = null; + + enable: boolean = false; + disabledReason: string = ''; + value: any; + + id: string; + options: ControlParamsOptions; + type: CONTROL_TYPES; + label: string; + ancestors: Array> = []; + + constructor( + public controlParams: ControlParams, + public filterManager: FilterManager, + public useTimeFilter: boolean, + public SearchSource: SearchSourceClass + ) { this.id = controlParams.id; this.controlParams = controlParams; this.options = controlParams.options; this.type = controlParams.type; this.label = controlParams.label ? controlParams.label : controlParams.fieldName; - this.useTimeFilter = useTimeFilter; - this.filterManager = filterManager; - this.SearchSource = SearchSource; // restore state from kibana filter context this.reset(); @@ -59,28 +80,20 @@ export class Control { ); } - async fetch() { - throw new Error('fetch method not defined, subclass are required to implement'); - } + abstract fetch(query: string): Promise; - destroy() { - throw new Error('destroy method not defined, subclass are required to implement'); - } + abstract destroy(): void; - format = value => { + format = (value: any) => { const field = this.filterManager.getField(); - if (field) { + if (field?.format?.convert) { return field.format.convert(value); } return value; }; - /** - * - * @param ancestors {array of Controls} - */ - setAncestors(ancestors) { + setAncestors(ancestors: Array>) { this.ancestors = ancestors; } @@ -110,17 +123,17 @@ export class Control { return this.enable; } - disable(reason) { + disable(reason: string) { this.enable = false; this.disabledReason = reason; } - set(newValue) { + set(newValue: any) { this.value = newValue; if (this.hasValue()) { - this._kbnFilter = this.filterManager.createFilter(this.value); + this.kbnFilter = this.filterManager.createFilter(this.value); } else { - this._kbnFilter = null; + this.kbnFilter = null; } } @@ -128,7 +141,7 @@ export class Control { * Remove any user changes to value by resetting value to that as provided by Kibana filter pills */ reset() { - this._kbnFilter = null; + this.kbnFilter = null; this.value = this.filterManager.getValueFromFilterBar(); } @@ -144,17 +157,17 @@ export class Control { } hasKbnFilter() { - if (this._kbnFilter) { + if (this.kbnFilter) { return true; } return false; } getKbnFilter() { - return this._kbnFilter; + return this.kbnFilter; } - hasValue() { + hasValue(): boolean { return this.value !== undefined; } } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/control_factory.ts similarity index 86% rename from src/legacy/core_plugins/input_control_vis/public/control/control_factory.js rename to src/legacy/core_plugins/input_control_vis/public/control/control_factory.ts index 6d3c7756f72aa..3dcc1d53d4211 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/control_factory.ts @@ -19,14 +19,15 @@ import { rangeControlFactory } from './range_control_factory'; import { listControlFactory } from './list_control_factory'; +import { ControlParams, CONTROL_TYPES } from '../editor_utils'; -export function controlFactory(controlParams) { +export function getControlFactory(controlParams: ControlParams) { let factory = null; switch (controlParams.type) { - case 'range': + case CONTROL_TYPES.RANGE: factory = rangeControlFactory; break; - case 'list': + case CONTROL_TYPES.LIST: factory = listControlFactory; break; default: diff --git a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts similarity index 71% rename from src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js rename to src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts index 2917dda5e96a7..c8fa5af5e052b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts @@ -16,15 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -import { timefilter } from 'ui/timefilter'; + +import { esFilters, IndexPattern, TimefilterSetup } from '../../../../../plugins/data/public'; +import { SearchSource as SearchSourceClass, SearchSourceFields } from '../legacy_imports'; export function createSearchSource( - SearchSource, - initialState, - indexPattern, - aggs, - useTimeFilter, - filters = [] + SearchSource: SearchSourceClass, + initialState: SearchSourceFields | null, + indexPattern: IndexPattern, + aggs: any, + useTimeFilter: boolean, + filters: esFilters.PhraseFilter[] = [], + timefilter: TimefilterSetup['timefilter'] ) { const searchSource = initialState ? new SearchSource(initialState) : new SearchSource(); // Do not not inherit from rootSearchSource to avoid picking up time and globals @@ -32,7 +35,10 @@ export function createSearchSource( searchSource.setField('filter', () => { const activeFilters = [...filters]; if (useTimeFilter) { - activeFilters.push(timefilter.createFilter(indexPattern)); + const filter = timefilter.createFilter(indexPattern); + if (filter) { + activeFilters.push(filter); + } } return activeFilters; }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.ts similarity index 66% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.ts index 95277ac073d75..fd2cbae121b7e 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.ts @@ -18,30 +18,45 @@ */ import expect from '@kbn/expect'; + import { FilterManager } from './filter_manager'; +import { coreMock } from '../../../../../../core/public/mocks'; +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; + +const setupMock = coreMock.createSetup(); + +class FilterManagerTest extends FilterManager { + createFilter() { + return {} as esFilters.Filter; + } + + getValueFromFilterBar() { + return null; + } +} describe('FilterManager', function() { const controlId = 'control1'; describe('findFilters', function() { - const indexPatternMock = {}; - let kbnFilters; - const queryFilterMock = { - getAppFilters: () => { - return kbnFilters; - }, - getGlobalFilters: () => { - return []; - }, - }; - let filterManager; + const indexPatternMock = {} as IndexPattern; + let kbnFilters: esFilters.Filter[]; + const queryFilterMock = new QueryFilterManager(setupMock.uiSettings); + queryFilterMock.getAppFilters = () => kbnFilters; + queryFilterMock.getGlobalFilters = () => []; + + let filterManager: FilterManagerTest; beforeEach(() => { kbnFilters = []; - filterManager = new FilterManager(controlId, 'field1', indexPatternMock, queryFilterMock); + filterManager = new FilterManagerTest(controlId, 'field1', indexPatternMock, queryFilterMock); }); test('should not find filters that are not controlled by any visualization', function() { - kbnFilters.push({}); + kbnFilters.push({} as esFilters.Filter); const foundFilters = filterManager.findFilters(); expect(foundFilters.length).to.be(0); }); @@ -51,7 +66,7 @@ describe('FilterManager', function() { meta: { controlledBy: 'anotherControl', }, - }); + } as esFilters.Filter); const foundFilters = filterManager.findFilters(); expect(foundFilters.length).to.be(0); }); @@ -61,7 +76,7 @@ describe('FilterManager', function() { meta: { controlledBy: controlId, }, - }); + } as esFilters.Filter); const foundFilters = filterManager.findFilters(); expect(foundFilters.length).to.be(1); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.ts similarity index 62% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.ts index 672f56746cf80..d80a74ed46eae 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.ts @@ -19,15 +19,33 @@ import _ from 'lodash'; -export class FilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter) { - this.controlId = controlId; - this.fieldName = fieldName; - this.indexPattern = indexPattern; - this.queryFilter = queryFilter; - } +import { + FilterManager as QueryFilterManager, + IndexPattern, + esFilters, +} from '../../../../../../plugins/data/public'; + +export abstract class FilterManager { + constructor( + public controlId: string, + public fieldName: string, + public indexPattern: IndexPattern, + public queryFilter: QueryFilterManager + ) {} + + /** + * Convert phrases into filter + * + * @param {any[]} phrases + * @returns PhraseFilter + * single phrase: match query + * multiple phrases: bool query with should containing list of match_phrase queries + */ + abstract createFilter(phrases: any): esFilters.Filter; + + abstract getValueFromFilterBar(): any; - getIndexPattern() { + getIndexPattern(): IndexPattern { return this.indexPattern; } @@ -35,11 +53,7 @@ export class FilterManager { return this.indexPattern.fields.getByName(this.fieldName); } - createFilter() { - throw new Error('Must implement createFilter.'); - } - - findFilters() { + findFilters(): esFilters.Filter[] { const kbnFilters = _.flatten([ this.queryFilter.getAppFilters(), this.queryFilter.getGlobalFilters(), @@ -48,8 +62,4 @@ export class FilterManager { return _.get(kbnFilter, 'meta.controlledBy') === this.controlId; }); } - - getValueFromFilterBar() { - throw new Error('Must implement getValueFromFilterBar.'); - } } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.ts similarity index 79% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.ts index 7aa1ec6632043..dc577ca7168d1 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.ts @@ -18,6 +18,12 @@ */ import expect from '@kbn/expect'; + +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; import { PhraseFilterManager } from './phrase_filter_manager'; describe('PhraseFilterManager', function() { @@ -28,22 +34,20 @@ describe('PhraseFilterManager', function() { const fieldMock = { name: 'field1', format: { - convert: val => { - return val; - }, + convert: (value: any) => value, }, }; - const indexPatternMock = { + const indexPatternMock: IndexPattern = { id: indexPatternId, fields: { - getByName: name => { - const fields = { field1: fieldMock }; + getByName: (name: string) => { + const fields: any = { field1: fieldMock }; return fields[name]; }, }, - }; - const queryFilterMock = {}; - let filterManager; + } as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: PhraseFilterManager; beforeEach(() => { filterManager = new PhraseFilterManager( controlId, @@ -83,22 +87,32 @@ describe('PhraseFilterManager', function() { }); describe('getValueFromFilterBar', function() { - const indexPatternMock = {}; - const queryFilterMock = {}; - let filterManager; - beforeEach(() => { - class MockFindFiltersPhraseFilterManager extends PhraseFilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter, delimiter) { - super(controlId, fieldName, indexPattern, queryFilter, delimiter); - this.mockFilters = []; - } - findFilters() { - return this.mockFilters; - } - setMockFilters(mockFilters) { - this.mockFilters = mockFilters; - } + class MockFindFiltersPhraseFilterManager extends PhraseFilterManager { + mockFilters: esFilters.Filter[]; + + constructor( + id: string, + fieldName: string, + indexPattern: IndexPattern, + queryFilter: QueryFilterManager + ) { + super(id, fieldName, indexPattern, queryFilter); + this.mockFilters = []; } + + findFilters() { + return this.mockFilters; + } + + setMockFilters(mockFilters: esFilters.Filter[]) { + this.mockFilters = mockFilters; + } + } + + const indexPatternMock: IndexPattern = {} as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: MockFindFiltersPhraseFilterManager; + beforeEach(() => { filterManager = new MockFindFiltersPhraseFilterManager( controlId, 'field1', @@ -119,7 +133,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(['ios']); }); @@ -145,7 +159,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(['ios', 'win xp']); }); @@ -169,7 +183,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(['ios', 'win xp']); }); @@ -185,7 +199,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(undefined); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts similarity index 68% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts index 1e60f8c4ebb67..b0b46be86f1e8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts @@ -18,37 +18,38 @@ */ import _ from 'lodash'; -import { FilterManager } from './filter_manager.js'; -import { esFilters } from '../../../../../../plugins/data/public'; + +import { FilterManager } from './filter_manager'; +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; export class PhraseFilterManager extends FilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter) { + constructor( + controlId: string, + fieldName: string, + indexPattern: IndexPattern, + queryFilter: QueryFilterManager + ) { super(controlId, fieldName, indexPattern, queryFilter); } - /** - * Convert phrases into filter - * - * @param {array} phrases - * @return {object} query filter - * single phrase: match query - * multiple phrases: bool query with should containing list of match_phrase queries - */ - createFilter(phrases) { - let newFilter; + createFilter(phrases: any): esFilters.PhraseFilter { + let newFilter: esFilters.PhraseFilter; + const value = this.indexPattern.fields.getByName(this.fieldName); + + if (!value) { + throw new Error(`Unable to find field with name: ${this.fieldName} on indexPattern`); + } + if (phrases.length === 1) { - newFilter = esFilters.buildPhraseFilter( - this.indexPattern.fields.getByName(this.fieldName), - phrases[0], - this.indexPattern - ); + newFilter = esFilters.buildPhraseFilter(value, phrases[0], this.indexPattern); } else { - newFilter = esFilters.buildPhrasesFilter( - this.indexPattern.fields.getByName(this.fieldName), - phrases, - this.indexPattern - ); + newFilter = esFilters.buildPhrasesFilter(value, phrases, this.indexPattern); } + newFilter.meta.key = this.fieldName; newFilter.meta.controlledBy = this.controlId; return newFilter; @@ -62,7 +63,7 @@ export class PhraseFilterManager extends FilterManager { const values = kbnFilters .map(kbnFilter => { - return this._getValueFromFilter(kbnFilter); + return this.getValueFromFilter(kbnFilter); }) .filter(value => value != null); @@ -78,15 +79,15 @@ export class PhraseFilterManager extends FilterManager { /** * Extract filtering value from kibana filters * - * @param {object} kbnFilter + * @param {esFilters.PhraseFilter} kbnFilter * @return {Array.} array of values pulled from filter */ - _getValueFromFilter(kbnFilter) { + private getValueFromFilter(kbnFilter: esFilters.PhraseFilter): any { // bool filter - multiple phrase filters if (_.has(kbnFilter, 'query.bool.should')) { - return _.get(kbnFilter, 'query.bool.should') - .map(kbnFilter => { - return this._getValueFromFilter(kbnFilter); + return _.get(kbnFilter, 'query.bool.should') + .map(kbnQueryFilter => { + return this.getValueFromFilter(kbnQueryFilter); }) .filter(value => { if (value) { diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.ts similarity index 68% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.ts index ffe2ebdad53bc..f4993a60c5b39 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.ts @@ -18,7 +18,13 @@ */ import expect from '@kbn/expect'; + import { RangeFilterManager } from './range_filter_manager'; +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; describe('RangeFilterManager', function() { const controlId = 'control1'; @@ -28,19 +34,19 @@ describe('RangeFilterManager', function() { const fieldMock = { name: 'field1', }; - const indexPatternMock = { + const indexPatternMock: IndexPattern = { id: indexPatternId, fields: { - getByName: name => { - const fields = { + getByName: (name: any) => { + const fields: any = { field1: fieldMock, }; return fields[name]; }, }, - }; - const queryFilterMock = {}; - let filterManager; + } as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: RangeFilterManager; beforeEach(() => { filterManager = new RangeFilterManager( controlId, @@ -62,22 +68,32 @@ describe('RangeFilterManager', function() { }); describe('getValueFromFilterBar', function() { - const indexPatternMock = {}; - const queryFilterMock = {}; - let filterManager; - beforeEach(() => { - class MockFindFiltersRangeFilterManager extends RangeFilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter) { - super(controlId, fieldName, indexPattern, queryFilter); - this.mockFilters = []; - } - findFilters() { - return this.mockFilters; - } - setMockFilters(mockFilters) { - this.mockFilters = mockFilters; - } + class MockFindFiltersRangeFilterManager extends RangeFilterManager { + mockFilters: esFilters.RangeFilter[]; + + constructor( + id: string, + fieldName: string, + indexPattern: IndexPattern, + queryFilter: QueryFilterManager + ) { + super(id, fieldName, indexPattern, queryFilter); + this.mockFilters = []; + } + + findFilters() { + return this.mockFilters; + } + + setMockFilters(mockFilters: esFilters.RangeFilter[]) { + this.mockFilters = mockFilters; } + } + + const indexPatternMock: IndexPattern = {} as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: MockFindFiltersRangeFilterManager; + beforeEach(() => { filterManager = new MockFindFiltersRangeFilterManager( controlId, 'field1', @@ -95,14 +111,15 @@ describe('RangeFilterManager', function() { lt: 3, }, }, + meta: {} as esFilters.RangeFilterMeta, }, - ]); + ] as esFilters.RangeFilter[]); const value = filterManager.getValueFromFilterBar(); expect(value).to.be.a('object'); expect(value).to.have.property('min'); - expect(value.min).to.be(1); + expect(value?.min).to.be(1); expect(value).to.have.property('max'); - expect(value.max).to.be(3); + expect(value?.max).to.be(3); }); test('should return undefined when filter value can not be extracted from Kibana filter', function() { @@ -114,8 +131,9 @@ describe('RangeFilterManager', function() { lte: 3, }, }, + meta: {} as esFilters.RangeFilterMeta, }, - ]); + ] as esFilters.RangeFilter[]); expect(filterManager.getValueFromFilterBar()).to.eql(undefined); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.ts similarity index 77% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.ts index 5a2e7b7d779bc..0a6819bd68e6f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.ts @@ -18,11 +18,17 @@ */ import _ from 'lodash'; -import { FilterManager } from './filter_manager.js'; -import { esFilters } from '../../../../../../plugins/data/public'; + +import { FilterManager } from './filter_manager'; +import { esFilters, IFieldType } from '../../../../../../plugins/data/public'; + +interface SliderValue { + min?: string | number; + max?: string | number; +} // Convert slider value into ES range filter -function toRange(sliderValue) { +function toRange(sliderValue: SliderValue) { return { gte: sliderValue.min, lte: sliderValue.max, @@ -30,8 +36,8 @@ function toRange(sliderValue) { } // Convert ES range filter into slider value -function fromRange(range) { - const sliderValue = {}; +function fromRange(range: esFilters.RangeFilterParams): SliderValue { + const sliderValue: SliderValue = {}; if (_.has(range, 'gte')) { sliderValue.min = _.get(range, 'gte'); } @@ -54,9 +60,10 @@ export class RangeFilterManager extends FilterManager { * @param {object} react-input-range value - POJO with `min` and `max` properties * @return {object} range filter */ - createFilter(value) { + createFilter(value: SliderValue): esFilters.RangeFilter { const newFilter = esFilters.buildRangeFilter( - this.indexPattern.fields.getByName(this.fieldName), + // TODO: Fix type to be required + this.indexPattern.fields.getByName(this.fieldName) as IFieldType, toRange(value), this.indexPattern ); @@ -65,13 +72,13 @@ export class RangeFilterManager extends FilterManager { return newFilter; } - getValueFromFilterBar() { + getValueFromFilterBar(): SliderValue | undefined { const kbnFilters = this.findFilters(); if (kbnFilters.length === 0) { return; } - let range; + let range: esFilters.RangeFilterParams; if (_.has(kbnFilters[0], 'script')) { range = _.get(kbnFilters[0], 'script.script.params'); } else { diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts similarity index 57% rename from src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts index 3b5ef7372bc1f..2420907727638 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts @@ -17,92 +17,33 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { listControlFactory } from './list_control_factory'; +import { listControlFactory, ListControl } from './list_control_factory'; +import { ControlParams, CONTROL_TYPES } from '../editor_utils'; +import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; +import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; -jest.mock('ui/timefilter', () => ({ - createFilter: jest.fn(), -})); +const MockSearchSource = getSearchSourceMock(); +const deps = getDepsMock(); -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - query: { - filterManager: { - fieldName: 'myNumberField', - getIndexPattern: () => ({ - fields: { - getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - }, - }, - }), - getAppFilters: jest.fn().mockImplementation(() => []), - getGlobalFilters: jest.fn().mockImplementation(() => []), - }, - }, - indexPatterns: { - get: () => ({ - fields: { - getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - }, - }, - }), - }, - }, - }, - }, +jest.doMock('./create_search_source.ts', () => ({ + createSearchSource: MockSearchSource, })); -chrome.getInjected.mockImplementation(key => { - switch (key) { - case 'autocompleteTimeout': - return 1000; - case 'autocompleteTerminateAfter': - return 100000; - } -}); - -function MockSearchSource() { - return { - setParent: () => {}, - setField: () => {}, - fetch: async () => { - return { - aggregations: { - termsAgg: { - buckets: [ - { - key: 'Zurich Airport', - doc_count: 691, - }, - { - key: 'Xi an Xianyang International Airport', - doc_count: 526, - }, - ], - }, - }, - }; - }, - }; -} - describe('hasValue', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; const useTimeFilter = false; - let listControl; + let listControl: ListControl; beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource); + listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); }); test('should be false when control has no value', () => { @@ -121,22 +62,25 @@ describe('hasValue', () => { }); describe('fetch', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; const useTimeFilter = false; - const SearchSource = jest.fn(MockSearchSource); - let listControl; + let listControl: ListControl; beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, SearchSource); + listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); }); test('should pass in timeout parameters from injected vars', async () => { await listControl.fetch(); - expect(SearchSource).toHaveBeenCalledWith({ + expect(MockSearchSource).toHaveBeenCalledWith({ timeout: `1000ms`, terminate_after: 100000, }); @@ -152,24 +96,37 @@ describe('fetch', () => { }); describe('fetch with ancestors', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; const useTimeFilter = false; - let listControl; + let listControl: ListControl; let parentControl; beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource); + listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); - const parentControlParams = { + const parentControlParams: ControlParams = { id: 'parent', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; - parentControl = await listControlFactory(parentControlParams, useTimeFilter, MockSearchSource); + parentControl = await listControlFactory( + parentControlParams, + useTimeFilter, + MockSearchSource, + deps + ); parentControl.clear(); listControl.setAncestors([parentControl]); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts similarity index 63% rename from src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js rename to src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts index d90b21eead5c6..56b42f295ce15 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts @@ -18,20 +18,30 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import { SearchSource as SearchSourceClass, SearchSourceFields } from '../legacy_imports'; import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control'; import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; import { createSearchSource } from './create_search_source'; -import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import chrome from 'ui/chrome'; +import { ControlParams } from '../editor_utils'; +import { InputControlVisDependencies } from '../plugin'; +import { IFieldType, TimefilterSetup } from '../../../../../plugins/data/public'; function getEscapedQuery(query = '') { // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators return query.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, match => `\\${match}`); } -const termsAgg = ({ field, size, direction, query }) => { - const terms = { +interface TermsAggArgs { + field?: IFieldType; + size: number | null; + direction: string; + query?: string; +} + +const termsAgg = ({ field, size, direction, query }: TermsAggArgs) => { + const terms: any = { order: { _count: direction, }, @@ -41,14 +51,14 @@ const termsAgg = ({ field, size, direction, query }) => { terms.size = size < 1 ? 1 : size; } - if (field.scripted) { + if (field?.scripted) { terms.script = { source: field.script, lang: field.lang, }; terms.value_type = field.type === 'number' ? 'float' : field.type; } else { - terms.field = field.name; + terms.field = field?.name; } if (query) { @@ -57,13 +67,34 @@ const termsAgg = ({ field, size, direction, query }) => { return { termsAgg: { - terms: terms, + terms, }, }; }; -class ListControl extends Control { - fetch = async query => { +export class ListControl extends Control { + private getInjectedVar: InputControlVisDependencies['core']['injectedMetadata']['getInjectedVar']; + private timefilter: TimefilterSetup['timefilter']; + + abortController?: AbortController; + lastAncestorValues: any; + lastQuery?: string; + partialResults?: boolean; + selectOptions?: string[]; + + constructor( + controlParams: ControlParams, + filterManager: PhraseFilterManager, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies + ) { + super(controlParams, filterManager, useTimeFilter, SearchSource); + this.getInjectedVar = deps.core.injectedMetadata.getInjectedVar; + this.timefilter = deps.data.query.timefilter.timefilter; + } + + fetch = async (query?: string) => { // Abort any in-progress fetch if (this.abortController) { this.abortController.abort(); @@ -101,9 +132,9 @@ class ListControl extends Control { } const fieldName = this.filterManager.fieldName; - const initialSearchSourceState = { - timeout: `${chrome.getInjected('autocompleteTimeout')}ms`, - terminate_after: chrome.getInjected('autocompleteTerminateAfter'), + const initialSearchSourceState: SearchSourceFields = { + timeout: `${this.getInjectedVar('autocompleteTimeout')}ms`, + terminate_after: Number(this.getInjectedVar('autocompleteTerminateAfter')), }; const aggs = termsAgg({ field: indexPattern.fields.getByName(fieldName), @@ -117,7 +148,8 @@ class ListControl extends Control { indexPattern, aggs, this.useTimeFilter, - ancestorFilters + ancestorFilters, + this.timefilter ); const abortSignal = this.abortController.signal; @@ -143,8 +175,8 @@ class ListControl extends Control { return; } - const selectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map(bucket => { - return bucket.key; + const selectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map((bucket: any) => { + return bucket?.key; }); if (selectOptions.length === 0 && !query) { @@ -167,29 +199,34 @@ class ListControl extends Control { } } -export async function listControlFactory(controlParams, useTimeFilter, SearchSource) { - let indexPattern; - try { - indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); - - // dynamic options are only allowed on String fields but the setting defaults to true so it could - // be enabled for non-string fields (since UI input is hidden for non-string fields). - // If field is not string, then disable dynamic options. - const field = indexPattern.fields.find(field => { - return field.name === controlParams.fieldName; - }); - if (field && field.type !== 'string') { - controlParams.options.dynamicOptions = false; - } - } catch (err) { - // ignore not found error and return control so it can be displayed in disabled state. +export async function listControlFactory( + controlParams: ControlParams, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies +) { + const [, { data: dataPluginStart }] = await deps.core.getStartServices(); + const indexPattern = await dataPluginStart.indexPatterns.get(controlParams.indexPattern); + + // dynamic options are only allowed on String fields but the setting defaults to true so it could + // be enabled for non-string fields (since UI input is hidden for non-string fields). + // If field is not string, then disable dynamic options. + const field = indexPattern.fields.find(({ name }) => name === controlParams.fieldName); + if (field && field.type !== 'string') { + controlParams.options.dynamicOptions = false; } - const { filterManager } = npStart.plugins.data.query; - return new ListControl( + const listControl = new ListControl( controlParams, - new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager), + new PhraseFilterManager( + controlParams.id, + controlParams.fieldName, + indexPattern, + deps.data.query.filterManager + ), useTimeFilter, - SearchSource + SearchSource, + deps ); + return listControl; } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts similarity index 59% rename from src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts index b545c6e2834f3..5328aeb6c6a47 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts @@ -18,74 +18,37 @@ */ import { rangeControlFactory } from './range_control_factory'; +import { ControlParams, CONTROL_TYPES } from '../editor_utils'; +import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; +import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; -let esSearchResponse; -class MockSearchSource { - setParent() {} - setField() {} - async fetch() { - return esSearchResponse; - } -} - -jest.mock('ui/timefilter', () => ({ - createFilter: jest.fn(), -})); - -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - query: { - filterManager: { - fieldName: 'myNumberField', - getIndexPattern: () => ({ - fields: { - getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - }, - }, - }), - getAppFilters: jest.fn().mockImplementation(() => []), - getGlobalFilters: jest.fn().mockImplementation(() => []), - }, - }, - indexPatterns: { - get: () => ({ - fields: { - getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - }, - }, - }), - }, - }, - }, - }, -})); +const deps = getDepsMock(); describe('fetch', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myNumberField', options: {}, + type: CONTROL_TYPES.RANGE, + label: 'test', + indexPattern: {} as any, + parent: {} as any, }; const useTimeFilter = false; - let rangeControl; - beforeEach(async () => { - rangeControl = await rangeControlFactory(controlParams, useTimeFilter, MockSearchSource); - }); - test('should set min and max from aggregation results', async () => { - esSearchResponse = { + const esSearchResponse = { aggregations: { maxAgg: { value: 100 }, minAgg: { value: 10 }, }, }; + const rangeControl = await rangeControlFactory( + controlParams, + useTimeFilter, + getSearchSourceMock(esSearchResponse), + deps + ); await rangeControl.fetch(); expect(rangeControl.isEnabled()).toBe(true); @@ -95,12 +58,18 @@ describe('fetch', () => { test('should disable control when there are 0 hits', async () => { // ES response when the query does not match any documents - esSearchResponse = { + const esSearchResponse = { aggregations: { maxAgg: { value: null }, minAgg: { value: null }, }, }; + const rangeControl = await rangeControlFactory( + controlParams, + useTimeFilter, + getSearchSourceMock(esSearchResponse), + deps + ); await rangeControl.fetch(); expect(rangeControl.isEnabled()).toBe(false); @@ -109,7 +78,13 @@ describe('fetch', () => { test('should disable control when response is empty', async () => { // ES response for dashboardonly user who does not have read permissions on index is 200 (which is weird) // and there is not aggregations key - esSearchResponse = {}; + const esSearchResponse = {}; + const rangeControl = await rangeControlFactory( + controlParams, + useTimeFilter, + getSearchSourceMock(esSearchResponse), + deps + ); await rangeControl.fetch(); expect(rangeControl.isEnabled()).toBe(false); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts similarity index 63% rename from src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js rename to src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts index c99c794c1fcd5..b9191436b5968 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts @@ -18,22 +18,29 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import { SearchSource as SearchSourceClass } from '../legacy_imports'; import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control'; import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { createSearchSource } from './create_search_source'; -import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; +import { ControlParams } from '../editor_utils'; +import { InputControlVisDependencies } from '../plugin'; +import { IFieldType, TimefilterSetup } from '../.../../../../../../plugins/data/public'; -const minMaxAgg = field => { - const aggBody = {}; - if (field.scripted) { - aggBody.script = { - source: field.script, - lang: field.lang, - }; - } else { - aggBody.field = field.name; +const minMaxAgg = (field?: IFieldType) => { + const aggBody: any = {}; + if (field) { + if (field.scripted) { + aggBody.script = { + source: field.script, + lang: field.lang, + }; + } else { + aggBody.field = field.name; + } } + return { maxAgg: { max: aggBody, @@ -44,7 +51,23 @@ const minMaxAgg = field => { }; }; -class RangeControl extends Control { +export class RangeControl extends Control { + timefilter: TimefilterSetup['timefilter']; + abortController: any; + min: any; + max: any; + + constructor( + controlParams: ControlParams, + filterManager: RangeFilterManager, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies + ) { + super(controlParams, filterManager, useTimeFilter, SearchSource); + this.timefilter = deps.data.query.timefilter.timefilter; + } + async fetch() { // Abort any in-progress fetch if (this.abortController) { @@ -58,14 +81,15 @@ class RangeControl extends Control { } const fieldName = this.filterManager.fieldName; - const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName)); const searchSource = createSearchSource( this.SearchSource, null, indexPattern, aggs, - this.useTimeFilter + this.useTimeFilter, + [], + this.timefilter ); const abortSignal = this.abortController.signal; @@ -102,18 +126,25 @@ class RangeControl extends Control { } } -export async function rangeControlFactory(controlParams, useTimeFilter, SearchSource) { - let indexPattern; - try { - indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); - } catch (err) { - // ignore not found error and return control so it can be displayed in disabled state. - } - const { filterManager } = npStart.plugins.data.query; +export async function rangeControlFactory( + controlParams: ControlParams, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies +): Promise { + const [, { data: dataPluginStart }] = await deps.core.getStartServices(); + const indexPattern = await dataPluginStart.indexPatterns.get(controlParams.indexPattern); + return new RangeControl( controlParams, - new RangeFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager), + new RangeFilterManager( + controlParams.id, + controlParams.fieldName, + indexPattern, + deps.data.query.filterManager + ), useTimeFilter, - SearchSource + SearchSource, + deps ); } diff --git a/src/legacy/core_plugins/input_control_vis/public/editor_utils.js b/src/legacy/core_plugins/input_control_vis/public/editor_utils.ts similarity index 64% rename from src/legacy/core_plugins/input_control_vis/public/editor_utils.js rename to src/legacy/core_plugins/input_control_vis/public/editor_utils.ts index f5b4390342a0f..74def0a8d86f4 100644 --- a/src/legacy/core_plugins/input_control_vis/public/editor_utils.js +++ b/src/legacy/core_plugins/input_control_vis/public/editor_utils.ts @@ -16,21 +16,54 @@ * specific language governing permissions and limitations * under the License. */ +import { $Values } from '@kbn/utility-types'; export const CONTROL_TYPES = { - LIST: 'list', - RANGE: 'range', + LIST: 'list' as 'list', + RANGE: 'range' as 'range', }; +export type CONTROL_TYPES = $Values; -export const setControl = (controls, controlIndex, control) => [ +export interface ControlParamsOptions { + decimalPlaces?: number; + step?: number; + type?: string; + multiselect?: boolean; + dynamicOptions?: boolean; + size?: number; + order?: string; +} + +export interface ControlParams { + id: string; + type: CONTROL_TYPES; + label: string; + fieldName: string; + indexPattern: string; + parent: string; + options: ControlParamsOptions; +} + +export const setControl = ( + controls: ControlParams[], + controlIndex: number, + control: ControlParams +): ControlParams[] => [ ...controls.slice(0, controlIndex), control, ...controls.slice(controlIndex + 1), ]; -export const addControl = (controls, control) => [...controls, control]; +export const addControl = (controls: ControlParams[], control: ControlParams): ControlParams[] => [ + ...controls, + control, +]; -export const moveControl = (controls, controlIndex, direction) => { +export const moveControl = ( + controls: ControlParams[], + controlIndex: number, + direction: number +): ControlParams[] => { let newIndex; if (direction >= 0) { newIndex = controlIndex + 1; @@ -54,13 +87,13 @@ export const moveControl = (controls, controlIndex, direction) => { } }; -export const removeControl = (controls, controlIndex) => [ +export const removeControl = (controls: ControlParams[], controlIndex: number): ControlParams[] => [ ...controls.slice(0, controlIndex), ...controls.slice(controlIndex + 1), ]; -export const getDefaultOptions = type => { - const defaultOptions = {}; +export const getDefaultOptions = (type: CONTROL_TYPES): ControlParamsOptions => { + const defaultOptions: ControlParamsOptions = {}; switch (type) { case CONTROL_TYPES.RANGE: defaultOptions.decimalPlaces = 0; @@ -77,17 +110,17 @@ export const getDefaultOptions = type => { return defaultOptions; }; -export const newControl = type => ({ +export const newControl = (type: CONTROL_TYPES): ControlParams => ({ id: new Date().getTime().toString(), indexPattern: '', fieldName: '', parent: '', label: '', - type: type, + type, options: getDefaultOptions(type), }); -export const getTitle = (controlParams, controlIndex) => { +export const getTitle = (controlParams: ControlParams, controlIndex: number): string => { let title = `${controlParams.type}: ${controlIndex}`; if (controlParams.label) { title = `${controlParams.label}`; diff --git a/src/legacy/core_plugins/input_control_vis/public/index.ts b/src/legacy/core_plugins/input_control_vis/public/index.ts new file mode 100644 index 0000000000000..e14c2cc4b69b6 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/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 { InputControlVisPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts similarity index 83% rename from src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js rename to src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts index 09c6749bcab94..aa1383587ea68 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts @@ -17,14 +17,15 @@ * under the License. */ -jest.mock('ui/new_platform'); +import { createInputControlVisFn } from './input_control_fn'; // eslint-disable-next-line import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; -import { inputControlVis } from './input_control_fn'; + +jest.mock('./legacy_imports.ts'); describe('interpreter/functions#input_control_vis', () => { - const fn = functionWrapper(inputControlVis); + const fn = functionWrapper(createInputControlVisFn); const visConfig = { controls: [ { @@ -47,8 +48,8 @@ describe('interpreter/functions#input_control_vis', () => { pinFilters: false, }; - it('returns an object with the correct structure', () => { - const actual = fn(undefined, { visConfig: JSON.stringify(visConfig) }); + it('returns an object with the correct structure', async () => { + const actual = await fn(null, { visConfig: JSON.stringify(visConfig) }); expect(actual).toMatchSnapshot(); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.js b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts similarity index 70% rename from src/legacy/core_plugins/input_control_vis/public/input_control_fn.js rename to src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts index 0bd435f502a5d..0482c0d2cbff3 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.js +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts @@ -17,10 +17,37 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; import { i18n } from '@kbn/i18n'; -export const inputControlVis = () => ({ +import { + ExpressionFunction, + KibanaDatatable, + Render, +} from '../../../../plugins/expressions/public'; + +const name = 'input_control_vis'; + +type Context = KibanaDatatable; + +interface Arguments { + visConfig: string; +} + +type VisParams = Required; + +interface RenderValue { + visType: 'input_control_vis'; + visConfig: VisParams; +} + +type Return = Promise>; + +export const createInputControlVisFn = (): ExpressionFunction< + typeof name, + Context, + Arguments, + Return +> => ({ name: 'input_control_vis', type: 'render', context: { @@ -33,9 +60,10 @@ export const inputControlVis = () => ({ visConfig: { types: ['string'], default: '"{}"', + help: '', }, }, - fn(context, args) { + async fn(context, args) { const params = JSON.parse(args.visConfig); return { type: 'render', @@ -47,5 +75,3 @@ export const inputControlVis = () => ({ }; }, }); - -functionsRegistry.register(inputControlVis); 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 new file mode 100644 index 0000000000000..b6774aa87b43c --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.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 { i18n } from '@kbn/i18n'; + +import { createInputControlVisController } from './vis_controller'; +import { getControlsTab } from './components/editor/controls_tab'; +import { OptionsTab } from './components/editor/options_tab'; +import { Status, defaultFeedbackMessage } from '../../visualizations/public'; +import { InputControlVisDependencies } from './plugin'; + +export function createInputControlVisTypeDefinition(deps: InputControlVisDependencies) { + const InputControlVisController = createInputControlVisController(deps); + const ControlsTab = getControlsTab(deps); + + return { + name: 'input_control_vis', + title: i18n.translate('inputControl.register.controlsTitle', { + defaultMessage: 'Controls', + }), + icon: 'visControls', + description: i18n.translate('inputControl.register.controlsDescription', { + defaultMessage: 'Create interactive controls for easy dashboard manipulation.', + }), + stage: 'experimental', + requiresUpdateStatus: [Status.PARAMS, Status.TIME], + feedbackMessage: defaultFeedbackMessage, + visualization: InputControlVisController, + visConfig: { + defaults: { + controls: [], + updateFiltersOnChange: false, + useTimeFilter: false, + pinFilters: false, + }, + }, + editor: 'default', + editorConfig: { + optionTabs: [ + { + name: 'controls', + title: i18n.translate('inputControl.register.tabs.controlsTitle', { + defaultMessage: 'Controls', + }), + editor: ControlsTab, + }, + { + name: 'options', + title: i18n.translate('inputControl.register.tabs.optionsTitle', { + defaultMessage: 'Options', + }), + editor: OptionsTab, + }, + ], + }, + requestHandler: 'none', + responseHandler: 'none', + }; +} diff --git a/src/legacy/core_plugins/input_control_vis/public/legacy.ts b/src/legacy/core_plugins/input_control_vis/public/legacy.ts new file mode 100644 index 0000000000000..438cdffdb323a --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/legacy.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 { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; + +import { plugin } from '.'; + +import { + InputControlVisPluginSetupDependencies, + InputControlVisPluginStartDependencies, +} from './plugin'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; + +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + data: npSetup.plugins.data, + visualizations: visualizationsSetup, +}; + +const startPlugins: Readonly = { + expressions: npStart.plugins.expressions, + data: npStart.plugins.data, + 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/ui/public/management/__tests__/index.js b/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts similarity index 65% rename from src/legacy/ui/public/management/__tests__/index.js rename to src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts index 6c794b3c189f0..864ce3b146689 100644 --- a/src/legacy/ui/public/management/__tests__/index.js +++ b/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts @@ -17,13 +17,13 @@ * under the License. */ -import expect from '@kbn/expect'; +import { SearchSource as SearchSourceClass } from 'ui/courier'; +import { Class } from '@kbn/utility-types'; -import { management } from '..'; -import { ManagementSection } from '../section'; +export { Vis, VisParams } from 'ui/vis'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +export { ValidatedDualRange } from 'ui/validated_range'; +export { SearchSourceFields } from 'ui/courier/types'; -describe('Management', () => { - it('provides ManagementSection', () => { - expect(management).to.be.a(ManagementSection); - }); -}); +export type SearchSource = Class; +export const SearchSource = SearchSourceClass; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/index.js b/src/legacy/core_plugins/input_control_vis/public/lineage/index.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/lineage/index.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/index.ts diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.js b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.ts similarity index 94% rename from src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.ts index de1b589b7dfa9..a0cd648007ecc 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.ts @@ -23,11 +23,11 @@ import { CONTROL_TYPES, newControl } from '../editor_utils'; test('creates lineage map', () => { const control1 = newControl(CONTROL_TYPES.LIST); - control1.id = 1; + control1.id = '1'; const control2 = newControl(CONTROL_TYPES.LIST); - control2.id = 2; + control2.id = '2'; const control3 = newControl(CONTROL_TYPES.LIST); - control3.id = 3; + control3.id = '3'; control2.parent = control1.id; control3.parent = control2.id; @@ -40,9 +40,9 @@ test('creates lineage map', () => { test('safely handles circular graph', () => { const control1 = newControl(CONTROL_TYPES.LIST); - control1.id = 1; + control1.id = '1'; const control2 = newControl(CONTROL_TYPES.LIST); - control2.id = 2; + control2.id = '2'; control1.parent = control2.id; control2.parent = control1.id; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.js b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.ts similarity index 80% rename from src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.ts index a08c5d1670a09..d74782c373942 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.ts @@ -18,18 +18,19 @@ */ import _ from 'lodash'; +import { ControlParams } from '../editor_utils'; -export function getLineageMap(controlParamsList) { - function getControlParamsById(controlId) { +export function getLineageMap(controlParamsList: ControlParams[]) { + function getControlParamsById(controlId: string) { return controlParamsList.find(controlParams => { return controlParams.id === controlId; }); } - const lineageMap = new Map(); + const lineageMap = new Map(); controlParamsList.forEach(rootControlParams => { const lineage = [rootControlParams.id]; - const getLineage = controlParams => { + const getLineage = (controlParams: ControlParams) => { if ( _.has(controlParams, 'parent') && controlParams.parent !== '' && @@ -37,7 +38,10 @@ export function getLineageMap(controlParamsList) { ) { lineage.push(controlParams.parent); const parent = getControlParamsById(controlParams.parent); - getLineage(parent); + + if (parent) { + getLineage(parent); + } } }; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.js b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.ts similarity index 98% rename from src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.ts index fe180357067a9..af6e2444b486f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.ts @@ -22,7 +22,7 @@ import { getLineageMap } from './lineage_map'; import { getParentCandidates } from './parent_candidates'; import { CONTROL_TYPES, newControl } from '../editor_utils'; -function createControlParams(id) { +function createControlParams(id: any) { const controlParams = newControl(CONTROL_TYPES.LIST); controlParams.id = id; controlParams.indexPattern = 'indexPatternId'; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.js b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.ts similarity index 85% rename from src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.ts index 17005c24dd41d..af4fddef19001 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.ts @@ -17,9 +17,13 @@ * under the License. */ -import { getTitle } from '../editor_utils'; +import { getTitle, ControlParams } from '../editor_utils'; -export function getParentCandidates(controlParamsList, controlId, lineageMap) { +export function getParentCandidates( + controlParamsList: ControlParams[], + controlId: string, + lineageMap: Map +) { return controlParamsList .filter(controlParams => { // Ignore controls that do not have index pattern and field set @@ -28,7 +32,7 @@ export function getParentCandidates(controlParamsList, controlId, lineageMap) { } // Ignore controls that would create a circular graph const lineage = lineageMap.get(controlParams.id); - if (lineage.includes(controlId)) { + if (lineage?.includes(controlId)) { return false; } return true; diff --git a/src/legacy/core_plugins/input_control_vis/public/plugin.ts b/src/legacy/core_plugins/input_control_vis/public/plugin.ts new file mode 100644 index 0000000000000..e9ffad8b35f21 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/plugin.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; + +import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; +import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { createInputControlVisFn } from './input_control_fn'; +import { createInputControlVisTypeDefinition } from './input_control_vis_type'; + +type InputControlVisCoreSetup = CoreSetup; + +export interface InputControlVisDependencies { + core: InputControlVisCoreSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export interface InputControlVisPluginSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export interface InputControlVisPluginStartDependencies { + expressions: ReturnType; + visualizations: VisualizationsStart; + data: DataPublicPluginStart; +} + +/** @internal */ +export class InputControlVisPlugin implements Plugin, void> { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: InputControlVisCoreSetup, + { expressions, visualizations, data }: InputControlVisPluginSetupDependencies + ) { + const visualizationDependencies: Readonly = { + core, + data, + }; + + expressions.registerFunction(createInputControlVisFn); + visualizations.types.createBaseVisualization( + createInputControlVisTypeDefinition(visualizationDependencies) + ); + } + + public start(core: CoreStart, deps: InputControlVisPluginStartDependencies) { + // nothing to do here + } +} diff --git a/src/legacy/core_plugins/input_control_vis/public/register_vis.js b/src/legacy/core_plugins/input_control_vis/public/register_vis.js deleted file mode 100644 index 09993be3614f2..0000000000000 --- a/src/legacy/core_plugins/input_control_vis/public/register_vis.js +++ /dev/null @@ -1,72 +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 { VisController } from './vis_controller'; -import { ControlsTab } from './components/editor/controls_tab'; -import { OptionsTab } from './components/editor/options_tab'; -import { i18n } from '@kbn/i18n'; -import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; -import { Status, defaultFeedbackMessage } from '../../visualizations/public'; - -export const inputControlVisDefinition = { - name: 'input_control_vis', - title: i18n.translate('inputControl.register.controlsTitle', { - defaultMessage: 'Controls', - }), - icon: 'visControls', - description: i18n.translate('inputControl.register.controlsDescription', { - defaultMessage: 'Create interactive controls for easy dashboard manipulation.', - }), - stage: 'experimental', - requiresUpdateStatus: [Status.PARAMS, Status.TIME], - feedbackMessage: defaultFeedbackMessage, - visualization: VisController, - visConfig: { - defaults: { - controls: [], - updateFiltersOnChange: false, - useTimeFilter: false, - pinFilters: false, - }, - }, - editor: 'default', - editorConfig: { - optionTabs: [ - { - name: 'controls', - title: i18n.translate('inputControl.register.tabs.controlsTitle', { - defaultMessage: 'Controls', - }), - editor: ControlsTab, - }, - { - name: 'options', - title: i18n.translate('inputControl.register.tabs.optionsTitle', { - defaultMessage: 'Options', - }), - editor: OptionsTab, - }, - ], - }, - requestHandler: 'none', - responseHandler: 'none', -}; - -// register the provider with the visTypes registry -visualizations.types.createBaseVisualization(inputControlVisDefinition); diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js deleted file mode 100644 index 6a1e23769e28c..0000000000000 --- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -import { InputControlVis } from './components/vis/input_control_vis'; -import { controlFactory } from './control/control_factory'; -import { getLineageMap } from './lineage'; -import { npStart } from 'ui/new_platform'; -import { SearchSource } from '../../../ui/public/courier/search_source/search_source'; - -class VisController { - constructor(el, vis) { - this.el = el; - this.vis = vis; - this.controls = []; - - this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); - - this.filterManager = npStart.plugins.data.query.filterManager; - this.updateSubsciption = this.filterManager.getUpdates$().subscribe(this.queryBarUpdateHandler); - } - - async render(visData, visParams, status) { - if (status.params || (visParams.useTimeFilter && status.time)) { - this.visParams = visParams; - this.controls = []; - this.controls = await this.initControls(); - this.drawVis(); - } - } - - destroy() { - this.updateSubsciption.unsubscribe(); - unmountComponentAtNode(this.el); - this.controls.forEach(control => control.destroy()); - } - - drawVis = () => { - render( - - - , - this.el - ); - }; - - async initControls() { - const controlParamsList = this.visParams.controls.filter(controlParams => { - // ignore controls that do not have indexPattern or field - return controlParams.indexPattern && controlParams.fieldName; - }); - - const controlFactoryPromises = controlParamsList.map(controlParams => { - const factory = controlFactory(controlParams); - return factory(controlParams, this.visParams.useTimeFilter, SearchSource); - }); - const controls = await Promise.all(controlFactoryPromises); - - const getControl = id => { - return controls.find(control => { - return id === control.id; - }); - }; - - const controlInitPromises = []; - getLineageMap(controlParamsList).forEach((lineage, controlId) => { - // first lineage item is the control. remove it - lineage.shift(); - const ancestors = []; - lineage.forEach(ancestorId => { - ancestors.push(getControl(ancestorId)); - }); - const control = getControl(controlId); - control.setAncestors(ancestors); - controlInitPromises.push(control.fetch()); - }); - - await Promise.all(controlInitPromises); - return controls; - } - - stageFilter = async (controlIndex, newValue) => { - this.controls[controlIndex].set(newValue); - if (this.visParams.updateFiltersOnChange) { - // submit filters on each control change - this.submitFilters(); - } else { - // Do not submit filters, just update vis so controls are updated with latest value - await this.updateNestedControls(); - this.drawVis(); - } - }; - - submitFilters = () => { - const stagedControls = this.controls.filter(control => { - return control.hasChanged(); - }); - - const newFilters = stagedControls - .filter(control => { - return control.hasKbnFilter(); - }) - .map(control => { - return control.getKbnFilter(); - }); - - stagedControls.forEach(control => { - // to avoid duplicate filters, remove any old filters for control - control.filterManager.findFilters().forEach(existingFilter => { - this.filterManager.removeFilter(existingFilter); - }); - }); - - // Clean up filter pills for nested controls that are now disabled because ancestors are not set. - // This has to be done after looking up the staged controls because otherwise removing a filter - // will re-sync the controls of all other filters. - this.controls.map(control => { - if (control.hasAncestors() && control.hasUnsetAncestor()) { - control.filterManager.findFilters().forEach(existingFilter => { - this.filterManager.removeFilter(existingFilter); - }); - } - }); - - this.filterManager.addFilters(newFilters, this.visParams.pinFilters); - }; - - clearControls = async () => { - this.controls.forEach(control => { - control.clear(); - }); - await this.updateNestedControls(); - this.drawVis(); - }; - - updateControlsFromKbn = async () => { - this.controls.forEach(control => { - control.reset(); - }); - await this.updateNestedControls(); - this.drawVis(); - }; - - async updateNestedControls() { - const fetchPromises = this.controls.map(async control => { - if (control.hasAncestors()) { - await control.fetch(); - } - }); - return await Promise.all(fetchPromises); - } - - hasChanges = () => { - return this.controls - .map(control => { - return control.hasChanged(); - }) - .reduce((a, b) => { - return a || b; - }); - }; - - hasValues = () => { - return this.controls - .map(control => { - return control.hasValue(); - }) - .reduce((a, b) => { - return a || b; - }); - }; - - refreshControl = async (controlIndex, query) => { - await this.controls[controlIndex].fetch(query); - this.drawVis(); - }; -} - -export { VisController }; diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx new file mode 100644 index 0000000000000..849b58b8ee2da --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.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 from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { I18nStart } from 'kibana/public'; +import { Vis, VisParams, SearchSource } from './legacy_imports'; + +import { InputControlVis } from './components/vis/input_control_vis'; +import { getControlFactory } from './control/control_factory'; +import { getLineageMap } from './lineage'; +import { ControlParams } from './editor_utils'; +import { RangeControl } from './control/range_control_factory'; +import { ListControl } from './control/list_control_factory'; +import { InputControlVisDependencies } from './plugin'; +import { FilterManager, esFilters } from '../../../../plugins/data/public'; + +export const createInputControlVisController = (deps: InputControlVisDependencies) => { + return class InputControlVisController { + private I18nContext?: I18nStart['Context']; + + controls: Array; + queryBarUpdateHandler: () => void; + filterManager: FilterManager; + updateSubsciption: any; + visParams?: VisParams; + + constructor(public el: Element, public vis: Vis) { + this.controls = []; + + this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); + + this.filterManager = deps.data.query.filterManager; + this.updateSubsciption = this.filterManager + .getUpdates$() + .subscribe(this.queryBarUpdateHandler); + } + + async render(visData: any, visParams: VisParams, status: any) { + if (status.params || (visParams.useTimeFilter && status.time)) { + this.visParams = visParams; + this.controls = []; + this.controls = await this.initControls(); + const [{ i18n }] = await deps.core.getStartServices(); + this.I18nContext = i18n.Context; + this.drawVis(); + } + } + + destroy() { + this.updateSubsciption.unsubscribe(); + unmountComponentAtNode(this.el); + this.controls.forEach(control => control.destroy()); + } + + drawVis = () => { + if (!this.I18nContext) { + throw new Error('no i18n context found'); + } + + render( + + + , + this.el + ); + }; + + async initControls() { + const controlParamsList = (this.visParams?.controls as ControlParams[])?.filter( + controlParams => { + // ignore controls that do not have indexPattern or field + return controlParams.indexPattern && controlParams.fieldName; + } + ); + + const controlFactoryPromises = controlParamsList.map(controlParams => { + const factory = getControlFactory(controlParams); + return factory(controlParams, this.visParams?.useTimeFilter, SearchSource, deps); + }); + const controls = await Promise.all(controlFactoryPromises); + + const getControl = (controlId: string) => { + return controls.find(({ id }) => id === controlId); + }; + + const controlInitPromises: Array> = []; + getLineageMap(controlParamsList).forEach((lineage, controlId) => { + // first lineage item is the control. remove it + lineage.shift(); + const ancestors: Array = []; + lineage.forEach(ancestorId => { + const control = getControl(ancestorId); + + if (control) { + ancestors.push(control); + } + }); + const control = getControl(controlId); + + if (control) { + control.setAncestors(ancestors); + controlInitPromises.push(control.fetch()); + } + }); + + await Promise.all(controlInitPromises); + return controls; + } + + stageFilter = async (controlIndex: number, newValue: any) => { + this.controls[controlIndex].set(newValue); + if (this.visParams?.updateFiltersOnChange) { + // submit filters on each control change + this.submitFilters(); + } else { + // Do not submit filters, just update vis so controls are updated with latest value + await this.updateNestedControls(); + this.drawVis(); + } + }; + + submitFilters = () => { + const stagedControls = this.controls.filter(control => { + return control.hasChanged(); + }); + + const newFilters = stagedControls + .map(control => control.getKbnFilter()) + .filter((filter): filter is esFilters.Filter => { + return filter !== null; + }); + + stagedControls.forEach(control => { + // to avoid duplicate filters, remove any old filters for control + control.filterManager.findFilters().forEach(existingFilter => { + this.filterManager.removeFilter(existingFilter); + }); + }); + + // Clean up filter pills for nested controls that are now disabled because ancestors are not set. + // This has to be done after looking up the staged controls because otherwise removing a filter + // will re-sync the controls of all other filters. + this.controls.map(control => { + if (control.hasAncestors() && control.hasUnsetAncestor()) { + control.filterManager.findFilters().forEach(existingFilter => { + this.filterManager.removeFilter(existingFilter); + }); + } + }); + + this.filterManager.addFilters(newFilters, this.visParams?.pinFilters); + }; + + clearControls = async () => { + this.controls.forEach(control => { + control.clear(); + }); + await this.updateNestedControls(); + this.drawVis(); + }; + + updateControlsFromKbn = async () => { + this.controls.forEach(control => { + control.reset(); + }); + await this.updateNestedControls(); + this.drawVis(); + }; + + async updateNestedControls() { + const fetchPromises = this.controls.map(async control => { + if (control.hasAncestors()) { + await control.fetch(); + } + }); + return await Promise.all(fetchPromises); + } + + hasChanges = () => { + return this.controls.map(control => control.hasChanged()).some(control => control); + }; + + hasValues = () => { + return this.controls + .map(control => { + return control.hasValue(); + }) + .reduce((a, b) => { + return a || b; + }); + }; + + refreshControl = async (controlIndex: number, query: any) => { + await this.controls[controlIndex].fetch(query); + this.drawVis(); + }; + }; +}; diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 1cbcba273df41..51177aabddf4f 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -26,15 +26,11 @@ import { importApi } from './server/routes/api/import'; import { exportApi } from './server/routes/api/export'; import { homeApi } from './server/routes/api/home'; import { managementApi } from './server/routes/api/management'; -import { scriptsApi } from './server/routes/api/scripts'; -import { registerSuggestionsApi } from './server/routes/api/suggestions'; -import { registerKqlTelemetryApi } from './server/routes/api/kql_telemetry'; import { registerFieldFormats } from './server/field_formats/register'; import { registerTutorials } from './server/tutorials/register'; import * as systemApi from './server/lib/system_api'; import mappings from './mappings.json'; import { getUiSettingDefaults } from './ui_setting_defaults'; -import { makeKQLUsageCollector } from './server/lib/kql_usage_collector'; import { registerCspCollector } from './server/lib/csp_usage_collector'; import { injectVars } from './inject_vars'; import { i18n } from '@kbn/i18n'; @@ -338,16 +334,12 @@ export default function(kibana) { init: async function(server) { const { usageCollection } = server.newPlatform.setup.plugins; // routes - scriptsApi(server); importApi(server); exportApi(server); homeApi(server); managementApi(server); - registerSuggestionsApi(server); - registerKqlTelemetryApi(server); registerFieldFormats(server); registerTutorials(server); - makeKQLUsageCollector(usageCollection, server); registerCspCollector(usageCollection, server); server.expose('systemApi', systemApi); server.injectUiAppVars('kibana', () => injectVars(server)); diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc b/src/legacy/core_plugins/kibana/public/.eslintrc deleted file mode 100644 index cc44af915ba25..0000000000000 --- a/src/legacy/core_plugins/kibana/public/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -rules: - no-console: 2 - 'import/no-default-export': error diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc.js b/src/legacy/core_plugins/kibana/public/.eslintrc.js new file mode 100644 index 0000000000000..160adcc5b63f1 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/.eslintrc.js @@ -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. + */ + +const path = require('path'); + +/** + * Builds custom restricted paths configuration for the shimmed plugins within the kibana plugin. + * These custom rules extend the default checks in the top level `eslintrc.js` by also checking two other things: + * * Making sure nothing within np_ready imports from the `ui` directory + * * Making sure no other code is importing things deep from within the shimmed plugins + * @param shimmedPlugins List of plugin names within the kibana plugin that are partially np ready + * @returns zones configuration for the no-restricted-paths linter + */ +function buildRestrictedPaths(shimmedPlugins) { + return shimmedPlugins.map(shimmedPlugin => ([{ + target: [ + `src/legacy/core_plugins/kibana/public/${shimmedPlugin}/np_ready/**/*`, + ], + from: [ + 'ui/**/*', + 'src/legacy/ui/**/*', + 'src/legacy/core_plugins/kibana/public/**/*', + 'src/legacy/core_plugins/data/public/**/*', + '!src/legacy/core_plugins/data/public/index.ts', + `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, + ], + allowSameFolder: false, + errorMessage: `${shimmedPlugin} is a shimmed plugin that is not allowed to import modules from the legacy platform. If you need legacy modules for the transition period, import them either in the legacy_imports, kibana_services or index module.`, + }, { + target: [ + 'src/**/*', + `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, + 'x-pack/**/*', + ], + from: [ + `src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, + `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/index.ts`, + ], + allowSameFolder: false, + errorMessage: `kibana/public/${shimmedPlugin} is behaving like a NP plugin and does not allow deep imports. If you need something from within ${shimmedPlugin} in another plugin, consider re-exporting it from the top level index module`, + }])).reduce((acc, part) => [...acc, ...part], []); +} + +module.exports = { + rules: { + 'no-console': 2, + 'import/no-default-export': 'error', + '@kbn/eslint/no-restricted-paths': [ + 'error', + { + basePath: path.resolve(__dirname, '../../../../../'), + zones: buildRestrictedPaths(['visualize', 'discover', 'dashboard', 'devTools', 'home']), + }, + ], + }, +}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index 4ea658bcd03ef..178014a691be3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -230,14 +230,18 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` type="dashboardApp" >

{title}

- -
- - {isChangeable && ( - - - } - > - onChange()} - iconSide="right" - iconType="arrowDown" - color="text" - /> - - - )} - - ); -} diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index 1335dbb01d4a7..1cefabe08c2d5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -69,24 +69,24 @@ import { configureAppAngularModule } from 'ui/legacy_compat'; import { IndexPatterns } from '../../../../../plugins/data/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; -import { createDocTableDirective } from './angular/doc_table/doc_table'; -import { createTableHeaderDirective } from './angular/doc_table/components/table_header'; +import { createDocTableDirective } from './np_ready/angular/doc_table/doc_table'; +import { createTableHeaderDirective } from './np_ready/angular/doc_table/components/table_header'; import { createToolBarPagerButtonsDirective, createToolBarPagerTextDirective, -} from './angular/doc_table/components/pager'; -import { createTableRowDirective } from './angular/doc_table/components/table_row'; -import { createPagerFactory } from './angular/doc_table/lib/pager/pager_factory'; -import { createInfiniteScrollDirective } from './angular/doc_table/infinite_scroll'; -import { createDocViewerDirective } from './angular/doc_viewer'; -import { createFieldSearchDirective } from './components/field_chooser/discover_field_search_directive'; -import { createIndexPatternSelectDirective } from './components/field_chooser/discover_index_pattern_directive'; -import { createStringFieldProgressBarDirective } from './components/field_chooser/string_progress_bar'; +} from './np_ready/angular/doc_table/components/pager'; +import { createTableRowDirective } from './np_ready/angular/doc_table/components/table_row'; +import { createPagerFactory } from './np_ready/angular/doc_table/lib/pager/pager_factory'; +import { createInfiniteScrollDirective } from './np_ready/angular/doc_table/infinite_scroll'; +import { createDocViewerDirective } from './np_ready/angular/doc_viewer'; +import { createFieldSearchDirective } from './np_ready/components/field_chooser/discover_field_search_directive'; +import { createIndexPatternSelectDirective } from './np_ready/components/field_chooser/discover_index_pattern_directive'; +import { createStringFieldProgressBarDirective } from './np_ready/components/field_chooser/string_progress_bar'; // @ts-ignore -import { createFieldChooserDirective } from './components/field_chooser/field_chooser'; +import { createFieldChooserDirective } from './np_ready/components/field_chooser/field_chooser'; // @ts-ignore -import { createDiscoverFieldDirective } from './components/field_chooser/discover_field'; +import { createDiscoverFieldDirective } from './np_ready/components/field_chooser/discover_field'; import { DiscoverStartPlugins } from './plugin'; /** diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts index 6ea658682c89d..e85408dc9bf6b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -36,3 +36,5 @@ export const pluginInstance = plugin({} as PluginInitializerContext); SavedObjectRegistryProvider.register((savedSearches: any) => { return savedSearches; }); + +export { createSavedSearchesService } from './saved_searches/saved_searches'; diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index d13d0dc868a58..ae388a243dd2b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -17,7 +17,7 @@ * under the License. */ import angular from 'angular'; // just used in embeddables and discover controller -import { DiscoverServices } from './helpers/build_services'; +import { DiscoverServices } from './build_services'; let angularModule: any = null; let services: DiscoverServices | null = null; @@ -47,6 +47,10 @@ export function setServices(newServices: any) { services = newServices; } +// import directives that +import 'ui/directives/css_truncate'; +import 'ui/directives/field_name'; + // EXPORT legacy static dependencies, should be migrated when available in a new version; export { angular }; export { wrapInI18nContext } from 'ui/i18n'; @@ -59,6 +63,9 @@ export { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern, SearchSource, + EsQuerySortValue, + SortDirection, + SearchSourceContract, } from '../../../../ui/public/courier'; // @ts-ignore export { intervalOptions } from 'ui/agg_types/buckets/_interval_options'; @@ -78,6 +85,8 @@ export { tabifyAggResponse } from 'ui/agg_response/tabify'; export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; +// @ts-ignore +export { formatMsg, formatStack } from 'ui/notify/lib/index'; // EXPORT types export { Vis } from 'ui/vis'; @@ -90,3 +99,6 @@ export { export { ElasticSearchHit } from 'ui/registry/doc_views_types'; export { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; export { Adapters } from 'ui/inspector/types'; +export { DocView, DocViewInput } from 'ui/registry/doc_views_types'; +export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; +export { IInjector } from 'ui/chrome'; diff --git a/src/legacy/core_plugins/kibana/public/discover/_discover.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/_discover.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/_hacks.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/_hacks.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/_hacks.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/_hacks.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/_index.scss new file mode 100644 index 0000000000000..0de036b1e1707 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/_index.scss @@ -0,0 +1,15 @@ +// Discover plugin styles +@import 'mixins'; +@import 'discover'; +@import 'hacks'; + +// Prefix all styles with "dsc" to avoid conflicts. +// Examples +// dscTable +// dscTable__footer +// monChart__legend--small +// monChart__legend-isLoading + +@import 'components/index'; +@import 'angular/index'; +@import 'embeddable/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/_mixins.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/_mixins.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/_mixins.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/_mixins.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/_index.scss new file mode 100644 index 0000000000000..9e00ade3d41f6 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/_index.scss @@ -0,0 +1,3 @@ +@import 'directives/index'; +@import 'doc_table/index'; +@import 'context/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/context.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js index d33e3424e4647..a370c66ae330b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js @@ -19,12 +19,12 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getAngularModule, getServices, subscribeWithScope } from './../kibana_services'; +import { getAngularModule, getServices, subscribeWithScope } from '../../kibana_services'; import './context_app'; import contextAppRouteTemplate from './context.html'; import { getRootBreadcrumbs } from '../helpers/breadcrumbs'; -import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; +import { FilterStateManager } from '../../../../../data/public'; const { chrome } = getServices(); const k7Breadcrumbs = $route => { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/NOTES.md b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/NOTES.md similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/NOTES.md rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/NOTES.md diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js index 3bc83cacaaf28..53be4e5bd0f2d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import moment from 'moment'; -import { SearchSource } from '../../../../kibana_services'; +import { SearchSource } from '../../../../../kibana_services'; export function createIndexPatternsStub() { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts index fd71b7c49e837..a6c6d91084625 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts @@ -17,14 +17,14 @@ * under the License. */ -import { IndexPattern, SearchSource } from '../../../kibana_services'; +import { IndexPattern, SearchSource } from '../../../../kibana_services'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; -import { esFilters, IndexPatternsContract } from '../../../../../../../../plugins/data/public'; +import { esFilters, IndexPatternsContract } from '../../../../../../../../../plugins/data/public'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/date_conversion.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/date_conversion.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/date_conversion.test.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/date_conversion.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/sorting.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/sorting.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/date_conversion.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/date_conversion.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/date_conversion.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/date_conversion.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts index 19c2ee2cdfe10..1351421e1af04 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts @@ -20,7 +20,7 @@ import { EsQuerySortValue, SortDirection, SearchSourceContract, -} from '../../../../../../../../ui/public/courier'; +} from '../../../../../kibana_services'; import { convertTimeValueToIso } from './date_conversion'; import { EsHitRecordList } from '../context'; import { IntervalValue } from './generate_intervals'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts index cb4878239ff92..373dc37e56f6f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SortDirection } from '../../../../../../../../ui/public/courier'; +import { SortDirection } from '../../../../../kibana_services'; export type IntervalValue = number | null; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_search_after.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_search_after.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_sort.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_sort.ts index 39c69112e58cb..8bcf5328f24ba 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_sort.ts @@ -17,7 +17,7 @@ * under the License. */ -import { EsQuerySortValue, SortDirection } from '../../../../../../../../ui/public/courier/types'; +import { EsQuerySortValue, SortDirection } from '../../../../../kibana_services'; /** * Returns `EsQuerySort` which is used to sort records in the ES query diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/sorting.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/sorting.ts index 4a0f531845f46..ef1be8d48d338 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/sorting.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IndexPattern } from '../../../../kibana_services'; +import { IndexPattern } from '../../../../../kibana_services'; export enum SortDirection { asc = 'asc', diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_action_bar.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_action_bar.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_action_bar.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_action_bar.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts index 55a378367392c..697b039adde81 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { getAngularModule, wrapInI18nContext } from '../../../../kibana_services'; +import { getAngularModule, wrapInI18nContext } from '../../../../../kibana_services'; import { ActionBar } from './action_bar'; getAngularModule().directive('contextActionBar', function(reactDirective: any) { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_warning.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_warning.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_warning.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_warning.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/index.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/index.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js index b7f6fab676a1e..966ecffda7755 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js @@ -20,13 +20,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { getServices, SearchSource } from '../../../kibana_services'; +import { getServices, SearchSource } from '../../../../kibana_services'; import { fetchAnchorProvider } from '../api/anchor'; import { fetchContextProvider } from '../api/context'; import { getQueryParameterActions } from '../query_parameters'; import { FAILURE_REASONS, LOADING_STATUS } from './constants'; -import { MarkdownSimple } from '../../../../../../kibana_react/public'; +import { MarkdownSimple } from '../../../../../../../kibana_react/public'; export function QueryActionsProvider(Promise) { const fetchAnchor = fetchAnchorProvider(getServices().indexPatterns, new SearchSource()); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/constants.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/constants.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query/constants.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/constants.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query/state.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/state.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query/state.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/state.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js index 01573f8983d0f..c5f1836bcc0e1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js @@ -18,8 +18,8 @@ */ import _ from 'lodash'; -import { getServices } from '../../../kibana_services'; -import { generateFilters } from '../../../../../../../../plugins/data/public'; +import { getServices } from '../../../../kibana_services'; +import { generateFilters } from '../../../../../../../../../plugins/data/public'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, QUERY_PARAMETER_KEYS } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/constants.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/constants.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/state.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/state.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/state.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/state.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context_app.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/context_app.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js index 3f91980ccc50f..5fa0958249d79 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { getServices, callAfterBindingsWorkaround, getAngularModule } from './../kibana_services'; +import { getServices, callAfterBindingsWorkaround, getAngularModule } from '../../kibana_services'; import contextAppTemplate from './context_app.html'; import './context/components/action_bar'; import { getFirstSortableField } from './context/api/utils/sorting'; @@ -36,9 +36,6 @@ import { const { timefilter } = getServices(); -// load directives -import '../../../../data/public/legacy'; - const module = getAngularModule(); module.directive('contextApp', function ContextApp() { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/__snapshots__/no_results.test.js.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/__snapshots__/no_results.test.js.snap similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/__snapshots__/no_results.test.js.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/__snapshots__/no_results.test.js.snap index ed7a4b5d548ed..98cb3ccf6dd91 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/__snapshots__/no_results.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/__snapshots__/no_results.test.js.snap @@ -23,6 +23,7 @@ Array [ class="euiIcon euiIcon--medium euiIcon-isLoading euiCallOutHeader__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -214,6 +215,7 @@ Array [ class="euiIcon euiIcon--medium euiIcon-isLoading euiCallOutHeader__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -253,6 +255,7 @@ Array [ class="euiIcon euiIcon--medium euiIcon-isLoading euiCallOutHeader__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -360,6 +363,7 @@ Array [ class="euiIcon euiIcon--medium euiIcon-isLoading euiCallOutHeader__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/_histogram.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_histogram.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/_histogram.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_histogram.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/_no_results.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_no_results.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/_no_results.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_no_results.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx index 496e1cf375588..28ce64c0a5f9c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx @@ -45,7 +45,7 @@ import { import { i18n } from '@kbn/i18n'; import { EuiChartThemeType } from '@elastic/eui/src/themes/charts/themes'; import { Subscription } from 'rxjs'; -import { getServices, timezoneProvider } from '../../kibana_services'; +import { getServices, timezoneProvider } from '../../../kibana_services'; export interface DiscoverHistogramProps { chartData: any; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js similarity index 91% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js index eb4cf10a2d28f..1a3922dfc2008 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js @@ -17,12 +17,11 @@ * under the License. */ -import '../../../../../../ui/public/render_complete/directive'; import { DiscoverNoResults } from './no_results'; import { DiscoverUninitialized } from './uninitialized'; import { DiscoverUnsupportedIndexPattern } from './unsupported_index_pattern'; import { DiscoverHistogram } from './histogram'; -import { getAngularModule, wrapInI18nContext } from '../../kibana_services'; +import { getAngularModule, wrapInI18nContext } from '../../../kibana_services'; const app = getAngularModule(); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.js index d22a8d495bd0e..ba02068590c14 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.js @@ -32,7 +32,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { getServices } from '../../kibana_services'; +import { getServices } from '../../../kibana_services'; // eslint-disable-next-line react/prefer-stateless-function export class DiscoverNoResults extends Component { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.test.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.test.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.test.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.test.js index 33dff54f94c7f..7de792c612993 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.test.js @@ -22,7 +22,7 @@ import { renderWithIntl } from 'test_utils/enzyme_helpers'; import { DiscoverNoResults } from './no_results'; -jest.mock('../../kibana_services', () => { +jest.mock('../../../kibana_services', () => { return { getServices: () => ({ docLinks: { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/uninitialized.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/uninitialized.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/unsupported_index_pattern.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/unsupported_index_pattern.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/discover.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/angular/discover.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 39a7bb96e11e0..abf025524522b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -57,7 +57,8 @@ import { SavedObjectSaveModal, getAngularModule, ensureDefaultIndexPattern, -} from '../kibana_services'; + registerTimefilterWithGlobalStateFactory, +} from '../../kibana_services'; const { core, @@ -72,10 +73,9 @@ const { } = getServices(); import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; -import { generateFilters } from '../../../../../../plugins/data/public'; +import { generateFilters } from '../../../../../../../plugins/data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; -import { registerTimefilterWithGlobalStateFactory } from '../../../../../ui/public/timefilter/setup_router'; -import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; +import { FilterStateManager } from '../../../../../data/public'; const { getSavedQuery } = data.query.savedQueries; @@ -328,6 +328,7 @@ function discoverController( makeUrl: searchId => { return kbnUrl.eval('#/discover/{{id}}', { id: searchId }); }, + I18nContext: core.i18n.Context, }); }, }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts index af9556656afab..459dcfb30d17b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { getAngularModule, wrapInI18nContext, getServices } from '../kibana_services'; +import { getAngularModule, wrapInI18nContext, getServices } from '../../kibana_services'; // @ts-ignore import { getRootBreadcrumbs } from '../helpers/breadcrumbs'; import html from './doc.html'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_doc_table.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_doc_table.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_index.scss new file mode 100644 index 0000000000000..3663d807851c4 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_index.scss @@ -0,0 +1,2 @@ +@import 'doc_table'; +@import 'components/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/columns.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/columns.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_index.scss new file mode 100644 index 0000000000000..6a294c1ed173d --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_index.scss @@ -0,0 +1,2 @@ +@import 'table_header'; +@import 'table_row/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_table_header.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_table_header.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_table_header.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_table_header.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts index 3a037971a1253..f21f3b17c6955 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { wrapInI18nContext } from '../../../../kibana_services'; +import { wrapInI18nContext } from '../../../../../kibana_services'; import { ToolBarPagerText } from './tool_bar_pager_text'; import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts index 055f14f164476..a5cb9180333a4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { wrapInI18nContext } from 'ui/i18n'; import { IUiSettingsClient } from 'kibana/public'; import { TableHeader } from './table_header/table_header'; +import { wrapInI18nContext } from '../../../../kibana_services'; export function createTableHeaderDirective(reactDirective: any, config: IUiSettingsClient) { return reactDirective( diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx index 80f963c8ccb3e..13833d724967a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { IndexPattern } from '../../../../kibana_services'; +import { IndexPattern } from '../../../../../kibana_services'; // @ts-ignore -import { shortenDottedString } from '../../../../../../common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../../../../../../common/utils/shorten_dotted_string'; export type SortOrder = [string, 'asc' | 'desc']; export interface ColumnProps { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx index e5706b5e3c9bb..ef3d4ecc4b18f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx @@ -23,7 +23,7 @@ import { TableHeader } from './table_header'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; -import { IndexPattern, IFieldType } from '../../../../kibana_services'; +import { IndexPattern, IFieldType } from '../../../../../kibana_services'; function getMockIndexPattern() { return ({ diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.tsx similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.tsx index 71674710ac855..17b961dbe6832 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { IndexPattern } from '../../../../kibana_services'; +import { IndexPattern } from '../../../../../kibana_services'; // @ts-ignore import { TableHeaderColumn } from './table_header_column'; import { SortOrder, getDisplayedColumns } from './helpers'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header_column.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header_column.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts index 8ff4ab46ef532..8df035d098469 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts @@ -24,15 +24,15 @@ import { IUiSettingsClient } from 'kibana/public'; import rison from 'rison-node'; import '../../doc_viewer'; // @ts-ignore -import { noWhiteSpace } from '../../../../../common/utils/no_white_space'; +import { noWhiteSpace } from '../../../../../../common/utils/no_white_space'; import openRowHtml from './table_row/open.html'; import detailsHtml from './table_row/details.html'; -import { dispatchRenderComplete } from '../../../../../../../../plugins/kibana_utils/public'; +import { dispatchRenderComplete } from '../../../../../../../../../plugins/kibana_utils/public'; import cellTemplateHtml from '../components/table_row/cell.html'; import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; -import { esFilters } from '../../../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../../plugins/data/public'; // guesstimate at the minimum number of chars wide cells in the table should be const MIN_LINE_LENGTH = 20; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_cell.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_cell.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_cell.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_cell.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_details.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_details.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_details.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_details.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_index.scss new file mode 100644 index 0000000000000..c7ccdaa39ff65 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_index.scss @@ -0,0 +1,3 @@ +@import 'cell'; +@import 'details'; +@import 'open'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_open.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_open.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_open.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_open.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/cell.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/cell.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/details.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/open.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/open.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/truncate_by_height.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/truncate_by_height.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/truncate_by_height.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/truncate_by_height.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.ts index 92ebc24c6e378..3329ffc7cd102 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.ts @@ -23,7 +23,7 @@ import html from './doc_table.html'; import './infinite_scroll'; import './components/table_header'; import './components/table_row'; -import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; +import { dispatchRenderComplete } from '../../../../../../../../plugins/kibana_utils/public'; import './components/pager'; import './lib/pager'; // @ts-ignore diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table_strings.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table_strings.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/doc_table_strings.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table_strings.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/index.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/index.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/infinite_scroll.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/infinite_scroll.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/infinite_scroll.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.d.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.d.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.d.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.d.ts index ebf715a64d939..0bf8a93a88367 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.d.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IIndexPattern } from '../../../../../../../../plugins/data/public'; +import { IIndexPattern } from '../../../../../../../../../plugins/data/public'; import { SortOrder } from '../components/table_header/helpers'; export function getSort( diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort_for_search_source.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort_for_search_source.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort_for_search_source.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort_for_search_source.ts index 952eadf7cbd94..26bba4589cf6a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/get_sort_for_search_source.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort_for_search_source.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { IndexPattern } from '../../../kibana_services'; +import { IndexPattern } from '../../../../kibana_services'; import { SortOrder } from '../components/table_header/helpers'; import { getSort } from './get_sort'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/pager/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/pager/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/pager/pager.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/pager/pager.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/pager/pager_factory.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/lib/pager/pager_factory.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/pager/pager_factory.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/get_painless_error.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/get_painless_error.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/index.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/index.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/application.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/application.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/application.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/application.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss new file mode 100644 index 0000000000000..0491430e5fddd --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss @@ -0,0 +1,3 @@ +@import 'fetch_error/index'; +@import 'field_chooser/index'; +@import 'doc_viewer/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.test.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.test.tsx index 4df56483fa5c6..656e8598aa12f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.test.tsx @@ -28,7 +28,7 @@ jest.mock('../doc_viewer/doc_viewer', () => ({ DocViewer: 'test', })); -jest.mock('../../kibana_services', () => { +jest.mock('../../../kibana_services', () => { return { getServices: () => ({ metadata: { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx index 7020addb2bc6d..819eb9df592bd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx @@ -22,7 +22,7 @@ import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic import { IndexPatternsContract } from 'src/plugins/data/public'; import { DocViewer } from '../doc_viewer/doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; -import { ElasticSearchHit, getServices } from '../../kibana_services'; +import { ElasticSearchHit, getServices } from '../../../kibana_services'; export interface ElasticSearchResult { hits: { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.ts index 20bffe829de16..a40d9731a04f5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc/use_es_doc_search.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.ts @@ -17,7 +17,7 @@ * under the License. */ import { useEffect, useState } from 'react'; -import { ElasticSearchHit, IndexPattern } from '../../kibana_services'; +import { ElasticSearchHit, IndexPattern } from '../../../kibana_services'; import { DocProps } from './doc'; export enum ElasticRequestState { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_doc_viewer.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_doc_viewer.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_doc_viewer.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_doc_viewer.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.test.tsx similarity index 79% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.test.tsx index 158ed4ccc7759..c0644a6458694 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.test.tsx @@ -21,33 +21,46 @@ import { mount, shallow } from 'enzyme'; import { DocViewer } from './doc_viewer'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { - addDocView, - emptyDocViews, - DocViewRenderProps, - getDocViewsSorted as mockGetDocViewsSorted, -} from 'ui/registry/doc_views'; +import { DocViewRenderProps, DocViewInput, getServices } from '../../../kibana_services'; + +jest.mock('../../../kibana_services', () => { + const docViews: DocViewInput[] = []; + + function addDocView(docView: DocViewInput) { + docViews.push(docView); + } -jest.mock('../../kibana_services', () => { return { getServices: () => ({ docViewsRegistry: { - getDocViewsSorted: (hit: any) => { - return mockGetDocViewsSorted(hit); + getDocViewsSorted: () => { + return docViews; }, + addDocView, + docViews, }, }), + formatMsg: (x: any) => String(x), + formatStack: (x: any) => String(x), }; }); +const { + docViewsRegistry: { docViews, addDocView }, +} = getServices(); + +function emptyDocViews() { + docViews.length = 0; +} + beforeEach(() => { emptyDocViews(); jest.clearAllMocks(); }); test('Render with 3 different tabs', () => { - addDocView({ order: 20, title: 'React component', component: () =>
test
}); addDocView({ order: 10, title: 'Render function', render: jest.fn() }); + addDocView({ order: 20, title: 'React component', component: () =>
test
}); addDocView({ order: 30, title: 'Invalid doc view' }); const renderProps = { hit: {} } as DocViewRenderProps; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.tsx similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.tsx index a2d58439ad031..00926d70db25c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.tsx @@ -17,9 +17,8 @@ * under the License. */ import React from 'react'; -import { DocView } from 'ui/registry/doc_views_types'; import { EuiTabbedContent } from '@elastic/eui'; -import { getServices, DocViewRenderProps } from '../../kibana_services'; +import { getServices, DocViewRenderProps, DocView } from '../../../kibana_services'; import { DocViewerTab } from './doc_viewer_tab'; /** diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_error.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_error.tsx index 80b9cb5110db7..201ed562cfd6f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_error.tsx @@ -18,8 +18,7 @@ */ import React from 'react'; import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; -// @ts-ignore -import { formatMsg, formatStack } from 'ui/notify/lib/index'; +import { formatMsg, formatStack } from '../../../kibana_services'; interface Props { error: Error | string | null; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.test.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.test.tsx index 476d7cef159fb..c100e71b5f2b5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { DocViewRenderTab } from './doc_viewer_render_tab'; -import { DocViewRenderProps } from '../../kibana_services'; +import { DocViewRenderProps } from '../../../kibana_services'; test('Mounting and unmounting DocViewerRenderTab', () => { const unmountFn = jest.fn(); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.tsx index 8ac11caefff90..31a8808a3a1c9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { useRef, useEffect } from 'react'; -import { DocViewRenderFn, DocViewRenderProps } from '../../kibana_services'; +import { DocViewRenderFn, DocViewRenderProps } from '../../../kibana_services'; interface Props { render: DocViewRenderFn; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_tab.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_tab.tsx index 19558129eae8d..e08b0b2323d81 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_tab.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; -import { DocViewRenderProps, DocViewRenderFn } from '../../kibana_services'; +import { DocViewRenderProps, DocViewRenderFn } from '../../../kibana_services'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/_fetch_error.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/_fetch_error.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/fetch_error/_fetch_error.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/_fetch_error.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/fetch_error/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx index 8f67c1952f998..d2dda32f318fe 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx @@ -19,7 +19,7 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; -import { getAngularModule, wrapInI18nContext, getServices } from '../../kibana_services'; +import { getAngularModule, wrapInI18nContext, getServices } from '../../../kibana_services'; interface Props { fetchError: { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/fetch_error/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap new file mode 100644 index 0000000000000..42c11152e2633 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DiscoverIndexPattern Invalid props dont cause an exception: "" 1`] = `""`; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_field_chooser.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_index.scss new file mode 100644 index 0000000000000..91daed8ea048e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_index.scss @@ -0,0 +1 @@ +@import 'field_chooser'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/change_indexpattern.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/change_indexpattern.tsx new file mode 100644 index 0000000000000..60842ac81ee03 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/change_indexpattern.tsx @@ -0,0 +1,122 @@ +/* + * 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 React, { useState } from 'react'; +import { + EuiButtonEmpty, + EuiPopover, + EuiPopoverTitle, + EuiSelectable, + EuiButtonEmptyProps, +} from '@elastic/eui'; +import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable'; +import { IndexPatternRef } from './types'; + +export type ChangeIndexPatternTriggerProps = EuiButtonEmptyProps & { + label: string; + title?: string; +}; + +// TODO: refactor to shared component with ../../../../../../../../x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern + +export function ChangeIndexPattern({ + indexPatternRefs, + indexPatternId, + onChangeIndexPattern, + trigger, + selectableProps, +}: { + trigger: ChangeIndexPatternTriggerProps; + indexPatternRefs: IndexPatternRef[]; + onChangeIndexPattern: (newId: string) => void; + indexPatternId?: string; + selectableProps?: EuiSelectableProps; +}) { + const [isPopoverOpen, setPopoverIsOpen] = useState(false); + + const createTrigger = function() { + const { label, title, ...rest } = trigger; + return ( + setPopoverIsOpen(!isPopoverOpen)} + {...rest} + > + {label} + + ); + }; + + return ( + setPopoverIsOpen(false)} + className="eui-textTruncate" + anchorClassName="eui-textTruncate" + display="block" + panelPaddingSize="s" + ownFocus + > +
+ + {i18n.translate('kbn.discover.fieldChooser.indexPattern.changeIndexPatternTitle', { + defaultMessage: 'Change index pattern', + })} + + ({ + label: title, + key: id, + value: id, + checked: id === indexPatternId ? 'on' : undefined, + }))} + onChange={choices => { + const choice = (choices.find(({ checked }) => checked) as unknown) as { + value: string; + }; + onChangeIndexPattern(choice.value); + setPopoverIsOpen(false); + }} + searchProps={{ + compressed: true, + ...(selectableProps ? selectableProps.searchProps : undefined), + }} + > + {(list, search) => ( + <> + {search} + {list} + + )} + +
+
+ ); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.html diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.js index 0d2d0788dd175..f7f219a287492 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.js @@ -20,10 +20,8 @@ import $ from 'jquery'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../../kibana_services'; +import { getServices } from '../../../kibana_services'; import html from './discover_field.html'; -import 'ui/directives/css_truncate'; -import 'ui/directives/field_name'; import './string_progress_bar'; import detailsHtml from './lib/detail_views/string.html'; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search_directive.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search_directive.ts index 69865ec424325..6d570349ee0c6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search_directive.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { wrapInI18nContext } from '../../kibana_services'; +import { wrapInI18nContext } from '../../../kibana_services'; import { DiscoverFieldSearch } from './discover_field_search'; export function createFieldSearchDirective(reactDirective: any) { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx new file mode 100644 index 0000000000000..96b8cc383888e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx @@ -0,0 +1,100 @@ +/* + * 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 { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; + +// @ts-ignore +import { ShallowWrapper } from 'enzyme'; +import { ChangeIndexPattern } from './change_indexpattern'; +import { SavedObject } from 'kibana/server'; +import { DiscoverIndexPattern } from './discover_index_pattern'; +import { EuiSelectable, EuiSelectableList } from '@elastic/eui'; + +const indexPattern1 = { + id: 'test1', + attributes: { + title: 'test1 title', + }, +} as SavedObject; + +const indexPattern2 = { + id: 'test2', + attributes: { + title: 'test2 title', + }, +} as SavedObject; + +const defaultProps = { + indexPatternList: [indexPattern1, indexPattern2], + selectedIndexPattern: indexPattern1, + setIndexPattern: jest.fn(async () => {}), +}; + +function getIndexPatternPickerList(instance: ShallowWrapper) { + return instance + .find(ChangeIndexPattern) + .first() + .dive() + .find(EuiSelectable); +} + +function getIndexPatternPickerOptions(instance: ShallowWrapper) { + return getIndexPatternPickerList(instance) + .dive() + .find(EuiSelectableList) + .prop('options'); +} + +function selectIndexPatternPickerOption(instance: ShallowWrapper, selectedLabel: string) { + const options: Array<{ label: string; checked?: 'on' | 'off' }> = getIndexPatternPickerOptions( + instance + ).map((option: any) => + option.label === selectedLabel + ? { ...option, checked: 'on' } + : { ...option, checked: undefined } + ); + return getIndexPatternPickerList(instance).prop('onChange')!(options); +} + +describe('DiscoverIndexPattern', () => { + test('Invalid props dont cause an exception', () => { + const props = { + indexPatternList: null, + selectedIndexPattern: null, + setIndexPattern: jest.fn(), + } as any; + + expect(shallow()).toMatchSnapshot(`""`); + }); + test('should list all index patterns', () => { + const instance = shallow(); + + expect(getIndexPatternPickerOptions(instance)!.map((option: any) => option.label)).toEqual([ + 'test1 title', + 'test2 title', + ]); + }); + + test('should switch data panel to target index pattern', () => { + const instance = shallow(); + + selectIndexPatternPickerOption(instance, 'test2 title'); + expect(defaultProps.setIndexPattern).toHaveBeenCalledWith('test2'); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx new file mode 100644 index 0000000000000..37338decce2c2 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx @@ -0,0 +1,85 @@ +/* + * 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 { SavedObject } from 'kibana/server'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { IndexPatternRef } from './types'; +import { ChangeIndexPattern } from './change_indexpattern'; +export interface DiscoverIndexPatternProps { + /** + * list of available index patterns, if length > 1, component offers a "change" link + */ + indexPatternList: SavedObject[]; + /** + * currently selected index pattern, due to angular issues it's undefined at first rendering + */ + selectedIndexPattern: SavedObject; + /** + * triggered when user selects a new index pattern + */ + setIndexPattern: (id: string) => void; +} + +/** + * Component allows you to select an index pattern in discovers side bar + */ +export function DiscoverIndexPattern({ + indexPatternList, + selectedIndexPattern, + setIndexPattern, +}: DiscoverIndexPatternProps) { + if (!indexPatternList || indexPatternList.length === 0 || !selectedIndexPattern) { + // just in case, shouldn't happen + return null; + } + const options: IndexPatternRef[] = indexPatternList.map(entity => ({ + id: entity.id, + title: entity.attributes!.title, + })); + + const [selected, setSelected] = useState({ + id: selectedIndexPattern.id, + title: selectedIndexPattern.attributes!.title, + }); + + return ( +
+ + { + const indexPattern = options.find(pattern => pattern.id === id); + if (indexPattern) { + setIndexPattern(id); + setSelected(indexPattern); + } + }} + /> + +
+ ); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern_directive.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern_directive.ts index 46c8fa854847a..8bbeac086f093 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern_directive.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { wrapInI18nContext } from '../../kibana_services'; +import { wrapInI18nContext } from '../../../kibana_services'; import { DiscoverIndexPattern } from './discover_index_pattern'; export function createIndexPatternSelectDirective(reactDirective: any) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html similarity index 58% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html index adf4b1b4326e8..1587c2af79752 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html @@ -13,64 +13,6 @@ types="fieldTypes" > -
-
- - -
-
- - -
-
- - -
-
- -
- -
diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js index cf636a1675eb0..47b3ec6b07e8e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js @@ -23,7 +23,7 @@ import { fieldCalculator } from './lib/field_calculator'; import './discover_field'; import './discover_field_search_directive'; import './discover_index_pattern_directive'; -import { FieldList } from '../../../../../../../plugins/data/public'; +import { FieldList } from '../../../../../../../../plugins/data/public'; import fieldChooserTemplate from './field_chooser.html'; export function createFieldChooserDirective($location, config, $route) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/detail_views/string.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/detail_views/string.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/field_calculator.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/lib/field_calculator.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/field_calculator.js diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/string_progress_bar.tsx similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/string_progress_bar.tsx index 7e4fc79839a52..0c5e7fa69357d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/string_progress_bar.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiText, EuiToolTip } from '@elastic/eui'; -import { wrapInI18nContext } from '../../kibana_services'; +import { wrapInI18nContext } from '../../../kibana_services'; interface Props { percent: number; diff --git a/src/legacy/core_plugins/timelion/public/services/saved_sheet_register.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts similarity index 88% rename from src/legacy/core_plugins/timelion/public/services/saved_sheet_register.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts index 0353a4d742dd3..302bf5165777c 100644 --- a/src/legacy/core_plugins/timelion/public/services/saved_sheet_register.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts @@ -17,8 +17,7 @@ * under the License. */ -import './saved_sheets'; - -export default function savedSearchObjectFn(savedSheets) { - return savedSheets; +export interface IndexPatternRef { + id: string; + title: string; } diff --git a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/help_menu/help_menu_util.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/help_menu/help_menu_util.js index eb40130137e00..37fa79b490d56 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/help_menu/help_menu_util.js @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { getServices } from '../../kibana_services'; +import { getServices } from '../../../kibana_services'; const { docLinks } = getServices(); export function addHelpMenuToAppChrome(chrome) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/__snapshots__/open_search_panel.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/__snapshots__/open_search_panel.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/open_search_panel.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/open_search_panel.js index ec1763f44f25f..ebe4cbb1ddb69 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/open_search_panel.js @@ -32,8 +32,8 @@ import { EuiFlyoutBody, EuiTitle, } from '@elastic/eui'; -import { SavedObjectFinderUi } from '../../../../../../../plugins/kibana_react/public'; -import { getServices } from '../../kibana_services'; +import { SavedObjectFinderUi } from '../../../../../../../../plugins/kibana_react/public'; +import { getServices } from '../../../kibana_services'; const SEARCH_OBJECT_TYPE = 'search'; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/open_search_panel.test.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/open_search_panel.test.js index 22487421bf61b..c8f2ca220386d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/open_search_panel.test.js @@ -20,7 +20,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -jest.mock('../../kibana_services', () => { +jest.mock('../../../kibana_services', () => { return { getServices: () => ({ core: { uiSettings: {}, savedObjects: {} }, diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/show_open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/show_open_search_panel.js similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/components/top_nav/show_open_search_panel.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/show_open_search_panel.js index 4beee3c02fa6f..e40d700b48885 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/show_open_search_panel.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/top_nav/show_open_search_panel.js @@ -20,11 +20,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { OpenSearchPanel } from './open_search_panel'; -import { I18nContext } from 'ui/i18n'; let isOpen = false; -export function showOpenSearchPanel({ makeUrl }) { +export function showOpenSearchPanel({ makeUrl, I18nContext }) { if (isOpen) { return; } diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/_embeddables.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/_embeddables.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/_embeddables.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/_embeddables.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts index 273c7d80f216c..14f7057045251 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts @@ -21,7 +21,6 @@ import * as Rx from 'rxjs'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import { SearchSourceContract } from '../../../../../ui/public/courier'; import { esFilters, TimeRange, @@ -31,12 +30,12 @@ import { getTime, Query, IFieldType, -} from '../../../../../../plugins/data/public'; +} from '../../../../../../../plugins/data/public'; import { APPLY_FILTER_TRIGGER, Container, Embeddable, -} from '../../../../embeddable_api/public/np_ready/public'; +} from '../../../../../embeddable_api/public/np_ready/public'; import * as columnActions from '../angular/doc_table/actions/columns'; import { SavedSearch } from '../types'; import searchTemplate from './search_template.html'; @@ -51,7 +50,8 @@ import { getServices, IndexPattern, RequestAdapter, -} from '../kibana_services'; + SearchSourceContract, +} from '../../kibana_services'; import { SEARCH_EMBEDDABLE_TYPE } from './constants'; interface SearchScope extends ng.IScope { diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts index b5475b2629c70..3226b3af93cee 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable_factory.ts @@ -18,15 +18,14 @@ */ import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import { IInjector } from 'ui/chrome'; -import { getServices } from '../kibana_services'; +import { getServices, IInjector } from '../../kibana_services'; import { EmbeddableFactory, ErrorEmbeddable, Container, -} from '../../../../../../plugins/embeddable/public'; +} from '../../../../../../../plugins/embeddable/public'; -import { TimeRange } from '../../../../../../plugins/data/public'; +import { TimeRange } from '../../../../../../../plugins/data/public'; import { SearchEmbeddable } from './search_embeddable'; import { SearchInput, SearchOutput } from './types'; import { SEARCH_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_template.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_template.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/search_template.html rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_template.html diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/types.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/types.ts index adfa3d5acbf7a..3d6acb0963bed 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/types.ts @@ -20,7 +20,12 @@ import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from 'src/plugins/embeddable/public'; import { SavedSearch } from '../types'; import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; -import { esFilters, IIndexPattern, TimeRange, Query } from '../../../../../../plugins/data/public'; +import { + esFilters, + IIndexPattern, + TimeRange, + Query, +} from '../../../../../../../plugins/data/public'; export interface SearchInput extends EmbeddableInput { timeRange: TimeRange; diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/helpers/breadcrumbs.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/helpers/breadcrumbs.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/helpers/breadcrumbs.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/helpers/get_index_pattern_id.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/helpers/get_index_pattern_id.ts index bd62460fd6868..8f4d1b28624a4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/helpers/get_index_pattern_id.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/helpers/get_index_pattern_id.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IIndexPattern } from '../../../../../../plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../plugins/data/common/index_patterns'; export function findIndexPatternById( indexPatterns: IIndexPattern[], diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/register_feature.ts similarity index 58% rename from src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/register_feature.ts index eb8c2aec91558..74255642ab2c9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/register_feature.ts @@ -18,24 +18,22 @@ */ import { i18n } from '@kbn/i18n'; import { - FeatureCatalogueRegistryProvider, FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; + HomePublicPluginSetup, +} from '../../../../../../plugins/home/public'; -export function registerFeature() { - FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'discover', - title: i18n.translate('kbn.discover.discoverTitle', { - defaultMessage: 'Discover', - }), - description: i18n.translate('kbn.discover.discoverDescription', { - defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', - }), - icon: 'discoverApp', - path: '/app/kibana#/discover', - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - }; +export function registerFeature(home: HomePublicPluginSetup) { + home.featureCatalogue.register({ + id: 'discover', + title: i18n.translate('kbn.discover.discoverTitle', { + defaultMessage: 'Discover', + }), + description: i18n.translate('kbn.discover.discoverDescription', { + defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', + }), + icon: 'discoverApp', + path: '/app/kibana#/discover', + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, }); } diff --git a/src/legacy/core_plugins/kibana/public/discover/types.d.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/types.d.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/types.d.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/types.d.ts index 6cdd802fa2800..c8920e351fcca 100644 --- a/src/legacy/core_plugins/kibana/public/discover/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/types.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SearchSourceContract } from '../../../../ui/public/courier'; +import { SearchSourceContract } from '../kibana_services'; import { SortOrder } from './angular/doc_table/components/table_header/helpers'; export { SortOrder } from './angular/doc_table/components/table_header/helpers'; diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index b5a8e25dc11ea..0cff1e66f3636 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -20,16 +20,17 @@ import { AppMountParameters, CoreSetup, CoreStart, Plugin } from 'kibana/public' import angular from 'angular'; import { IUiActionsStart } from 'src/plugins/ui_actions/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { registerFeature } from './helpers/register_feature'; +import { registerFeature } from './np_ready/register_feature'; import './kibana_services'; import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embeddable/public'; import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular'; import { setAngularModule, setServices } from './kibana_services'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; import { EuiUtilsStart } from '../../../../../plugins/eui_utils/public'; -import { buildServices } from './helpers/build_services'; +import { buildServices } from './build_services'; import { SharePluginStart } from '../../../../../plugins/share/public'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; +import { HomePublicPluginSetup } from '../../../../../plugins/home/public'; /** * These are the interfaces with your public contracts. You should export these @@ -42,6 +43,7 @@ export interface DiscoverSetupPlugins { uiActions: IUiActionsStart; embeddable: IEmbeddableSetup; kibana_legacy: KibanaLegacySetup; + home: HomePublicPluginSetup; } export interface DiscoverStartPlugins { uiActions: IUiActionsStart; @@ -84,10 +86,11 @@ export class DiscoverPlugin implements Plugin { } await this.initializeServices(); await this.initializeInnerAngular(); - const { renderApp } = await import('./application'); + const { renderApp } = await import('./np_ready/application'); return renderApp(innerAngularName, params.element); }, }); + registerFeature(plugins.home); } start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart { @@ -115,14 +118,13 @@ export class DiscoverPlugin implements Plugin { }; this.registerEmbeddable(core, plugins); - registerFeature(); } /** * register embeddable with a slimmer embeddable version of inner angular */ private async registerEmbeddable(core: CoreStart, plugins: DiscoverStartPlugins) { - const { SearchEmbeddableFactory } = await import('./embeddable'); + const { SearchEmbeddableFactory } = await import('./np_ready/embeddable'); const getInjector = async () => { if (!this.initializeServices) { throw Error('Discover plugin registerEmbeddable: initializeServices is undefined'); diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/index.ts b/src/legacy/core_plugins/kibana/public/discover/saved_searches/index.ts index b9601e2fd257a..1dd99025b4b70 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/index.ts @@ -17,4 +17,5 @@ * under the License. */ -import './saved_searches'; +export * from './saved_searches'; +import './saved_searches_register'; diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.ts b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.ts index 46fdd3a7baedc..abd3d46820c18 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.ts +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches.ts @@ -16,22 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { npStart } from 'ui/new_platform'; -// @ts-ignore -import { uiModules } from 'ui/modules'; import { SavedObjectLoader } from 'ui/saved_objects'; import { SavedObjectKibanaServices } from 'ui/saved_objects/types'; -// @ts-ignore -import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; import { createSavedSearchClass } from './_saved_search'; -// Register this service with the saved object registry so it can be -// edited by the object editor. -savedObjectManagementRegistry.register({ - service: 'savedSearches', - title: 'searches', -}); - export function createSavedSearchesService(services: SavedObjectKibanaServices) { const SavedSearchClass = createSavedSearchClass(services); const savedSearchLoader = new SavedObjectLoader( @@ -50,14 +38,3 @@ export function createSavedSearchesService(services: SavedObjectKibanaServices) return savedSearchLoader; } -// this is needed for saved object management -const module = uiModules.get('discover/saved_searches'); -module.service('savedSearches', () => { - const services = { - savedObjectsClient: npStart.core.savedObjects.client, - indexPatterns: npStart.plugins.data.indexPatterns, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, - }; - return createSavedSearchesService(services); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches_register.ts b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches_register.ts new file mode 100644 index 0000000000000..bdb1495a33925 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_searches_register.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { npStart } from 'ui/new_platform'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +// @ts-ignore +import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; + +import { createSavedSearchesService } from './saved_searches'; + +// this is needed for saved object management +// Register this service with the saved object registry so it can be +// edited by the object editor. +savedObjectManagementRegistry.register({ + service: 'savedSearches', + title: 'searches', +}); +const module = uiModules.get('discover/saved_searches'); +module.service('savedSearches', () => { + const services = { + savedObjectsClient: npStart.core.savedObjects.client, + indexPatterns: npStart.plugins.data.indexPatterns, + chrome: npStart.core.chrome, + overlays: npStart.core.overlays, + }; + return createSavedSearchesService(services); +}); diff --git a/src/legacy/core_plugins/kibana/public/home/_index.scss b/src/legacy/core_plugins/kibana/public/home/_index.scss index 192091fb04e3c..f42254c1096ce 100644 --- a/src/legacy/core_plugins/kibana/public/home/_index.scss +++ b/src/legacy/core_plugins/kibana/public/home/_index.scss @@ -1 +1 @@ -@import './components/index'; +@import 'np_ready/components/index'; diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/welcome.test.tsx.snap b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/welcome.test.tsx.snap deleted file mode 100644 index 2007a3bb773cf..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/welcome.test.tsx.snap +++ /dev/null @@ -1,188 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render a Welcome screen with no telemetry disclaimer 1`] = ` - -
-
-
- - - - - -

- -

-
- -

- -

-
- -
-
-
- - - - - - - -
-
-
-`; - -exports[`should render a Welcome screen with the telemetry disclaimer 1`] = ` - -
-
-
- - - - - -

- -

-
- -

- -

-
- -
-
-
- - - - - - - - - - - - - - - - - -
-
-
-`; diff --git a/src/legacy/core_plugins/kibana/public/home/render_app.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/application.tsx similarity index 96% rename from src/legacy/core_plugins/kibana/public/home/render_app.tsx rename to src/legacy/core_plugins/kibana/public/home/np_ready/application.tsx index a8c35144a45b0..8345491d99972 100644 --- a/src/legacy/core_plugins/kibana/public/home/render_app.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/application.tsx @@ -22,7 +22,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { HomeApp } from './components/home_app'; -import { getServices } from './kibana_services'; +import { getServices } from '../kibana_services'; export const renderApp = async (element: HTMLElement) => { const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/add_data.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/add_data.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/__snapshots__/add_data.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/add_data.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/home.test.js.snap similarity index 99% rename from src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/home.test.js.snap index 0bf8c808ae920..c1131cbe559f6 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/home.test.js.snap @@ -1072,8 +1072,8 @@ exports[`home welcome should show the normal home page if welcome screen is disa exports[`home welcome should show the welcome screen if enabled, and there are no index patterns defined 1`] = ` `; diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/recently_accessed.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/recently_accessed.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/__snapshots__/recently_accessed.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/recently_accessed.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/sample_data_view_data_button.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/sample_data_view_data_button.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/__snapshots__/sample_data_view_data_button.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/sample_data_view_data_button.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/synopsis.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/synopsis.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/__snapshots__/synopsis.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/synopsis.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/welcome.test.tsx.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/welcome.test.tsx.snap new file mode 100644 index 0000000000000..e36a6e0a5a9fb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/welcome.test.tsx.snap @@ -0,0 +1,449 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render a Welcome screen with no telemetry disclaimer 1`] = ` + +
+
+
+ + + + + +

+ +

+
+ +

+ +

+
+ +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+`; + +exports[`should render a Welcome screen with the telemetry disclaimer 1`] = ` + +
+
+
+ + + + + +

+ +

+
+ +

+ +

+
+ +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+`; + +exports[`should render a Welcome screen with the telemetry disclaimer when optIn is false 1`] = ` + +
+
+
+ + + + + +

+ +

+
+ +

+ +

+
+ +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+`; + +exports[`should render a Welcome screen with the telemetry disclaimer when optIn is true 1`] = ` + +
+
+
+ + + + + +

+ +

+
+ +

+ +

+
+ +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+`; diff --git a/src/legacy/core_plugins/kibana/public/home/components/_add_data.scss b/src/legacy/core_plugins/kibana/public/home/np_ready/components/_add_data.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/_add_data.scss rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/_add_data.scss diff --git a/src/legacy/core_plugins/kibana/public/home/components/_home.scss b/src/legacy/core_plugins/kibana/public/home/np_ready/components/_home.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/_home.scss rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/_home.scss diff --git a/src/legacy/core_plugins/kibana/public/home/components/_index.scss b/src/legacy/core_plugins/kibana/public/home/np_ready/components/_index.scss similarity index 52% rename from src/legacy/core_plugins/kibana/public/home/components/_index.scss rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/_index.scss index af23752e54287..870099ffb350e 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/_index.scss +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/_index.scss @@ -5,10 +5,10 @@ // homChart__legend--small // homChart__legend-isLoading -@import './add_data'; -@import './home'; -@import './sample_data_set_cards'; -@import './synopsis'; -@import './welcome'; +@import 'add_data'; +@import 'home'; +@import 'sample_data_set_cards'; +@import 'synopsis'; +@import 'welcome'; -@import './tutorial/tutorial'; +@import 'tutorial/tutorial'; diff --git a/src/legacy/core_plugins/kibana/public/home/components/_sample_data_set_cards.scss b/src/legacy/core_plugins/kibana/public/home/np_ready/components/_sample_data_set_cards.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/_sample_data_set_cards.scss rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/_sample_data_set_cards.scss diff --git a/src/legacy/core_plugins/kibana/public/home/components/_synopsis.scss b/src/legacy/core_plugins/kibana/public/home/np_ready/components/_synopsis.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/_synopsis.scss rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/_synopsis.scss diff --git a/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss b/src/legacy/core_plugins/kibana/public/home/np_ready/components/_welcome.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/_welcome.scss rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/_welcome.scss diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/add_data.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/home/components/add_data.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/add_data.js index 8ea9d78507ceb..a49620be2d229 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/add_data.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; import { EuiButton, diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/add_data.test.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/home/components/add_data.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/add_data.test.js index 9457f766409b8..86eec564f0b61 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/add_data.test.js @@ -20,9 +20,9 @@ import React from 'react'; import { AddData } from './add_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; -jest.mock('../kibana_services', () => { +jest.mock('../../kibana_services', () => { const mock = { getBasePath: jest.fn(() => 'path'), }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/feature_directory.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/feature_directory.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/home/components/feature_directory.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/feature_directory.js index 447a54bd89701..5545944a1029f 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/feature_directory.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/feature_directory.js @@ -31,7 +31,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { FeatureCatalogueCategory } from '../../../../../../../plugins/home/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/home/components/home.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/home.js index c87ceb9777c74..5c32a463da115 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.js @@ -38,8 +38,8 @@ import { } from '@elastic/eui'; import { Welcome } from './welcome'; -import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { getServices } from '../kibana_services'; +import { FeatureCatalogueCategory } from '../../../../../../../plugins/home/public'; +import { getServices } from '../../kibana_services'; const KEY_ENABLE_WELCOME = 'home:welcome:show'; @@ -51,10 +51,7 @@ export class Home extends Component { getServices().getInjected('disableWelcomeScreen') || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false' ); - const showTelemetryDisclaimer = getServices().getInjected( - 'telemetryNotifyUserAboutOptInDefault' - ); - + const currentOptInStatus = this.props.getOptInStatus(); this.state = { // If welcome is enabled, we wait for loading to complete // before rendering. This prevents an annoying flickering @@ -63,7 +60,7 @@ export class Home extends Component { isLoading: isWelcomeEnabled, isNewKibanaInstance: false, isWelcomeEnabled, - showTelemetryDisclaimer, + currentOptInStatus, }; } @@ -222,14 +219,13 @@ export class Home extends Component { renderLoading() { return ''; } - renderWelcome() { return ( ); } @@ -269,4 +265,5 @@ Home.propTypes = { urlBasePath: PropTypes.string.isRequired, mlEnabled: PropTypes.bool.isRequired, onOptInSeen: PropTypes.func.isRequired, + getOptInStatus: PropTypes.func.isRequired, }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/home/components/home.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js index 780e2af695381..be2ceb66f69d0 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js @@ -23,9 +23,10 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; import { Home } from './home'; -import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -jest.mock('../kibana_services', () => ({ +import { FeatureCatalogueCategory } from '../../../../../../../plugins/home/public'; + +jest.mock('../../kibana_services', () => ({ getServices: () => ({ getBasePath: () => 'path', getInjected: () => '', @@ -63,6 +64,10 @@ describe('home', () => { setItem: sinon.mock(), }, urlBasePath: 'goober', + onOptInSeen() { + return false; + }, + getOptInStatus: jest.fn(), }; }); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.mocks.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.mocks.ts index cd7bc82fe3345..a0b9d7c779b02 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.mocks.ts @@ -22,7 +22,7 @@ import { overlayServiceMock, httpServiceMock, injectedMetadataServiceMock, -} from '../../../../../../core/public/mocks'; +} from '../../../../../../../core/public/mocks'; jest.doMock('ui/new_platform', () => { return { diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js similarity index 93% rename from src/legacy/core_plugins/kibana/public/home/components/home_app.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js index 5a12eb0a66cf1..6532737cc02e8 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js @@ -27,16 +27,17 @@ import { Tutorial } from './tutorial/tutorial'; import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; +// TODO This is going to be refactored soon +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { npSetup } from 'ui/new_platform'; - export function HomeApp({ directories }) { const { getInjected, savedObjectsClient, getBasePath, addBasePath, - telemetryOptInProvider: { setOptInNoticeSeen }, + telemetryOptInProvider: { setOptInNoticeSeen, getOptIn }, } = getServices(); const { cloud } = npSetup.plugins; const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); @@ -87,6 +88,7 @@ export function HomeApp({ directories }) { localStorage={localStorage} urlBasePath={getBasePath()} onOptInSeen={setOptInNoticeSeen} + getOptInStatus={getOptIn} /> diff --git a/src/legacy/core_plugins/kibana/public/home/components/recently_accessed.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/recently_accessed.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/recently_accessed.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/recently_accessed.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/recently_accessed.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/recently_accessed.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/recently_accessed.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/recently_accessed.test.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data/index.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data/index.tsx diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_card.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_set_card.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/sample_data_set_card.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_set_card.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_set_cards.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_set_cards.js index 7daf10e5f01f8..198e0d95271d7 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_set_cards.js @@ -24,7 +24,7 @@ import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; import { SampleDataSetCard, INSTALLED_STATUS, UNINSTALLED_STATUS } from './sample_data_set_card'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; import { listSampleDataSets, diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_view_data_button.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_view_data_button.js index c9bd32a7d14d5..e6f5c07c94f9f 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_view_data_button.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import { EuiButton, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; export class SampleDataViewDataButton extends React.Component { addBasePath = getServices().addBasePath; diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_view_data_button.test.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_view_data_button.test.js index f594ec1264c94..e33c206ed8482 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/sample_data_view_data_button.test.js @@ -22,7 +22,7 @@ import { shallow } from 'enzyme'; import { SampleDataViewDataButton } from './sample_data_view_data_button'; -jest.mock('../kibana_services', () => ({ +jest.mock('../../kibana_services', () => ({ getServices: () => ({ addBasePath: path => `root${path}`, }), diff --git a/src/legacy/core_plugins/kibana/public/home/components/synopsis.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/synopsis.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/synopsis.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/synopsis.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.test.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/content.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/content.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/content.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/content.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/footer.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/footer.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/footer.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/footer.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/instruction_set.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/instruction_set.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/introduction.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/introduction.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/introduction.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/introduction.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap similarity index 98% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap index 07744dd0f6f4b..161061868dab2 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap @@ -508,6 +508,7 @@ exports[`bulkCreate should display success message when bulkCreate is successful aria-label="complete" className="euiIcon euiIcon--medium euiIcon-isLoaded euiStepNumber__icon" focusable="false" + role="img" style={null} > + diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/tutorial.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/__snapshots__/tutorial.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/_tutorial.scss b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/_tutorial.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/_tutorial.scss rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/_tutorial.scss diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/content.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/content.js similarity index 94% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/content.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/content.js index db1f55b503e84..669eb6c4c42cd 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/content.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/content.js @@ -19,7 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Markdown } from '../../../../../kibana_react/public'; +import { Markdown } from '../../../../../../kibana_react/public'; const whiteListedRules = ['backticks', 'emphasis', 'link', 'list']; diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/content.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/content.test.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/content.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/content.test.js index d3a4d7085a0aa..64864b6a5404d 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/content.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/content.test.js @@ -22,7 +22,7 @@ import { shallow } from 'enzyme'; import { Content } from './content'; -jest.mock('../../../../../kibana_react/public', () => { +jest.mock('../../../../../../kibana_react/public', () => { return { Markdown: () =>
, }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/footer.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/footer.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/footer.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/footer.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/footer.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/footer.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/footer.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/footer.test.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction_set.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction_set.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.js index 7ec2133a98ca1..4f60de00819e7 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction_set.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import { Instruction } from './instruction'; import { ParameterForm } from './parameter_form'; import { Content } from './content'; -import { getDisplayText } from '../../../../common/tutorials/instruction_variant'; +import { getDisplayText } from '../../../../../common/tutorials/instruction_variant'; import { EuiTabs, EuiTab, diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction_set.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.test.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction_set.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.test.js index 6c9ce530f6b20..21c3ddeceff6b 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/instruction_set.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.test.js @@ -45,7 +45,7 @@ const instructionVariants = [ }, ]; -jest.mock('../../../../../kibana_react/public', () => { +jest.mock('../../../../../../kibana_react/public', () => { return { Markdown: () =>
, }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/introduction.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/introduction.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/introduction.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/introduction.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/introduction.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/introduction.test.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/introduction.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/introduction.test.js index ae87bc6030c9a..8862ef7334f93 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/introduction.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/introduction.test.js @@ -22,7 +22,7 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { Introduction } from './introduction'; -jest.mock('../../../../../kibana_react/public', () => { +jest.mock('../../../../../../kibana_react/public', () => { return { Markdown: () =>
, }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/number_parameter.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/number_parameter.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/number_parameter.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/number_parameter.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/parameter_form.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/parameter_form.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/parameter_form.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/parameter_form.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js index 62116ae1a0663..daf996444eb3c 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js @@ -18,7 +18,7 @@ */ import { Writer } from 'mustache'; -import { getServices } from '../../kibana_services'; +import { getServices } from '../../../kibana_services'; const TEMPLATE_TAGS = ['{', '}']; diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/saved_objects_installer.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/saved_objects_installer.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/saved_objects_installer.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/saved_objects_installer.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/saved_objects_installer.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/saved_objects_installer.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/saved_objects_installer.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/saved_objects_installer.test.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/status_check_states.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/status_check_states.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/status_check_states.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/status_check_states.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/string_parameter.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/string_parameter.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/string_parameter.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/string_parameter.js diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/tutorial.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/tutorial.js index 7461db9c54cc8..314ddf2196f06 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/tutorial.js @@ -37,7 +37,7 @@ import { import * as StatusCheckStates from './status_check_states'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../../kibana_services'; +import { getServices } from '../../../kibana_services'; const INSTRUCTIONS_TYPE = { ELASTIC_CLOUD: 'elasticCloud', diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/tutorial.test.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/tutorial.test.js index 41d83d7562f6e..733223fe79046 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/tutorial.test.js @@ -22,7 +22,7 @@ import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; import { Tutorial } from './tutorial'; -jest.mock('../../kibana_services', () => ({ +jest.mock('../../../kibana_services', () => ({ getServices: () => ({ getBasePath: jest.fn(() => 'path'), chrome: { @@ -30,7 +30,7 @@ jest.mock('../../kibana_services', () => ({ }, }), })); -jest.mock('../../../../../kibana_react/public', () => { +jest.mock('../../../../../../kibana_react/public', () => { return { Markdown: () =>
, }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial_directory.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial_directory.js index 0c537c8e9ae8a..06da6f35ee42e 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial_directory.js @@ -22,7 +22,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { SampleDataSetCards } from './sample_data_set_cards'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; import { EuiPage, diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx similarity index 65% rename from src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx index 21dcfd9ef15de..28bdab14193c4 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Welcome } from './welcome'; -jest.mock('../kibana_services', () => ({ +jest.mock('../../kibana_services', () => ({ getServices: () => ({ addBasePath: (path: string) => `root${path}`, trackUiMetric: () => {}, @@ -35,7 +35,25 @@ jest.mock('../kibana_services', () => ({ test('should render a Welcome screen with the telemetry disclaimer', () => { const component = shallow( // @ts-ignore - {}} showTelemetryDisclaimer={true} onOptInSeen={() => {}} /> + {}} onOptInSeen={() => {}} /> + ); + + expect(component).toMatchSnapshot(); +}); + +test('should render a Welcome screen with the telemetry disclaimer when optIn is true', () => { + const component = shallow( + // @ts-ignore + {}} onOptInSeen={() => {}} currentOptInStatus={true} /> + ); + + expect(component).toMatchSnapshot(); +}); + +test('should render a Welcome screen with the telemetry disclaimer when optIn is false', () => { + const component = shallow( + // @ts-ignore + {}} onOptInSeen={() => {}} currentOptInStatus={false} /> ); expect(component).toMatchSnapshot(); @@ -45,7 +63,7 @@ test('should render a Welcome screen with no telemetry disclaimer', () => { // @ts-ignore const component = shallow( // @ts-ignore - {}} showTelemetryDisclaimer={false} onOptInSeen={() => {}} /> + {}} onOptInSeen={() => {}} /> ); expect(component).toMatchSnapshot(); @@ -56,7 +74,7 @@ test('fires opt-in seen when mounted', () => { shallow( // @ts-ignore - {}} showTelemetryDisclaimer={true} onOptInSeen={seen} /> + {}} onOptInSeen={seen} /> ); expect(seen).toHaveBeenCalled(); diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx similarity index 68% rename from src/legacy/core_plugins/kibana/public/home/components/welcome.tsx rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx index c8de0bf7bb936..9bbb7aaceb915 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx @@ -23,7 +23,7 @@ * in Elasticsearch. */ -import React from 'react'; +import React, { Fragment } from 'react'; import { EuiLink, EuiTextColor, @@ -36,15 +36,14 @@ import { EuiPortal, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; import { SampleDataCard } from './sample_data'; - interface Props { urlBasePath: string; onSkip: () => void; onOptInSeen: () => any; - showTelemetryDisclaimer: boolean; + currentOptInStatus: boolean; } /** @@ -84,9 +83,42 @@ export class Welcome extends React.Component { document.removeEventListener('keydown', this.hideOnEsc); } - render() { - const { urlBasePath, showTelemetryDisclaimer } = this.props; + private renderTelemetryEnabledOrDisabledText = () => { + if (this.props.currentOptInStatus) { + return ( + + + + + + + ); + } else { + return ( + + + + + + + ); + } + }; + render() { + const { urlBasePath } = this.props; return (
@@ -121,34 +153,23 @@ export class Welcome extends React.Component { onDecline={this.onSampleDataDecline} /> - {showTelemetryDisclaimer && ( - - - - - + + + - - - - - )} + + {this.renderTelemetryEnabledOrDisabledText()} + diff --git a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js b/src/legacy/core_plugins/kibana/public/home/np_ready/load_tutorials.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/home/load_tutorials.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/load_tutorials.js index be84027296259..6a0a01ebda8db 100644 --- a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/load_tutorials.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { getServices } from './kibana_services'; +import { getServices } from '../kibana_services'; import { i18n } from '@kbn/i18n'; const baseUrlLP = getServices().addBasePath('/api/kibana/home/tutorials_LP'); diff --git a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js b/src/legacy/core_plugins/kibana/public/home/np_ready/sample_data_client.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/home/sample_data_client.js rename to src/legacy/core_plugins/kibana/public/home/np_ready/sample_data_client.js index 600b1c3cb7dff..34c85d8d2c350 100644 --- a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/sample_data_client.js @@ -17,7 +17,7 @@ * under the License. */ -import { getServices } from './kibana_services'; +import { getServices } from '../kibana_services'; const sampleDataUrl = '/api/sample_data'; diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index fc1747d71d069..a998e4d07ab15 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -88,7 +88,7 @@ export class HomePlugin implements Plugin { indexPatternService: this.dataStart!.indexPatterns, ...angularDependencies, }); - const { renderApp } = await import('./render_app'); + const { renderApp } = await import('./np_ready/application'); return await renderApp(params.element); }, }); diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 611fe613ad99c..3b49af9a4a6a6 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -11,13 +11,14 @@ @import './dev_tools/index'; // Discover styles -@import './discover/index'; +@import 'discover/index'; // Home styles @import './home/index'; // 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/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap index ca04ac8fcfaab..f758511990d6f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -93,7 +93,6 @@ exports[`Table should render normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -136,9 +135,11 @@ exports[`Table should render the boolean template (false) 1`] = ``; exports[`Table should render the boolean template (true) 1`] = ` `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap index f2a55649fe4d7..4716fb8f77633 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -55,7 +55,6 @@ exports[`Table should render normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap index 415bae7389e97..7856572373e79 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -78,7 +78,6 @@ exports[`Table should render normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx index f54afc4a5e359..f254a6bc22a0d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx @@ -59,7 +59,7 @@ const columns = [ ))} ), - dataType: 'string', + dataType: 'string' as const, sortable: ({ sort }: { sort: string }) => sort, }, ]; @@ -72,7 +72,7 @@ const pagination = { const sorting = { sort: { field: 'title', - direction: 'asc', + direction: 'asc' as const, }, }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap index 843c8207c88c3..731a3379491c1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap @@ -54,7 +54,6 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap index a9175e7b2a63e..ace06e0420a7c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap @@ -90,7 +90,6 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -116,7 +115,6 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` } } responsive={true} - sorting={false} /> @@ -411,7 +409,6 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -448,7 +445,6 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` } } responsive={true} - sorting={false} /> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap index 6060e96f3cfb6..941a0ffded820 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap @@ -83,7 +83,6 @@ exports[`Relationships should render dashboards normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -155,7 +154,6 @@ exports[`Relationships should render dashboards normally 1`] = ` ], } } - sorting={false} />
@@ -294,7 +292,6 @@ exports[`Relationships should render index patterns normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -371,7 +368,6 @@ exports[`Relationships should render index patterns normally 1`] = ` ], } } - sorting={false} />
@@ -461,7 +457,6 @@ exports[`Relationships should render searches normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -538,7 +533,6 @@ exports[`Relationships should render searches normally 1`] = ` ], } } - sorting={false} />
@@ -628,7 +622,6 @@ exports[`Relationships should render visualizations normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -700,7 +693,6 @@ exports[`Relationships should render visualizations normally 1`] = ` ], } } - sorting={false} />
diff --git a/src/legacy/core_plugins/kibana/public/visualize/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/_index.scss index fbd218a64b5fb..0632831578bd0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/_index.scss +++ b/src/legacy/core_plugins/kibana/public/visualize/_index.scss @@ -1,11 +1,2 @@ -// Prefix all styles with "vis" to avoid conflicts. -// Examples -// visChart -// visChart__legend -// visChart__legend--small -// visChart__legend-isLoading - -@import './editor/index'; -@import './embeddable/index'; -@import './listing/index'; -@import './wizard/index'; +// Visualize plugin styles +@import 'np_ready/index'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/editor/_index.scss deleted file mode 100644 index 15c3c6df0ac03..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './editor'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/embeddable/_index.scss deleted file mode 100644 index 6b31803e7c8c5..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './visualize_lab_disabled'; -@import './embeddables'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 7c22bb3d0eaeb..bd605c5393d21 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -32,6 +32,9 @@ import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +export * from './np_ready/visualize_constants'; +export { showNewVisModal } from './np_ready/wizard'; + /** * Get dependencies relying on the global angular context. * They also have to get resolved together with the legacy imports above diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 38cedc7463e7c..991ea9b214b0a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -31,7 +31,8 @@ import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { SharePluginStart } from '../../../../../plugins/share/public'; import { DataPublicPluginStart, IndexPatternsContract } from '../../../../../plugins/data/public'; import { VisualizationsStart } from '../../../visualizations/public'; -import { SavedVisualizations } from './types'; +import { SavedVisualizations } from './np_ready/types'; +import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; export interface VisualizeKibanaServices { addBasePath: (url: string) => string; @@ -54,6 +55,7 @@ export interface VisualizeKibanaServices { uiSettings: IUiSettingsClient; visualizeCapabilities: any; visualizations: VisualizationsStart; + usageCollection?: UsageCollectionSetup; } let services: VisualizeKibanaServices | null = null; 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 b9909e522b571..febd566539bbe 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -72,4 +72,7 @@ export { defaultEditor } from 'ui/vis/editors/default/default'; export { VisType } from 'ui/vis'; export { wrapInI18nContext } from 'ui/i18n'; -export { VisSavedObject } from './embeddable/visualize_embeddable'; +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'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/listing/_index.scss deleted file mode 100644 index 0829e9af7039b..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './listing'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/_index.scss new file mode 100644 index 0000000000000..f97ae012055b0 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/_index.scss @@ -0,0 +1,10 @@ +// Prefix all styles with "vis" to avoid conflicts. +// Examples +// visChart +// visChart__legend +// visChart__legend--small +// visChart__legend-isLoading + +@import 'editor/index'; +@import 'listing/index'; +@import 'wizard/index'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/visualize/application.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts index 3161576eacf71..dcd68a26743ab 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts @@ -38,12 +38,12 @@ import { PrivateProvider, PromiseServiceCreator, StateManagementConfigProvider, -} from './legacy_imports'; -import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; +} from '../legacy_imports'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public'; // @ts-ignore import { initVisualizeApp } from './legacy_app'; -import { VisualizeKibanaServices } from './kibana_services'; +import { VisualizeKibanaServices } from '../kibana_services'; let angularModuleInstance: IModule | null = null; diff --git a/src/legacy/core_plugins/kibana/public/visualize/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/breadcrumbs.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/breadcrumbs.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/breadcrumbs.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/_editor.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss similarity index 90% rename from src/legacy/core_plugins/kibana/public/visualize/editor/_editor.scss rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss index dfe74acd47ea8..f738820677beb 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/_editor.scss +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss @@ -1,11 +1,7 @@ .visEditor { @include flex-parent(); - @include euiBreakpoint('l', 'xl') { - position: absolute; - width: 100%; - height: 100%; - } + height: 100%; @include euiBreakpoint('xs', 's', 'm') { .visualization { diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_index.scss new file mode 100644 index 0000000000000..9d3ca4b539947 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_index.scss @@ -0,0 +1 @@ +@import 'editor'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/editor/editor.html rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/visualize/editor/editor.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index f745e65cc5d1c..ed9bec9db4112 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -21,33 +21,33 @@ import angular from 'angular'; import _ from 'lodash'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import '../saved_visualizations/saved_visualizations'; +import '../../saved_visualizations/saved_visualizations'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { migrateAppState } from './lib'; -import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs } from '../breadcrumbs'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; -import { unhashUrl } from '../../../../../../plugins/kibana_utils/public'; +import { FilterStateManager } from '../../../../../data/public'; +import { unhashUrl } from '../../../../../../../plugins/kibana_utils/public'; import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; import { + subscribeWithScope, absoluteToParsedUrl, KibanaParsedUrl, migrateLegacyQuery, SavedObjectSaveModal, showSaveModal, stateMonitorFactory, - subscribeWithScope, -} from '../legacy_imports'; + DashboardConstants, +} from '../../legacy_imports'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; export function initEditorDirective(app, deps) { app.directive('visualizeApp', function() { diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/lib/index.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/editor/lib/index.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/index.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/migrate_app_state.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/editor/lib/migrate_app_state.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/migrate_app_state.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/global_state_sync.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/global_state_sync.ts index 71156bc38d498..f29fb72a9fbc5 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/global_state_sync.ts @@ -17,8 +17,8 @@ * under the License. */ -import { State } from './legacy_imports'; -import { DataPublicPluginStart as DataStart } from '../../../../../plugins/data/public'; +import { State } from '../legacy_imports'; +import { DataPublicPluginStart as DataStart } from '../../../../../../plugins/data/public'; /** * Helper function to sync the global state with the various state providers diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/help_menu/help_menu_util.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/help_menu/help_menu_util.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/visualize/legacy_app.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js index e948862071f69..d99771ccc912d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js @@ -29,7 +29,7 @@ import { VisualizeListingController } from './listing/visualize_listing'; import { ensureDefaultIndexPattern, registerTimefilterWithGlobalStateFactory, -} from './legacy_imports'; +} from '../legacy_imports'; import { syncOnMount } from './global_state_sync'; import { diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/_index.scss new file mode 100644 index 0000000000000..924c164e467d8 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/_index.scss @@ -0,0 +1 @@ +@import 'listing'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/_listing.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/_listing.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/listing/_listing.scss rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/_listing.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.html similarity index 93% rename from src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.html index 4ee8809fab228..522d20fffafd3 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.html @@ -17,6 +17,7 @@ add-base-path="listingController.addBasePath" ui-settings="listingController.uiSettings" saved-objects="listingController.savedObjects" + usage-collection="listingController.usageCollection" >
diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js index ca6660f34a0a6..b7d034b78d13f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js @@ -23,8 +23,8 @@ import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; -import { wrapInI18nContext } from '../legacy_imports'; +import { getServices } from '../../kibana_services'; +import { wrapInI18nContext } from '../../legacy_imports'; export function initListingDirective(app) { app.directive('visualizeListingTable', reactDirective => @@ -37,6 +37,7 @@ export function initListingDirective(app) { ['addBasePath', { watchDepth: 'reference' }], ['uiSettings', { watchDepth: 'reference' }], ['savedObjects', { watchDepth: 'reference' }], + ['usageCollection', { watchDepth: 'reference' }], 'isOpen', ]) ); @@ -58,6 +59,7 @@ export function VisualizeListingController($injector, createNewVis) { uiSettings, visualizations, core: { docLinks, savedObjects }, + usageCollection, } = getServices(); const kbnUrl = $injector.get('kbnUrl'); @@ -68,6 +70,7 @@ export function VisualizeListingController($injector, createNewVis) { this.addBasePath = addBasePath; this.uiSettings = uiSettings; this.savedObjects = savedObjects; + this.usageCollection = usageCollection; this.createNewVis = () => { this.showNewVisModal = true; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js index 890fa64af9693..840e647edcc86 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js @@ -21,11 +21,11 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { TableListView } from '../../../../../../../src/plugins/kibana_react/public'; +import { TableListView } from '../../../../../../../plugins/kibana_react/public'; import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { getServices } from '../kibana_services'; +import { getServices } from '../../kibana_services'; class VisualizeListingTable extends Component { constructor(props) { diff --git a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/visualize/types.d.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts index b6a3981215384..f47a54baac9a1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisSavedObject } from './legacy_imports'; +import { VisSavedObject } from '../legacy_imports'; export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_app.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_app.ts index c64287a0e63b8..1e7ac668697de 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_app.ts @@ -18,7 +18,7 @@ */ import { IModule } from 'angular'; -import { VisualizeKibanaServices } from './kibana_services'; +import { VisualizeKibanaServices } from '../kibana_services'; // @ts-ignore import { initEditorDirective } from './editor/editor'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_constants.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/visualize_constants.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/__snapshots__/new_vis_modal.test.tsx.snap similarity index 97% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/__snapshots__/new_vis_modal.test.tsx.snap index ca6b872c73f8f..0b44c7dc4e860 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -169,6 +169,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -226,6 +227,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -275,6 +277,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiBetaBadge__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -290,6 +293,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -325,6 +329,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -361,6 +366,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -494,6 +500,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -551,6 +558,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -600,6 +608,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiBetaBadge__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -615,6 +624,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -650,6 +660,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -686,6 +697,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -758,6 +770,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -815,6 +828,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -864,6 +878,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiBetaBadge__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -879,6 +894,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -914,6 +930,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -950,6 +967,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -1031,16 +1049,18 @@ exports[`NewVisModal filter for visualization types should render as expected 1` type="cross" >
-
-
- -
-
-
- -
-
- -
`; @@ -472,10 +452,11 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` className="euiButtonEmpty__content" >
- +
+ + + +
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index f2d2f28fc975a..2e1efabf47b2c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -80,7 +80,7 @@ const ErrorGroupOverview: React.FC = () => { const localUIFiltersConfig = useMemo(() => { const config: React.ComponentProps = { - filterNames: ['host', 'containerId', 'podName'], + filterNames: ['host', 'containerId', 'podName', 'serviceVersion'], params: { serviceName }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts index de423e967b0b3..f83daa4ea1a8a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts @@ -10,7 +10,7 @@ import uuid from 'uuid'; import * as rest from '../../../../../services/rest/watcher'; import { createErrorGroupWatch } from '../createErrorGroupWatch'; import { esResponse } from './esResponse'; -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; // disable html escaping since this is also disabled in watcher\s mustache implementation mustache.escape = value => value; @@ -30,7 +30,7 @@ describe('createErrorGroupWatch', () => { jest.spyOn(uuid, 'v4').mockReturnValue(Buffer.from('mocked-uuid')); createWatchResponse = await createErrorGroupWatch({ - http: {} as HttpServiceBase, + http: {} as HttpSetup, emails: ['my@email.dk', 'mySecond@email.dk'], schedule: { daily: { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts index 1d21e35f122d9..d45453e24f1c9 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import url from 'url'; import uuid from 'uuid'; -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; import { ERROR_CULPRIT, ERROR_EXC_HANDLED, @@ -35,7 +35,7 @@ export interface Schedule { } interface Arguments { - http: HttpServiceBase; + http: HttpSetup; emails: string[]; schedule: Schedule; serviceName: string; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index 4158bb877e459..91483b4b52d90 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -148,7 +148,9 @@ export class ServiceIntegrations extends React.Component { panels={[ { id: 0, - items: this.getPanelItems(license.features.ml?.is_available) + items: this.getPanelItems( + license?.getFeature('ml').isAvailable + ) } ]} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx new file mode 100644 index 0000000000000..c5771995daa24 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx @@ -0,0 +1,66 @@ +/* + * 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 { + EuiEmptyPrompt, + EuiButton, + EuiPanel, + EuiFlexGroup, + EuiFlexItem +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useKibanaUrl } from '../../../hooks/useKibanaUrl'; + +export function PlatinumLicensePrompt() { + // Set the height to give it some top margin + const style = { height: '60vh' }; + + const licensePageUrl = useKibanaUrl( + '/app/kibana', + '/management/elasticsearch/license_management/home' + ); + + return ( + + + + + {i18n.translate( + 'xpack.apm.serviceMap.licensePromptButtonText', + { + defaultMessage: 'Start 30-day Platinum trial' + } + )} + + ]} + body={ +

+ {i18n.translate('xpack.apm.serviceMap.licensePromptBody', { + defaultMessage: + "In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data." + })} +

+ } + title={ +

+ {i18n.translate('xpack.apm.serviceMap.licensePromptTitle', { + defaultMessage: 'Service maps is available in Platinum.' + })} +

+ } + /> +
+
+
+ ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx index 9b0668028c42f..16a91116ae762 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -4,12 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import React from 'react'; import { useFetcher } from '../../../hooks/useFetcher'; -import { Cytoscape } from './Cytoscape'; +import { useLicense } from '../../../hooks/useLicense'; +import { useUrlParams } from '../../../hooks/useUrlParams'; import { Controls } from './Controls'; +import { Cytoscape } from './Cytoscape'; +import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; interface ServiceMapProps { serviceName?: string; @@ -53,8 +55,11 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { ); const elements = Array.isArray(data) ? data : []; + const license = useLicense(); + const isValidPlatinumLicense = + license?.isActive && license?.type === 'platinum'; - return ( + return isValidPlatinumLicense ? ( + ) : ( + ); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx index 8005fc17f2a20..d01093be801a2 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMetrics/index.tsx @@ -31,7 +31,7 @@ export function ServiceMetrics({ agentName }: ServiceMetricsProps) { const localFiltersConfig: React.ComponentProps = useMemo( () => ({ - filterNames: ['host', 'containerId', 'podName'], + filterNames: ['host', 'containerId', 'podName', 'serviceVersion'], params: { serviceName, serviceNodeName diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap index 489d4f2908cbe..9b2a2c8f2490a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap @@ -111,12 +111,14 @@ NodeList [ class="euiIcon euiIcon--medium euiIcon-isLoaded euiButton__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" > + <path - d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 0 0-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 0 1 0 8.373zM8 15A6.956 6.956 0 0 1 3.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 0 0 2.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 0 1 8 15zm-5.601-2.813a6.963 6.963 0 0 1 0-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 0 0 3 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 1 1 8 4a4 4 0 0 1 0 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 0 0 8 3a4.979 4.979 0 0 0-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 0 1 8 1zm0-1a8.001 8.001 0 1 0 .003 16.002A8.001 8.001 0 0 0 8 0z" + d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 00-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 010 8.373zM8 15A6.956 6.956 0 013.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 002.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 018 15zm-5.601-2.813a6.963 6.963 0 010-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 003 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 118 4a4 4 0 010 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 008 3a4.979 4.979 0 00-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 018 1zm0-1a8.001 8.001 0 10.003 16.002A8.001 8.001 0 008 0z" fill-rule="evenodd" /> </svg> diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx index dbd0e9d3b9d22..9d6639b000762 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -50,7 +50,7 @@ export function TransactionDetails() { const localUIFiltersConfig = useMemo(() => { const config: React.ComponentProps<typeof LocalUIFilters> = { - filterNames: ['transactionResult'], + filterNames: ['transactionResult', 'serviceVersion'], projection: PROJECTION.TRANSACTIONS, params: { transactionName, diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx index de356b5812e9a..439e3d80eef4f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -96,7 +96,13 @@ export function TransactionOverview() { const localFiltersConfig: React.ComponentProps<typeof LocalUIFilters> = useMemo( () => ({ - filterNames: ['transactionResult', 'host', 'containerId', 'podName'], + filterNames: [ + 'transactionResult', + 'host', + 'containerId', + 'podName', + 'serviceVersion' + ], params: { serviceName, transactionType diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx index c99c7f12062e2..fcc0dc7d26695 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx @@ -21,7 +21,8 @@ const ErrorOverviewLink = ({ serviceName, query, ...rest }: Props) => { urlParams, 'host', 'containerId', - 'podName' + 'podName', + 'serviceVersion' ); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx index dd988f3e3720d..7d21e1efa44f2 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx @@ -19,7 +19,8 @@ const MetricOverviewLink = ({ serviceName, ...rest }: Props) => { urlParams, 'host', 'containerId', - 'podName' + 'podName', + 'serviceVersion' ); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx index 925d85fa0663e..527c3da9e7e1c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx @@ -24,7 +24,8 @@ const ServiceNodeMetricOverviewLink = ({ urlParams, 'host', 'containerId', - 'podName' + 'podName', + 'serviceVersion' ); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx index 10e55d3382448..db1b6ec117bf4 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx @@ -19,7 +19,8 @@ const ServiceNodeOverviewLink = ({ serviceName, ...rest }: Props) => { urlParams, 'host', 'containerId', - 'podName' + 'podName', + 'serviceVersion' ); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx index a4ac05379615a..784f9b36ff621 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx @@ -27,7 +27,11 @@ export const TransactionDetailLink = ({ }: Props) => { const { urlParams } = useUrlParams(); - const persistedFilters = pickKeys(urlParams, 'transactionResult'); + const persistedFilters = pickKeys( + urlParams, + 'transactionResult', + 'serviceVersion' + ); return ( <APMLink diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx index d300b259fccb8..af60a0a748445 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx @@ -20,7 +20,8 @@ const TransactionOverviewLink = ({ serviceName, ...rest }: Props) => { 'transactionResult', 'host', 'containerId', - 'podName' + 'podName', + 'serviceVersion' ); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx b/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx index f68e2978f680f..e1cf07c03dee9 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/LoadingStatePrompt.tsx @@ -5,20 +5,14 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; export function LoadingStatePrompt() { return ( - <EuiEmptyPrompt - title={ - <div> - {i18n.translate('xpack.apm.loading.prompt', { - defaultMessage: 'Loading...' - })} - </div> - } - titleSize="s" - /> + <EuiFlexGroup justifyContent="spaceAround"> + <EuiFlexItem grow={false}> + <EuiLoadingSpinner size="l" /> + </EuiFlexItem> + </EuiFlexGroup> ); } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx index 53c66e04c468a..c8404b02afe70 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { sortByOrder } from 'lodash'; import React, { useMemo, useCallback, ReactNode } from 'react'; import { useUrlParams } from '../../../hooks/useUrlParams'; @@ -69,8 +69,8 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { const sort = useMemo(() => { return { sort: { - field: sortField, - direction: sortDirection + field: sortField as keyof T, + direction: sortDirection as 'asc' | 'desc' } }; }, [sortField, sortDirection]); @@ -78,7 +78,7 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { const onTableChange = useCallback( (options: { page: { index: number; size: number }; - sort: { field: string; direction: 'asc' | 'desc' }; + sort?: { field: keyof T; direction: 'asc' | 'desc' }; }) => { history.push({ ...history.location, @@ -86,8 +86,8 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { ...toQuery(history.location.search), page: options.page.index, pageSize: options.page.size, - sortField: options.sort.field, - sortDirection: options.sort.direction + sortField: options.sort!.field, + sortDirection: options.sort!.direction }) }); }, @@ -107,7 +107,7 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { <EuiBasicTable noItemsMessage={noItemsMessage} items={renderedItems} - columns={columns} + columns={(columns as unknown) as Array<EuiBasicTableColumn<T>>} // EuiBasicTableColumn is stricter than ITableColumn pagination={pagination} sorting={sort} onChange={onTableChange} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap index 5b17b124a321d..ea1b825c856ad 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap @@ -185,14 +185,18 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] type="arrowRight" > <EuiIconEmpty + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiAccordion__icon" focusable="false" + role="img" style={null} > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiAccordion__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap index 63f56b8db5c50..48e442ce734cf 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap @@ -21,12 +21,14 @@ exports[`TransactionActionMenu component should match the snapshot 1`] = ` class="euiIcon euiIcon--medium euiIcon-isLoaded euiButtonEmpty__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" > + <title /> <path - d="M13.069 5.157L8.384 9.768a.546.546 0 0 1-.768 0L2.93 5.158a.552.552 0 0 0-.771 0 .53.53 0 0 0 0 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 0 0 0-.76.552.552 0 0 0-.771 0z" + d="M13.069 5.157L8.384 9.768a.546.546 0 01-.768 0L2.93 5.158a.552.552 0 00-.771 0 .53.53 0 000 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 000-.76.552.552 0 00-.771 0z" fill-rule="non-zero" /> </svg> diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx new file mode 100644 index 0000000000000..fb087612f8e3d --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx @@ -0,0 +1,74 @@ +/* + * 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 { VerticalGridLines } from 'react-vis'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { + EuiIcon, + EuiToolTip, + EuiFlexGroup, + EuiFlexItem, + EuiText +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Maybe } from '../../../../../typings/common'; +import { Annotation } from '../../../../../common/annotations'; +import { PlotValues, SharedPlot } from './plotUtils'; +import { asAbsoluteDateTime } from '../../../../utils/formatters'; + +interface Props { + annotations: Annotation[]; + plotValues: PlotValues; + width: number; + overlay: Maybe<HTMLElement>; +} + +const style = { + stroke: theme.euiColorSecondary, + strokeDasharray: 'none' +}; + +export function AnnotationsPlot(props: Props) { + const { plotValues, annotations } = props; + + const tickValues = annotations.map(annotation => annotation.time); + + return ( + <> + <SharedPlot plotValues={plotValues}> + <VerticalGridLines tickValues={tickValues} style={style} /> + </SharedPlot> + {annotations.map(annotation => ( + <div + key={annotation.id} + style={{ + position: 'absolute', + left: plotValues.x(annotation.time) - 8, + top: -2 + }} + > + <EuiToolTip + title={asAbsoluteDateTime(annotation.time, 'seconds')} + content={ + <EuiFlexGroup> + <EuiFlexItem grow={true}> + <EuiText> + {i18n.translate('xpack.apm.version', { + defaultMessage: 'Version' + })} + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}>{annotation.text}</EuiFlexItem> + </EuiFlexGroup> + } + > + <EuiIcon type="tag" color={theme.euiColorSecondary} /> + </EuiToolTip> + </div> + ))} + </> + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js index 11755e13bfdd6..848c975942ff6 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js @@ -16,6 +16,8 @@ import { truncate } from '../../../../style/variables'; import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon } from '@elastic/eui'; const Container = styled.div` display: flex; @@ -73,9 +75,12 @@ export default function Legends({ noHits, series, seriesEnabledState, - truncateLegends + truncateLegends, + hasAnnotations, + showAnnotations, + onAnnotationsToggle }) { - if (noHits) { + if (noHits && !hasAnnotations) { return null; } @@ -107,6 +112,30 @@ export default function Legends({ /> ); })} + {hasAnnotations && ( + <Legend + key="annotations" + onClick={() => { + if (onAnnotationsToggle) { + onAnnotationsToggle(); + } + }} + text={ + <LegendContent> + {i18n.translate('xpack.apm.serviceVersion', { + defaultMessage: 'Service version' + })} + </LegendContent> + } + indicator={() => ( + <div style={{ marginRight: px(units.quarter) }}> + <EuiIcon type="tag" color={theme.euiColorSecondary} /> + </div> + )} + disabled={!showAnnotations} + color={theme.euiColorSecondary} + /> + )} <MoreSeries hiddenSeriesCount={hiddenSeriesCount} /> </Container> ); @@ -118,5 +147,8 @@ Legends.propTypes = { noHits: PropTypes.bool.isRequired, series: PropTypes.array.isRequired, seriesEnabledState: PropTypes.array.isRequired, - truncateLegends: PropTypes.bool.isRequired + truncateLegends: PropTypes.bool.isRequired, + hasAnnotations: PropTypes.bool, + showAnnotations: PropTypes.bool, + onAnnotationsToggle: PropTypes.func }; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/index.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/index.js index e87d60c9b3fe8..f59c30d2f4d2f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/index.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/index.js @@ -13,6 +13,7 @@ import Legends from './Legends'; import StaticPlot from './StaticPlot'; import InteractivePlot from './InteractivePlot'; import VoronoiPlot from './VoronoiPlot'; +import { AnnotationsPlot } from './AnnotationsPlot'; import { createSelector } from 'reselect'; import { getPlotValues } from './plotUtils'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; @@ -28,7 +29,8 @@ export class InnerCustomPlot extends PureComponent { seriesEnabledState: [], isDrawing: false, selectionStart: null, - selectionEnd: null + selectionEnd: null, + showAnnotations: true }; getEnabledSeries = createSelector( @@ -122,7 +124,7 @@ export class InnerCustomPlot extends PureComponent { } render() { - const { series, truncateLegends, width } = this.props; + const { series, truncateLegends, width, annotations } = this.props; if (!width) { return null; @@ -166,6 +168,14 @@ export class InnerCustomPlot extends PureComponent { tickFormatX={this.props.tickFormatX} /> + {this.state.showAnnotations && !isEmpty(annotations) && ( + <AnnotationsPlot + plotValues={plotValues} + width={width} + annotations={annotations || []} + /> + )} + <InteractivePlot plotValues={plotValues} hoverX={this.props.hoverX} @@ -192,6 +202,13 @@ export class InnerCustomPlot extends PureComponent { hiddenSeriesCount={hiddenSeriesCount} clickLegend={this.clickLegend} seriesEnabledState={this.state.seriesEnabledState} + hasAnnotations={!isEmpty(annotations)} + showAnnotations={this.state.showAnnotations} + onAnnotationsToggle={() => { + this.setState(({ showAnnotations }) => ({ + showAnnotations: !showAnnotations + })); + }} /> </Fragment> ); @@ -209,7 +226,14 @@ InnerCustomPlot.propTypes = { truncateLegends: PropTypes.bool, width: PropTypes.number.isRequired, height: PropTypes.number, - stackBy: PropTypes.string + stackBy: PropTypes.string, + annotations: PropTypes.arrayOf( + PropTypes.shape({ + type: PropTypes.string, + id: PropTypes.string, + firstSeen: PropTypes.number + }) + ) }; InnerCustomPlot.defaultProps = { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.test.ts b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.test.ts index 55bfb490e8588..b130deed7f098 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.test.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.test.ts @@ -6,6 +6,7 @@ // @ts-ignore import * as plotUtils from './plotUtils'; +import { TimeSeries, Coordinate } from '../../../../../typings/timeseries'; describe('plotUtils', () => { describe('getPlotValues', () => { @@ -34,7 +35,10 @@ describe('plotUtils', () => { expect( plotUtils .getPlotValues( - [{ data: { x: 0, y: 200 } }, { data: { x: 0, y: 300 } }], + [ + { data: [{ x: 0, y: 200 }] }, + { data: [{ x: 0, y: 300 }] } + ] as Array<TimeSeries<Coordinate>>, [], { height: 1, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.tsx similarity index 74% rename from x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.js rename to x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.tsx index 4186f6c899750..10eb4659ea695 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/plotUtils.tsx @@ -11,6 +11,7 @@ import d3 from 'd3'; import PropTypes from 'prop-types'; import React from 'react'; +import { TimeSeries, Coordinate } from '../../../../../typings/timeseries'; import { unit } from '../../../../style/variables'; import { getTimezoneOffsetInMs } from './getTimezoneOffsetInMs'; @@ -22,20 +23,23 @@ const XY_MARGIN = { bottom: unit * 2 }; -const getXScale = (xMin, xMax, width) => { +const getXScale = (xMin: number, xMax: number, width: number) => { return scaleLinear() .domain([xMin, xMax]) .range([XY_MARGIN.left, width - XY_MARGIN.right]); }; -const getYScale = (yMin, yMax) => { +const getYScale = (yMin: number, yMax: number) => { return scaleLinear() .domain([yMin, yMax]) .range([XY_HEIGHT, 0]) .nice(); }; -function getFlattenedCoordinates(visibleSeries, enabledSeries) { +function getFlattenedCoordinates( + visibleSeries: Array<TimeSeries<Coordinate>>, + enabledSeries: Array<TimeSeries<Coordinate>> +) { const enabledCoordinates = flatten(enabledSeries.map(serie => serie.data)); if (!isEmpty(enabledCoordinates)) { return enabledCoordinates; @@ -44,10 +48,24 @@ function getFlattenedCoordinates(visibleSeries, enabledSeries) { return flatten(visibleSeries.map(serie => serie.data)); } +export type PlotValues = ReturnType<typeof getPlotValues>; + export function getPlotValues( - visibleSeries, - enabledSeries, - { width, yMin = 0, yMax = 'max', height, stackBy } + visibleSeries: Array<TimeSeries<Coordinate>>, + enabledSeries: Array<TimeSeries<Coordinate>>, + { + width, + yMin = 0, + yMax = 'max', + height, + stackBy + }: { + width: number; + yMin?: number | 'min'; + yMax?: number | 'max'; + height: number; + stackBy?: 'x' | 'y'; + } ) { const flattenedCoordinates = getFlattenedCoordinates( visibleSeries, @@ -59,10 +77,10 @@ export function getPlotValues( const xMax = d3.max(flattenedCoordinates, d => d.x); if (yMax === 'max') { - yMax = d3.max(flattenedCoordinates, d => d.y); + yMax = d3.max(flattenedCoordinates, d => d.y ?? 0); } if (yMin === 'min') { - yMin = d3.min(flattenedCoordinates, d => d.y); + yMin = d3.min(flattenedCoordinates, d => d.y ?? 0); } const [xMinZone, xMaxZone] = [xMin, xMax].map(x => { @@ -101,11 +119,19 @@ export function getPlotValues( }; } -export function SharedPlot({ plotValues, ...props }) { +export function SharedPlot({ + plotValues, + ...props +}: { + plotValues: PlotValues; + children: React.ReactNode; +}) { const { XY_HEIGHT: height, XY_MARGIN: margin, XY_WIDTH: width } = plotValues; return ( - <div style={{ position: 'absolute', top: 0, left: 0 }}> + <div + style={{ position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }} + > <XYPlot dontCheckIfEmpty height={height} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap index ff917dd95bf96..c46cbbbcccc0b 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap @@ -46,6 +46,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -2094,6 +2095,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -2126,6 +2128,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -2818,6 +2821,7 @@ Object { "selectionEnd": null, "selectionStart": null, "seriesEnabledState": Array [], + "showAnnotations": true, } `; @@ -2969,6 +2973,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -5017,6 +5022,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -5283,6 +5289,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -5975,6 +5982,7 @@ Object { "selectionEnd": null, "selectionStart": null, "seriesEnabledState": Array [], + "showAnnotations": true, } `; @@ -5992,6 +6000,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -6355,6 +6364,7 @@ Array [ style={ Object { "left": 0, + "pointerEvents": "none", "position": "absolute", "top": 0, } @@ -6394,5 +6404,6 @@ Object { "selectionEnd": null, "selectionStart": null, "seriesEnabledState": Array [], + "showAnnotations": true, } `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.js index 912859f6e0419..601482430b00f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.js @@ -37,6 +37,7 @@ export default class Legend extends PureComponent { radius = units.minus - 1, disabled = false, clickable = false, + indicator, ...rest } = this.props; return ( @@ -47,7 +48,7 @@ export default class Legend extends PureComponent { fontSize={fontSize} {...rest} > - <Indicator color={color} radius={radius} /> + {indicator ? indicator() : <Indicator color={color} radius={radius} />} {text} </Container> ); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 97794bf66687b..b0555da705a30 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -170,7 +170,7 @@ export class TransactionCharts extends Component<TransactionChartProps> { </EuiFlexItem> <LicenseContext.Consumer> {license => - this.renderMLHeader(license.features.ml?.is_available) + this.renderMLHeader(license?.getFeature('ml').isAvailable) } </LicenseContext.Consumer> </EuiFlexGroup> diff --git a/x-pack/legacy/plugins/apm/public/context/ChartsSyncContext.tsx b/x-pack/legacy/plugins/apm/public/context/ChartsSyncContext.tsx index c2676a35d8e78..afce0811b48f6 100644 --- a/x-pack/legacy/plugins/apm/public/context/ChartsSyncContext.tsx +++ b/x-pack/legacy/plugins/apm/public/context/ChartsSyncContext.tsx @@ -7,6 +7,8 @@ import React, { useMemo, useState } from 'react'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { history } from '../utils/history'; +import { useUrlParams } from '../hooks/useUrlParams'; +import { useFetcher } from '../hooks/useFetcher'; const ChartsSyncContext = React.createContext<{ hoverX: number | null; @@ -17,6 +19,31 @@ const ChartsSyncContext = React.createContext<{ const ChartsSyncContextProvider: React.FC = ({ children }) => { const [time, setTime] = useState<number | null>(null); + const { urlParams, uiFilters } = useUrlParams(); + + const { start, end, serviceName } = urlParams; + const { environment } = uiFilters; + + const { data = { annotations: [] } } = useFetcher( + callApmApi => { + if (start && end && serviceName) { + return callApmApi({ + pathname: '/api/apm/services/{serviceName}/annotations', + params: { + path: { + serviceName + }, + query: { + start, + end, + environment + } + } + }); + } + }, + [start, end, environment, serviceName] + ); const value = useMemo(() => { const hoverXHandlers = { @@ -43,11 +70,12 @@ const ChartsSyncContextProvider: React.FC = ({ children }) => { }) }); }, - hoverX: time + hoverX: time, + annotations: data.annotations }; return { ...hoverXHandlers }; - }, [time, setTime]); + }, [time, data.annotations]); return <ChartsSyncContext.Provider value={value} children={children} />; }; diff --git a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx index 4bb246a2a745b..714e4c8985678 100644 --- a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx +++ b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx @@ -3,33 +3,27 @@ * 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 { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher'; -import { loadLicense, LicenseApiResponse } from '../../services/rest/xpack'; -import { InvalidLicenseNotification } from './InvalidLicenseNotification'; +import { useObservable } from '../../../../../../../src/plugins/kibana_react/public'; +import { ILicense } from '../../../../../../plugins/licensing/public'; import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { InvalidLicenseNotification } from './InvalidLicenseNotification'; -const initialLicense: LicenseApiResponse = { - features: {}, - license: { - is_active: false - } -}; -export const LicenseContext = React.createContext(initialLicense); +export const LicenseContext = React.createContext<ILicense | undefined>( + undefined +); -export const LicenseProvider: React.FC = ({ children }) => { - const { http } = useApmPluginContext().core; - const { data = initialLicense, status } = useFetcher( - () => loadLicense(http), - [http] - ); - const hasValidLicense = data.license.is_active; +export function LicenseProvider({ children }: { children: React.ReactChild }) { + const { license$ } = useApmPluginContext().plugins.licensing; + const license = useObservable(license$); + const hasInvalidLicense = !license?.isActive; // if license is invalid show an error message - if (status === FETCH_STATUS.SUCCESS && !hasValidLicense) { + if (hasInvalidLicense) { return <InvalidLicenseNotification />; } // render rest of application and pass down license via context - return <LicenseContext.Provider value={data} children={children} />; -}; + return <LicenseContext.Provider value={license} children={children} />; +} diff --git a/x-pack/legacy/plugins/apm/public/hooks/useKibanaUrl.ts b/x-pack/legacy/plugins/apm/public/hooks/useKibanaUrl.ts new file mode 100644 index 0000000000000..0b1c416040a2f --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/hooks/useKibanaUrl.ts @@ -0,0 +1,19 @@ +/* + * 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 url from 'url'; +import { useApmPluginContext } from './useApmPluginContext'; + +export function useKibanaUrl( + /** The path to the plugin */ path: string, + /** The hash path */ hash: string +) { + const { core } = useApmPluginContext(); + return url.format({ + pathname: core.http.basePath.prepend(path), + hash + }); +} diff --git a/x-pack/legacy/plugins/infra/common/log_summary/log_summary.ts b/x-pack/legacy/plugins/apm/public/hooks/useLicense.ts similarity index 60% rename from x-pack/legacy/plugins/infra/common/log_summary/log_summary.ts rename to x-pack/legacy/plugins/apm/public/hooks/useLicense.ts index 79d8fcc9fa60f..ca828e49706a8 100644 --- a/x-pack/legacy/plugins/infra/common/log_summary/log_summary.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useLicense.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface LogSummaryBucket { - count: number; - end: number; - start: number; -} +import { useContext } from 'react'; +import { LicenseContext } from '../context/LicenseContext'; -export type SummaryBucketSize = 'y' | 'M' | 'w' | 'd' | 'h' | 'm' | 's'; +export function useLicense() { + return useContext(LicenseContext); +} diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 64784617442ef..216af91fbb591 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -6,35 +6,37 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Router, Route, Switch } from 'react-router-dom'; +import { Route, Router, Switch } from 'react-router-dom'; +import { ApmRoute } from '@elastic/apm-rum-react'; import styled from 'styled-components'; import { metadata } from 'ui/metadata'; -import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public'; import { + CoreSetup, CoreStart, + PackageInfo, Plugin, - CoreSetup, - PluginInitializerContext, - PackageInfo + PluginInitializerContext } from '../../../../../../src/core/public'; import { DataPublicPluginSetup } from '../../../../../../src/plugins/data/public'; -import { history } from '../utils/history'; -import { LocationProvider } from '../context/LocationContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; -import { px, unit, units } from '../style/variables'; -import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; -import { LicenseProvider } from '../context/LicenseContext'; -import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; +import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public'; +import { LicensingPluginSetup } from '../../../../../plugins/licensing/public'; import { routes } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; +import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; +import { ApmPluginContext } from '../context/ApmPluginContext'; +import { LicenseProvider } from '../context/LicenseContext'; +import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; +import { LocationProvider } from '../context/LocationContext'; import { MatchedRouteProvider } from '../context/MatchedRouteContext'; +import { UrlParamsProvider } from '../context/UrlParamsContext'; import { createStaticIndexPattern } from '../services/rest/index_pattern'; -import { setHelpExtension } from './setHelpExtension'; -import { setReadonlyBadge } from './updateBadge'; +import { px, unit, units } from '../style/variables'; +import { history } from '../utils/history'; import { featureCatalogueEntry } from './featureCatalogueEntry'; import { getConfigFromInjectedMetadata } from './getConfigFromInjectedMetadata'; +import { setHelpExtension } from './setHelpExtension'; import { toggleAppLinkInNav } from './toggleAppLinkInNav'; -import { ApmPluginContext } from '../context/ApmPluginContext'; +import { setReadonlyBadge } from './updateBadge'; export const REACT_APP_ROOT_ID = 'react-apm-root'; @@ -51,7 +53,7 @@ const App = () => { <Route component={ScrollToTopOnPathChange} /> <Switch> {routes.map((route, i) => ( - <Route key={i} {...route} /> + <ApmRoute key={i} {...route} /> ))} </Switch> </MainContainer> @@ -64,6 +66,7 @@ export type ApmPluginStart = void; export interface ApmPluginSetupDeps { data: DataPublicPluginSetup; home: HomePublicPluginSetup; + licensing: LicensingPluginSetup; } export interface ConfigSchema { diff --git a/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts b/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts index 31ba1e8d40aaa..95ebed1fcb2a6 100644 --- a/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts +++ b/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts @@ -7,10 +7,10 @@ import { mockNow } from '../../utils/testHelpers'; import { clearCache, callApi } from '../rest/callApi'; import { SessionStorageMock } from './SessionStorageMock'; -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; -type HttpMock = HttpServiceBase & { - get: jest.SpyInstance<HttpServiceBase['get']>; +type HttpMock = HttpSetup & { + get: jest.SpyInstance<HttpSetup['get']>; }; describe('callApi', () => { diff --git a/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts b/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts index e8a9fa74bd1da..9cca9469bba0e 100644 --- a/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts +++ b/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts @@ -6,7 +6,7 @@ import * as callApiExports from '../rest/callApi'; import { createCallApmApi, APMClient } from '../rest/createCallApmApi'; -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; const callApi = jest .spyOn(callApiExports, 'callApi') @@ -15,7 +15,7 @@ const callApi = jest describe('callApmApi', () => { let callApmApi: APMClient; beforeEach(() => { - callApmApi = createCallApmApi({} as HttpServiceBase); + callApmApi = createCallApmApi({} as HttpSetup); }); afterEach(() => { diff --git a/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts b/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts index 853ba5023f6fd..43ecb860a1f1a 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts @@ -7,7 +7,7 @@ import { isString, startsWith } from 'lodash'; import LRU from 'lru-cache'; import hash from 'object-hash'; -import { HttpServiceBase, HttpFetchOptions } from 'kibana/public'; +import { HttpSetup, HttpFetchOptions } from 'kibana/public'; export type FetchOptions = Omit<HttpFetchOptions, 'body'> & { pathname: string; @@ -42,7 +42,7 @@ export function clearCache() { export type CallApi = typeof callApi; export async function callApi<T = void>( - http: HttpServiceBase, + http: HttpSetup, fetchOptions: FetchOptions ): Promise<T> { const cacheKey = getCacheKey(fetchOptions); diff --git a/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts index 964cc12794075..b4d060adec5a1 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts @@ -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 { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; import { callApi, FetchOptions } from './callApi'; import { APMAPI } from '../../../server/routes/create_apm_api'; import { Client } from '../../../server/routes/typings'; @@ -17,7 +17,7 @@ export type APMClientOptions = Omit<FetchOptions, 'query' | 'body'> & { }; }; -export const createCallApmApi = (http: HttpServiceBase) => +export const createCallApmApi = (http: HttpSetup) => ((options: APMClientOptions) => { const { pathname, params = {}, ...opts } = options; diff --git a/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts b/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts index 477a9f96cc4fb..8e1234dd55e69 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/index_pattern.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; import { createCallApmApi } from './createCallApmApi'; -export const createStaticIndexPattern = async (http: HttpServiceBase) => { +export const createStaticIndexPattern = async (http: HttpSetup) => { const callApmApi = createCallApmApi(http); return await callApmApi({ method: 'POST', diff --git a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts index e495a8968a7f3..e42b9536362e0 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -32,7 +32,7 @@ interface StartedMLJobApiResponse { jobs: MlResponseItem[]; } -async function getTransactionIndices(http: HttpServiceBase) { +async function getTransactionIndices(http: HttpSetup) { const callApmApi: APMClient = createCallApmApi(http); const indices = await callApmApi({ method: 'GET', @@ -48,7 +48,7 @@ export async function startMLJob({ }: { serviceName: string; transactionType: string; - http: HttpServiceBase; + http: HttpSetup; }) { const transactionIndices = await getTransactionIndices(http); const groups = ['apm', serviceName.toLowerCase()]; @@ -90,7 +90,7 @@ export async function getHasMLJob({ }: { serviceName: string; transactionType: string; - http: HttpServiceBase; + http: HttpSetup; }) { try { await callApi<MLJobApiResponse>(http, { diff --git a/x-pack/legacy/plugins/apm/public/services/rest/watcher.ts b/x-pack/legacy/plugins/apm/public/services/rest/watcher.ts index dfa64b5368ee9..259f2af33ba9a 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/watcher.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/watcher.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; import { callApi } from './callApi'; export async function createWatch({ @@ -12,7 +12,7 @@ export async function createWatch({ watch, http }: { - http: HttpServiceBase; + http: HttpSetup; id: string; watch: any; }) { diff --git a/x-pack/legacy/plugins/apm/public/services/rest/xpack.ts b/x-pack/legacy/plugins/apm/public/services/rest/xpack.ts deleted file mode 100644 index 95e283cd67709..0000000000000 --- a/x-pack/legacy/plugins/apm/public/services/rest/xpack.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { HttpServiceBase } from 'kibana/public'; -import { callApi } from './callApi'; - -export interface LicenseApiResponse { - license: { - is_active: boolean; - }; - features: { - beats_management?: Record<string, unknown>; - graph?: Record<string, unknown>; - grokdebugger?: Record<string, unknown>; - index_management?: Record<string, unknown>; - logstash?: Record<string, unknown>; - ml?: { - is_available: boolean; - license_type: number; - has_expired: boolean; - enable_links: boolean; - show_links: boolean; - }; - reporting?: Record<string, unknown>; - rollup?: Record<string, unknown>; - searchprofiler?: Record<string, unknown>; - security?: Record<string, unknown>; - spaces?: Record<string, unknown>; - tilemap?: Record<string, unknown>; - watcher?: { - is_available: boolean; - enable_links: boolean; - show_links: boolean; - }; - }; -} - -export async function loadLicense(http: HttpServiceBase) { - return callApi<LicenseApiResponse>(http, { - pathname: `/api/xpack/v1/info` - }); -} diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 0c8a7cbc17884..862c982d6b5ac 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -19,7 +19,11 @@ import { MemoryRouter } from 'react-router-dom'; import { APMConfig } from '../../../../../plugins/apm/server'; import { LocationProvider } from '../context/LocationContext'; import { PromiseReturnType } from '../../typings/common'; -import { ESFilter } from '../../typings/elasticsearch'; +import { + ESFilter, + ESSearchResponse, + ESSearchRequest +} from '../../typings/elasticsearch'; import { ApmPluginContext, ApmPluginContextValue @@ -117,29 +121,41 @@ interface MockSetup { }; } +interface Options { + mockResponse?: ( + request: ESSearchRequest + ) => ESSearchResponse<unknown, ESSearchRequest>; +} + export async function inspectSearchParams( - fn: (mockSetup: MockSetup) => Promise<any> + fn: (mockSetup: MockSetup) => Promise<any>, + options: Options = {} ) { - const clientSpy = jest.fn().mockReturnValueOnce({ - hits: { - total: 0 - } + const spy = jest.fn().mockImplementation(async request => { + return options.mockResponse + ? options.mockResponse(request) + : { + hits: { + hits: { + total: { + value: 0 + } + } + } + }; }); - const internalClientSpy = jest.fn().mockReturnValueOnce({ - hits: { - total: 0 - } - }); + let response; + let error; const mockSetup = { start: 1528113600000, end: 1528977600000, client: { - search: clientSpy + search: spy } as any, internalClient: { - search: internalClientSpy + search: spy } as any, config: new Proxy( {}, @@ -164,21 +180,18 @@ export async function inspectSearchParams( dynamicIndexPattern: null as any }; try { - await fn(mockSetup); - } catch { + response = await fn(mockSetup); + } catch (err) { + error = err; // we're only extracting the search params } - let params; - if (clientSpy.mock.calls.length) { - params = clientSpy.mock.calls[0][0]; - } else { - params = internalClientSpy.mock.calls[0][0]; - } - return { - params, - teardown: () => clientSpy.mockClear() + params: spy.mock.calls[0][0], + response, + error, + spy, + teardown: () => spy.mockClear() }; } diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md index 0f928fe626bd3..6b21f08b7695e 100644 --- a/x-pack/legacy/plugins/apm/readme.md +++ b/x-pack/legacy/plugins/apm/readme.md @@ -31,11 +31,8 @@ _Docker Compose is required_ ### Setup default APM users -APM behaves differently depending on which the role and permissions a logged in user has. -For testing purposes APM has invented 4 custom users: - - -**elastic**: Apps: read/write. Indices: read/write (all) +APM behaves differently depending on which the role and permissions a logged in user has. +For testing purposes APM uses 3 custom users: **apm_read_user**: Apps: read. Indices: read (`apm-*`) @@ -44,10 +41,10 @@ For testing purposes APM has invented 4 custom users: **kibana_write_user** Apps: read/write. Indices: None -To create the 4 users with the correct roles run the following script: +To create the users with the correct roles run the following script: ```sh -node x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js --username <github-username> +node x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js --role-suffix <github-username-or-something-unique> ``` The users will be created with the password specified in kibana.dev.yml for `elasticsearch.password` diff --git a/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts index 66f2a8d1ac79f..85aa43f78f7dd 100644 --- a/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts +++ b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts @@ -20,13 +20,13 @@ const config = yaml.safeLoad( ) ); -const GITHUB_USERNAME = argv.username as string; const KIBANA_INDEX = config['kibana.index'] as string; const TASK_MANAGER_INDEX = config['xpack.task_manager.index'] as string; -const ELASTICSEARCH_USERNAME = (argv.esUsername as string) || 'elastic'; -const ELASTICSEARCH_PASSWORD = (argv.esPassword || +const KIBANA_ROLE_SUFFIX = argv.roleSuffix as string; +const ELASTICSEARCH_USERNAME = (argv.username as string) || 'elastic'; +const ELASTICSEARCH_PASSWORD = (argv.password || config['elasticsearch.password']) as string; -const KIBANA_BASE_URL = (argv.baseUrl as string) || 'http://localhost:5601'; +const KIBANA_BASE_URL = (argv.kibanaUrl as string) || 'http://localhost:5601'; interface User { username: string; @@ -40,51 +40,76 @@ const getKibanaBasePath = once(async () => { try { await axios.request({ url: KIBANA_BASE_URL, maxRedirects: 0 }); } catch (e) { - const err = e as AxiosError; - const { location } = err.response?.headers; - const isBasePath = RegExp(/^\/\w{3}$/).test(location); - return isBasePath ? location : ''; + if (isAxiosError(e)) { + const location = e.response?.headers?.location; + const isBasePath = RegExp(/^\/\w{3}$/).test(location); + return isBasePath ? location : ''; + } + + throw e; } return ''; }); init().catch(e => { - if (e.response) { - console.log( - JSON.stringify({ request: e.config, response: e.response.data }, null, 2) + if (e instanceof AbortError) { + console.error(e.message); + } else if (isAxiosError(e)) { + console.error( + `${e.config.method?.toUpperCase() || 'GET'} ${e.config.url} (Code: ${ + e.response?.status + })` ); - return; - } - console.log(e); + if (e.response) { + console.error( + JSON.stringify( + { request: e.config, response: e.response.data }, + null, + 2 + ) + ); + } + } else { + console.error(e); + } }); async function init() { + const version = await getKibanaVersion(); + console.log(`Connected to Kibana ${version}`); + + const isKibanaLocal = KIBANA_BASE_URL.startsWith('http://localhost'); + // kibana.index must be different from `.kibana` - if (KIBANA_INDEX === '.kibana') { + if (isKibanaLocal && KIBANA_INDEX === '.kibana') { console.log( 'kibana.dev.yml: Please use a custom "kibana.index". Example: "kibana.index: .kibana-john"' ); return; } - if (!KIBANA_INDEX.startsWith('.kibana')) { + if (isKibanaLocal && !KIBANA_INDEX.startsWith('.kibana')) { console.log( 'kibana.dev.yml: "kibana.index" must be prefixed with `.kibana`. Example: "kibana.index: .kibana-john"' ); return; } - if (TASK_MANAGER_INDEX && !TASK_MANAGER_INDEX.startsWith('.kibana')) { + if ( + isKibanaLocal && + TASK_MANAGER_INDEX && + !TASK_MANAGER_INDEX.startsWith('.kibana') + ) { console.log( 'kibana.dev.yml: "xpack.task_manager.index" must be prefixed with `.kibana`. Example: "xpack.task_manager.index: .kibana-task-manager-john"' ); return; } - if (!GITHUB_USERNAME) { + if (!KIBANA_ROLE_SUFFIX) { console.log( - 'Please specify your github username with `--username <username>` ' + 'Please specify a unique suffix that will be added to your roles with `--role-suffix <suffix>` ' ); return; } @@ -95,8 +120,8 @@ async function init() { return; } - const KIBANA_READ_ROLE = `kibana_read_${GITHUB_USERNAME}`; - const KIBANA_WRITE_ROLE = `kibana_write_${GITHUB_USERNAME}`; + const KIBANA_READ_ROLE = `kibana_read_${KIBANA_ROLE_SUFFIX}`; + const KIBANA_WRITE_ROLE = `kibana_write_${KIBANA_ROLE_SUFFIX}`; // create roles await createRole({ roleName: KIBANA_READ_ROLE, privilege: 'read' }); @@ -132,16 +157,18 @@ async function isSecurityEnabled() { } async function callKibana<T>(options: AxiosRequestConfig): Promise<T> { - const basePath = await getKibanaBasePath(); - const { data } = await axios.request({ + const kibanaBasePath = await getKibanaBasePath(); + const reqOptions = { ...options, - baseURL: KIBANA_BASE_URL + basePath, + baseURL: KIBANA_BASE_URL + kibanaBasePath, auth: { username: ELASTICSEARCH_USERNAME, password: ELASTICSEARCH_PASSWORD }, headers: { 'kbn-xsrf': 'true', ...options.headers } - }); + }; + + const { data } = await axios.request(reqOptions); return data; } @@ -222,10 +249,8 @@ async function getUser(username: string) { url: `/internal/security/users/${username}` }); } catch (e) { - const err = e as AxiosError; - // return empty if user doesn't exist - if (err.response?.status === 404) { + if (isAxiosError(e) && e.response?.status === 404) { return null; } @@ -240,13 +265,51 @@ async function getRole(roleName: string) { url: `/api/security/role/${roleName}` }); } catch (e) { - const err = e as AxiosError; - // return empty if role doesn't exist - if (err.response?.status === 404) { + if (isAxiosError(e) && e.response?.status === 404) { return null; } throw e; } } + +async function getKibanaVersion() { + try { + const res: { version: { number: number } } = await callKibana({ + method: 'GET', + url: `/api/status` + }); + return res.version.number; + } catch (e) { + if (isAxiosError(e)) { + switch (e.response?.status) { + case 401: + throw new AbortError( + `Could not access Kibana with the provided credentials. Username: "${e.config.auth?.username}". Password: "${e.config.auth?.password}"` + ); + + case 404: + throw new AbortError( + `Could not get version on ${e.config.url} (Code: 404)` + ); + + default: + throw new AbortError( + `Cannot access Kibana on ${e.config.baseURL}. Please specify Kibana with: "--kibana-url <url>"` + ); + } + } + throw e; + } +} + +function isAxiosError(e: AxiosError | Error): e is AxiosError { + return 'isAxiosError' in e; +} + +class AbortError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js b/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js index a3dc3d66f56a0..825c1a526fcc5 100644 --- a/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js +++ b/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js @@ -12,7 +12,7 @@ * The two roles will be assigned to the already existing users: `apm_read_user`, `apm_write_user`, `kibana_write_user` * * This makes it possible to use the existing cloud users locally - * Usage: node setup-kibana-security.js --username YOUR-GITHUB-USERNAME + * Usage: node setup-kibana-security.js --role-suffix <YOUR-GITHUB-USERNAME-OR-SOMETHING-UNIQUE> ******************************/ // compile typescript on the fly diff --git a/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/multiple-versions.json b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/multiple-versions.json new file mode 100644 index 0000000000000..7e2d2405d681c --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/multiple-versions.json @@ -0,0 +1,34 @@ +{ + "took": 444, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 10000, + "relation": "gte" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "versions": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "8.0.0", + "doc_count": 615285 + }, + { + "key": "7.5.0", + "doc_count": 615285 + } + ] + } + } +} diff --git a/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/no-versions.json b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/no-versions.json new file mode 100644 index 0000000000000..fa5c63f1b9a54 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/no-versions.json @@ -0,0 +1,25 @@ +{ + "took": 398, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 10000, + "relation": "gte" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "versions": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [] + } + } +} diff --git a/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/one-version.json b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/one-version.json new file mode 100644 index 0000000000000..56303909bcd6f --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/one-version.json @@ -0,0 +1,30 @@ +{ + "took": 444, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 10000, + "relation": "gte" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "versions": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "8.0.0", + "doc_count": 615285 + } + ] + } + } +} diff --git a/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/versions-first-seen.json b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/versions-first-seen.json new file mode 100644 index 0000000000000..c53b28c8bf594 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/services/annotations/__fixtures__/versions-first-seen.json @@ -0,0 +1,24 @@ +{ + "took": 4750, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 10000, + "relation": "gte" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "first_seen": { + "value": 1.5281138E12, + "value_as_string": "2018-06-04T12:00:00.000Z" + } + } +} diff --git a/x-pack/legacy/plugins/apm/server/lib/services/annotations/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/services/annotations/index.test.ts new file mode 100644 index 0000000000000..75ac0642a1b8c --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/services/annotations/index.test.ts @@ -0,0 +1,98 @@ +/* + * 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 { getServiceAnnotations } from '.'; +import { + SearchParamsMock, + inspectSearchParams +} from '../../../../public/utils/testHelpers'; +import noVersions from './__fixtures__/no-versions.json'; +import oneVersion from './__fixtures__/one-version.json'; +import multipleVersions from './__fixtures__/multiple-versions.json'; +import versionsFirstSeen from './__fixtures__/versions-first-seen.json'; + +describe('getServiceAnnotations', () => { + let mock: SearchParamsMock; + + afterEach(() => { + mock.teardown(); + }); + + describe('with 0 versions', () => { + it('returns no annotations', async () => { + mock = await inspectSearchParams( + setup => + getServiceAnnotations({ + setup, + serviceName: 'foo', + environment: 'bar' + }), + { + mockResponse: () => noVersions + } + ); + + expect(mock.response).toEqual({ annotations: [] }); + }); + }); + + describe('with 1 version', () => { + it('returns no annotations', async () => { + mock = await inspectSearchParams( + setup => + getServiceAnnotations({ + setup, + serviceName: 'foo', + environment: 'bar' + }), + { + mockResponse: () => oneVersion + } + ); + + expect(mock.response).toEqual({ annotations: [] }); + }); + }); + + describe('with more than 1 version', () => { + it('returns two annotations', async () => { + const responses = [ + multipleVersions, + versionsFirstSeen, + versionsFirstSeen + ]; + mock = await inspectSearchParams( + setup => + getServiceAnnotations({ + setup, + serviceName: 'foo', + environment: 'bar' + }), + { + mockResponse: () => responses.shift() + } + ); + + expect(mock.spy.mock.calls.length).toBe(3); + + expect(mock.response).toEqual({ + annotations: [ + { + id: '8.0.0', + text: '8.0.0', + time: 1.5281138e12, + type: 'version' + }, + { + id: '7.5.0', + text: '7.5.0', + time: 1.5281138e12, + type: 'version' + } + ] + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/server/lib/services/annotations/index.ts b/x-pack/legacy/plugins/apm/server/lib/services/annotations/index.ts new file mode 100644 index 0000000000000..c03746ca220ee --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/services/annotations/index.ts @@ -0,0 +1,114 @@ +/* + * 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 { isNumber } from 'lodash'; +import { Annotation, AnnotationType } from '../../../../common/annotations'; +import { ESFilter } from '../../../../typings/elasticsearch'; +import { + SERVICE_NAME, + SERVICE_ENVIRONMENT, + PROCESSOR_EVENT +} from '../../../../common/elasticsearch_fieldnames'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { rangeFilter } from '../../helpers/range_filter'; +import { SERVICE_VERSION } from '../../../../common/elasticsearch_fieldnames'; + +export async function getServiceAnnotations({ + setup, + serviceName, + environment +}: { + serviceName: string; + environment?: string; + setup: Setup & SetupTimeRange; +}) { + const { start, end, client, indices } = setup; + + const filter: ESFilter[] = [ + { term: { [PROCESSOR_EVENT]: 'transaction' } }, + { range: rangeFilter(start, end) }, + { term: { [SERVICE_NAME]: serviceName } } + ]; + + if (environment) { + filter.push({ term: { [SERVICE_ENVIRONMENT]: environment } }); + } + + const versions = + ( + await client.search({ + index: indices['apm_oss.transactionIndices'], + body: { + size: 0, + track_total_hits: false, + query: { + bool: { + filter + } + }, + aggs: { + versions: { + terms: { + field: SERVICE_VERSION + } + } + } + } + }) + ).aggregations?.versions.buckets.map(bucket => bucket.key) ?? []; + + if (versions.length > 1) { + const annotations = await Promise.all( + versions.map(async version => { + const response = await client.search({ + index: indices['apm_oss.transactionIndices'], + body: { + size: 0, + query: { + bool: { + filter: filter + .filter(esFilter => !Object.keys(esFilter).includes('range')) + .concat({ + term: { + [SERVICE_VERSION]: version + } + }) + } + }, + aggs: { + first_seen: { + min: { + field: '@timestamp' + } + } + }, + track_total_hits: false + } + }); + + const firstSeen = response.aggregations?.first_seen.value; + + if (!isNumber(firstSeen)) { + throw new Error( + 'First seen for version was unexpectedly undefined or null.' + ); + } + + if (firstSeen < start || firstSeen > end) { + return null; + } + + return { + type: AnnotationType.VERSION, + id: version, + time: firstSeen, + text: version + }; + }) + ); + return { annotations: annotations.filter(Boolean) as Annotation[] }; + } + return { annotations: [] }; +} diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index e451f89af5620..e7b2330b472d8 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -54,10 +54,6 @@ export function getApmIndicesConfig(config: APMConfig): ApmIndicesConfig { }; } -// export async function getApmIndices(context: APMRequestHandlerContext) { -// return _getApmIndices(context.core, context.config); -// } - export async function getApmIndices({ config, savedObjectsClient diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts index a0149bec728c5..6363b996ce5bb 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/config.ts @@ -9,7 +9,8 @@ import { POD_NAME, SERVICE_AGENT_NAME, HOST_NAME, - TRANSACTION_RESULT + TRANSACTION_RESULT, + SERVICE_VERSION } from '../../../../common/elasticsearch_fieldnames'; const filtersByName = { @@ -42,6 +43,12 @@ const filtersByName = { defaultMessage: 'Transaction result' }), fieldName: TRANSACTION_RESULT + }, + serviceVersion: { + title: i18n.translate('xpack.apm.localFilters.titles.serviceVersion', { + defaultMessage: 'Service version' + }), + fieldName: SERVICE_VERSION } }; diff --git a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts index 3e97d851acd29..c1a9838e90406 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts @@ -82,7 +82,7 @@ export function createApi() { // if any validation is defined. Not having validation currently // means we don't get the payload. See // https://github.com/elastic/kibana/issues/50179 - body: schema.nullable(anyObject) as typeof anyObject, + body: schema.nullable(anyObject), params: anyObject, query: anyObject } diff --git a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts index 95488591d4b89..e98842151da84 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts @@ -17,7 +17,8 @@ import { serviceAgentNameRoute, serviceTransactionTypesRoute, servicesRoute, - serviceNodeMetadataRoute + serviceNodeMetadataRoute, + serviceAnnotationsRoute } from './services'; import { agentConfigurationRoute, @@ -75,6 +76,7 @@ const createApmApi = () => { .add(serviceTransactionTypesRoute) .add(servicesRoute) .add(serviceNodeMetadataRoute) + .add(serviceAnnotationsRoute) // Agent configuration .add(agentConfigurationAgentNameRoute) diff --git a/x-pack/legacy/plugins/apm/server/routes/services.ts b/x-pack/legacy/plugins/apm/server/routes/services.ts index 91495bb96b032..78cb092b85db6 100644 --- a/x-pack/legacy/plugins/apm/server/routes/services.ts +++ b/x-pack/legacy/plugins/apm/server/routes/services.ts @@ -19,6 +19,7 @@ import { getServiceNodeMetadata } from '../lib/services/get_service_node_metadat import { createRoute } from './create_route'; import { uiFiltersRt, rangeRt } from './default_api_types'; import { getServiceMap } from '../lib/services/map'; +import { getServiceAnnotations } from '../lib/services/annotations'; export const servicesRoute = createRoute(() => ({ path: '/api/apm/services', @@ -98,3 +99,29 @@ export const serviceMapRoute = createRoute(() => ({ return new Boom('Not found', { statusCode: 404 }); } })); + +export const serviceAnnotationsRoute = createRoute(() => ({ + path: '/api/apm/services/{serviceName}/annotations', + params: { + path: t.type({ + serviceName: t.string + }), + query: t.intersection([ + rangeRt, + t.partial({ + environment: t.string + }) + ]) + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; + const { environment } = context.params.query; + + return getServiceAnnotations({ + setup, + serviceName, + environment + }); + } +})); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/database/index.ts b/x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts similarity index 85% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/database/index.ts rename to x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts index 4e09b5d0e9e2d..6f500caabd824 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/database/index.ts +++ b/x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './adapter_types'; +declare module '@elastic/apm-rum-react'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/index.ts b/x-pack/legacy/plugins/apm/typings/react-vis.d.ts similarity index 88% rename from x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/index.ts rename to x-pack/legacy/plugins/apm/typings/react-vis.d.ts index eba07aaa17fd2..aef8efc30d555 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/index.ts +++ b/x-pack/legacy/plugins/apm/typings/react-vis.d.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './setup_steps'; +declare module 'react-vis'; diff --git a/x-pack/legacy/plugins/apm/typings/timeseries.ts b/x-pack/legacy/plugins/apm/typings/timeseries.ts index d64486d8e71e9..600be15ea229f 100644 --- a/x-pack/legacy/plugins/apm/typings/timeseries.ts +++ b/x-pack/legacy/plugins/apm/typings/timeseries.ts @@ -15,12 +15,14 @@ export interface RectCoordinate { x0: number; } -export interface TimeSeries { +export interface TimeSeries< + TCoordinate extends { x: number } = Coordinate | RectCoordinate +> { title: string; titleShort?: string; hideLegend?: boolean; hideTooltipValue?: boolean; - data: Array<Coordinate | RectCoordinate>; + data: TCoordinate[]; legendValue?: string; type: string; color: string; 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 8b101196d21ee..26ddd682405cb 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 @@ -93,7 +93,7 @@ export class Table extends React.Component<TableProps, TableState> { }; const selectionOptions = hideTableControls - ? null + ? undefined : { onSelectionChange: this.setSelection, selectable: () => true, @@ -148,7 +148,7 @@ export class Table extends React.Component<TableProps, TableState> { ); } - private onTableChange = (page: { index: number; size: number } = { index: 0, size: 50 }) => { + private onTableChange = ({ page }: { page: { index: number; size: number } }) => { if (this.props.onTableChange) { this.props.onTableChange(page.index, page.size); } diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx index 6f03f884563e1..6fff7574c39e6 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -14,7 +14,7 @@ import { ConnectedLink } from '../navigation/connected_link'; import { TagBadge } from '../tag'; export interface ColumnDefinition { - align?: string; + align?: 'left' | 'right' | 'center' | undefined; field: string; name: string; sortable?: boolean; diff --git a/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx b/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx index 3eaf550cb8c77..3952b44f82561 100644 --- a/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx +++ b/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx @@ -13,6 +13,7 @@ import { EuiSpacer, EuiText, EuiTitle, + EuiBasicTableColumn, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; @@ -98,7 +99,7 @@ class BeatDetailPageUi extends React.PureComponent<PageProps, PageState> { ), })); - const columns = [ + const columns: Array<EuiBasicTableColumn<ConfigurationBlock>> = [ { field: 'displayValue', name: intl.formatMessage({ diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js index 61dbd07cfc568..662078585422f 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js @@ -83,9 +83,8 @@ module.exports = async ({ config }) => { loader: 'css-loader', options: { importLoaders: 2, - modules: { - localIdentName: '[name]__[local]___[hash:base64:5]', - }, + modules: true, + localIdentName: '[name]__[local]___[hash:base64:5]', }, }, { 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 546e8967a7439..3669bd3e08201 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,8 +6,8 @@ // @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 { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize_embeddable/constants'; +import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants'; export const EmbeddableTypes = { map: MAP_SAVED_OBJECT_TYPE, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts index 221469ad8ded1..4895571115898 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_search.ts @@ -5,7 +5,7 @@ */ import { ExpressionFunction } from 'src/plugins/expressions/common/types'; -import { SearchInput } from 'src/legacy/core_plugins/kibana/public/discover/embeddable'; +import { SearchInput } from 'src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable'; import { EmbeddableTypes, EmbeddableExpressionType, 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 b00ee446a8724..f6eb377e2698b 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/kibana/public/visualize_embeddable'; import { EmbeddableTypes, EmbeddableExpressionType, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot index 7d37bf184b49b..b9c6d258821f2 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot @@ -16,9 +16,11 @@ exports[`Storyshots renderers/DropdownFilter default 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -61,9 +63,11 @@ exports[`Storyshots renderers/DropdownFilter with choices 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -107,9 +111,11 @@ exports[`Storyshots renderers/DropdownFilter with choices and new value 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -153,9 +159,11 @@ exports[`Storyshots renderers/DropdownFilter with choices and value 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -181,9 +189,11 @@ exports[`Storyshots renderers/DropdownFilter with new value 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 6c9d4ce4459b3..be4db66a84d83 100644 --- 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.examples.storyshot @@ -60,10 +60,11 @@ exports[`Storyshots arguments/AxisConfig extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -141,10 +142,11 @@ exports[`Storyshots arguments/AxisConfig/components extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 4bed74ddb6c60..014c365055fa3 100644 --- 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.examples.storyshot @@ -43,10 +43,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -120,10 +121,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -196,10 +198,11 @@ exports[`Storyshots arguments/DateFormat with preset format 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 6de7df3454dde..e93ad3be31f1d 100644 --- 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.examples.storyshot @@ -53,10 +53,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -140,10 +141,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -226,10 +228,11 @@ exports[`Storyshots arguments/NumberFormat with preset format 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot index 89ed05a1eea93..454ef0a79d10a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot @@ -80,10 +80,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -113,10 +114,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -147,10 +149,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -177,10 +180,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -275,10 +279,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -308,10 +313,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -342,10 +348,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -372,10 +379,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot index 9ffc95c82531e..77359957b8443 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot @@ -127,9 +127,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#000", @@ -156,9 +158,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#fff", @@ -185,9 +189,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#fff", @@ -214,9 +220,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#fff", diff --git a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot index 25198ac2e79f3..5da0ea8738c37 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot @@ -389,10 +389,11 @@ exports[`Storyshots components/Color/ColorManager interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -407,10 +408,11 @@ exports[`Storyshots components/Color/ColorManager interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -807,10 +809,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -825,10 +828,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -894,10 +898,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -912,10 +917,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -981,10 +987,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -999,10 +1006,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot index 05dd67960db05..badbf96029f12 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot @@ -364,9 +364,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -713,9 +715,11 @@ exports[`Storyshots components/Color/ColorPalette six colors, wrap at 4 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -985,9 +989,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", diff --git a/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot index 1af31bdef47fe..6e25ce3d4b0d4 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot @@ -220,10 +220,11 @@ exports[`Storyshots components/Color/ColorPicker interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -238,10 +239,11 @@ exports[`Storyshots components/Color/ColorPicker interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -307,9 +309,11 @@ exports[`Storyshots components/Color/ColorPicker six colors 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -511,10 +515,11 @@ exports[`Storyshots components/Color/ColorPicker six colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -529,10 +534,11 @@ exports[`Storyshots components/Color/ColorPicker six colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -767,10 +773,11 @@ exports[`Storyshots components/Color/ColorPicker six colors, value missing 1`] = type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -785,10 +792,11 @@ exports[`Storyshots components/Color/ColorPicker six colors, value missing 1`] = type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -833,9 +841,11 @@ exports[`Storyshots components/Color/ColorPicker three colors 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -959,10 +969,11 @@ exports[`Storyshots components/Color/ColorPicker three colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -977,10 +988,11 @@ exports[`Storyshots components/Color/ColorPicker three colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot index ec282e9a44fde..efaa34001971e 100644 --- a/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot @@ -55,10 +55,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -209,10 +210,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -255,9 +257,11 @@ Array [ className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -412,10 +416,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -566,10 +571,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -766,10 +772,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -920,10 +927,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -966,9 +974,11 @@ Array [ className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1122,10 +1132,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1276,10 +1287,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1322,9 +1334,11 @@ Array [ className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot index f09921607ef46..5eedf32020e4c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot @@ -16,9 +16,11 @@ exports[`Storyshots components/Elements/ElementCard with click handler 1`] = ` className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -118,9 +120,11 @@ exports[`Storyshots components/Elements/ElementCard with tags 1`] = ` className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -289,9 +293,11 @@ exports[`Storyshots components/Elements/ElementCard with title and description 1 className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index c88869ea22f1a..27a2a60180930 100644 --- 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.examples.storyshot @@ -28,10 +28,11 @@ exports[`Storyshots components/Elements/ElementControls has two buttons 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -57,10 +58,11 @@ exports[`Storyshots components/Elements/ElementControls has two buttons 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 01774f849dfe7..c9fb77061572d 100644 --- 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.examples.storyshot @@ -75,10 +75,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -104,10 +105,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -182,10 +184,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -211,10 +214,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -289,10 +293,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -318,10 +323,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -411,10 +417,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls and filter 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -440,10 +447,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls and filter 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 085662eeb65c7..7f18c2dc85215 100644 --- 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.examples.storyshot @@ -20,10 +20,11 @@ exports[`Storyshots components/FileUpload default 1`] = ` className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 20996df68747c..c1cb45123f04b 100644 --- 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.examples.storyshot @@ -47,10 +47,11 @@ exports[`Storyshots components/FontPicker default 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -129,10 +130,11 @@ exports[`Storyshots components/FontPicker with value 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 79b547102b089..5ee984885bbc0 100644 --- 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.examples.storyshot @@ -71,9 +71,11 @@ exports[`Storyshots components/ItemGrid complex grid 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -100,9 +102,11 @@ exports[`Storyshots components/ItemGrid complex grid 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#FFF", @@ -129,9 +133,11 @@ exports[`Storyshots components/ItemGrid complex grid 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#FFF", @@ -151,27 +157,33 @@ exports[`Storyshots components/ItemGrid icon grid 1`] = ` className="item-grid-row" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} xmlns="http://www.w3.org/2000/svg" /> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} xmlns="http://www.w3.org/2000/svg" /> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index e02d64e3e0647..9397a67402e51 100644 --- 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.examples.storyshot @@ -55,10 +55,11 @@ exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} 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.examples.storyshot index 5a5472e422963..09fc81c382d11 100644 --- 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.examples.storyshot @@ -43,10 +43,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader default 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -85,10 +86,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader default 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -148,10 +150,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader without layer controls 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -190,10 +193,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader without layer controls 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot index ec6d0cbe4d1ec..562feb8111e41 100644 --- a/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot @@ -55,9 +55,11 @@ exports[`Storyshots components/Tags/Tag as health 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#666666", @@ -88,9 +90,11 @@ exports[`Storyshots components/Tags/Tag as health with color 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#9b3067", diff --git a/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot index 1e41a56b82f9e..9dcf55642c66f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot @@ -76,9 +76,11 @@ Array [ className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#cc3b54", @@ -106,9 +108,11 @@ Array [ className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#9b3067", @@ -136,9 +140,11 @@ Array [ className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#d41e93", diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot index a94f593661cf3..1888d01a3ac93 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot @@ -71,10 +71,11 @@ exports[`Storyshots components/Export/PDFPanel default 1`] = ` className="euiButton__content" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButton__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot index c9f5cf67f734a..9c7fca6d78190 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot @@ -28,10 +28,11 @@ exports[`Storyshots components/Export/WorkpadExport disabled 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -72,10 +73,11 @@ exports[`Storyshots components/Export/WorkpadExport enabled 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot index 9e3bebbd71273..2915d3bfef57b 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot @@ -139,10 +139,11 @@ exports[`Storyshots arguments/ContainerStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -208,10 +209,11 @@ exports[`Storyshots arguments/ContainerStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -367,10 +369,11 @@ exports[`Storyshots arguments/ContainerStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -626,10 +629,11 @@ exports[`Storyshots arguments/ContainerStyle/components appearance form 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -695,10 +699,11 @@ exports[`Storyshots arguments/ContainerStyle/components appearance form 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -853,10 +858,11 @@ exports[`Storyshots arguments/ContainerStyle/components border form 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1123,10 +1129,11 @@ exports[`Storyshots arguments/ContainerStyle/components extended template 1`] = className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1192,10 +1199,11 @@ exports[`Storyshots arguments/ContainerStyle/components extended template 1`] = className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1351,10 +1359,11 @@ exports[`Storyshots arguments/ContainerStyle/components extended template 1`] = className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot index 3b41da4cd3d54..8fa2d406831a4 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot @@ -66,10 +66,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -163,10 +164,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -252,10 +254,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -341,10 +344,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot index c843a62702b16..72477cec2603b 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot @@ -173,12 +173,14 @@ exports[`Storyshots arguments/SeriesStyle/components simple: no series 1`] = ` onMouseOver={[Function]} > <svg + aria-hidden={true} aria-label="Info" className="euiIcon euiIcon--medium euiIcon--warning euiIcon-isLoading" focusable="true" height={16} onBlur={[Function]} onFocus={[Function]} + role="img" style={null} tabIndex={0} viewBox="0 0 16 16" diff --git a/x-pack/legacy/plugins/canvas/public/lib/loading_indicator.ts b/x-pack/legacy/plugins/canvas/public/lib/loading_indicator.ts index a95f4278b6a69..4b9e548d5c718 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/loading_indicator.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/loading_indicator.ts @@ -16,7 +16,7 @@ export interface LoadingIndicatorInterface { const loadingCount$ = new Rx.BehaviorSubject(0); -export const initLoadingIndicator = (addLoadingCount: CoreStart['http']['addLoadingCount']) => +export const initLoadingIndicator = (addLoadingCount: CoreStart['http']['addLoadingCountSource']) => addLoadingCount(loadingCount$); export const loadingIndicator = { diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index 5190e8521101b..9828845d9ffa9 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -77,7 +77,7 @@ export class CanvasPlugin initLocationProvider(core, plugins); initStore(core, plugins); initClipboard(plugins.__LEGACY.storage); - initLoadingIndicator(core.http.addLoadingCount); + initLoadingIndicator(core.http.addLoadingCountSource); const CanvasRootController = CanvasRootControllerFactory(core, plugins); plugins.__LEGACY.setRootController('canvas', CanvasRootController); diff --git a/x-pack/legacy/plugins/canvas/server/plugin.ts b/x-pack/legacy/plugins/canvas/server/plugin.ts index b338971103381..2dc87e4a61e04 100644 --- a/x-pack/legacy/plugins/canvas/server/plugin.ts +++ b/x-pack/legacy/plugins/canvas/server/plugin.ts @@ -63,8 +63,8 @@ export class Plugin { registerCanvasUsageCollector(plugins.usageCollection, core); loadSampleData( - plugins.sampleData.addSavedObjectsToSampleDataset, - plugins.sampleData.addAppLinksToSampleDataset + plugins.home.sampleData.addSavedObjectsToSampleDataset, + plugins.home.sampleData.addAppLinksToSampleDataset ); } } diff --git a/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.ts b/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.ts index 08a71badb33ed..ed505c09cc7a4 100644 --- a/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.ts +++ b/x-pack/legacy/plugins/canvas/server/sample_data/load_sample_data.ts @@ -7,9 +7,12 @@ import { CANVAS as label } from '../../i18n'; // @ts-ignore Untyped local import { ecommerceSavedObjects, flightsSavedObjects, webLogsSavedObjects } from './index'; +import { SampleDataRegistrySetup } from '../../../../../../src/plugins/home/server'; -// @ts-ignore: Untyped in Kibana -export function loadSampleData(addSavedObjectsToSampleDataset, addAppLinksToSampleDataset) { +export function loadSampleData( + addSavedObjectsToSampleDataset: SampleDataRegistrySetup['addSavedObjectsToSampleDataset'], + addAppLinksToSampleDataset: SampleDataRegistrySetup['addAppLinksToSampleDataset'] +) { const now = new Date(); const nowTimestamp = now.toISOString(); @@ -27,23 +30,29 @@ export function loadSampleData(addSavedObjectsToSampleDataset, addAppLinksToSamp } addSavedObjectsToSampleDataset('ecommerce', updateCanvasWorkpadTimestamps(ecommerceSavedObjects)); - addAppLinksToSampleDataset('ecommerce', { - path: '/app/canvas#/workpad/workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e', - icon: 'canvasApp', - label, - }); + addAppLinksToSampleDataset('ecommerce', [ + { + path: '/app/canvas#/workpad/workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e', + icon: 'canvasApp', + label, + }, + ]); addSavedObjectsToSampleDataset('flights', updateCanvasWorkpadTimestamps(flightsSavedObjects)); - addAppLinksToSampleDataset('flights', { - path: '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0', - icon: 'canvasApp', - label, - }); + addAppLinksToSampleDataset('flights', [ + { + path: '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0', + icon: 'canvasApp', + label, + }, + ]); addSavedObjectsToSampleDataset('logs', updateCanvasWorkpadTimestamps(webLogsSavedObjects)); - addAppLinksToSampleDataset('logs', { - path: '/app/canvas#/workpad/workpad-ad72a4e9-b422-480c-be6d-a64a0b79541d', - icon: 'canvasApp', - label, - }); + addAppLinksToSampleDataset('logs', [ + { + path: '/app/canvas#/workpad/workpad-ad72a4e9-b422-480c-be6d-a64a0b79541d', + icon: 'canvasApp', + label, + }, + ]); } diff --git a/x-pack/legacy/plugins/canvas/server/shim.ts b/x-pack/legacy/plugins/canvas/server/shim.ts index 7641e51f14e56..1ca6e28bd347e 100644 --- a/x-pack/legacy/plugins/canvas/server/shim.ts +++ b/x-pack/legacy/plugins/canvas/server/shim.ts @@ -7,7 +7,7 @@ import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; import { Legacy } from 'kibana'; -import { CoreSetup as ExistingCoreSetup } from 'src/core/server'; +import { HomeServerPluginSetup } from 'src/plugins/home/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PluginSetupContract } from '../../../../plugins/features/server'; @@ -23,6 +23,7 @@ export interface CoreSetup { export interface PluginsSetup { features: PluginSetupContract; + home: HomeServerPluginSetup; interpreter: { register: (specs: any) => any; }; @@ -39,9 +40,7 @@ export interface PluginsSetup { export async function createSetupShim( server: Legacy.Server ): Promise<{ coreSetup: CoreSetup; pluginsSetup: PluginsSetup }> { - // @ts-ignore: New Platform object not typed - const setup: ExistingCoreSetup = server.newPlatform.setup.core; - + const setup = server.newPlatform.setup.core; return { coreSetup: { ...setup, @@ -58,17 +57,12 @@ export async function createSetupShim( pluginsSetup: { // @ts-ignore: New Platform not typed features: server.newPlatform.setup.plugins.features, + home: server.newPlatform.setup.plugins.home, // @ts-ignore Interpreter plugin not typed on legacy server interpreter: server.plugins.interpreter, kibana: { injectedUiAppVars: await server.getInjectedUiAppVars('kibana'), }, - sampleData: { - // @ts-ignore: Missing from Legacy Server Type - addSavedObjectsToSampleDataset: server.addSavedObjectsToSampleDataset, - // @ts-ignore: Missing from Legacy Server Type - addAppLinksToSampleDataset: server.addAppLinksToSampleDataset, - }, usageCollection: server.newPlatform.setup.plugins.usageCollection, }, }; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap index 782d5364de821..acd68622f1af0 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap @@ -9,7 +9,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with default propertie </style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div><div class=\\"root\\" style=\\"height: 48px;\\"><div class=\\"root\\"><div class=\\"slideContainer\\"><div class=\\"root\\" style=\\"height: 100px; width: 150px;\\"><div class=\\"preview\\" style=\\"height: 720px; width: 1080px;\\"><div id=\\"page-7186b301-f8a7-4c65-8b89-38d68d31cfc4\\" class=\\"root\\" style=\\"height: 720px; width: 1080px; background: rgb(119, 119, 119);\\"><div class=\\"canvasPositionable canvasInteractable\\" style=\\"width: 1082px; height: 205.37748344370857px; margin-left: -541px; margin-top: -102.68874172185429px; position: absolute;\\"><div class=\\"root\\"><div class=\\"container s2042575598\\" style=\\"overflow: hidden;\\"><style type=\\"text/css\\">.s2042575598 .canvasRenderEl h1 { font-size: 150px; text-align: center; color: #d3d3d3; } -</style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div></div></div><div class=\\"bar\\" style=\\"bottom: 0px;\\"><div class=\\"euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem title\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><a class=\\"euiLink euiLink--primary\\" href=\\"https://www.elastic.co\\" rel=\\"\\" title=\\"Powered by Elastic.co\\"><svg width=\\"32\\" height=\\"32\\" viewBox=\\"0 0 32 32\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--large euiIcon-isLoaded\\" focusable=\\"false\\"><g fill=\\"none\\"><path fill=\\"#FDD009\\" d=\\"M11.934 13.152l7.353 3.356 7.42-6.507c.107-.537.16-1.072.16-1.633 0-4.578-3.721-8.303-8.295-8.303a8.288 8.288 0 0 0-6.84 3.61l-1.234 6.409 1.436 3.068z\\"></path><path fill=\\"#23BAB1\\" d=\\"M4.322 20.947a8.461 8.461 0 0 0-.162 1.657c0 4.59 3.731 8.326 8.317 8.326a8.288 8.288 0 0 0 6.873-3.646l1.224-6.387-1.634-3.127-7.383-3.368-7.235 6.545z\\"></path><path fill=\\"#EE5097\\" d=\\"M4.276 8.208L9.315 9.4l1.104-5.736a3.976 3.976 0 0 0-2.413-.815 3.978 3.978 0 0 0-3.971 3.976c0 .484.08.948.24 1.383\\"></path><path fill=\\"#17A7E0\\" d=\\"M3.838 9.41c-2.251.747-3.817 2.907-3.817 5.284 0 2.314 1.43 4.38 3.576 5.198l7.07-6.398-1.298-2.776-5.53-1.308z\\"></path><path fill=\\"#92C73D\\" d=\\"M20.642 27.284a3.945 3.945 0 0 0 2.4.822 3.977 3.977 0 0 0 3.972-3.975c0-.484-.08-.948-.24-1.383l-5.036-1.18-1.096 5.716z\\"></path><path fill=\\"#0678A0\\" d=\\"M21.667 20.247l5.543 1.298c2.252-.745 3.818-2.907 3.818-5.284a5.553 5.553 0 0 0-3.583-5.19l-7.25 6.36 1.472 2.816z\\"></path></g></svg></a></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\" style=\\"min-width: 0; cursor: default;\\"><div class=\\"euiText euiText--small\\"><div class=\\"euiTextColor euiTextColor--ghost\\"><div class=\\"eui-textTruncate\\">My Canvas Workpad</div></div></div></div></div></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\" style=\\"margin: 0px 12px;\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button disabled=\\"\\" class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" data-test-subj=\\"pageControlsPrevPage\\" aria-label=\\"Previous Page\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"><path fill-rule=\\"nonzero\\" d=\\"M10.843 13.069L6.232 8.384a.546.546 0 0 1 0-.768l4.61-4.685a.552.552 0 0 0 0-.771.53.53 0 0 0-.759 0l-4.61 4.684a1.65 1.65 0 0 0 0 2.312l4.61 4.684a.53.53 0 0 0 .76 0 .552.552 0 0 0 0-.771z\\"></path></svg></button></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button class=\\"euiButtonEmpty euiButtonEmpty--ghost euiButtonEmpty--small\\" type=\\"button\\" data-test-subj=\\"pageControlsCurrentPage\\"><span class=\\"euiButtonEmpty__content\\"><span class=\\"euiButtonEmpty__text\\"><div class=\\"euiText euiText--small\\"><div class=\\"euiTextColor euiTextColor--ghost\\">Page 1</div></div></span></span></button></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button disabled=\\"\\" class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" data-test-subj=\\"pageControlsNextPage\\" aria-label=\\"Next Page\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"><path fill-rule=\\"nonzero\\" d=\\"M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z\\"></path></svg></button></div></div><div class=\\"euiFlexGroup euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><div class=\\"euiPopover euiPopover--anchorUpRight euiPopover--withTitle\\" id=\\"settings\\"><div class=\\"euiPopover__anchor\\"><button class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" aria-label=\\"Settings\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"><path d=\\"M.164 10.329L1.87 8 .163 5.67c.18-.601.43-1.19.758-1.757a8.197 8.197 0 0 1 1.142-1.535l2.872.313L6.099.05a8.166 8.166 0 0 1 3.8-.003l1.166 2.644 2.872-.313a8.166 8.166 0 0 1 1.899 3.293L14.13 8l1.706 2.33c-.18.601-.43 1.19-.758 1.757a8.197 8.197 0 0 1-1.142 1.535l-2.872-.313-1.164 2.641a8.166 8.166 0 0 1-3.8.003l-1.166-2.644-2.872.313a8.166 8.166 0 0 1-1.899-3.293zm4.663 1.986a1 1 0 0 1 1.023.591l.957 2.17c.79.134 1.597.132 2.387-.001l.956-2.169a1 1 0 0 1 1.023-.59l2.358.256a7.23 7.23 0 0 0 1.194-2.068l-1.401-1.913a1 1 0 0 1 0-1.182l1.4-1.912a7.165 7.165 0 0 0-1.192-2.069l-2.359.257a1 1 0 0 1-1.023-.591L9.193.924a7.165 7.165 0 0 0-2.387.001L5.85 3.094a1 1 0 0 1-1.023.59l-2.358-.256a7.23 7.23 0 0 0-1.194 2.068l1.401 1.913a1 1 0 0 1 0 1.182l-1.4 1.912c.28.751.681 1.45 1.192 2.069l2.359-.257zM8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-1a2 2 0 1 0 0-4 2 2 0 0 0 0 4z\\"></path></svg></button></div></div></div></div></div></div></div></div></div></div></div>" +</style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div></div></div><div class=\\"bar\\" style=\\"bottom: 0px;\\"><div class=\\"euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem title\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><a class=\\"euiLink euiLink--primary\\" href=\\"https://www.elastic.co\\" rel=\\"\\" title=\\"Powered by Elastic.co\\"><svg width=\\"32\\" height=\\"32\\" viewBox=\\"0 0 32 32\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--large euiIcon-isLoaded\\" focusable=\\"false\\" role=\\"img\\" aria-hidden=\\"true\\"><title>
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
"`; @@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
"`; @@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
"`; @@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
"`; @@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot index c3352b52c591d..6a33dba76c126 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot @@ -1325,9 +1325,11 @@ exports[`Storyshots shareables/Canvas component 1`] = ` title="Powered by Elastic.co" >
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot index 6570016336d9e..7b3ac299f80ad 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot @@ -1278,9 +1278,11 @@ exports[`Storyshots shareables/Footer contextual: austin 1`] = ` title="Powered by Elastic.co" > can navigate Autoplay Settings 1`] = ` Auto Play @@ -95,13 +101,16 @@ exports[` can navigate Autoplay Settings 1`] = ` class="euiContextMenu__itemLayout" > can navigate Autoplay Settings 2`] = ` Auto Play @@ -237,13 +255,16 @@ exports[` can navigate Autoplay Settings 2`] = ` class="euiContextMenu__itemLayout" > can navigate Toolbar Settings, closes when activated 1`] = Auto Play @@ -558,13 +597,16 @@ exports[` can navigate Toolbar Settings, closes when activated 1`] = class="euiContextMenu__itemLayout" > can navigate Toolbar Settings, closes when activated 2`] = Auto Play @@ -700,13 +751,16 @@ exports[` can navigate Toolbar Settings, closes when activated 2`] = class="euiContextMenu__itemLayout" >
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/webpack.config.js b/x-pack/legacy/plugins/canvas/shareable_runtime/webpack.config.js index dad5415f6768f..c711f9510a10b 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/webpack.config.js @@ -99,11 +99,10 @@ module.exports = { { loader: 'css-loader', options: { - localsConvention: 'camelCaseOnly', + modules: true, + localIdentName: '[name]__[local]___[hash:base64:5]', + camelCase: true, sourceMap: !isProd, - modules: { - localIdentName: '[name]__[local]___[hash:base64:5]', - }, }, }, { diff --git a/x-pack/legacy/plugins/console_extensions/spec/generated/ml.delete_trained_model.json b/x-pack/legacy/plugins/console_extensions/spec/generated/ml.delete_trained_model.json new file mode 100644 index 0000000000000..343fa904c4216 --- /dev/null +++ b/x-pack/legacy/plugins/console_extensions/spec/generated/ml.delete_trained_model.json @@ -0,0 +1,11 @@ +{ + "ml.delete_trained_model": { + "methods": [ + "DELETE" + ], + "patterns": [ + "_ml/inference/{model_id}" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/delete-inference.html" + } +} diff --git a/x-pack/legacy/plugins/console_extensions/spec/generated/ml.get_trained_models.json b/x-pack/legacy/plugins/console_extensions/spec/generated/ml.get_trained_models.json new file mode 100644 index 0000000000000..cdeaca9654b77 --- /dev/null +++ b/x-pack/legacy/plugins/console_extensions/spec/generated/ml.get_trained_models.json @@ -0,0 +1,19 @@ +{ + "ml.get_trained_models": { + "url_params": { + "allow_no_match": "__flag__", + "include_model_definition": "__flag__", + "decompress_definition": "__flag__", + "from": 0, + "size": 0 + }, + "methods": [ + "GET" + ], + "patterns": [ + "_ml/inference/{model_id}", + "_ml/inference" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/get-inference.html" + } +} diff --git a/x-pack/legacy/plugins/console_extensions/spec/generated/ml.get_trained_models_stats.json b/x-pack/legacy/plugins/console_extensions/spec/generated/ml.get_trained_models_stats.json new file mode 100644 index 0000000000000..ab05e203b3980 --- /dev/null +++ b/x-pack/legacy/plugins/console_extensions/spec/generated/ml.get_trained_models_stats.json @@ -0,0 +1,17 @@ +{ + "ml.get_trained_models_stats": { + "url_params": { + "allow_no_match": "__flag__", + "from": 0, + "size": 0 + }, + "methods": [ + "GET" + ], + "patterns": [ + "_ml/inference/{model_id}/_stats", + "_ml/inference/_stats" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/get-inference-stats.html" + } +} diff --git a/x-pack/legacy/plugins/console_extensions/spec/ingest/index.js b/x-pack/legacy/plugins/console_extensions/spec/ingest/index.js index f5ca413e5e2e1..def311a6baf59 100644 --- a/x-pack/legacy/plugins/console_extensions/spec/ingest/index.js +++ b/x-pack/legacy/plugins/console_extensions/spec/ingest/index.js @@ -37,4 +37,33 @@ const enrichProcessorDefinition = { }, }; -export const processors = [enrichProcessorDefinition]; +// Based on https://www.elastic.co/guide/en/elasticsearch/reference/master/inference-processor.html +const inferenceProcessorDefinition = { + inference: { + __template: { + model_id: '', + inference_config: {}, + field_mappings: {}, + }, + target_field: '', + model_id: '', + field_mappings: { + __template: {}, + }, + inference_config: { + regression: { + __template: {}, + results_field: '', + }, + classification: { + __template: {}, + results_field: '', + num_top_classes: 2, + top_classes_results_field: '', + }, + }, + ...commonPipelineParams, + }, +}; + +export const processors = [enrichProcessorDefinition, inferenceProcessorDefinition]; 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 f0a0bf90d9e49..470fa00734d27 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,6 @@ import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; import 'ui/autoload/all'; import 'ui/kbn_top_nav'; -import 'plugins/kibana/dashboard'; import 'ui/vislib'; import 'ui/agg_response'; import 'ui/agg_types'; @@ -39,10 +38,7 @@ import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; import { showAppRedirectNotification } from 'ui/notify'; -import { - DashboardConstants, - createDashboardEditUrl, -} from 'plugins/kibana/dashboard/dashboard_constants'; +import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard'; uiModules .get('kibana') diff --git a/x-pack/legacy/plugins/graph/public/angular/__tests__/workspace.js b/x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.test.js similarity index 75% rename from x-pack/legacy/plugins/graph/public/angular/__tests__/workspace.js rename to x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.test.js index a09f94ca6ce9f..6179467966764 100644 --- a/x-pack/legacy/plugins/graph/public/angular/__tests__/workspace.js +++ b/x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.test.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -const gws = require('../graph_client_workspace.js'); -const expect = require('@kbn/expect'); +import gws from './graph_client_workspace'; + describe('graphui-workspace', function() { describe('createWorkspace()', function() { // var fooResource=null; @@ -47,7 +47,7 @@ describe('graphui-workspace', function() { }); it('initializeWorkspace', function() { const { workspace } = init(); - expect(workspace.nodes).to.have.length(0); + expect(workspace.nodes.length).toEqual(0); }); it('simpleSearch', function() { //Test that a graph is loaded from a free-text search @@ -79,16 +79,16 @@ describe('graphui-workspace', function() { }; workspace.simpleSearch('myquery', {}, 2); - expect(workspace.nodes).to.have.length(2); - expect(workspace.edges).to.have.length(1); - expect(workspace.selectedNodes).to.have.length(0); - expect(workspace.blacklistedNodes).to.have.length(0); + expect(workspace.nodes.length).toEqual(2); + expect(workspace.edges.length).toEqual(1); + expect(workspace.selectedNodes.length).toEqual(0); + expect(workspace.blacklistedNodes.length).toEqual(0); const nodeA = workspace.getNode(workspace.makeNodeId('field1', 'a')); - expect(nodeA).to.be.an(Object); + expect(typeof nodeA).toBe('object'); const nodeD = workspace.getNode(workspace.makeNodeId('field1', 'd')); - expect(nodeD).to.be(undefined); + expect(nodeD).toBe(undefined); }); it('expandTest', function() { @@ -121,10 +121,10 @@ describe('graphui-workspace', function() { }; workspace.simpleSearch('myquery', {}, 2); - expect(workspace.nodes).to.have.length(2); - expect(workspace.edges).to.have.length(1); - expect(workspace.selectedNodes).to.have.length(0); - expect(workspace.blacklistedNodes).to.have.length(0); + expect(workspace.nodes.length).toEqual(2); + expect(workspace.edges.length).toEqual(1); + expect(workspace.selectedNodes.length).toEqual(0); + expect(workspace.blacklistedNodes.length).toEqual(0); mockedResult = { vertices: [ @@ -151,8 +151,8 @@ describe('graphui-workspace', function() { ], }; workspace.expandGraph(); - expect(workspace.nodes).to.have.length(3); //we already had b from initial query - expect(workspace.edges).to.have.length(2); + expect(workspace.nodes.length).toEqual(3); //we already had b from initial query + expect(workspace.edges.length).toEqual(2); }); it('selectionTest', function() { @@ -203,38 +203,38 @@ describe('graphui-workspace', function() { }; workspace.simpleSearch('myquery', {}, 2); - expect(workspace.selectedNodes).to.have.length(0); + expect(workspace.selectedNodes.length).toEqual(0); const nodeA1 = workspace.getNode(workspace.makeNodeId('field1', 'a1')); - expect(nodeA1).to.be.an(Object); + expect(typeof nodeA1).toEqual('object'); const nodeA2 = workspace.getNode(workspace.makeNodeId('field1', 'a2')); - expect(nodeA2).to.be.an(Object); + expect(typeof nodeA2).toEqual('object'); const nodeB1 = workspace.getNode(workspace.makeNodeId('field1', 'b1')); - expect(nodeB1).to.be.an(Object); + expect(typeof nodeB1).toEqual('object'); const nodeB2 = workspace.getNode(workspace.makeNodeId('field1', 'b2')); - expect(nodeB2).to.be.an(Object); + expect(typeof nodeB2).toEqual('object'); - expect(workspace.selectedNodes).to.have.length(0); + expect(workspace.selectedNodes.length).toEqual(0); workspace.selectNode(nodeA1); - expect(workspace.selectedNodes).to.have.length(1); + expect(workspace.selectedNodes.length).toEqual(1); workspace.selectInvert(); - expect(workspace.selectedNodes).to.have.length(3); + expect(workspace.selectedNodes.length).toEqual(3); workspace.selectInvert(); - expect(workspace.selectedNodes).to.have.length(1); + expect(workspace.selectedNodes.length).toEqual(1); workspace.deselectNode(nodeA1); - expect(workspace.selectedNodes).to.have.length(0); + expect(workspace.selectedNodes.length).toEqual(0); workspace.selectAll(); - expect(workspace.selectedNodes).to.have.length(4); + expect(workspace.selectedNodes.length).toEqual(4); workspace.selectInvert(); - expect(workspace.selectedNodes).to.have.length(0); + expect(workspace.selectedNodes.length).toEqual(0); workspace.selectNode(nodeA1); - expect(workspace.selectedNodes).to.have.length(1); + expect(workspace.selectedNodes.length).toEqual(1); workspace.selectNeighbours(); - expect(workspace.selectedNodes).to.have.length(2); + expect(workspace.selectedNodes.length).toEqual(2); workspace.selectNeighbours(); //Should have reached full extent of a1-a2 island. - expect(workspace.selectedNodes).to.have.length(2); + expect(workspace.selectedNodes.length).toEqual(2); }); it('undoRedoDeletes', function() { @@ -266,31 +266,31 @@ describe('graphui-workspace', function() { }; workspace.simpleSearch('myquery', {}, 2); - expect(workspace.nodes).to.have.length(2); + expect(workspace.nodes.length).toEqual(2); let nodeA1 = workspace.getNode(workspace.makeNodeId('field1', 'a1')); - expect(nodeA1).to.be.an(Object); + expect(typeof nodeA1).toEqual('object'); const nodeA2 = workspace.getNode(workspace.makeNodeId('field1', 'a2')); - expect(nodeA2).to.be.an(Object); + expect(typeof nodeA2).toEqual('object'); workspace.selectNode(nodeA1); workspace.deleteSelection(); - expect(workspace.nodes).to.have.length(1); + expect(workspace.nodes.length).toEqual(1); nodeA1 = workspace.getNode(workspace.makeNodeId('field1', 'a1')); - expect(nodeA1).to.be(undefined); + expect(nodeA1).toBe(undefined); workspace.undo(); - expect(workspace.nodes).to.have.length(2); + expect(workspace.nodes.length).toEqual(2); nodeA1 = workspace.getNode(workspace.makeNodeId('field1', 'a1')); - expect(nodeA1).to.be.an(Object); + expect(typeof nodeA1).toEqual('object'); workspace.redo(); - expect(workspace.nodes).to.have.length(1); + expect(workspace.nodes.length).toEqual(1); nodeA1 = workspace.getNode(workspace.makeNodeId('field1', 'a1')); - expect(nodeA1).to.be(undefined); + expect(nodeA1).toBe(undefined); workspace.undo(); - expect(workspace.nodes).to.have.length(2); + expect(workspace.nodes.length).toEqual(2); }); it('undoRedoGroupings', function() { @@ -322,36 +322,36 @@ describe('graphui-workspace', function() { }; workspace.simpleSearch('myquery', {}, 2); - expect(workspace.nodes).to.have.length(2); + expect(workspace.nodes.length).toEqual(2); const nodeA1 = workspace.getNode(workspace.makeNodeId('field1', 'a1')); - expect(nodeA1).to.be.an(Object); + expect(typeof nodeA1).toEqual('object'); const nodeA2 = workspace.getNode(workspace.makeNodeId('field1', 'a2')); - expect(nodeA2).to.be.an(Object); + expect(typeof nodeA2).toEqual('object'); workspace.selectNode(nodeA2); workspace.mergeSelections(nodeA1); let groupedItems = workspace.returnUnpackedGroupeds([nodeA1]); - expect(groupedItems).to.have.length(2); + expect(groupedItems.length).toEqual(2); workspace.undo(); groupedItems = workspace.returnUnpackedGroupeds([nodeA1]); - expect(groupedItems).to.have.length(1); + expect(groupedItems.length).toEqual(1); workspace.redo(); groupedItems = workspace.returnUnpackedGroupeds([nodeA1]); - expect(groupedItems).to.have.length(2); + expect(groupedItems.length).toEqual(2); //Grouped deletes delete all grouped items workspace.selectNone(); workspace.selectNode(nodeA1); workspace.deleteSelection(); - expect(workspace.nodes).to.have.length(0); - expect(workspace.selectedNodes).to.have.length(0); + expect(workspace.nodes.length).toEqual(0); + expect(workspace.selectedNodes.length).toEqual(0); workspace.undo(); - expect(workspace.nodes).to.have.length(2); + expect(workspace.nodes.length).toEqual(2); groupedItems = workspace.returnUnpackedGroupeds([nodeA1]); - expect(groupedItems).to.have.length(2); + expect(groupedItems.length).toEqual(2); }); }); }); diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js index 6ada589f3740c..d0dbf34abc055 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/legacy/plugins/graph/public/app.js @@ -12,7 +12,7 @@ import { Provider } from 'react-redux'; import { isColorDark, hexToRgb } from '@elastic/eui'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; -import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +import { showSaveModal } from './legacy_imports'; import appTemplate from './angular/templates/index.html'; import listingTemplate from './angular/templates/listing_ng_wrapper.html'; @@ -32,24 +32,21 @@ import { asAngularSyncedObservable } from './helpers/as_observable'; import { colorChoices } from './helpers/style_choices'; import { createGraphStore, datasourceSelector, hasFieldsSelector } from './state_management'; import { formatHttpError } from './helpers/format_http_error'; -import { checkLicense } from '../../../../plugins/graph/common/check_license'; export function initGraphApp(angularModule, deps) { const { chrome, - savedGraphWorkspaces, toastNotifications, savedObjectsClient, indexPatterns, - kbnBaseUrl, addBasePath, getBasePath, npData, config, - savedObjectRegistry, + savedWorkspaceLoader, capabilities, coreStart, - Storage, + storage, canEditDrillDownUrls, graphSavePolicy, } = deps; @@ -110,22 +107,19 @@ export function initGraphApp(angularModule, deps) { template: listingTemplate, badge: getReadonlyBadge, controller($location, $scope) { - const services = savedObjectRegistry.byLoaderPropertiesName; - const graphService = services['Graph workspace']; - $scope.listingLimit = config.get('savedObjects:listingLimit'); $scope.create = () => { $location.url(getNewPath()); }; $scope.find = search => { - return graphService.find(search, $scope.listingLimit); + return savedWorkspaceLoader.find(search, $scope.listingLimit); }; $scope.editItem = workspace => { $location.url(getEditPath(workspace)); }; $scope.getViewUrl = workspace => getEditUrl(addBasePath, workspace); $scope.delete = workspaces => { - return graphService.delete(workspaces.map(({ id }) => id)); + return savedWorkspaceLoader.delete(workspaces.map(({ id }) => id)); }; $scope.capabilities = capabilities; $scope.initialFilter = $location.search().filter || ''; @@ -139,14 +133,14 @@ export function initGraphApp(angularModule, deps) { resolve: { savedWorkspace: function($route) { return $route.current.params.id - ? savedGraphWorkspaces.get($route.current.params.id).catch(function() { + ? savedWorkspaceLoader.get($route.current.params.id).catch(function() { toastNotifications.addDanger( i18n.translate('xpack.graph.missingWorkspaceErrorMessage', { defaultMessage: 'Missing workspace', }) ); }) - : savedGraphWorkspaces.get(); + : savedWorkspaceLoader.get(); }, indexPatterns: function() { return savedObjectsClient @@ -300,7 +294,7 @@ export function initGraphApp(angularModule, deps) { // register things on scope passed down to react components $scope.pluginDataStart = npData; - $scope.storage = new Storage(window.localStorage); + $scope.storage = storage; $scope.coreStart = coreStart; $scope.loading = false; $scope.reduxStore = store; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/application.ts similarity index 82% rename from x-pack/legacy/plugins/graph/public/render_app.ts rename to x-pack/legacy/plugins/graph/public/application.ts index e892643cf8031..69bc789974632 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/application.ts @@ -11,16 +11,6 @@ import { EuiConfirmModal } from '@elastic/eui'; // They can stay even after NP cutover import angular from 'angular'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; -import 'ui/angular-bootstrap'; -import 'ace'; -import 'ui/kbn_top_nav'; -import { configureAppAngularModule } from 'ui/legacy_compat'; -// @ts-ignore -import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore -import { confirmModalFactory } from 'ui/modals/confirm_modal'; -// @ts-ignore -import { addAppRedirectMessageToUrl } from 'ui/notify'; // type imports import { @@ -31,6 +21,13 @@ import { ToastsStart, IUiSettingsClient, } from 'kibana/public'; +import { + configureAppAngularModule, + createTopNavDirective, + createTopNavHelper, + confirmModalFactory, + addAppRedirectMessageToUrl, +} from './legacy_imports'; // @ts-ignore import { initGraphApp } from './app'; import { @@ -40,6 +37,8 @@ import { import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { checkLicense } from '../../../../plugins/graph/common/check_license'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; +import { createSavedWorkspacesLoader } from './services/persistence/saved_workspace_loader'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; /** * These are dependencies of the Graph app besides the base dependencies @@ -47,7 +46,7 @@ import { NavigationPublicPluginStart as NavigationStart } from '../../../../../s * plugins in LP-world, but if they are migrated only the import path in the plugin * itself changes */ -export interface GraphDependencies extends LegacyAngularInjectedDependencies { +export interface GraphDependencies { element: HTMLElement; appBasePath: string; capabilities: Record>; @@ -62,27 +61,11 @@ export interface GraphDependencies extends LegacyAngularInjectedDependencies { savedObjectsClient: SavedObjectsClientContract; addBasePath: (url: string) => string; getBasePath: () => string; - Storage: any; + storage: Storage; canEditDrillDownUrls: boolean; graphSavePolicy: string; } -/** - * Dependencies of the Graph app which rely on the global angular instance. - * These dependencies have to be migrated to their NP counterparts. - */ -export interface LegacyAngularInjectedDependencies { - /** - * Instance of SavedObjectRegistryProvider - */ - savedObjectRegistry: any; - kbnBaseUrl: any; - /** - * Instance of SavedWorkspacesProvider - */ - savedGraphWorkspaces: any; -} - export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { const graphAngularModule = createLocalAngularModule(deps.navigation); configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart, true); @@ -92,12 +75,20 @@ export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) const licenseAllowsToShowThisPage = info.showAppLink && info.enableAppLink; if (!licenseAllowsToShowThisPage) { - const newUrl = addAppRedirectMessageToUrl(deps.addBasePath(deps.kbnBaseUrl), info.message); + const newUrl = addAppRedirectMessageToUrl(deps.addBasePath('/app/kibana'), info.message); window.location.href = newUrl; } }); - initGraphApp(graphAngularModule, deps); + const savedWorkspaceLoader = createSavedWorkspacesLoader({ + chrome: deps.coreStart.chrome, + indexPatterns: deps.npData.indexPatterns, + overlays: deps.coreStart.overlays, + savedObjectsClient: deps.coreStart.savedObjects.client, + basePath: deps.coreStart.http.basePath, + }); + + initGraphApp(graphAngularModule, { ...deps, savedWorkspaceLoader }); const $injector = mountGraphApp(appBasePath, element); return () => { licenseSubscription.unsubscribe(); diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts index 995e261a5c7a7..d9854acb9332c 100644 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -4,41 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -// legacy imports currently necessary to power Graph -// for a cutover all of these have to be resolved -import 'uiExports/savedObjectTypes'; -import 'uiExports/autocompleteProviders'; -import 'ui/autoload/all'; -import chrome from 'ui/chrome'; -import { IPrivate } from 'ui/private'; -// @ts-ignore -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; - import { npSetup, npStart } from 'ui/new_platform'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { GraphPlugin } from './plugin'; -// @ts-ignore -import { SavedWorkspacesProvider } from './angular/services/saved_workspaces'; -import { LegacyAngularInjectedDependencies } from './render_app'; - -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularInjectedDependencies(): Promise { - const injector = await chrome.dangerouslyGetActiveInjector(); - - const Private = injector.get('Private'); - - return { - savedObjectRegistry: Private(SavedObjectRegistryProvider), - kbnBaseUrl: injector.get('kbnBaseUrl'), - savedGraphWorkspaces: Private(SavedWorkspacesProvider), - }; -} - type XpackNpSetupDeps = typeof npSetup.plugins & { licensing: LicensingPluginSetup; }; @@ -46,16 +15,10 @@ type XpackNpSetupDeps = typeof npSetup.plugins & { (async () => { const instance = new GraphPlugin(); instance.setup(npSetup.core, { - __LEGACY: { - Storage, - }, ...(npSetup.plugins as XpackNpSetupDeps), }); instance.start(npStart.core, { npData: npStart.plugins.data, navigation: npStart.plugins.navigation, - __LEGACY: { - angularDependencies: await getAngularInjectedDependencies(), - }, }); })(); diff --git a/x-pack/legacy/plugins/graph/public/legacy_imports.ts b/x-pack/legacy/plugins/graph/public/legacy_imports.ts new file mode 100644 index 0000000000000..7ea2cf6dd901b --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/legacy_imports.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. + */ + +import 'ui/angular-bootstrap'; +import 'ace'; + +export { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types'; +export { configureAppAngularModule } from 'ui/legacy_compat'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +export { confirmModalFactory } from 'ui/modals/confirm_modal'; +// @ts-ignore +export { addAppRedirectMessageToUrl } from 'ui/notify'; +export { SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; +export { createSavedObjectClass } from 'ui/saved_objects/saved_object'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; diff --git a/x-pack/legacy/plugins/graph/public/plugin.ts b/x-pack/legacy/plugins/graph/public/plugin.ts index f30742f2c00d2..ab610d76be101 100644 --- a/x-pack/legacy/plugins/graph/public/plugin.ts +++ b/x-pack/legacy/plugins/graph/public/plugin.ts @@ -7,7 +7,7 @@ // NP type imports import { CoreSetup, CoreStart, Plugin, SavedObjectsClientContract } from 'src/core/public'; import { Plugin as DataPlugin } from 'src/plugins/data/public'; -import { LegacyAngularInjectedDependencies } from './render_app'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; @@ -17,30 +17,20 @@ export interface GraphPluginStartDependencies { } export interface GraphPluginSetupDependencies { - __LEGACY: { - Storage: any; - }; licensing: LicensingPluginSetup; } -export interface GraphPluginStartDependencies { - __LEGACY: { - angularDependencies: LegacyAngularInjectedDependencies; - }; -} - export class GraphPlugin implements Plugin { private navigationStart: NavigationStart | null = null; private npDataStart: ReturnType | null = null; private savedObjectsClient: SavedObjectsClientContract | null = null; - private angularDependencies: LegacyAngularInjectedDependencies | null = null; - setup(core: CoreSetup, { __LEGACY: { Storage }, licensing }: GraphPluginSetupDependencies) { + setup(core: CoreSetup, { licensing }: GraphPluginSetupDependencies) { core.application.register({ id: 'graph', title: 'Graph', mount: async ({ core: contextCore }, params) => { - const { renderApp } = await import('./render_app'); + const { renderApp } = await import('./application'); return renderApp({ ...params, licensing, @@ -53,26 +43,21 @@ export class GraphPlugin implements Plugin { 'canEditDrillDownUrls' ) as boolean, graphSavePolicy: core.injectedMetadata.getInjectedVar('graphSavePolicy') as string, - Storage, + storage: new Storage(window.localStorage), capabilities: contextCore.application.capabilities.graph, coreStart: contextCore, chrome: contextCore.chrome, config: contextCore.uiSettings, toastNotifications: contextCore.notifications.toasts, indexPatterns: this.npDataStart!.indexPatterns, - ...this.angularDependencies!, }); }, }); } - start( - core: CoreStart, - { npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies - ) { + start(core: CoreStart, { npData, navigation }: GraphPluginStartDependencies) { this.navigationStart = navigation; this.npDataStart = npData; - this.angularDependencies = angularDependencies; this.savedObjectsClient = core.savedObjects.client; } diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.ts b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace.ts similarity index 93% rename from x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.ts rename to x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace.ts index bcde72a02f02e..d25fcc89d6a1f 100644 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace.ts @@ -3,10 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types'; -import { createSavedObjectClass } from 'ui/saved_objects/saved_object'; import { i18n } from '@kbn/i18n'; import { extractReferences, injectReferences } from './saved_workspace_references'; +import { + createSavedObjectClass, + SavedObject, + SavedObjectKibanaServices, +} from '../../legacy_imports'; export interface SavedWorkspace extends SavedObject { wsState?: string; diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.ts b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts similarity index 78% rename from x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.ts rename to x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts index e28bb60fb466b..8ddff0be0d06d 100644 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts @@ -4,24 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { IBasePath } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { createSavedWorkspaceClass } from './saved_workspace'; +import { SavedObjectKibanaServices } from '../../legacy_imports'; -export function SavedWorkspacesProvider() { - const savedObjectsClient = npStart.core.savedObjects.client; - const services = { - savedObjectsClient, - indexPatterns: npStart.plugins.data.indexPatterns, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, - }; - +export function createSavedWorkspacesLoader( + services: SavedObjectKibanaServices & { basePath: IBasePath } +) { + const { savedObjectsClient, basePath } = services; const SavedWorkspace = createSavedWorkspaceClass(services); const urlFor = (id: string) => - npSetup.core.http.basePath.prepend(`/app/graph#/workspace/${encodeURIComponent(id)}`); + basePath.prepend(`/app/graph#/workspace/${encodeURIComponent(id)}`); const mapHits = (hit: { id: string; attributes: Record }) => { const source = hit.attributes; source.id = hit.id; @@ -72,5 +67,3 @@ export function SavedWorkspacesProvider() { }, }; } - -SavedObjectRegistryProvider.register(SavedWorkspacesProvider); diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.ts b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.test.ts rename to x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.test.ts diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.ts b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/services/saved_workspace_references.ts rename to x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.ts diff --git a/x-pack/legacy/plugins/graph/public/services/save_modal.tsx b/x-pack/legacy/plugins/graph/public/services/save_modal.tsx index 5930d2283b7c0..d949ac1d4a600 100644 --- a/x-pack/legacy/plugins/graph/public/services/save_modal.tsx +++ b/x-pack/legacy/plugins/graph/public/services/save_modal.tsx @@ -5,9 +5,9 @@ */ import React from 'react'; -import { SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; import { GraphWorkspaceSavedObject, GraphSavePolicy } from '../types'; import { SaveModal, OnSaveGraphProps } from '../components/save_modal'; +import { SaveResult } from '../legacy_imports'; export type SaveWorkspaceHandler = ( saveOptions: { diff --git a/x-pack/legacy/plugins/graph/public/types/persistence.ts b/x-pack/legacy/plugins/graph/public/types/persistence.ts index 7883e81fb9b8e..7fc5e15d9ea72 100644 --- a/x-pack/legacy/plugins/graph/public/types/persistence.ts +++ b/x-pack/legacy/plugins/graph/public/types/persistence.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject } from 'ui/saved_objects/types'; import { AdvancedSettings, UrlTemplate, WorkspaceField } from './app_state'; import { WorkspaceNode, WorkspaceEdge } from './workspace_state'; +import { SavedObject } from '../legacy_imports'; type Omit = Pick>; diff --git a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index a7cc8843140c8..bd42346e36135 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -263,16 +263,18 @@ exports[`ilm summary extension should return extension when index has lifecycle type="cross" >