diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 53270e4517192..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 diff --git a/.github/workflows/pr-project-assigner.yml b/.github/workflows/pr-project-assigner.yml index 8eab1b99957cd..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:AppArch", "projectName": "kibana-app-arch", "columnId": 6173897}]' + 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/.i18nrc.json b/.i18nrc.json index 23f3d6ee33829..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", 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/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/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/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-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-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-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/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/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 7025e96d9ecb4..c652bb1c94887 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -98,6 +98,7 @@ beforeEach(() => { core: { capabilities: capabilitiesServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), plugins: { contracts: new Map() }, }, plugins: {}, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 412f4570887a4..2e8a467eff995 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -312,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/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/index.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts similarity index 65% rename from src/legacy/core_plugins/input_control_vis/index.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts index a21b79e28fb7f..881412a7c56fd 100644 --- a/src/legacy/core_plugins/input_control_vis/index.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts @@ -17,14 +17,15 @@ * under the License. */ -import { resolve } from 'path'; +import { ShallowWrapper, ReactWrapper } from 'enzyme'; -export default function(kibana) { - return new kibana.Plugin({ - uiExports: { - visTypes: ['plugins/input_control_vis/register_vis'], - interpreter: ['plugins/input_control_vis/input_control_fn'], - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - }, - }); -} +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/core_plugins/input_control_vis/public/legacy_imports.ts b/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts new file mode 100644 index 0000000000000..864ce3b146689 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SearchSource as SearchSourceClass } from 'ui/courier'; +import { Class } from '@kbn/utility-types'; + +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'; + +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 dd74119737657..111b74d8aa6af 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -26,14 +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 { 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'; @@ -325,15 +322,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); - 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__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index 653e7d4215eef..1c450879ee553 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -18,7 +18,10 @@ */ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen'; +import { + DashboardEmptyScreen, + DashboardEmptyScreenProps, +} from '../np_ready/dashboard_empty_screen'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_saved_dashboard_mock.ts b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_saved_dashboard_mock.ts index bf7135098ea74..1c2405b5824f2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_saved_dashboard_mock.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_saved_dashboard_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { searchSourceMock } from '../../../../../ui/public/courier/search_source/mocks'; +import { searchSourceMock } from 'ui/courier/search_source/mocks'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; export function getSavedDashboardMock( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index dccc4c11e334f..76d9933a3d567 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -30,6 +30,8 @@ import { start as embeddables } from '../../../embeddable_api/public/np_ready/pu import './saved_dashboard/saved_dashboards'; import './dashboard_config'; +export * from './np_ready/dashboard_constants'; + /** * 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/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index adae063a1470b..d3c17d1176c10 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -57,7 +57,7 @@ export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_to // @ts-ignore export { PromiseServiceCreator } from 'ui/promises/promises'; // @ts-ignore -export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url/index'; // @ts-ignore export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { configureAppAngularModule } from 'ui/legacy_compat'; @@ -65,3 +65,5 @@ export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_mon export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; +export { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize_embeddable'; +export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts index 09677f149c064..ad4feacde0815 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts @@ -37,7 +37,7 @@ jest.mock( jest.mock('ui/new_platform'); import { migratePanelsTo730 } from './migrate_to_730_panels'; -import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from '../types'; +import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from '../np_ready/types'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts index b1b5a57ca27dc..b0d20b4482728 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts @@ -34,7 +34,7 @@ import { SavedDashboardPanel620, SavedDashboardPanel630, SavedDashboardPanel610, -} from '../types'; +} from '../np_ready/types'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts index 7fba2f4003f31..c264358a8f81f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { GridData } from '../types'; +import { GridData } from '../np_ready/types'; import { Doc, DocPre700 } from '../../../migrations/types'; export interface SavedObjectAttributes { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/dashboard/application.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts index 3482197e381e6..298dcee12dc35 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts @@ -19,7 +19,6 @@ import { EuiConfirmModal, EuiIcon } from '@elastic/eui'; import angular, { IModule } from 'angular'; -import { IPrivate } from 'ui/private'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { AppMountContext, @@ -28,7 +27,7 @@ import { SavedObjectsClientContract, IUiSettingsClient, } from 'kibana/public'; -import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { GlobalStateProvider, StateManagementConfigProvider, @@ -43,14 +42,15 @@ import { RedirectWhenMissingProvider, confirmModalFactory, configureAppAngularModule, -} from './legacy_imports'; + IPrivate, +} from '../legacy_imports'; // @ts-ignore import { initDashboardApp } from './legacy_app'; -import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; -import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; -import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; -import { SharePluginStart } from '../../../../../plugins/share/public'; +import { IEmbeddableStart } from '../../../../../../plugins/embeddable/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../../plugins/data/public'; +import { SharePluginStart } from '../../../../../../plugins/share/public'; export interface RenderDeps { core: LegacyCoreStart; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx index 04a8e68276fc2..e9fdc335ba572 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx @@ -25,10 +25,10 @@ import { AppState as TAppState, IInjector, KbnUrl, -} from './legacy_imports'; +} from '../legacy_imports'; -import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; -import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; +import { ViewMode } from '../../../../embeddable_api/public/np_ready/public'; +import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; import { IIndexPattern, @@ -36,7 +36,7 @@ import { Query, esFilters, SavedQuery, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../../plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; import { RenderDeps } from './application'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 24b64a88998f4..5ba457b6c3362 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -37,9 +37,10 @@ import { KbnUrl, SavedObjectSaveOpts, unhashUrl, -} from './legacy_imports'; -import { FilterStateManager, IndexPattern } from '../../../data/public'; -import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; + VISUALIZE_EMBEDDABLE_TYPE, +} from '../legacy_imports'; +import { FilterStateManager, IndexPattern } from '../../../../data/public'; +import { Query, SavedQuery, IndexPatternsContract } from '../../../../../../plugins/data/public'; import { DashboardContainer, @@ -47,14 +48,14 @@ import { DashboardContainerFactory, DashboardContainerInput, DashboardPanelState, -} from '../../../dashboard_embeddable_container/public/np_ready/public'; +} from '../../../../dashboard_embeddable_container/public/np_ready/public'; import { isErrorEmbeddable, ErrorEmbeddable, ViewMode, openAddPanelFlyout, EmbeddableFactoryNotFoundError, -} from '../../../embeddable_api/public/np_ready/public'; +} from '../../../../embeddable_api/public/np_ready/public'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; @@ -67,13 +68,12 @@ import { getTopNavConfig } from './top_nav/get_top_nav_config'; import { TopNavIds } from './top_nav/top_nav_ids'; import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; import { RenderDeps } from './application'; import { SavedObjectFinderProps, SavedObjectFinderUi, -} from '../../../../../plugins/kibana_react/public'; +} from '../../../../../../plugins/kibana_react/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_constants.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_constants.tsx rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts index 8b786144c7420..4d5101e1f9e5f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts @@ -19,8 +19,8 @@ import './np_core.test.mocks'; import { DashboardStateManager } from './dashboard_state_manager'; -import { getAppStateMock, getSavedDashboardMock } from './__tests__'; -import { AppStateClass } from './legacy_imports'; +import { getAppStateMock, getSavedDashboardMock } from '../__tests__'; +import { AppStateClass } from '../legacy_imports'; import { DashboardAppState } from './types'; import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts index ac8628ec2a9d9..6df18757da6f5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts @@ -23,23 +23,23 @@ import _ from 'lodash'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; -import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; +import { ViewMode } from '../../../../../../plugins/embeddable/public'; import { stateMonitorFactory, StateMonitor, AppStateClass as TAppStateClass, migrateLegacyQuery, -} from './legacy_imports'; +} from '../legacy_imports'; import { Query, esFilters, TimefilterContract as Timefilter, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../../plugins/data/public'; import { getAppStateDefaults, migrateAppState } from './lib'; import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters'; import { FilterUtils } from './lib/filter_utils'; -import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; +import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; import { SavedDashboardPanel, DashboardAppState, DashboardAppStateDefaults } from './types'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_strings.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_strings.ts index d932116d08dc8..d9406ccba18ba 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_strings.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_strings.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; +import { ViewMode } from '../../../../../../plugins/embeddable/public'; /** * @param title {string} the current title of the dashboard diff --git a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/global_state_sync.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/global_state_sync.ts index 8a733f940734b..1a6c2b09ee3fc 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/global_state_sync.ts @@ -17,8 +17,8 @@ * under the License. */ -import { State } from './legacy_imports'; -import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { State } from '../legacy_imports'; +import { DataPublicPluginStart as NpDataStart } 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/dashboard/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/help_menu/help_menu_util.ts similarity index 80% rename from src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/help_menu/help_menu_util.ts index 55abfa179b56d..bc281c6eb340f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/help_menu/help_menu_util.ts @@ -18,9 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { CoreStart } from 'kibana/public'; -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome( + chrome: CoreStart['chrome'], + docLinks: CoreStart['docLinks'] +) { chrome.setHelpExtension({ appName: i18n.translate('kbn.dashboard.helpMenu.appName', { defaultMessage: 'Dashboards', @@ -28,7 +31,7 @@ export function addHelpMenuToAppChrome(chrome) { links: [ { linkType: 'documentation', - href: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, + href: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/dashboard.html`, }, ], }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js similarity index 92% rename from src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js index 927717948168a..a4fda5eda8a32 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js @@ -22,23 +22,35 @@ import { i18n } from '@kbn/i18n'; import dashboardTemplate from './dashboard_app.html'; import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; -import { ensureDefaultIndexPattern } from './legacy_imports'; +import { + ensureDefaultIndexPattern, + registerTimefilterWithGlobalStateFactory, +} from '../legacy_imports'; import { initDashboardAppDirective } from './dashboard_app'; import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; import { InvalidJSONProperty, SavedObjectNotFound, -} from '../../../../../plugins/kibana_utils/public'; +} from '../../../../../../plugins/kibana_utils/public'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; -import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; import { syncOnMount } from './global_state_sync'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); app.directive('dashboardListing', function(reactDirective) { - return reactDirective(DashboardListing); + return reactDirective(DashboardListing, [ + ['core', { watchDepth: 'reference' }], + ['createItem', { watchDepth: 'reference' }], + ['getViewUrl', { watchDepth: 'reference' }], + ['editItem', { watchDepth: 'reference' }], + ['findItems', { watchDepth: 'reference' }], + ['deleteItems', { watchDepth: 'reference' }], + ['listingLimit', { watchDepth: 'reference' }], + ['hideWriteControls', { watchDepth: 'reference' }], + ['initialFilter', { watchDepth: 'reference' }], + ]); }); function createNewDashboardCtrl($scope) { @@ -116,6 +128,7 @@ export function initDashboardApp(app, deps) { }, ]); addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); + $scope.core = deps.core; }, resolve: { dash: function($rootScope, $route, redirectWhenMissing, kbnUrl) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/filter_utils.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/filter_utils.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/filter_utils.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/filter_utils.ts index 19a0c32210737..6fbc04969b1c8 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/filter_utils.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/filter_utils.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import moment, { Moment } from 'moment'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../plugins/data/public'; /** * @typedef {Object} QueryFilter diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts similarity index 90% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts index eb4a4356fb27a..eceb51f17d164 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/get_app_state_defaults.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ViewMode } from '../../../../../../../src/plugins/embeddable/public'; -import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; +import { ViewMode } from '../../../../../../../plugins/embeddable/public'; +import { SavedObjectDashboard } from '../../saved_dashboard/saved_dashboard'; import { DashboardAppStateDefaults } from '../types'; export function getAppStateDefaults( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/index.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/index.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts index c4ad754548459..4083900c7ede7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts @@ -19,7 +19,7 @@ import semver from 'semver'; import { i18n } from '@kbn/i18n'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../../../ui_metric/public'; import { DashboardAppState, SavedDashboardPanelTo60, @@ -29,7 +29,7 @@ import { SavedDashboardPanel640To720, SavedDashboardPanel620, } from '../types'; -import { migratePanelsTo730 } from '../migrations/migrate_to_730_panels'; +import { migratePanelsTo730 } from '../../migrations/migrate_to_730_panels'; /** * Attempts to migrate the state stored in the URL into the latest version of it. diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts index e322677433ce6..691c87122564f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts @@ -18,7 +18,7 @@ */ import { TimefilterContract } from 'src/plugins/data/public'; -import { SavedObjectSaveOpts } from '../legacy_imports'; +import { SavedObjectSaveOpts } from '../../legacy_imports'; import { updateSavedDashboard } from './update_saved_dashboard'; import { DashboardStateManager } from '../dashboard_state_manager'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts index ce9096b3a56f0..2072b5d4f6eb0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts @@ -19,9 +19,9 @@ import _ from 'lodash'; import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; -import { AppState } from '../legacy_imports'; +import { AppState } from '../../legacy_imports'; import { FilterUtils } from './filter_utils'; -import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; +import { SavedObjectDashboard } from '../../saved_dashboard/saved_dashboard'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/__snapshots__/dashboard_listing.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/__snapshots__/dashboard_listing.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.js index 98581223afa46..827fe6eabe784 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.js @@ -19,13 +19,12 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; + import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; - import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { npStart } from 'ui/new_platform'; -import { TableListView } from '../../../../../../../src/plugins/kibana_react/public'; +import { TableListView } from '../../../../../../../plugins/kibana_react/public'; export const EMPTY_FILTER = ''; @@ -60,8 +59,8 @@ export class DashboardListing extends React.Component { tableListTitle={i18n.translate('kbn.dashboard.listing.dashboardsTitle', { defaultMessage: 'Dashboards', })} - toastNotifications={npStart.core.notifications.toasts} - uiSettings={npStart.core.uiSettings} + toastNotifications={this.props.core.notifications.toasts} + uiSettings={this.props.core.uiSettings} /> ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.test.js similarity index 90% rename from src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.test.js index a933607d256a9..c47a54ad60460 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.test.js @@ -42,17 +42,6 @@ jest.mock( { virtual: true } ); -jest.mock('ui/new_platform', () => { - return { - npStart: { - core: { - notifications: { toasts: {} }, - uiSettings: { get: jest.fn(() => 10) }, - }, - }, - }; -}); - import React from 'react'; import { shallow } from 'enzyme'; @@ -83,6 +72,7 @@ test('renders empty page in before initial fetch to avoid flickering', () => { getViewUrl={() => {}} listingLimit={1000} hideWriteControls={false} + core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} /> ); expect(component).toMatchSnapshot(); @@ -100,6 +90,7 @@ describe('after fetch', () => { listingLimit={1000} hideWriteControls={false} initialFilter="my dashboard" + core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} /> ); @@ -121,6 +112,7 @@ describe('after fetch', () => { getViewUrl={() => {}} listingLimit={1000} hideWriteControls={false} + core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} /> ); @@ -142,6 +134,7 @@ describe('after fetch', () => { getViewUrl={() => {}} listingLimit={1} hideWriteControls={false} + core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} /> ); @@ -163,6 +156,7 @@ describe('after fetch', () => { getViewUrl={() => {}} listingLimit={1} hideWriteControls={true} + core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} /> ); @@ -184,6 +178,7 @@ describe('after fetch', () => { getViewUrl={() => {}} listingLimit={1} hideWriteControls={false} + core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} /> ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing_ng_wrapper.html b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing_ng_wrapper.html similarity index 94% rename from src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing_ng_wrapper.html rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing_ng_wrapper.html index a1dd9f30bdd81..f473e91af7ae9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing_ng_wrapper.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing_ng_wrapper.html @@ -1,4 +1,5 @@ ({ +jest.mock('../../legacy_imports', () => ({ SavedObjectSaveModal: () => null, })); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/save_modal.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/save_modal.tsx index 0640b2be431be..bd53fd5a13083 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/save_modal.tsx @@ -21,7 +21,7 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui'; -import { SavedObjectSaveModal } from '../legacy_imports'; +import { SavedObjectSaveModal } from '../../legacy_imports'; interface SaveOptions { newTitle: string; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/show_clone_modal.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/show_clone_modal.tsx diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/show_options_popover.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/show_options_popover.tsx diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/top_nav_ids.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/top_nav_ids.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/types.ts rename to src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts index 371274401739e..e3eb25a208856 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts @@ -18,7 +18,7 @@ */ import { ViewMode } from 'src/plugins/embeddable/public'; -import { AppState } from './legacy_imports'; +import { AppState } from '../legacy_imports'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, @@ -26,8 +26,8 @@ import { RawSavedDashboardPanel630, RawSavedDashboardPanel640To720, RawSavedDashboardPanel730ToLatest, -} from './migrations/types'; -import { Query, esFilters } from '../../../../../plugins/data/public'; +} from '../migrations/types'; +import { Query, esFilters } from '../../../../../../plugins/data/public'; export type NavAction = (anchorElement?: any) => void; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 0e0d315ce592d..76deff201e5ee 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -26,13 +26,13 @@ import { SavedObjectsClientContract, } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { RenderDeps } from './application'; +import { RenderDeps } from './np_ready/application'; import { DataStart } from '../../../data/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; -import { DashboardConstants } from './dashboard_constants'; +import { DashboardConstants } from './np_ready/dashboard_constants'; import { FeatureCatalogueCategory, HomePublicPluginSetup, @@ -105,7 +105,7 @@ export class DashboardPlugin implements Plugin { dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), }; - const { renderApp } = await import('./application'); + const { renderApp } = await import('./np_ready/application'); return renderApp(params.element, params.appBasePath, deps); }, }; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.ts index dc52b3ab7ad17..7f4d7402fcffc 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.ts @@ -20,9 +20,9 @@ import { SearchSourceContract } from 'ui/courier'; import { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types'; import { createSavedObjectClass } from 'ui/saved_objects/saved_object'; import { extractReferences, injectReferences } from './saved_dashboard_references'; -import { createDashboardEditUrl } from '../dashboard_constants'; import { esFilters, Query, RefreshInterval } from '../../../../../../plugins/data/public'; +import { createDashboardEditUrl } from '..'; export interface SavedObjectDashboard extends SavedObject { id?: string; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js index dcd04d31af076..378a9e9325655 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js @@ -20,7 +20,7 @@ import _ from 'lodash'; import { pluginInstance } from 'plugins/kibana/discover/index'; import ngMock from 'ng_mock'; -import { fieldCalculator } from '../../components/field_chooser/lib/field_calculator'; +import { fieldCalculator } from '../../np_ready/components/field_chooser/lib/field_calculator'; import expect from '@kbn/expect'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/get_sort.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/get_sort.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/get_sort.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/get_sort.js index f264f0fb4d754..d9fdcc48608d2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/get_sort.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/get_sort.js @@ -20,7 +20,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { getSort } from '../../lib/get_sort'; +import { getSort } from '../../../np_ready/angular/doc_table/lib/get_sort'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; const defaultSort = [{ time: 'desc' }]; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/rows_headers.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/rows_headers.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js index 677a067e7b3fe..012f2b6061ee4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/rows_headers.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js @@ -50,14 +50,18 @@ describe('Doc Table', function() { // Stub `getConverterFor` for a field in the indexPattern to return mock data. // Returns `val` if provided, otherwise generates fake data for the field. fakeRowVals = getFakeRowVals('formatted', 0, mapping); - stubFieldFormatConverter = function($root, field, val = null) { - $root.indexPattern.fields.getByName(field).format.getConverterFor = () => (...args) => { + stubFieldFormatConverter = function($root, field, val) { + const convertFn = (value, type, options) => { if (val) { return val; } - const fieldName = _.get(args, '[1].name', null); + const fieldName = _.get(options, 'field.name', null); + return fakeRowVals[fieldName] || ''; }; + + $root.indexPattern.fields.getByName(field).format.convert = convertFn; + $root.indexPattern.fields.getByName(field).format.getConverterFor = () => convertFn; }; }) ); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/_utils.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/_utils.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js index 1dbdea4c89f00..90614cf3c132c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js @@ -20,8 +20,8 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../actions'; -import { createIndexPatternsStub } from '../../api/__tests__/_stubs'; +import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; +import { createIndexPatternsStub } from '../../np_ready/angular/context/api/__tests__/_stubs'; import { pluginInstance } from 'plugins/kibana/discover/index'; import { npStart } from 'ui/new_platform'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js index 52db1bab64945..1ad4bdbea210d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../actions'; +import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; describe('context app', function() { beforeEach(() => pluginInstance.initializeInnerAngular()); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js index 6cc5e1ae6db04..e9ec2c300faa1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js @@ -22,7 +22,7 @@ import ngMock from 'ng_mock'; import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../actions'; +import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; describe('context app', function() { beforeEach(() => pluginInstance.initializeInnerAngular()); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js index a976cf0d04d20..15f3eefac3fd1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js @@ -22,7 +22,7 @@ import ngMock from 'ng_mock'; import { pluginInstance } from 'plugins/kibana/discover/index'; import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../actions'; +import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; describe('context app', function() { beforeEach(() => pluginInstance.initializeInnerAngular()); diff --git a/src/legacy/core_plugins/kibana/public/discover/_index.scss b/src/legacy/core_plugins/kibana/public/discover/_index.scss index 0d70bb993fac1..386472a9f6e01 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_index.scss @@ -1,26 +1,2 @@ // Discover plugin styles -@import 'mixins'; - -// Prefix all styles with "dsc" to avoid conflicts. -// Examples -// dscTable -// dscTable__footer -// monChart__legend--small -// monChart__legend-isLoading - -@import 'components/fetch_error/index'; -@import 'components/field_chooser/index'; -@import 'angular/directives/index'; -@import 'angular/doc_table/index'; - -@import 'hacks'; - -@import 'discover'; - -@import 'embeddable/index'; - -// Doc Viewer -@import 'components/doc_viewer/index'; - -// Context styles -@import 'angular/context/index'; +@import 'np_ready/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_index.scss deleted file mode 100644 index 23d6609a5cffb..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './doc_table'; -@import './components/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_index.scss deleted file mode 100644 index a73ff41db7c03..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './table_header'; -@import './table_row/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_index.scss deleted file mode 100644 index a452ce140ba93..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_row/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import './cell'; -@import './details'; -@import './open'; diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/build_services.ts similarity index 88% rename from src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts rename to src/legacy/core_plugins/kibana/public/discover/build_services.ts index 27c7c00fb729c..aaacde7e4060f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/build_services.ts @@ -26,14 +26,13 @@ import { } from 'kibana/public'; import * as docViewsRegistry from 'ui/registry/doc_views'; import { FilterManager, TimefilterContract, IndexPatternsContract } from 'src/plugins/data/public'; +import { createSavedSearchesService } from './saved_searches'; // @ts-ignore -import { createSavedSearchesService } from '../saved_searches'; -// @ts-ignore -import { DiscoverStartPlugins } from '../plugin'; -import { DataStart } from '../../../../data/public'; -import { EuiUtilsStart } from '../../../../../../plugins/eui_utils/public'; -import { SavedSearch } from '../types'; -import { SharePluginStart } from '../../../../../../plugins/share/public'; +import { DiscoverStartPlugins } from './plugin'; +import { DataStart } from '../../../data/public'; +import { EuiUtilsStart } from '../../../../../plugins/eui_utils/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { SavedSearch } from './np_ready/types'; export interface DiscoverServices { addBasePath: (path: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap deleted file mode 100644 index d7737bbfe4078..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DiscoverIndexPattern A single index pattern is just displayed 1`] = ` - -`; - -exports[`DiscoverIndexPattern Invalid props dont cause an exception: "" 1`] = `""`; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_index.scss b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_index.scss deleted file mode 100644 index ad65834519eb1..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './field_chooser'; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern.test.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern.test.tsx deleted file mode 100644 index 68ef0f5b46694..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern.test.tsx +++ /dev/null @@ -1,75 +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 { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; -// @ts-ignore -import { findTestSubject } from '@elastic/eui/lib/test'; -import { SavedObject } from 'kibana/server'; -import { DiscoverIndexPattern, DiscoverIndexPatternProps } from './discover_index_pattern'; -import { comboBoxKeyCodes } from '@elastic/eui'; - -const indexPattern1 = { - id: 'test1', - attributes: { - title: 'test1', - }, -} as SavedObject; - -const indexPattern2 = { - id: 'test2', - attributes: { - title: 'test2', - }, -} as SavedObject; - -describe('DiscoverIndexPattern', () => { - test('Invalid props dont cause an exception', () => { - const props = { - indexPatternList: null, - selectedIndexPattern: null, - setIndexPattern: jest.fn(), - } as any; - - expect(shallowWithIntl()).toMatchSnapshot(`""`); - }); - test('A single index pattern is just displayed', () => { - const props = { - indexPatternList: [indexPattern1], - selectedIndexPattern: indexPattern1, - setIndexPattern: jest.fn(), - } as DiscoverIndexPatternProps; - - expect(shallowWithIntl()).toMatchSnapshot(); - }); - - test('Multiple index patterns are selectable', () => { - const props = { - indexPatternList: [indexPattern1, indexPattern2], - selectedIndexPattern: indexPattern2, - setIndexPattern: jest.fn(), - } as DiscoverIndexPatternProps; - const component = mountWithIntl(); - findTestSubject(component, 'indexPattern-switch-link').simulate('click'); - - const searchInput = findTestSubject(component, 'comboBoxSearchInput'); - searchInput.simulate('change', { target: { value: 'test1' } }); - searchInput.simulate('keyDown', { keyCode: comboBoxKeyCodes.ENTER }); - expect(props.setIndexPattern).toBeCalledWith('test1'); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern.tsx deleted file mode 100644 index 55f58d352ac23..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern.tsx +++ /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 React, { useState } from 'react'; -import { EuiComboBox } from '@elastic/eui'; -import { SavedObject } from 'kibana/server'; -import { DiscoverIndexPatternTitle } from './discover_index_pattern_title'; - -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 [selected, setSelected] = useState(selectedIndexPattern); - const [showCombo, setShowCombo] = useState(false); - const options = indexPatternList.map(entity => ({ - value: entity.id, - label: entity.attributes!.title, - })); - const selectedOptions = selected - ? [{ value: selected.id, label: selected.attributes.title }] - : []; - - const findIndexPattern = (id?: string) => indexPatternList.find(entity => entity.id === id); - - if (!showCombo) { - return ( - 1} - onChange={() => setShowCombo(true)} - title={selected.attributes ? selected.attributes.title : ''} - /> - ); - } - - /** - * catches a EuiComboBox related 'Can't perform a React state update on an unmounted component' - * warning in console by delaying the hiding/removal of the EuiComboBox a bit - */ - function hideCombo() { - setTimeout(() => setShowCombo(false), 50); - } - - return ( - hideCombo()} - onChange={choices => { - const newSelected = choices[0] && findIndexPattern(choices[0].value); - if (newSelected) { - setSelected(newSelected); - setIndexPattern(newSelected.id); - } - hideCombo(); - }} - inputRef={el => { - // auto focus input element when combo box is displayed - if (el) { - el.focus(); - } - }} - options={options} - selectedOptions={selectedOptions} - singleSelection={{ asPlainText: true }} - /> - ); -} diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_title.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_title.tsx deleted file mode 100644 index fb5c3c8d45ce8..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_title.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import { EuiToolTip, EuiFlexItem, EuiFlexGroup, EuiTitle, EuiButtonEmpty } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -export interface DiscoverIndexPatternTitleProps { - /** - * determines whether the change link is displayed - */ - isChangeable: boolean; - /** - * function triggered when the change link is clicked - */ - onChange: () => void; - /** - * title of the current index pattern - */ - title: string; -} - -/** - * Component displaying the title of the current selected index pattern - * and if changeable is true, a link is provided to change the index pattern - */ -export function DiscoverIndexPatternTitle({ - isChangeable, - onChange, - title, -}: DiscoverIndexPatternTitleProps) { - return ( - - - - -

{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/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 100% 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 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/kibana/server/routes/api/scripts/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts similarity index 86% rename from src/legacy/core_plugins/kibana/server/routes/api/scripts/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts index 441963b02f14f..302bf5165777c 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/scripts/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts @@ -17,8 +17,7 @@ * under the License. */ -import { registerLanguages } from './register_languages'; - -export function scriptsApi(server) { - registerLanguages(server); +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/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/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 100% 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 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/components/__snapshots__/welcome.test.tsx.snap b/src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/welcome.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/home/components/__snapshots__/welcome.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/home/np_ready/components/__snapshots__/welcome.test.tsx.snap 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 98% 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 d552dd070c86d..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'; 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 98% 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 1f46cf2875fee..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: () => '', 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 96% 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 29f24f5b841a3..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,7 +27,9 @@ 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 { 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 100% 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 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 98% 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 42c6e6ff6056a..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: () => {}, 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 99% 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 435bf98ca7840..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 @@ -36,7 +36,7 @@ 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 { 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/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 100% 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 diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/_dialog.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/_dialog.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/_dialog.scss rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/_dialog.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/_index.scss new file mode 100644 index 0000000000000..a10b4b1b347b7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/_index.scss @@ -0,0 +1 @@ +@import 'dialog'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/index.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/index.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/index.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.test.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.test.tsx index 8e4d50d27a453..2005133e6d03e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.test.tsx @@ -20,11 +20,10 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { VisType } from '../legacy_imports'; -import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +import { VisType } from '../../legacy_imports'; +import { TypesStart } from '../../../../../visualizations/public/np_ready/public/types'; -jest.mock('ui/new_platform'); -jest.mock('../legacy_imports', () => ({ +jest.mock('../../legacy_imports', () => ({ State: () => null, AppState: () => null, })); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.tsx similarity index 86% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.tsx index e84797302589d..9e8f46407f591 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.tsx @@ -22,13 +22,17 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; -import { VisType } from '../legacy_imports'; +import { VisType } from '../../legacy_imports'; import { VisualizeConstants } from '../visualize_constants'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; -import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; +import { + TypesStart, + VisTypeAlias, +} from '../../../../../visualizations/public/np_ready/public/types'; +import { UsageCollectionSetup } from '../../../../../../../plugins/usage_collection/public'; interface TypeSelectionProps { isOpen: boolean; @@ -38,6 +42,7 @@ interface TypeSelectionProps { addBasePath: (path: string) => string; uiSettings: IUiSettingsClient; savedObjects: SavedObjectsStart; + usageCollection?: UsageCollectionSetup; } interface TypeSelectionState { @@ -53,7 +58,9 @@ class NewVisModal extends React.Component; + private readonly trackUiMetric: + | ((type: UiStatsMetricType, eventNames: string | string[], count?: number) => void) + | undefined; constructor(props: TypeSelectionProps) { super(props); @@ -63,7 +70,10 @@ class NewVisModal extends React.Component void; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/show_new_vis.tsx similarity index 86% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/show_new_vis.tsx index 88838e16c40e2..567b7e861ad8e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/show_new_vis.tsx @@ -23,7 +23,8 @@ import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; -import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +import { TypesStart } from '../../../../../visualizations/public/np_ready/public/types'; +import { UsageCollectionSetup } from '../../../../../../../plugins/usage_collection/public'; interface ShowNewVisModalParams { editorParams?: string[]; @@ -34,7 +35,8 @@ export function showNewVisModal( { editorParams = [] }: ShowNewVisModalParams = {}, addBasePath: (path: string) => string, uiSettings: IUiSettingsClient, - savedObjects: SavedObjectsStart + savedObjects: SavedObjectsStart, + usageCollection?: UsageCollectionSetup ) { const container = document.createElement('div'); const onClose = () => { @@ -53,6 +55,7 @@ export function showNewVisModal( addBasePath={addBasePath} uiSettings={uiSettings} savedObjects={savedObjects} + usageCollection={usageCollection} /> ); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/index.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/index.ts rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/index.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.tsx diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/type_selection.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/type_selection.tsx index 38cd7fbe315ad..28cafde45a714 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/type_selection.tsx @@ -34,13 +34,13 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { VisType } from '../../legacy_imports'; -import { memoizeLast } from '../../../../../visualizations/public/np_ready/public/legacy/memoize'; -import { VisTypeAlias } from '../../../../../visualizations/public'; +import { VisType } from '../../../legacy_imports'; +import { memoizeLast } from '../../../../../../visualizations/public/np_ready/public/legacy/memoize'; +import { VisTypeAlias } from '../../../../../../visualizations/public'; import { NewVisHelp } from './new_vis_help'; import { VisHelpText } from './vis_help_text'; import { VisTypeIcon } from './vis_type_icon'; -import { TypesStart } from '../../../../../visualizations/public/np_ready/public/types'; +import { TypesStart } from '../../../../../../visualizations/public/np_ready/public/types'; export interface VisTypeListEntry extends VisType { highlighted: boolean; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/vis_help_text.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/vis_help_text.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/vis_help_text.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/vis_help_text.tsx diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/vis_type_icon.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/vis_type_icon.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/vis_type_icon.tsx rename to src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/vis_type_icon.tsx diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 9dff936761a4b..d1afa2d065194 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -34,16 +34,20 @@ import { NavigationPublicPluginStart as NavigationStart } from '../../../../../p import { SharePluginStart } from '../../../../../plugins/share/public'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; import { VisualizationsStart } from '../../../visualizations/public'; -import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; -import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; -import { VisualizeConstants } from './visualize_constants'; +import { VisualizeConstants } from './np_ready/visualize_constants'; import { setServices, VisualizeKibanaServices } from './kibana_services'; import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../../plugins/home/public'; -import { defaultEditor, VisEditorTypesRegistryProvider } from './legacy_imports'; -import { SavedVisualizations } from './types'; +import { + defaultEditor, + VisEditorTypesRegistryProvider, + VisualizeEmbeddableFactory, + VISUALIZE_EMBEDDABLE_TYPE, +} from './legacy_imports'; +import { SavedVisualizations } from './np_ready/types'; +import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; export interface LegacyAngularInjectedDependencies { legacyChrome: any; @@ -66,6 +70,7 @@ export interface VisualizePluginSetupDependencies { }; home: HomePublicPluginSetup; kibana_legacy: KibanaLegacySetup; + usageCollection?: UsageCollectionSetup; } export class VisualizePlugin implements Plugin { @@ -80,7 +85,12 @@ export class VisualizePlugin implements Plugin { public async setup( core: CoreSetup, - { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: VisualizePluginSetupDependencies + { + home, + kibana_legacy, + __LEGACY: { getAngularDependencies }, + usageCollection, + }: VisualizePluginSetupDependencies ) { kibana_legacy.registerLegacyApp({ id: 'visualize', @@ -118,10 +128,11 @@ export class VisualizePlugin implements Plugin { uiSettings: contextCore.uiSettings, visualizeCapabilities: contextCore.application.capabilities.visualize, visualizations, + usageCollection, }; setServices(deps); - const { renderApp } = await import('./application'); + const { renderApp } = await import('./np_ready/application'); return renderApp(params.element, params.appBasePath, deps); }, }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts index 3490e0ab127ed..9f7ba342d803f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts @@ -29,12 +29,12 @@ import { Vis } from 'ui/vis'; import { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types'; import { createSavedObjectClass } from 'ui/saved_objects/saved_object'; import { updateOldState } from '../../../../visualizations/public'; -import { VisualizeConstants } from '../visualize_constants'; import { extractReferences, injectReferences } from './saved_visualization_references'; import { IIndexPattern } from '../../../../../../plugins/data/public'; import { VisSavedObject } from '../legacy_imports'; import { createSavedSearchesService } from '../../discover'; +import { VisualizeConstants } from '..'; async function _afterEsResp(savedVis: VisSavedObject, services: any) { await _getLinkedSavedSearch(savedVis, services); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts index 6549b317d1634..b71a10ab000d8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts @@ -18,7 +18,7 @@ */ import { extractReferences, injectReferences } from './saved_visualization_references'; -import { VisSavedObject } from '../embeddable/visualize_embeddable'; +import { VisSavedObject } from '../../visualize_embeddable/visualize_embeddable'; describe('extractReferences', () => { test('extracts nothing if savedSearchId is empty', () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts index dd8c2e9d2b74f..0c76aaff4345d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts @@ -17,7 +17,7 @@ * under the License. */ import { SavedObjectAttributes, SavedObjectReference } from 'kibana/server'; -import { VisSavedObject } from '../embeddable/visualize_embeddable'; +import { VisSavedObject } from '../../visualize_embeddable/visualize_embeddable'; export function extractReferences({ attributes, diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts index 7425250bffe1a..aa8d20fed4828 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts @@ -22,10 +22,10 @@ import { uiModules } from 'ui/modules'; import { SavedObjectLoader } from 'ui/saved_objects'; import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; -import { createVisualizeEditUrl } from '../visualize_constants'; // @ts-ignore import { findListItems } from './find_list_items'; import { createSavedVisClass } from './_saved_vis'; +import { createVisualizeEditUrl } from '..'; const app = uiModules.get('app/visualize'); app.service('savedVisualizations', function() { diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/wizard/_index.scss deleted file mode 100644 index 328af16f3eebd..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './dialog'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/_embeddables.scss b/src/legacy/core_plugins/kibana/public/visualize_embeddable/_embeddables.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/_embeddables.scss rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/_embeddables.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/_index.scss b/src/legacy/core_plugins/kibana/public/visualize_embeddable/_index.scss new file mode 100644 index 0000000000000..c1e3809657bfa --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize_embeddable/_index.scss @@ -0,0 +1,2 @@ +@import 'visualize_lab_disabled'; +@import 'embeddables'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/_visualize_lab_disabled.scss b/src/legacy/core_plugins/kibana/public/visualize_embeddable/_visualize_lab_disabled.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/_visualize_lab_disabled.scss rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/_visualize_lab_disabled.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/constants.ts b/src/legacy/core_plugins/kibana/public/visualize_embeddable/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/constants.ts rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_embeddable.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_embeddable.tsx index d8792a761b186..f9dfd5d2b98f4 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_embeddable.tsx @@ -19,7 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Embeddable, EmbeddableOutput } from '../../../../../../plugins/embeddable/public'; +import { Embeddable, EmbeddableOutput } from '../../../../../plugins/embeddable/public'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_visualization.tsx b/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_visualization.tsx rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts index 7fe3678bb1f77..36efc4b86d0d3 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts @@ -20,7 +20,7 @@ import { npStart } from 'ui/new_platform'; import { VisSavedObject } from './visualize_embeddable'; -import { indexPatterns, IIndexPattern } from '../../../../../../plugins/data/public'; +import { indexPatterns, IIndexPattern } from '../../../../../plugins/data/public'; export async function getIndexPattern( savedVis: VisSavedObject diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/index.ts b/src/legacy/core_plugins/kibana/public/visualize_embeddable/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/index.ts rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/index.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts index 45cc1dc5fb9dd..6aade246d5f65 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts @@ -38,16 +38,16 @@ import { onlyDisabledFiltersChanged, esFilters, mapAndFlattenFilters, -} from '../../../../../../plugins/data/public'; +} from '../../../../../plugins/data/public'; import { EmbeddableInput, EmbeddableOutput, Embeddable, Container, APPLY_FILTER_TRIGGER, -} from '../../../../../../plugins/embeddable/public'; -import { dispatchRenderComplete } from '../../../../../../plugins/kibana_utils/public'; -import { SavedSearch } from '../../discover/types'; +} from '../../../../../plugins/embeddable/public'; +import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; +import { SavedSearch } from '../discover/np_ready/types'; const getKeys = (o: T): Array => Object.keys(o) as Array; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx similarity index 93% rename from src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx rename to src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx index a377dafe9e512..dd6723fb578af 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx @@ -35,7 +35,7 @@ import 'uiExports/visualize'; import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; -import { npStart } from 'ui/new_platform'; +import { npSetup, npStart } from 'ui/new_platform'; import { Legacy } from 'kibana'; @@ -45,10 +45,10 @@ import { ErrorEmbeddable, Container, EmbeddableOutput, -} from '../../../../../../plugins/embeddable/public'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; -import { showNewVisModal } from '../wizard'; -import { SavedVisualizations } from '../types'; +} from '../../../../../plugins/embeddable/public'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { showNewVisModal } from '../visualize'; +import { SavedVisualizations } from '../visualize/np_ready/types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; import { @@ -58,7 +58,7 @@ import { VisSavedObject, } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +import { TypesStart } from '../../../visualizations/public/np_ready/public/types'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -200,7 +200,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< }, npStart.core.http.basePath.prepend, npStart.core.uiSettings, - npStart.core.savedObjects + npStart.core.savedObjects, + npSetup.plugins.usageCollection ); } return undefined; diff --git a/src/legacy/core_plugins/kibana/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/ui_setting_defaults.js index 682da20ca4118..196d9662f8b15 100644 --- a/src/legacy/core_plugins/kibana/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/ui_setting_defaults.js @@ -20,6 +20,7 @@ import moment from 'moment-timezone'; import numeralLanguages from '@elastic/numeral/languages'; import { i18n } from '@kbn/i18n'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../plugins/data/common'; export function getUiSettingDefaults() { const weekdays = moment.weekdays().slice(); @@ -121,7 +122,7 @@ export function getUiSettingDefaults() { }, 'search:queryLanguage': { name: queryLanguageSettingName, - value: 'kuery', + value: DEFAULT_QUERY_LANGUAGE, description: i18n.translate('kbn.advancedSettings.searchQueryLanguageText', { defaultMessage: 'Query language used by the query bar. KQL is a new language built specifically for Kibana.', diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts index 605ffdd6f2134..e7fa13409ab04 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts @@ -17,13 +17,13 @@ * under the License. */ -import { HttpServiceBase } from '../../../../../../../../core/public'; +import { HttpSetup } from '../../../../../../../../core/public'; import { IndexPatternCreationConfig, UrlHandler, IndexPatternCreationOption } from './config'; export class IndexPatternCreationManager { private configs: IndexPatternCreationConfig[]; - constructor(private readonly httpClient: HttpServiceBase) { + constructor(private readonly httpClient: HttpSetup) { this.configs = []; } diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts index b9e07564a324c..b421024b60f4b 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts @@ -17,12 +17,12 @@ * under the License. */ -import { HttpServiceBase } from '../../../../../../../core/public'; +import { HttpSetup } from '../../../../../../../core/public'; import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; import { IndexPatternListManager, IndexPatternListConfig } from './list'; interface SetupDependencies { - httpClient: HttpServiceBase; + httpClient: HttpSetup; } /** diff --git a/src/legacy/core_plugins/timelion/index.ts b/src/legacy/core_plugins/timelion/index.ts index 77e62ed02718c..ec121647f4e47 100644 --- a/src/legacy/core_plugins/timelion/index.ts +++ b/src/legacy/core_plugins/timelion/index.ts @@ -31,7 +31,7 @@ const experimentalLabel = i18n.translate('timelion.uiSettings.experimentalLabel' const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => new Plugin({ - require: ['kibana', 'elasticsearch'], + require: ['kibana', 'elasticsearch', 'data'], config(Joi: any) { return Joi.object({ enabled: Joi.boolean().default(true), diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index bff847becb7a8..365e74e93c4ad 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -40,6 +40,7 @@ import 'ui/directives/saved_object_finder'; import 'ui/directives/listen'; import 'ui/kbn_top_nav'; import 'ui/saved_objects/ui/saved_object_save_as_checkbox'; +import '../../data/public/legacy'; import './services/saved_sheets'; import './services/_saved_sheet'; import './services/saved_sheet_register'; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index a2f98b8c64e53..1f6600ea56a12 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -110,7 +110,7 @@ describe('Table Vis - Controller', () => { (cfg: any) => cfg, 'time', stubFields, - npStart.core.uiSettings + npStart.core ); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/index.ts b/src/legacy/core_plugins/vis_type_timeseries/index.ts index eb20c0f23965a..9ca14b28e19b2 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/index.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/index.ts @@ -19,13 +19,9 @@ import { resolve } from 'path'; import { Legacy } from 'kibana'; -import { PluginInitializerContext } from 'src/core/server'; -import { CoreSetup } from 'src/core/server'; - -import { plugin } from './server/'; -import { CustomCoreSetup } from './server/plugin'; import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; +import { VisTypeTimeseriesSetup } from '../../../plugins/vis_type_timeseries/server'; const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => new Plugin({ @@ -38,10 +34,9 @@ const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPlu injectDefaultVars: server => ({}), }, init: (server: Legacy.Server) => { - const initializerContext = {} as PluginInitializerContext; - const core = { http: { server } } as CoreSetup & CustomCoreSetup; - - plugin(initializerContext).setup(core); + const visTypeTimeSeriesPlugin = server.newPlatform.setup.plugins + .metrics as VisTypeTimeseriesSetup; + visTypeTimeSeriesPlugin.__legacy.registerLegacyAPI({ server }); }, config(Joi: any) { return Joi.object({ diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/index.ts b/src/legacy/core_plugins/vis_type_timeseries/server/index.ts index 14047c31f2dcd..c010628ca04bf 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/index.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/server/index.ts @@ -17,9 +17,5 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/server'; -import { MetricsServerPlugin as Plugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} +export { init } from './init'; +export { getVisData, GetVisData, GetVisDataOptions } from './lib/get_vis_data'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/plugin.ts b/src/legacy/core_plugins/vis_type_timeseries/server/init.ts similarity index 59% rename from src/legacy/core_plugins/vis_type_timeseries/server/plugin.ts rename to src/legacy/core_plugins/vis_type_timeseries/server/init.ts index ce4ab64ffa07a..7b42ae8098016 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/server/init.ts @@ -17,9 +17,6 @@ * under the License. */ -import { Legacy } from 'kibana'; -import { PluginInitializerContext, CoreSetup } from 'kibana/server'; - // @ts-ignore import { fieldsRoutes } from './routes/fields'; // @ts-ignore @@ -28,30 +25,15 @@ import { visDataRoutes } from './routes/vis'; import { SearchStrategiesRegister } from './lib/search_strategies/search_strategies_register'; // @ts-ignore import { getVisData } from './lib/get_vis_data'; +import { Framework } from '../../../../plugins/vis_type_timeseries/server'; -// TODO: Remove as CoreSetup is completed. -export interface CustomCoreSetup { - http: { - server: Legacy.Server; - }; -} - -export class MetricsServerPlugin { - public initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this.initializerContext = initializerContext; - } - - public setup(core: CoreSetup & CustomCoreSetup) { - const { http } = core; - - fieldsRoutes(http.server); - visDataRoutes(http.server); +export const init = async (framework: Framework, __LEGACY: any) => { + const { core } = framework; + const router = core.http.createRouter(); - // Expose getVisData to allow plugins to use TSVB's backend for metrics - http.server.expose('getVisData', getVisData); + visDataRoutes(router, framework); - SearchStrategiesRegister.init(http.server); - } -} + // [LEGACY_TODO] + fieldsRoutes(__LEGACY.server); + SearchStrategiesRegister.init(__LEGACY.server); +}; diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_vis_data.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_vis_data.js deleted file mode 100644 index 055d4477825c7..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_vis_data.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { getPanelData } from './vis_data/get_panel_data'; - -export function getVisData(req) { - const promises = req.payload.panels.map(getPanelData(req)); - return Promise.all(promises).then(res => { - return res.reduce((acc, data) => { - return _.assign(acc, data); - }, {}); - }); -} diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_vis_data.ts new file mode 100644 index 0000000000000..58e624fa13442 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -0,0 +1,101 @@ +/* + * 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 { RequestHandlerContext } from 'src/core/server'; +import _ from 'lodash'; +import { first, map } from 'rxjs/operators'; +import { getPanelData } from './vis_data/get_panel_data'; +import { Framework } from '../../../../../plugins/vis_type_timeseries/server'; + +interface GetVisDataResponse { + [key: string]: GetVisDataPanel; +} + +interface GetVisDataPanel { + id: string; + series: GetVisDataSeries[]; +} + +interface GetVisDataSeries { + id: string; + label: string; + data: GetVisDataDataPoint[]; +} + +type GetVisDataDataPoint = [number, number]; + +export interface GetVisDataOptions { + timerange?: any; + panels?: any; + filters?: any; + state?: any; + query?: any; +} + +export type GetVisData = ( + requestContext: RequestHandlerContext, + options: GetVisDataOptions, + framework: Framework +) => Promise; + +export function getVisData( + requestContext: RequestHandlerContext, + options: GetVisDataOptions, + framework: Framework +): Promise { + // NOTE / TODO: This facade has been put in place to make migrating to the New Platform easier. It + // removes the need to refactor many layers of dependencies on "req", and instead just augments the top + // level object passed from here. The layers should be refactored fully at some point, but for now + // this works and we are still using the New Platform services for these vis data portions. + const reqFacade: any = { + payload: options, + getUiSettingsService: () => requestContext.core.uiSettings.client, + getSavedObjectsClient: () => requestContext.core.savedObjects.client, + server: { + plugins: { + elasticsearch: { + getCluster: () => { + return { + callWithRequest: async (req: any, endpoint: string, params: any) => { + return await requestContext.core.elasticsearch.dataClient.callAsCurrentUser( + endpoint, + params + ); + }, + }; + }, + }, + }, + }, + getEsShardTimeout: async () => { + return await framework.globalConfig$ + .pipe( + first(), + map(config => config.elasticsearch.shardTimeout.asMilliseconds()) + ) + .toPromise(); + }, + }; + const promises = reqFacade.payload.panels.map(getPanelData(reqFacade)); + return Promise.all(promises).then(res => { + return res.reduce((acc, data) => { + return _.assign(acc as any, data); + }, {}); + }) as Promise; +} diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js index 2b53fc5a93b7b..b19f6a3241597 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js @@ -17,20 +17,14 @@ * under the License. */ -import moment from 'moment'; -import { of } from 'rxjs'; import { expect } from 'chai'; import { getEsShardTimeout } from '../../helpers/get_es_shard_timeout'; describe('getEsShardTimeout', () => { it('should return the elasticsearch.shardTimeout', async () => { const req = { - server: { - newPlatform: { - __internals: { - elasticsearch: { legacy: { config$: of({ shardTimeout: moment.duration(12345) }) } }, - }, - }, + getEsShardTimeout: async () => { + return 12345; }, }; diff --git a/src/legacy/server/sample_data/index.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_panel_data.d.ts similarity index 93% rename from src/legacy/server/sample_data/index.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_panel_data.d.ts index 2e24946761f20..2f86236fefedc 100644 --- a/src/legacy/server/sample_data/index.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_panel_data.d.ts @@ -17,4 +17,4 @@ * under the License. */ -export { sampleDataMixin } from './sample_data_mixin'; +export function getPanelData(req: any): any; diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js index e3745bd6963e0..b4eb9e6b108ff 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js @@ -48,7 +48,6 @@ export async function getSeriesData(req, panel) { const data = await searchRequest.search(searches); const series = data.map(handleResponseBody(panel)); - let annotations = null; if (panel.annotations && panel.annotations.length) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.js index c8acfa730af67..4eb3075e9b658 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.js @@ -16,13 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { first, map } from 'rxjs/operators'; export async function getEsShardTimeout(req) { - return await req.server.newPlatform.__internals.elasticsearch.legacy.config$ - .pipe( - first(), - map(config => config.shardTimeout.asMilliseconds()) - ) - .toPromise(); + return await req.getEsShardTimeout(); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.js b/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.js index 15ff7b9a6c90b..d2ded81309ffa 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.js @@ -17,23 +17,28 @@ * under the License. */ +import { schema } from '@kbn/config-schema'; import { getVisData } from '../lib/get_vis_data'; -import Boom from 'boom'; -export const visDataRoutes = server => { - server.route({ - path: '/api/metrics/vis/data', - method: 'POST', - handler: async req => { - try { - return await getVisData(req); - } catch (err) { - if (err.isBoom && err.status === 401) { - return err; - } +const escapeHatch = schema.object({}, { allowUnknowns: true }); - throw Boom.boomify(err, { statusCode: 500 }); - } +export const visDataRoutes = (router, framework) => { + router.post( + { + path: '/api/metrics/vis/data', + validate: { + body: escapeHatch, + }, }, - }); + async (requestContext, request, response) => { + try { + const results = await getVisData(requestContext, request.body, framework); + return response.ok({ body: results }); + } catch (error) { + return response.internalError({ + body: error.message, + }); + } + } + ); }; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 9a18f7e8a3003..1cce1e1e6aeb6 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -44,6 +44,7 @@ import { UsageCollectionSetup } from '../../plugins/usage_collection/server'; import { IndexPatternsServiceFactory } from './index_patterns'; import { Capabilities } from '../../core/server'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; +import { HomeServerPluginSetup } from '../../plugins/home/server'; // lot of legacy code was assuming this type only had these two methods export type KibanaConfig = Pick; @@ -99,6 +100,7 @@ type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promi export interface PluginsSetup { usageCollection: UsageCollectionSetup; + home: HomeServerPluginSetup; [key: string]: object; } diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index ffa16fb0a0ad1..98484a0de6f65 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -35,7 +35,6 @@ import optimizeMixin from '../../optimize'; import * as Plugins from './plugins'; import { indexPatternsMixin } from './index_patterns'; import { savedObjectsMixin } from './saved_objects/saved_objects_mixin'; -import { sampleDataMixin } from './sample_data'; import { capabilitiesMixin } from './capabilities'; import { urlShorteningMixin } from './url_shortening'; import { serverExtensionsMixin } from './server_extensions'; @@ -112,9 +111,6 @@ export default class KbnServer { // setup capabilities routes capabilitiesMixin, - // setup routes for installing/uninstalling sample data sets - sampleDataMixin, - // setup routes for short urls urlShorteningMixin, diff --git a/src/legacy/server/sample_data/README.md b/src/legacy/server/sample_data/README.md deleted file mode 100644 index 9e93504348922..0000000000000 --- a/src/legacy/server/sample_data/README.md +++ /dev/null @@ -1,20 +0,0 @@ -### What happens when a user installs a sample data set? -1) Kibana deletes existing Elastic search indicies for the sample data set if they exist from previous installs. -2) Kibana creates Elasticsearch indicies with the provided field mappings. -3) Kibana uses bulk insert to ingest the new-line delimited json into the Elasticsearch index. Kibana migrates timestamps provided in new-line delimited json to the current time frame for any date field defined in `timeFields` -4) Kibana will install all saved objects for sample data set. This will override any saved objects previouslly installed for sample data set. - -Elasticsearch index names are prefixed with `kibana_sample_data_`. For more details see [createIndexName](/src/legacy/server/sample_data/routes/lib/create_index_name.js) - -Sample data sets typically provide data that spans 5 weeks from the past and 5 weeks into the future so users see data relative to `now` for a few weeks after installing sample data sets. - -### Adding new sample data sets -Use [existing sample data sets](/src/legacy/server/sample_data/data_sets) as examples. -To avoid bloating the Kibana distribution, keep data set size to a minimum. - -Follow the steps below to add new Sample data sets to Kibana. -1) Create new-line delimited json containing sample data. -2) Create file with Elasticsearch field mappings for sample data indices. -3) Create Kibana saved objects for sample data including index-patterns, visualizations, and dashboards. The best way to extract the saved objects is from the Kibana management -> saved objects [export UI](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html#_export) -4) Define sample data spec conforming to [Data Set Schema](/src/legacy/server/sample_data/data_set_schema.js). -5) Register sample data by calling `server.registerSampleDataset(yourSpecProvider)` where `yourSpecProvider` is a function that returns an object containing your sample data spec from step 4. diff --git a/src/legacy/server/sample_data/routes/install.js b/src/legacy/server/sample_data/routes/install.js deleted file mode 100644 index 42635618e1ad6..0000000000000 --- a/src/legacy/server/sample_data/routes/install.js +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Boom from 'boom'; -import Joi from 'joi'; -import { usage } from '../usage'; -import { loadData } from './lib/load_data'; -import { createIndexName } from './lib/create_index_name'; -import { - dateToIso8601IgnoringTime, - translateTimeRelativeToDifference, - translateTimeRelativeToWeek, -} from './lib/translate_timestamp'; - -function insertDataIntoIndex( - dataIndexConfig, - index, - nowReference, - request, - server, - callWithRequest -) { - const bulkInsert = async docs => { - function updateTimestamps(doc) { - dataIndexConfig.timeFields.forEach(timeFieldName => { - if (doc[timeFieldName]) { - doc[timeFieldName] = dataIndexConfig.preserveDayOfWeekTimeOfDay - ? translateTimeRelativeToWeek( - doc[timeFieldName], - dataIndexConfig.currentTimeMarker, - nowReference - ) - : translateTimeRelativeToDifference( - doc[timeFieldName], - dataIndexConfig.currentTimeMarker, - nowReference - ); - } - }); - return doc; - } - - const insertCmd = { index: { _index: index } }; - - const bulk = []; - docs.forEach(doc => { - bulk.push(insertCmd); - bulk.push(updateTimestamps(doc)); - }); - const resp = await callWithRequest(request, 'bulk', { body: bulk }); - if (resp.errors) { - server.log( - ['warning'], - `sample_data install errors while bulk inserting. Elasticsearch response: ${JSON.stringify( - resp, - null, - '' - )}` - ); - return Promise.reject( - new Error(`Unable to load sample data into index "${index}", see kibana logs for details`) - ); - } - }; - - return loadData(dataIndexConfig.dataPath, bulkInsert); -} - -export const createInstallRoute = () => ({ - path: '/api/sample_data/{id}', - method: 'POST', - config: { - validate: { - query: Joi.object().keys({ now: Joi.date().iso() }), - params: Joi.object() - .keys({ id: Joi.string().required() }) - .required(), - }, - handler: async (request, h) => { - const { server, params, query } = request; - - const sampleDataset = server.getSampleDatasets().find(({ id }) => id === params.id); - if (!sampleDataset) { - return h.response().code(404); - } - - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - - const now = query.now ? query.now : new Date(); - const nowReference = dateToIso8601IgnoringTime(now); - - const counts = {}; - for (let i = 0; i < sampleDataset.dataIndices.length; i++) { - const dataIndexConfig = sampleDataset.dataIndices[i]; - const index = createIndexName(sampleDataset.id, dataIndexConfig.id); - - // clean up any old installation of dataset - try { - await callWithRequest(request, 'indices.delete', { index }); - } catch (err) { - // ignore delete errors - } - - try { - const createIndexParams = { - index: index, - body: { - settings: { index: { number_of_shards: 1, auto_expand_replicas: '0-1' } }, - mappings: { properties: dataIndexConfig.fields }, - }, - }; - await callWithRequest(request, 'indices.create', createIndexParams); - } catch (err) { - const errMsg = `Unable to create sample data index "${index}", error: ${err.message}`; - server.log(['warning'], errMsg); - return h.response(errMsg).code(err.status); - } - - try { - const count = await insertDataIntoIndex( - dataIndexConfig, - index, - nowReference, - request, - server, - callWithRequest - ); - counts[index] = count; - } catch (err) { - server.log(['warning'], `sample_data install errors while loading data. Error: ${err}`); - return h.response(err.message).code(500); - } - } - - let createResults; - try { - createResults = await request - .getSavedObjectsClient() - .bulkCreate(sampleDataset.savedObjects, { overwrite: true }); - } catch (err) { - server.log(['warning'], `bulkCreate failed, error: ${err.message}`); - return Boom.badImplementation( - `Unable to load kibana saved objects, see kibana logs for details` - ); - } - const errors = createResults.saved_objects.filter(savedObjectCreateResult => { - return Boolean(savedObjectCreateResult.error); - }); - if (errors.length > 0) { - server.log( - ['warning'], - `sample_data install errors while loading saved objects. Errors: ${errors.join(',')}` - ); - return h - .response(`Unable to load kibana saved objects, see kibana logs for details`) - .code(403); - } - - // track the usage operation in a non-blocking way - usage(request).addInstall(params.id); - - return h.response({ - elasticsearchIndicesCreated: counts, - kibanaSavedObjectsLoaded: sampleDataset.savedObjects.length, - }); - }, - }, -}); diff --git a/src/legacy/server/sample_data/routes/lib/create_index_name.test.js b/src/legacy/server/sample_data/routes/lib/create_index_name.test.js deleted file mode 100644 index e285dc0120ac8..0000000000000 --- a/src/legacy/server/sample_data/routes/lib/create_index_name.test.js +++ /dev/null @@ -1,30 +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 { createIndexName } from './create_index_name'; - -test('should include sampleDataSetId and dataIndexId in elasticsearch index name', async () => { - expect(createIndexName('mySampleDataSetId', 'myDataIndexId')).toBe( - 'kibana_sample_data_mySampleDataSetId_myDataIndexId' - ); -}); - -test('should only include sampleDataSetId when sampleDataSetId and dataIndexId are identical', async () => { - expect(createIndexName('flights', 'flights')).toBe('kibana_sample_data_flights'); -}); diff --git a/src/legacy/server/sample_data/routes/lib/load_data.test.js b/src/legacy/server/sample_data/routes/lib/load_data.test.js deleted file mode 100644 index 0af07e14ff965..0000000000000 --- a/src/legacy/server/sample_data/routes/lib/load_data.test.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { loadData } from './load_data'; - -test('load flight data', async () => { - let myDocsCount = 0; - const bulkInsertMock = docs => { - myDocsCount += docs.length; - }; - const count = await loadData( - './src/legacy/server/sample_data/data_sets/flights/flights.json.gz', - bulkInsertMock - ); - expect(myDocsCount).toBe(13059); - expect(count).toBe(13059); -}); - -test('load log data', async () => { - let myDocsCount = 0; - const bulkInsertMock = docs => { - myDocsCount += docs.length; - }; - const count = await loadData( - './src/legacy/server/sample_data/data_sets/logs/logs.json.gz', - bulkInsertMock - ); - expect(myDocsCount).toBe(14074); - expect(count).toBe(14074); -}); - -test('load ecommerce data', async () => { - let myDocsCount = 0; - const bulkInsertMock = docs => { - myDocsCount += docs.length; - }; - const count = await loadData( - './src/legacy/server/sample_data/data_sets/ecommerce/ecommerce.json.gz', - bulkInsertMock - ); - expect(myDocsCount).toBe(4675); - expect(count).toBe(4675); -}); diff --git a/src/legacy/server/sample_data/routes/lib/translate_timestamp.test.js b/src/legacy/server/sample_data/routes/lib/translate_timestamp.test.js deleted file mode 100644 index e03d4870ad064..0000000000000 --- a/src/legacy/server/sample_data/routes/lib/translate_timestamp.test.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { translateTimeRelativeToWeek } from './translate_timestamp'; - -describe('translateTimeRelativeToWeek', () => { - const sourceReference = '2018-01-02T00:00:00'; //Tuesday - const targetReference = '2018-04-25T18:24:58.650'; // Wednesday - - describe('2 weeks before', () => { - test('should properly adjust timestamp when day is before targetReference day of week', () => { - const source = '2017-12-18T23:50:00'; // Monday, -2 week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-09T23:50:00'); // Monday 2 week before targetReference week - }); - - test('should properly adjust timestamp when day is same as targetReference day of week', () => { - const source = '2017-12-20T23:50:00'; // Wednesday, -2 week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-11T23:50:00'); // Wednesday 2 week before targetReference week - }); - - test('should properly adjust timestamp when day is after targetReference day of week', () => { - const source = '2017-12-22T16:16:50'; // Friday, -2 week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-13T16:16:50'); // Friday 2 week before targetReference week - }); - }); - - describe('week before', () => { - test('should properly adjust timestamp when day is before targetReference day of week', () => { - const source = '2017-12-25T23:50:00'; // Monday, -1 week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-16T23:50:00'); // Monday 1 week before targetReference week - }); - - test('should properly adjust timestamp when day is same as targetReference day of week', () => { - const source = '2017-12-27T23:50:00'; // Wednesday, -1 week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-18T23:50:00'); // Wednesday 1 week before targetReference week - }); - - test('should properly adjust timestamp when day is after targetReference day of week', () => { - const source = '2017-12-29T16:16:50'; // Friday, -1 week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-20T16:16:50'); // Friday 1 week before targetReference week - }); - }); - - describe('same week', () => { - test('should properly adjust timestamp when day is before targetReference day of week', () => { - const source = '2018-01-01T23:50:00'; // Monday, same week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-23T23:50:00'); // Monday same week as targetReference - }); - - test('should properly adjust timestamp when day is same as targetReference day of week', () => { - const source = '2018-01-03T23:50:00'; // Wednesday, same week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-25T23:50:00'); // Wednesday same week as targetReference - }); - - test('should properly adjust timestamp when day is after targetReference day of week', () => { - const source = '2018-01-05T16:16:50'; // Friday, same week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-27T16:16:50'); // Friday same week as targetReference - }); - }); - - describe('week after', () => { - test('should properly adjust timestamp when day is before targetReference day of week', () => { - const source = '2018-01-08T23:50:00'; // Monday, 1 week after relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-04-30T23:50:00'); // Monday 1 week after targetReference week - }); - - test('should properly adjust timestamp when day is same as targetReference day of week', () => { - const source = '2018-01-10T23:50:00'; // Wednesday, same week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-05-02T23:50:00'); // Wednesday 1 week after targetReference week - }); - - test('should properly adjust timestamp when day is after targetReference day of week', () => { - const source = '2018-01-12T16:16:50'; // Friday, same week relative to sourceReference - const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); - expect(timestamp).toBe('2018-05-04T16:16:50'); // Friday 1 week after targetReference week - }); - }); -}); diff --git a/src/legacy/server/sample_data/routes/list.js b/src/legacy/server/sample_data/routes/list.js deleted file mode 100644 index 65370e6da9fb1..0000000000000 --- a/src/legacy/server/sample_data/routes/list.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { createIndexName } from './lib/create_index_name'; - -const NOT_INSTALLED = 'not_installed'; -const INSTALLED = 'installed'; -const UNKNOWN = 'unknown'; - -export const createListRoute = () => ({ - path: '/api/sample_data', - method: 'GET', - config: { - handler: async request => { - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); - - const sampleDatasets = request.server.getSampleDatasets().map(sampleDataset => { - return { - id: sampleDataset.id, - name: sampleDataset.name, - description: sampleDataset.description, - previewImagePath: sampleDataset.previewImagePath, - darkPreviewImagePath: sampleDataset.darkPreviewImagePath, - overviewDashboard: sampleDataset.overviewDashboard, - appLinks: sampleDataset.appLinks, - defaultIndex: sampleDataset.defaultIndex, - dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })), - }; - }); - - const isInstalledPromises = sampleDatasets.map(async sampleDataset => { - for (let i = 0; i < sampleDataset.dataIndices.length; i++) { - const dataIndexConfig = sampleDataset.dataIndices[i]; - const index = createIndexName(sampleDataset.id, dataIndexConfig.id); - try { - const indexExists = await callWithRequest(request, 'indices.exists', { index: index }); - if (!indexExists) { - sampleDataset.status = NOT_INSTALLED; - return; - } - - const { count } = await callWithRequest(request, 'count', { index: index }); - if (count === 0) { - sampleDataset.status = NOT_INSTALLED; - return; - } - } catch (err) { - sampleDataset.status = UNKNOWN; - sampleDataset.statusMsg = err.message; - return; - } - } - - try { - await request.getSavedObjectsClient().get('dashboard', sampleDataset.overviewDashboard); - } catch (err) { - // savedObjectClient.get() throws an boom error when object is not found. - if (_.get(err, 'output.statusCode') === 404) { - sampleDataset.status = NOT_INSTALLED; - return; - } - - sampleDataset.status = UNKNOWN; - sampleDataset.statusMsg = err.message; - return; - } - - sampleDataset.status = INSTALLED; - }); - - await Promise.all(isInstalledPromises); - return sampleDatasets; - }, - }, -}); diff --git a/src/legacy/server/sample_data/routes/uninstall.js b/src/legacy/server/sample_data/routes/uninstall.js deleted file mode 100644 index 6177c0379cd68..0000000000000 --- a/src/legacy/server/sample_data/routes/uninstall.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import Joi from 'joi'; -import { usage } from '../usage'; -import { createIndexName } from './lib/create_index_name'; - -export const createUninstallRoute = () => ({ - path: '/api/sample_data/{id}', - method: 'DELETE', - config: { - validate: { - params: Joi.object() - .keys({ - id: Joi.string().required(), - }) - .required(), - }, - handler: async (request, h) => { - const { server, params } = request; - const sampleDataset = server.getSampleDatasets().find(({ id }) => id === params.id); - - if (!sampleDataset) { - return h.response().code(404); - } - - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - - for (let i = 0; i < sampleDataset.dataIndices.length; i++) { - const dataIndexConfig = sampleDataset.dataIndices[i]; - const index = createIndexName(sampleDataset.id, dataIndexConfig.id); - - try { - await callWithRequest(request, 'indices.delete', { index: index }); - } catch (err) { - return h - .response(`Unable to delete sample data index "${index}", error: ${err.message}`) - .code(err.status); - } - } - - const deletePromises = sampleDataset.savedObjects.map(({ type, id }) => - request.getSavedObjectsClient().delete(type, id) - ); - - try { - await Promise.all(deletePromises); - } catch (err) { - // ignore 404s since users could have deleted some of the saved objects via the UI - if (_.get(err, 'output.statusCode') !== 404) { - return h - .response(`Unable to delete sample dataset saved objects, error: ${err.message}`) - .code(403); - } - } - - // track the usage operation in a non-blocking way - usage(request).addUninstall(params.id); - - return {}; - }, - }, -}); diff --git a/src/legacy/server/sample_data/sample_data_mixin.js b/src/legacy/server/sample_data/sample_data_mixin.js deleted file mode 100644 index 338d6fd8cb5da..0000000000000 --- a/src/legacy/server/sample_data/sample_data_mixin.js +++ /dev/null @@ -1,146 +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 Joi from 'joi'; -import { sampleDataSchema } from './data_set_schema'; -import { createListRoute, createInstallRoute, createUninstallRoute } from './routes'; -import { flightsSpecProvider, logsSpecProvider, ecommerceSpecProvider } from './data_sets'; -import { makeSampleDataUsageCollector } from './usage'; - -export function sampleDataMixin(kbnServer, server) { - server.route(createListRoute()); - server.route(createInstallRoute()); - server.route(createUninstallRoute()); - - const sampleDatasets = []; - - server.decorate('server', 'getSampleDatasets', () => { - return sampleDatasets; - }); - - server.decorate('server', 'registerSampleDataset', specProvider => { - const { error, value } = Joi.validate(specProvider(server), sampleDataSchema); - - if (error) { - throw new Error(`Unable to register sample dataset spec because it's invalid. ${error}`); - } - - const defaultIndexSavedObjectJson = value.savedObjects.find(savedObjectJson => { - return savedObjectJson.type === 'index-pattern' && savedObjectJson.id === value.defaultIndex; - }); - if (!defaultIndexSavedObjectJson) { - throw new Error( - `Unable to register sample dataset spec, defaultIndex: "${value.defaultIndex}" does not exist in savedObjects list.` - ); - } - - const dashboardSavedObjectJson = value.savedObjects.find(savedObjectJson => { - return savedObjectJson.type === 'dashboard' && savedObjectJson.id === value.overviewDashboard; - }); - if (!dashboardSavedObjectJson) { - throw new Error( - `Unable to register sample dataset spec, overviewDashboard: "${value.overviewDashboard}" does not exist in savedObjects list.` - ); - } - - sampleDatasets.push(value); - }); - - server.decorate('server', 'addSavedObjectsToSampleDataset', (id, savedObjects) => { - const sampleDataset = sampleDatasets.find(sampleDataset => { - return sampleDataset.id === id; - }); - - if (!sampleDataset) { - throw new Error(`Unable to find sample dataset with id: ${id}`); - } - - sampleDataset.savedObjects = sampleDataset.savedObjects.concat(savedObjects); - }); - - server.decorate('server', 'addAppLinksToSampleDataset', (id, appLinks) => { - const sampleDataset = sampleDatasets.find(sampleDataset => { - return sampleDataset.id === id; - }); - - if (!sampleDataset) { - throw new Error(`Unable to find sample dataset with id: ${id}`); - } - - sampleDataset.appLinks = sampleDataset.appLinks.concat(appLinks); - }); - - server.decorate( - 'server', - 'replacePanelInSampleDatasetDashboard', - ({ - sampleDataId, - dashboardId, - oldEmbeddableId, - embeddableId, - embeddableType, - embeddableConfig = {}, - }) => { - const sampleDataset = sampleDatasets.find(sampleDataset => { - return sampleDataset.id === sampleDataId; - }); - if (!sampleDataset) { - throw new Error(`Unable to find sample dataset with id: ${sampleDataId}`); - } - - const dashboard = sampleDataset.savedObjects.find(savedObject => { - return savedObject.id === dashboardId && savedObject.type === 'dashboard'; - }); - if (!dashboard) { - throw new Error(`Unable to find dashboard with id: ${dashboardId}`); - } - - try { - const reference = dashboard.references.find(reference => { - return reference.id === oldEmbeddableId; - }); - if (!reference) { - throw new Error(`Unable to find reference for embeddable: ${oldEmbeddableId}`); - } - reference.type = embeddableType; - reference.id = embeddableId; - - const panels = JSON.parse(dashboard.attributes.panelsJSON); - const panel = panels.find(panel => { - return panel.panelRefName === reference.name; - }); - if (!panel) { - throw new Error(`Unable to find panel for reference: ${reference.name}`); - } - panel.embeddableConfig = embeddableConfig; - dashboard.attributes.panelsJSON = JSON.stringify(panels); - } catch (error) { - throw new Error( - `Unable to replace panel with embeddable ${oldEmbeddableId}, error: ${error}` - ); - } - } - ); - - server.registerSampleDataset(flightsSpecProvider); - server.registerSampleDataset(logsSpecProvider); - server.registerSampleDataset(ecommerceSpecProvider); - - makeSampleDataUsageCollector(server); -} diff --git a/src/legacy/ui/public/agg_types/buckets/terms.ts b/src/legacy/ui/public/agg_types/buckets/terms.ts index e38f7ca4cc038..ef9ceb96b005d 100644 --- a/src/legacy/ui/public/agg_types/buckets/terms.ts +++ b/src/legacy/ui/public/agg_types/buckets/terms.ts @@ -17,7 +17,6 @@ * under the License. */ -import chrome from 'ui/chrome'; import { noop } from 'lodash'; import { i18n } from '@kbn/i18n'; import { SearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../../courier'; @@ -80,14 +79,8 @@ export const termsBucketAgg = new BucketAggType({ if (val === '__missing__') { return bucket.params.missingBucketLabel; } - const parsedUrl = { - origin: window.location.origin, - pathname: window.location.pathname, - basePath: chrome.getBasePath(), - }; - const converter = bucket.params.field.format.getConverterFor(type); - return converter(val, undefined, undefined, parsedUrl); + return bucket.params.field.format.convert(val, type); }; }, } as FieldFormat; diff --git a/src/legacy/ui/public/chrome/api/loading_count.js b/src/legacy/ui/public/chrome/api/loading_count.js index cafabbc5f7529..71d1e354773d5 100644 --- a/src/legacy/ui/public/chrome/api/loading_count.js +++ b/src/legacy/ui/public/chrome/api/loading_count.js @@ -24,7 +24,7 @@ const newPlatformHttp = npSetup.core.http; export function initLoadingCountApi(chrome) { const manualCount$ = new Rx.BehaviorSubject(0); - newPlatformHttp.addLoadingCount(manualCount$); + newPlatformHttp.addLoadingCountSource(manualCount$); chrome.loadingCount = new (class ChromeLoadingCountApi { /** diff --git a/src/legacy/ui/public/courier/search_source/types.ts b/src/legacy/ui/public/courier/search_source/types.ts index 293f3d49596c3..8fd6d8cfa5fa0 100644 --- a/src/legacy/ui/public/courier/search_source/types.ts +++ b/src/legacy/ui/public/courier/search_source/types.ts @@ -17,8 +17,7 @@ * under the License. */ import { NameList } from 'elasticsearch'; -import { esFilters, Query } from '../../../../../plugins/data/public'; -import { IndexPattern } from '../../../../core_plugins/data/public/index_patterns'; +import { esFilters, Query, IndexPattern } from '../../../../../plugins/data/public'; export type EsQuerySearchAfter = [string | number, string | number]; @@ -47,6 +46,8 @@ export interface SearchSourceFields { fields?: NameList; index?: IndexPattern; searchAfter?: EsQuerySearchAfter; + timeout?: string; + terminate_after?: number; } export interface SearchSourceOptions { diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 6e9f5c85aa1b2..7002b1c365eff 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -173,7 +173,7 @@ const capture$httpLoadingCount = (newPlatform: CoreStart) => ( $rootScope: IRootScopeService, $http: IHttpService ) => { - newPlatform.http.addLoadingCount( + newPlatform.http.addLoadingCountSource( new Rx.Observable(observer => { const unwatch = $rootScope.$watch(() => { const reqs = $http.pendingRequests || []; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 3b2a77cefb730..3d4292cef27f4 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -45,11 +45,18 @@ export const mockUiSettings = { 'format:defaultTypeMap': {}, }; -export const npSetup = { - core: { - chrome: {}, - uiSettings: mockUiSettings, +const mockCore = { + chrome: {}, + uiSettings: mockUiSettings, + http: { + basePath: { + get: sinon.fake.returns(''), + }, }, +}; + +export const npSetup = { + core: mockCore, plugins: { usageCollection: { allowTrackUserAgent: sinon.fake(), @@ -95,7 +102,7 @@ export const npSetup = { getSavedQueryCount: sinon.fake(), }, }, - fieldFormats: getFieldFormatsRegistry(mockUiSettings), + fieldFormats: getFieldFormatsRegistry(mockCore), }, share: { register: () => {}, @@ -224,7 +231,7 @@ export const npStart = { history: sinon.fake(), }, }, - fieldFormats: getFieldFormatsRegistry(mockUiSettings), + fieldFormats: getFieldFormatsRegistry(mockCore), }, share: { toggleShareContextMenu: () => {}, diff --git a/src/legacy/ui/public/registry/doc_views_helpers.tsx b/src/legacy/ui/public/registry/doc_views_helpers.tsx index d9e42e71dfff1..61545609f205d 100644 --- a/src/legacy/ui/public/registry/doc_views_helpers.tsx +++ b/src/legacy/ui/public/registry/doc_views_helpers.tsx @@ -26,7 +26,7 @@ import { AngularController, AngularDirective, } from './doc_views_types'; -import { DocViewerError } from '../../../core_plugins/kibana/public/discover/components/doc_viewer/doc_viewer_render_error'; +import { DocViewerError } from '../../../core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_error'; /** * Compiles and injects the give angular template into the given dom node diff --git a/src/legacy/ui/public/saved_objects/__tests__/saved_object.js b/src/legacy/ui/public/saved_objects/__tests__/saved_object.js index 30fb0c0c38aef..d9622ac3dc6d2 100644 --- a/src/legacy/ui/public/saved_objects/__tests__/saved_object.js +++ b/src/legacy/ui/public/saved_objects/__tests__/saved_object.js @@ -26,7 +26,7 @@ import { createSavedObjectClass } from '../saved_object'; import StubIndexPattern from 'test_utils/stub_index_pattern'; import { npStart } from 'ui/new_platform'; import { InvalidJSONProperty } from '../../../../../plugins/kibana_utils/public'; -import { mockUiSettings } from '../../new_platform/new_platform.karma_mock'; +import { npSetup } from '../../new_platform/new_platform.karma_mock'; const getConfig = cfg => cfg; @@ -310,7 +310,7 @@ describe('Saved Object', function() { getConfig, null, [], - mockUiSettings + npSetup.core ); indexPattern.title = indexPattern.id; savedObject.searchSource.setField('index', indexPattern); @@ -700,7 +700,7 @@ describe('Saved Object', function() { getConfig, null, [], - mockUiSettings + npSetup.core ); indexPattern.title = indexPattern.id; savedObject.searchSource.setField('index', indexPattern); diff --git a/src/legacy/ui/public/styles/_legacy/components/_sidebar.scss b/src/legacy/ui/public/styles/_legacy/components/_sidebar.scss index 571064a1f29c4..d44129b6ec849 100644 --- a/src/legacy/ui/public/styles/_legacy/components/_sidebar.scss +++ b/src/legacy/ui/public/styles/_legacy/components/_sidebar.scss @@ -112,8 +112,16 @@ align-self: center; } } +} - .index-pattern-selection:not(.euiComboBox) { - padding: $euiSizeS 0; - } +.indexPattern__container { + display: flex; + align-items: center; + height: $euiSize * 3; + margin-top: -$euiSizeS; +} + +.indexPattern__triggerButton { + @include euiTitle('xs'); + line-height: $euiSizeXXL; } diff --git a/src/legacy/ui/public/styles/_legacy/components/_table.scss b/src/legacy/ui/public/styles/_legacy/components/_table.scss index e7c1bda829f0e..c9472cbd2faa7 100644 --- a/src/legacy/ui/public/styles/_legacy/components/_table.scss +++ b/src/legacy/ui/public/styles/_legacy/components/_table.scss @@ -1,4 +1,4 @@ -@import '../../../../../core_plugins/kibana/public/discover/mixins'; +@import '../../../../../core_plugins/kibana/public/discover/np_ready/mixins'; .table { // Nesting diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index d754c1d395595..0b99810a85afe 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -23,7 +23,7 @@ import { AggConfig, Vis } from 'ui/vis'; import { npStart } from 'ui/new_platform'; import { SerializedFieldFormat } from 'src/plugins/expressions/public'; -import { IFieldFormatId, FieldFormat } from '../../../../../../plugins/data/public'; +import { IFieldFormatId, FieldFormat, ContentType } from '../../../../../../plugins/data/public'; import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; import { DateRangeKey, convertDateRangeToString } from '../../../agg_types/buckets/date_range'; @@ -129,42 +129,23 @@ export const getFormat: FormatFactory = mapping => { }); return new IpRangeFormat(); } else if (isTermsFieldFormat(mapping) && mapping.params) { - const params = mapping.params; + const { params } = mapping; + const convert = (val: string, type: ContentType) => { + const format = getFieldFormat(params.id, mapping.params); + + if (val === '__other__') { + return params.otherBucketLabel; + } + if (val === '__missing__') { + return params.missingBucketLabel; + } + + return format.convert(val, type); + }; + return { - getConverterFor: (type: string) => { - const format = getFieldFormat(params.id, mapping.params); - return (val: string) => { - if (val === '__other__') { - return params.otherBucketLabel; - } - if (val === '__missing__') { - return params.missingBucketLabel; - } - const parsedUrl = { - origin: window.location.origin, - pathname: window.location.pathname, - basePath: npStart.core.http.basePath, - }; - // @ts-ignore - return format.convert(val, undefined, undefined, parsedUrl); - }; - }, - convert: (val: string, type: string) => { - const format = getFieldFormat(params.id, mapping.params); - if (val === '__other__') { - return params.otherBucketLabel; - } - if (val === '__missing__') { - return params.missingBucketLabel; - } - const parsedUrl = { - origin: window.location.origin, - pathname: window.location.pathname, - basePath: npStart.core.http.basePath, - }; - // @ts-ignore - return format.convert(val, type, undefined, parsedUrl); - }, + convert, + getConverterFor: (type: ContentType) => (val: string) => convert(val, type), } as FieldFormat; } else { return getFieldFormat(id, mapping.params); diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts new file mode 100644 index 0000000000000..00786a0c72cf1 --- /dev/null +++ b/src/plugins/data/common/constants.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const DEFAULT_QUERY_LANGUAGE = 'kuery'; diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts index 006e0623be913..2c964097e7c0f 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts @@ -42,7 +42,11 @@ export const getPhrasesFilterField = (filter: PhrasesFilter) => { // Creates a filter where the given field matches one or more of the given values // params should be an array of values -export const buildPhrasesFilter = (field: IFieldType, params: any, indexPattern: IIndexPattern) => { +export const buildPhrasesFilter = ( + field: IFieldType, + params: any[], + indexPattern: IIndexPattern +) => { const index = indexPattern.id; const type = FILTERS.PHRASES; const key = field.name; diff --git a/src/plugins/data/common/field_formats/content_types/html_content_type.ts b/src/plugins/data/common/field_formats/content_types/html_content_type.ts index ba2236c41790f..1b6ee9e63fad1 100644 --- a/src/plugins/data/common/field_formats/content_types/html_content_type.ts +++ b/src/plugins/data/common/field_formats/content_types/html_content_type.ts @@ -26,7 +26,8 @@ const getConvertFn = ( format: IFieldFormat, convert?: HtmlContextTypeConvert ): HtmlContextTypeConvert => { - const fallbackHtml: HtmlContextTypeConvert = (value, field, hit) => { + const fallbackHtml: HtmlContextTypeConvert = (value, options = {}) => { + const { field, hit } = options; const formatted = escape(format.convert(value, 'text')); return !field || !hit || !hit.highlight || !hit.highlight[field.name] @@ -43,27 +44,23 @@ export const setup = ( ): HtmlContextTypeConvert => { const convert = getConvertFn(format, htmlContextTypeConvert); - const recurse: HtmlContextTypeConvert = (value, field, hit, meta) => { + const recurse: HtmlContextTypeConvert = (value, options = {}) => { if (value == null) { return asPrettyString(value); } if (!value || !isFunction(value.map)) { - return convert.call(format, value, field, hit, meta); + return convert.call(format, value, options); } - const subValues = value.map((v: any) => { - return recurse(v, field, hit, meta); - }); - const useMultiLine = subValues.some((sub: string) => { - return sub.indexOf('\n') > -1; - }); + const subValues = value.map((v: any) => recurse(v, options)); + const useMultiLine = subValues.some((sub: string) => sub.indexOf('\n') > -1); return subValues.join(',' + (useMultiLine ? '\n' : ' ')); }; - const wrap: HtmlContextTypeConvert = (value, field, hit, meta) => { - return `${recurse(value, field, hit, meta)}`; + const wrap: HtmlContextTypeConvert = (value, options) => { + return `${recurse(value, options)}`; }; return wrap; diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts index c9906fb136052..702e1579e945f 100644 --- a/src/plugins/data/common/field_formats/converters/source.ts +++ b/src/plugins/data/common/field_formats/converters/source.ts @@ -42,7 +42,9 @@ export class SourceFormat extends FieldFormat { textConvert: TextContextTypeConvert = value => JSON.stringify(value); - htmlConvert: HtmlContextTypeConvert = (value, field, hit) => { + htmlConvert: HtmlContextTypeConvert = (value, options = {}) => { + const { field, hit } = options; + if (!field) { const converter = this.getConverterFor('text') as Function; diff --git a/src/plugins/data/common/field_formats/converters/url.test.ts b/src/plugins/data/common/field_formats/converters/url.test.ts index 66307cefe08f7..b1107d46179bf 100644 --- a/src/plugins/data/common/field_formats/converters/url.test.ts +++ b/src/plugins/data/common/field_formats/converters/url.test.ts @@ -169,64 +169,64 @@ describe('UrlFormat', () => { describe('whitelist', () => { test('should assume a relative url if the value is not in the whitelist without a base path', () => { - const url = new UrlFormat({}); const parsedUrl = { origin: 'http://kibana', basePath: '', }; + const url = new UrlFormat({ parsedUrl }); const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function; - expect(converter('www.elastic.co', null, null, parsedUrl)).toBe( + expect(converter('www.elastic.co')).toBe( 'www.elastic.co' ); - expect(converter('elastic.co', null, null, parsedUrl)).toBe( + expect(converter('elastic.co')).toBe( 'elastic.co' ); - expect(converter('elastic', null, null, parsedUrl)).toBe( + expect(converter('elastic')).toBe( 'elastic' ); - expect(converter('ftp://elastic.co', null, null, parsedUrl)).toBe( + expect(converter('ftp://elastic.co')).toBe( 'ftp://elastic.co' ); }); test('should assume a relative url if the value is not in the whitelist with a basepath', () => { - const url = new UrlFormat({}); const parsedUrl = { origin: 'http://kibana', basePath: '/xyz', }; + const url = new UrlFormat({ parsedUrl }); const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function; - expect(converter('www.elastic.co', null, null, parsedUrl)).toBe( + expect(converter('www.elastic.co')).toBe( 'www.elastic.co' ); - expect(converter('elastic.co', null, null, parsedUrl)).toBe( + expect(converter('elastic.co')).toBe( 'elastic.co' ); - expect(converter('elastic', null, null, parsedUrl)).toBe( + expect(converter('elastic')).toBe( 'elastic' ); - expect(converter('ftp://elastic.co', null, null, parsedUrl)).toBe( + expect(converter('ftp://elastic.co')).toBe( 'ftp://elastic.co' ); }); test('should rely on parsedUrl', () => { - const url = new UrlFormat({}); const parsedUrl = { origin: 'http://kibana.host.com', basePath: '/abc', }; + const url = new UrlFormat({ parsedUrl }); const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function; - expect(converter('../app/kibana', null, null, parsedUrl)).toBe( + expect(converter('../app/kibana')).toBe( '../app/kibana' ); }); @@ -244,54 +244,52 @@ describe('UrlFormat', () => { }); test('should support multiple types of relative urls', () => { - const url = new UrlFormat({}); const parsedUrl = { origin: 'http://kibana.host.com', pathname: '/nbc/app/kibana#/discover', basePath: '/nbc', }; + const url = new UrlFormat({ parsedUrl }); const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function; - expect(converter('#/foo', null, null, parsedUrl)).toBe( + expect(converter('#/foo')).toBe( '#/foo' ); - expect(converter('/nbc/app/kibana#/discover', null, null, parsedUrl)).toBe( + expect(converter('/nbc/app/kibana#/discover')).toBe( '/nbc/app/kibana#/discover' ); - expect(converter('../foo/bar', null, null, parsedUrl)).toBe( + expect(converter('../foo/bar')).toBe( '../foo/bar' ); }); test('should support multiple types of urls w/o basePath', () => { - const url = new UrlFormat({}); const parsedUrl = { origin: 'http://kibana.host.com', pathname: '/app/kibana', }; + const url = new UrlFormat({ parsedUrl }); const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function; - expect(converter('10.22.55.66', null, null, parsedUrl)).toBe( + expect(converter('10.22.55.66')).toBe( '10.22.55.66' ); - expect( - converter('http://www.domain.name/app/kibana#/dashboard/', null, null, parsedUrl) - ).toBe( + expect(converter('http://www.domain.name/app/kibana#/dashboard/')).toBe( 'http://www.domain.name/app/kibana#/dashboard/' ); - expect(converter('/app/kibana', null, null, parsedUrl)).toBe( + expect(converter('/app/kibana')).toBe( '/app/kibana' ); - expect(converter('kibana#/dashboard/', null, null, parsedUrl)).toBe( + expect(converter('kibana#/dashboard/')).toBe( 'kibana#/dashboard/' ); - expect(converter('#/dashboard/', null, null, parsedUrl)).toBe( + expect(converter('#/dashboard/')).toBe( '#/dashboard/' ); }); diff --git a/src/plugins/data/common/field_formats/converters/url.ts b/src/plugins/data/common/field_formats/converters/url.ts index bd68dedf38a67..3c88511a4c63e 100644 --- a/src/plugins/data/common/field_formats/converters/url.ts +++ b/src/plugins/data/common/field_formats/converters/url.ts @@ -134,7 +134,11 @@ export class UrlFormat extends FieldFormat { textConvert: TextContextTypeConvert = value => this.formatLabel(value); - htmlConvert: HtmlContextTypeConvert = (rawValue, field, hit, parsedUrl) => { + htmlConvert: HtmlContextTypeConvert = (rawValue, options = {}) => { + const { field, hit } = options; + const { parsedUrl } = this._params; + const { basePath, pathname, origin } = parsedUrl || {}; + const url = escape(this.formatUrl(rawValue)); const label = escape(this.formatLabel(rawValue, url)); @@ -170,17 +174,17 @@ export class UrlFormat extends FieldFormat { if (!inWhitelist) { // Handles urls like: `#/discover` if (url[0] === '#') { - prefix = `${parsedUrl.origin}${parsedUrl.pathname}`; + prefix = `${origin}${pathname}`; } // Handle urls like: `/app/kibana` or `/xyz/app/kibana` - else if (url.indexOf(parsedUrl.basePath || '/') === 0) { - prefix = `${parsedUrl.origin}`; + else if (url.indexOf(basePath || '/') === 0) { + prefix = `${origin}`; } // Handle urls like: `../app/kibana` else { const prefixEnd = url[0] === '/' ? '' : '/'; - prefix = `${parsedUrl.origin}${parsedUrl.basePath || ''}/app${prefixEnd}`; + prefix = `${origin}${basePath || ''}/app${prefixEnd}`; } } diff --git a/src/plugins/data/common/field_formats/field_format.ts b/src/plugins/data/common/field_formats/field_format.ts index 85d276767b5a7..50f07846a3ceb 100644 --- a/src/plugins/data/common/field_formats/field_format.ts +++ b/src/plugins/data/common/field_formats/field_format.ts @@ -24,6 +24,8 @@ import { FIELD_FORMAT_IDS, FieldFormatConvert, FieldFormatConvertFunction, + HtmlContextTypeOptions, + TextContextTypeOptions, } from './types'; import { htmlContentTypeSetup, @@ -63,8 +65,20 @@ export abstract class FieldFormat { */ convertObject: FieldFormatConvert | undefined; + /** + * @property {htmlConvert} + * @protected + * have to remove the protected because of + * https://github.com/Microsoft/TypeScript/issues/17293 + */ htmlConvert: HtmlContextTypeConvert | undefined; + /** + * @property {textConvert} + * @protected + * have to remove the protected because of + * https://github.com/Microsoft/TypeScript/issues/17293 + */ textConvert: TextContextTypeConvert | undefined; /** @@ -76,7 +90,7 @@ export abstract class FieldFormat { protected readonly _params: any; protected getConfig: Function | undefined; - constructor(_params: any = {}, getConfig?: Function) { + constructor(_params: Record = {}, getConfig?: Function) { this._params = _params; if (getConfig) { @@ -94,11 +108,15 @@ export abstract class FieldFormat { * injecting into the DOM or a DOM attribute * @public */ - convert(value: any, contentType: ContentType = DEFAULT_CONTEXT_TYPE): string { + convert( + value: any, + contentType: ContentType = DEFAULT_CONTEXT_TYPE, + options?: HtmlContextTypeOptions | TextContextTypeOptions + ): string { const converter = this.getConverterFor(contentType); if (converter) { - return converter.call(this, value); + return converter.call(this, value, options); } return value; diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index fc8e6e20a1a96..dce3c66b0f886 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -24,15 +24,19 @@ export type ContentType = 'html' | 'text'; export { IFieldFormat } from './field_format'; /** @internal **/ -export type HtmlContextTypeConvert = ( - value: any, - field?: any, - hit?: Record, - meta?: any -) => string; +export interface HtmlContextTypeOptions { + field?: any; + hit?: Record; +} + +/** @internal **/ +export type HtmlContextTypeConvert = (value: any, options?: HtmlContextTypeOptions) => string; + +/** @internal **/ +export type TextContextTypeOptions = Record; /** @internal **/ -export type TextContextTypeConvert = (value: any) => string; +export type TextContextTypeConvert = (value: any, options?: TextContextTypeOptions) => string; /** @internal **/ export type FieldFormatConvertFunction = HtmlContextTypeConvert | TextContextTypeConvert; diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index b334342a57ec6..e02045de24e8f 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -24,3 +24,4 @@ export * from './index_patterns'; export * from './es_query'; export * from './utils'; export * from './types'; +export * from './constants'; diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index 998eaa55858d2..6553ce8ce4d91 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -3,5 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["uiActions"] + "requiredPlugins": ["uiActions"], + "optionalPlugins": ["usageCollection"] } diff --git a/src/plugins/data/public/field_formats_provider/field_formats.test.ts b/src/plugins/data/public/field_formats_provider/field_formats.test.ts new file mode 100644 index 0000000000000..e58435fc71418 --- /dev/null +++ b/src/plugins/data/public/field_formats_provider/field_formats.test.ts @@ -0,0 +1,136 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CoreSetup, IUiSettingsClient } from 'kibana/public'; + +import { FieldFormatRegisty } from './field_formats'; +import { + IFieldFormatType, + PercentFormat, + BoolFormat, + StringFormat, +} from '../../common/field_formats'; +import { coreMock } from '../../../../core/public/mocks'; + +const getValueOfPrivateField = (instance: any, field: string) => instance[field]; +const getUiSettingsMock = (data: any): IUiSettingsClient['get'] => () => data; + +describe('FieldFormatRegisty', () => { + let mockCoreSetup: CoreSetup; + let fieldFormatRegisty: FieldFormatRegisty; + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + fieldFormatRegisty = new FieldFormatRegisty(); + }); + + test('should allows to create an instance of "FieldFormatRegisty"', () => { + expect(fieldFormatRegisty).toBeDefined(); + expect(getValueOfPrivateField(fieldFormatRegisty, 'fieldFormats')).toBeDefined(); + expect(getValueOfPrivateField(fieldFormatRegisty, 'defaultMap')).toEqual({}); + }); + + describe('init', () => { + test('should provide an public "init" method', () => { + expect(fieldFormatRegisty.init).toBeDefined(); + expect(typeof fieldFormatRegisty.init).toBe('function'); + }); + + test('should set basePath value from "init" method', () => { + fieldFormatRegisty.init(mockCoreSetup); + + expect(getValueOfPrivateField(fieldFormatRegisty, 'basePath')).toBe( + mockCoreSetup.http.basePath.get() + ); + }); + + test('should populate the "defaultMap" object', () => { + const defaultMap = { + number: { id: 'number', params: {} }, + }; + + mockCoreSetup.uiSettings.get = getUiSettingsMock(defaultMap); + fieldFormatRegisty.init(mockCoreSetup); + expect(getValueOfPrivateField(fieldFormatRegisty, 'defaultMap')).toEqual(defaultMap); + }); + }); + + describe('register', () => { + test('should provide an public "register" method', () => { + expect(fieldFormatRegisty.register).toBeDefined(); + expect(typeof fieldFormatRegisty.register).toBe('function'); + }); + + test('should register field formats', () => { + fieldFormatRegisty.register([StringFormat, BoolFormat]); + + const registeredFieldFormatters: Map = getValueOfPrivateField( + fieldFormatRegisty, + 'fieldFormats' + ); + + expect(registeredFieldFormatters.size).toBe(2); + + expect(registeredFieldFormatters.get(BoolFormat.id)).toBe(BoolFormat); + expect(registeredFieldFormatters.get(StringFormat.id)).toBe(StringFormat); + expect(registeredFieldFormatters.get(PercentFormat.id)).toBeUndefined(); + }); + }); + + describe('getType', () => { + test('should provide an public "getType" method', () => { + expect(fieldFormatRegisty.getType).toBeDefined(); + expect(typeof fieldFormatRegisty.getType).toBe('function'); + }); + + test('should return the registered type of the field format by identifier', () => { + fieldFormatRegisty.register([StringFormat]); + + expect(fieldFormatRegisty.getType(StringFormat.id)).toBeDefined(); + }); + + test('should return void if the field format type has not been registered', () => { + fieldFormatRegisty.register([BoolFormat]); + + expect(fieldFormatRegisty.getType(StringFormat.id)).toBeUndefined(); + }); + }); + + describe('fieldFormatMetaParamsDecorator', () => { + test('should set meta params for all instances of FieldFormats', () => { + fieldFormatRegisty.register([StringFormat]); + + const ConcreteFormat = fieldFormatRegisty.getType(StringFormat.id); + + expect(ConcreteFormat).toBeDefined(); + + if (ConcreteFormat) { + const stringFormat = new ConcreteFormat({ + foo: 'foo', + }); + const params = getValueOfPrivateField(stringFormat, '_params'); + + expect(params).toHaveProperty('foo'); + expect(params).toHaveProperty('parsedUrl'); + expect(params.parsedUrl).toHaveProperty('origin'); + expect(params.parsedUrl).toHaveProperty('pathname'); + expect(params.parsedUrl).toHaveProperty('basePath'); + } + }); + }); +}); diff --git a/src/plugins/data/public/field_formats_provider/field_formats.ts b/src/plugins/data/public/field_formats_provider/field_formats.ts index 20e90b8e4a545..3d60965f2e532 100644 --- a/src/plugins/data/public/field_formats_provider/field_formats.ts +++ b/src/plugins/data/public/field_formats_provider/field_formats.ts @@ -18,7 +18,7 @@ */ import { forOwn, isFunction, memoize } from 'lodash'; -import { IUiSettingsClient } from 'kibana/public'; +import { IUiSettingsClient, CoreSetup } from 'kibana/public'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES, @@ -33,6 +33,7 @@ export class FieldFormatRegisty { private fieldFormats: Map; private uiSettings!: IUiSettingsClient; private defaultMap: Record; + private basePath?: string; constructor() { this.fieldFormats = new Map(); @@ -41,8 +42,9 @@ export class FieldFormatRegisty { getConfig = (key: string, override?: any) => this.uiSettings.get(key, override); - init(uiSettings: IUiSettingsClient) { + init({ uiSettings, http }: CoreSetup) { this.uiSettings = uiSettings; + this.basePath = http.basePath.get(); this.parseDefaultTypeMap(this.uiSettings.get('format:defaultTypeMap')); @@ -73,10 +75,14 @@ export class FieldFormatRegisty { * Get a derived FieldFormat class by its id. * * @param {IFieldFormatId} formatId - the format id - * @return {FieldFormat} + * @return {FieldFormat | void} */ - getType = (formatId: IFieldFormatId): IFieldFormatType | undefined => { - return this.fieldFormats.get(formatId); + getType = (formatId: IFieldFormatId): IFieldFormatType | void => { + const decoratedFieldFormat: any = this.fieldFormatMetaParamsDecorator(formatId); + + if (decoratedFieldFormat) { + return decoratedFieldFormat as IFieldFormatType; + } }; /** @@ -86,12 +92,12 @@ export class FieldFormatRegisty { * * @param {KBN_FIELD_TYPES} fieldType * @param {ES_FIELD_TYPES[]} esTypes - Array of ES data types - * @return {FieldFormat} + * @return {FieldFormat | void} */ getDefaultType = ( fieldType: KBN_FIELD_TYPES, esTypes: ES_FIELD_TYPES[] - ): IFieldFormatType | undefined => { + ): IFieldFormatType | void => { const config = this.getDefaultConfig(fieldType, esTypes); return this.getType(config.id); @@ -102,9 +108,9 @@ export class FieldFormatRegisty { * using the format:defaultTypeMap config map * * @param {ES_FIELD_TYPES[]} esTypes - Array of ES data types - * @return {ES_FIELD_TYPES} + * @return {ES_FIELD_TYPES | void} */ - getTypeNameByEsTypes = (esTypes: ES_FIELD_TYPES[] | undefined): ES_FIELD_TYPES | undefined => { + getTypeNameByEsTypes = (esTypes: ES_FIELD_TYPES[] | undefined): ES_FIELD_TYPES | void => { if (!Array.isArray(esTypes)) { return; } @@ -136,14 +142,14 @@ export class FieldFormatRegisty { * @return {FIELD_FORMATS_INSTANCES[number]} */ getInstance = memoize( - (formatId: IFieldFormatId): FieldFormat => { + (formatId: IFieldFormatId, params: Record = {}): FieldFormat => { const DerivedFieldFormat = this.getType(formatId); if (!DerivedFieldFormat) { throw new Error(`Field Format '${formatId}' not found!`); } - return new DerivedFieldFormat({}, this.getConfig); + return new DerivedFieldFormat(params, this.getConfig); } ); @@ -217,10 +223,33 @@ export class FieldFormatRegisty { } register = (fieldFormats: IFieldFormatType[]) => { - fieldFormats.forEach(fieldFormat => { - this.fieldFormats.set(fieldFormat.id, fieldFormat); - }); + fieldFormats.forEach(fieldFormat => this.fieldFormats.set(fieldFormat.id, fieldFormat)); return this; }; + + /** + * FieldFormat decorator - provide a one way to add meta-params for all field formatters + * + * @private + * @param {IFieldFormatId} formatId - the format id + * @return {FieldFormat | void} + */ + private fieldFormatMetaParamsDecorator = (formatId: IFieldFormatId): Function | void => { + const concreteFieldFormat = this.fieldFormats.get(formatId); + const decorateMetaParams = (customOptions: Record = {}) => ({ + parsedUrl: { + origin: window.location.origin, + pathname: window.location.pathname, + basePath: this.basePath, + }, + ...customOptions, + }); + + if (concreteFieldFormat) { + return function(params: Record = {}, getConfig?: Function) { + return new concreteFieldFormat(decorateMetaParams(params), getConfig); + }; + } + }; } diff --git a/src/plugins/data/public/field_formats_provider/field_formats_service.ts b/src/plugins/data/public/field_formats_provider/field_formats_service.ts index ea1a8af2930b0..42abeecc6fda0 100644 --- a/src/plugins/data/public/field_formats_provider/field_formats_service.ts +++ b/src/plugins/data/public/field_formats_provider/field_formats_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IUiSettingsClient } from 'src/core/public'; +import { CoreSetup } from 'src/core/public'; import { FieldFormatRegisty } from './field_formats'; import { @@ -38,19 +38,11 @@ import { UrlFormat, } from '../../common/'; -/** - * Field Format Service - * @internal - */ -interface FieldFormatsServiceDependencies { - uiSettings: IUiSettingsClient; -} - export class FieldFormatsService { private readonly fieldFormats: FieldFormatRegisty = new FieldFormatRegisty(); - public setup({ uiSettings }: FieldFormatsServiceDependencies) { - this.fieldFormats.init(uiSettings); + public setup(core: CoreSetup) { + this.fieldFormats.init(core); this.fieldFormats.register([ BoolFormat, diff --git a/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts index 02d61f8b32c86..59ee18b3dcb50 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/format_hit.ts @@ -19,6 +19,7 @@ import _ from 'lodash'; import { IndexPattern } from './index_pattern'; +import { ContentType } from '../../../common'; const formattedCache = new WeakMap(); const partialFormattedCache = new WeakMap(); @@ -26,14 +27,16 @@ const partialFormattedCache = new WeakMap(); // Takes a hit, merges it with any stored/scripted fields, and with the metaFields // returns a formatted version export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any) { - function convert(hit: Record, val: any, fieldName: string, type: string = 'html') { + function convert( + hit: Record, + val: any, + fieldName: string, + type: ContentType = 'html' + ) { const field = indexPattern.fields.getByName(fieldName); - if (!field) return defaultFormat.convert(val, type); - const parsedUrl = { - origin: window.location.origin, - pathname: window.location.pathname, - }; - return field.format.getConverterFor(type)(val, field, hit, parsedUrl); + const format = field ? field.format : defaultFormat; + + return format.convert(val, type, { field, hit }); } function formatHit(hit: Record, type: string = 'html') { diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index 591290065d024..919115d40b068 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -19,7 +19,7 @@ // eslint-disable-next-line max-classes-per-file import { IndexPatterns } from './index_patterns'; -import { SavedObjectsClientContract, IUiSettingsClient, HttpServiceBase } from 'kibana/public'; +import { SavedObjectsClientContract, IUiSettingsClient, HttpSetup } from 'kibana/public'; jest.mock('./index_pattern', () => { class IndexPattern { @@ -49,7 +49,7 @@ describe('IndexPatterns', () => { beforeEach(() => { const savedObjectsClient = {} as SavedObjectsClientContract; const uiSettings = {} as IUiSettingsClient; - const http = {} as HttpServiceBase; + const http = {} as HttpSetup; indexPatterns = new IndexPatterns(uiSettings, savedObjectsClient, http); }); diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index da58881b5b96e..8d7dd0f054366 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -21,7 +21,7 @@ import { SavedObjectsClientContract, SimpleSavedObject, IUiSettingsClient, - HttpServiceBase, + HttpStart, } from 'src/core/public'; import { createIndexPatternCache } from './_pattern_cache'; @@ -39,7 +39,7 @@ export class IndexPatterns { constructor( config: IUiSettingsClient, savedObjectsClient: SavedObjectsClientContract, - http: HttpServiceBase + http: HttpStart ) { this.apiClient = new IndexPatternsApiClient(http); this.config = config; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index 961e519338ac4..52d18170168d4 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -17,7 +17,7 @@ * under the License. */ -import { HttpServiceBase } from 'src/core/public'; +import { HttpSetup } from 'src/core/public'; import { indexPatterns } from '../'; const API_BASE_URL: string = `/api/index_patterns/`; @@ -33,9 +33,9 @@ export interface GetFieldsOptions { export type IIndexPatternsApiClient = PublicMethodsOf; export class IndexPatternsApiClient { - private http: HttpServiceBase; + private http: HttpSetup; - constructor(http: HttpServiceBase) { + constructor(http: HttpSetup) { this.http = http; } diff --git a/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts b/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts index 0faf6f4a10346..60b2023f25609 100644 --- a/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts +++ b/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts @@ -18,8 +18,9 @@ */ import { get } from 'lodash'; +import { IIndexPattern } from '../..'; -export function getFromSavedObject(savedObject: any) { +export function getFromSavedObject(savedObject: any): IIndexPattern | undefined { if (get(savedObject, 'attributes.fields') === undefined) { return; } diff --git a/src/plugins/data/public/query/timefilter/get_time.ts b/src/plugins/data/public/query/timefilter/get_time.ts index d3fbc17734f81..76f39da1cf706 100644 --- a/src/plugins/data/public/query/timefilter/get_time.ts +++ b/src/plugins/data/public/query/timefilter/get_time.ts @@ -18,10 +18,10 @@ */ import dateMath from '@elastic/datemath'; -import { TimeRange } from '../../../common'; +import { IIndexPattern } from '../..'; +import { TimeRange, IFieldType } from '../../../common'; // TODO: remove this -import { IndexPattern, Field } from '../../../../../legacy/core_plugins/data/public'; import { esFilters } from '../../../common'; interface CalculateBoundsOptions { @@ -36,7 +36,7 @@ export function calculateBounds(timeRange: TimeRange, options: CalculateBoundsOp } export function getTime( - indexPattern: IndexPattern | undefined, + indexPattern: IIndexPattern | undefined, timeRange: TimeRange, forceNow?: Date ) { @@ -45,7 +45,7 @@ export function getTime( return; } - const timefield: Field | undefined = indexPattern.fields.find( + const timefield: IFieldType | undefined = indexPattern.fields.find( field => field.name === indexPattern.timeFieldName ); diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.ts index 68076cd43c336..e64156c290db1 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/suggestions_provider/value_suggestions.ts @@ -19,13 +19,13 @@ import { memoize } from 'lodash'; -import { IUiSettingsClient, HttpServiceBase } from 'src/core/public'; +import { IUiSettingsClient, HttpSetup } from 'src/core/public'; import { IGetSuggestions } from './types'; import { IFieldType } from '../../common'; export function getSuggestionsProvider( uiSettings: IUiSettingsClient, - http: HttpServiceBase + http: HttpSetup ): IGetSuggestions { const requestSuggestions = memoize( ( diff --git a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx index ad453c4e5d11d..829c8205a8b52 100644 --- a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx @@ -20,18 +20,21 @@ import _ from 'lodash'; import React, { Component } from 'react'; -import { EuiComboBox } from '@elastic/eui'; +import { Required } from '@kbn/utility-types'; +import { EuiComboBox, EuiComboBoxProps } from '@elastic/eui'; + import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; import { getTitle } from '../../index_patterns/lib'; -export interface IndexPatternSelectProps { - onChange: (opt: any) => void; +export type IndexPatternSelectProps = Required< + Omit, 'isLoading' | 'onSearchChange' | 'options' | 'selectedOptions'>, + 'onChange' | 'placeholder' +> & { indexPatternId: string; - placeholder: string; - fieldTypes: string[]; - onNoIndexPatterns: () => void; + fieldTypes?: string[]; + onNoIndexPatterns?: () => void; savedObjectsClient: SavedObjectsClientContract; -} +}; interface IndexPatternSelectState { isLoading: boolean; @@ -136,7 +139,7 @@ export class IndexPatternSelect extends Component { try { const indexPatternFields = JSON.parse(savedObject.attributes.fields as any); return indexPatternFields.some((field: any) => { - return fieldTypes.includes(field.type); + return fieldTypes?.includes(field.type); }); } catch (err) { // Unable to parse fields JSON, invalid index pattern @@ -196,6 +199,7 @@ export class IndexPatternSelect extends Component { return ( { options={this.state.options} selectedOptions={this.state.selectedIndexPattern ? [this.state.selectedIndexPattern] : []} onChange={this.onChange} - {...rest} /> ); } diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 4de883295bd8a..2fce33793cd46 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -299,7 +299,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, "http": Object { - "addLoadingCount": [MockFunction], + "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { "isAnonymous": [MockFunction], "register": [MockFunction], @@ -320,8 +320,6 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "patch": [MockFunction], "post": [MockFunction], "put": [MockFunction], - "removeAllInterceptors": [MockFunction], - "stop": [MockFunction], }, "i18n": Object { "Context": [MockFunction], @@ -920,7 +918,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, "http": Object { - "addLoadingCount": [MockFunction], + "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { "isAnonymous": [MockFunction], "register": [MockFunction], @@ -941,8 +939,6 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "patch": [MockFunction], "post": [MockFunction], "put": [MockFunction], - "removeAllInterceptors": [MockFunction], - "stop": [MockFunction], }, "i18n": Object { "Context": [MockFunction], @@ -1529,7 +1525,7 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, "http": Object { - "addLoadingCount": [MockFunction], + "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { "isAnonymous": [MockFunction], "register": [MockFunction], @@ -1550,8 +1546,6 @@ exports[`QueryStringInput Should pass the query language to the language switche "patch": [MockFunction], "post": [MockFunction], "put": [MockFunction], - "removeAllInterceptors": [MockFunction], - "stop": [MockFunction], }, "i18n": Object { "Context": [MockFunction], @@ -2147,7 +2141,7 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, "http": Object { - "addLoadingCount": [MockFunction], + "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { "isAnonymous": [MockFunction], "register": [MockFunction], @@ -2168,8 +2162,6 @@ exports[`QueryStringInput Should pass the query language to the language switche "patch": [MockFunction], "post": [MockFunction], "put": [MockFunction], - "removeAllInterceptors": [MockFunction], - "stop": [MockFunction], }, "i18n": Object { "Context": [MockFunction], @@ -2756,7 +2748,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, "http": Object { - "addLoadingCount": [MockFunction], + "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { "isAnonymous": [MockFunction], "register": [MockFunction], @@ -2777,8 +2769,6 @@ exports[`QueryStringInput Should render the given query 1`] = ` "patch": [MockFunction], "post": [MockFunction], "put": [MockFunction], - "removeAllInterceptors": [MockFunction], - "stop": [MockFunction], }, "i18n": Object { "Context": [MockFunction], @@ -3374,7 +3364,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, "http": Object { - "addLoadingCount": [MockFunction], + "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { "isAnonymous": [MockFunction], "register": [MockFunction], @@ -3395,8 +3385,6 @@ exports[`QueryStringInput Should render the given query 1`] = ` "patch": [MockFunction], "post": [MockFunction], "put": [MockFunction], - "removeAllInterceptors": [MockFunction], - "stop": [MockFunction], }, "i18n": Object { "Context": [MockFunction], diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts index 5f1493a49ab7d..f563976cae9b8 100644 --- a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts @@ -17,7 +17,7 @@ * under the License. */ -import { APICaller } from 'src/core/server'; +import { APICaller } from 'kibana/server'; import { getFieldCapabilities, resolveTimePattern, createNoMatchingIndicesError } from './lib'; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts index 92b64fafddd66..734e845e85769 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts @@ -17,7 +17,7 @@ * under the License. */ -import { APICaller } from 'src/core/server'; +import { APICaller } from 'kibana/server'; import { convertEsError } from './errors'; import { FieldCapsResponse } from './field_capabilities'; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index c275fb714088e..2a30c89342cf3 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -19,7 +19,7 @@ import { defaults, indexBy, sortBy } from 'lodash'; -import { APICaller } from 'src/core/server'; +import { APICaller } from 'kibana/server'; import { callFieldCapsApi } from '../es_api'; import { FieldCapsResponse, readFieldCapsResponse } from './field_caps_response'; import { mergeOverrides } from './overrides'; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts index d8c56a8082cb1..7504d7bc9c460 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts @@ -20,7 +20,7 @@ import { chain } from 'lodash'; import moment from 'moment'; -import { APICaller } from 'src/core/server'; +import { APICaller } from 'kibana/server'; import { timePatternToWildcard } from './time_pattern_to_wildcard'; import { callIndexAliasApi, IndicesAliasResponse } from './es_api'; diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index 30d199c0e522e..78f34e21b9e41 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -17,13 +17,12 @@ * under the License. */ -import { CoreSetup } from 'kibana/server'; -import { Plugin } from '../../../../core/server'; +import { CoreSetup, Plugin } from 'kibana/server'; import { registerRoutes } from './routes'; export class IndexPatternsService implements Plugin { - public setup({ http, elasticsearch }: CoreSetup) { - registerRoutes(http, elasticsearch); + public setup({ http }: CoreSetup) { + registerRoutes(http); } public start() {} diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 7975e923e219b..8f017a73083ec 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -17,28 +17,11 @@ * under the License. */ -import { first } from 'rxjs/operators'; import { schema } from '@kbn/config-schema'; -import { - CoreSetup, - KibanaRequest, - RequestHandlerContext, - APICaller, - CallAPIOptions, -} from '../../../../core/server'; +import { HttpServiceSetup, RequestHandlerContext } from 'kibana/server'; import { IndexPatternsFetcher } from './fetcher'; -export function registerRoutes(http: CoreSetup['http'], elasticsearch: CoreSetup['elasticsearch']) { - const getIndexPatternsService = async (request: KibanaRequest): Promise => { - const client = await elasticsearch.dataClient$.pipe(first()).toPromise(); - const callCluster: APICaller = ( - endpoint: string, - params?: Record, - options?: CallAPIOptions - ) => client.asScoped(request).callAsCurrentUser(endpoint, params, options); - return new Promise(resolve => resolve(new IndexPatternsFetcher(callCluster))); - }; - +export function registerRoutes(http: HttpServiceSetup) { const parseMetaFields = (metaFields: string | string[]) => { let parsedFields: string[] = []; if (typeof metaFields === 'string') { @@ -62,8 +45,9 @@ export function registerRoutes(http: CoreSetup['http'], elasticsearch: CoreSetup }), }, }, - async (context: RequestHandlerContext, request: any, response: any) => { - const indexPatterns = await getIndexPatternsService(request); + async (context, request, response) => { + const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, meta_fields: metaFields } = request.query; let parsedFields: string[] = []; @@ -106,7 +90,8 @@ export function registerRoutes(http: CoreSetup['http'], elasticsearch: CoreSetup }, }, async (context: RequestHandlerContext, request: any, response: any) => { - const indexPatterns = await getIndexPatternsService(request); + const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, interval, look_back: lookBack, meta_fields: metaFields } = request.query; let parsedFields: string[] = []; diff --git a/src/plugins/data/server/kql_telemetry/index.ts b/src/plugins/data/server/kql_telemetry/index.ts new file mode 100644 index 0000000000000..0191113d95007 --- /dev/null +++ b/src/plugins/data/server/kql_telemetry/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { KqlTelemetryService } from './kql_telemetry_service'; diff --git a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts new file mode 100644 index 0000000000000..8f68e63703d40 --- /dev/null +++ b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { first } from 'rxjs/operators'; +import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; +import { registerKqlTelemetryRoute } from './route'; +import { UsageCollectionSetup } from '../../../usage_collection/server'; +import { makeKQLUsageCollector } from './usage_collector'; + +export class KqlTelemetryService implements Plugin { + constructor(private initializerContext: PluginInitializerContext) {} + + public setup( + { http, savedObjects }: CoreSetup, + { usageCollection }: { usageCollection?: UsageCollectionSetup } + ) { + registerKqlTelemetryRoute( + http.createRouter(), + savedObjects, + this.initializerContext.logger.get('data', 'kql-telemetry') + ); + + if (usageCollection) { + this.initializerContext.config.legacy.globalConfig$ + .pipe(first()) + .toPromise() + .then(config => makeKQLUsageCollector(usageCollection, config.kibana.index)) + .catch(e => { + this.initializerContext.logger + .get('kql-telemetry') + .warn(`Registering KQL telemetry collector failed: ${e}`); + }); + } + } + + public start() {} +} diff --git a/src/legacy/core_plugins/kibana/server/routes/api/kql_telemetry/index.js b/src/plugins/data/server/kql_telemetry/route.ts similarity index 57% rename from src/legacy/core_plugins/kibana/server/routes/api/kql_telemetry/index.js rename to src/plugins/data/server/kql_telemetry/route.ts index 430240169c81c..3185da22b12b3 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/kql_telemetry/index.js +++ b/src/plugins/data/server/kql_telemetry/route.ts @@ -17,30 +17,28 @@ * under the License. */ -import Joi from 'joi'; -import Boom from 'boom'; +import { CoreSetup, IRouter, Logger } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; -export function registerKqlTelemetryApi(server) { - server.route({ - path: '/api/kibana/kql_opt_in_telemetry', - method: 'POST', - config: { +export function registerKqlTelemetryRoute( + router: IRouter, + savedObjects: CoreSetup['savedObjects'], + logger: Logger +) { + router.post( + { + path: '/api/kibana/kql_opt_in_telemetry', validate: { - payload: Joi.object({ - opt_in: Joi.bool().required(), + body: schema.object({ + opt_in: schema.boolean(), }), }, - tags: ['api'], }, - handler: async function(request) { - const { - savedObjects: { getSavedObjectsRepository }, - } = server; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); + async (context, request, response) => { + const internalRepository = savedObjects.createScopedRepository(request); const { - payload: { opt_in: optIn }, + body: { opt_in: optIn }, } = request; const counterName = optIn ? 'optInCount' : 'optOutCount'; @@ -48,13 +46,19 @@ export function registerKqlTelemetryApi(server) { try { await internalRepository.incrementCounter('kql-telemetry', 'kql-telemetry', counterName); } catch (error) { - return new Boom('Something went wrong', { + logger.warn(`Unable to increment counter: ${error}`); + return response.customError({ statusCode: error.status, - data: { success: false }, + body: { + message: 'Something went wrong', + attributes: { + success: false, + }, + }, }); } - return { success: true }; - }, - }); + return response.ok({ body: { success: true } }); + } + ); } diff --git a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/fetch.test.js b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts similarity index 85% rename from src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/fetch.test.js rename to src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts index a5db4602872ee..446320b09757a 100644 --- a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/fetch.test.js +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts @@ -17,18 +17,22 @@ * under the License. */ -jest.mock('../../../ui_setting_defaults', () => ({ - getUiSettingDefaults: () => ({ 'search:queryLanguage': { value: 'lucene' } }), -})); - import { fetchProvider } from './fetch'; +import { APICaller } from 'kibana/server'; + +jest.mock('../../../common', () => ({ + DEFAULT_QUERY_LANGUAGE: 'lucene', +})); -let fetch; -let callCluster; +let fetch: ReturnType; +let callCluster: APICaller; -function setupMockCallCluster(optCount, language) { - callCluster = jest.fn((method, params) => { - if ('id' in params && params.id === 'kql-telemetry:kql-telemetry') { +function setupMockCallCluster( + optCount: { optInCount?: number; optOutCount?: number } | null, + language: string | undefined | null +) { + callCluster = (jest.fn((method, params) => { + if (params && 'id' in params && params.id === 'kql-telemetry:kql-telemetry') { if (optCount === null) { return Promise.resolve({ _index: '.kibana_1', @@ -46,9 +50,9 @@ function setupMockCallCluster(optCount, language) { }, }); } - } else if ('body' in params && params.body.query.term.type === 'config') { + } else if (params && 'body' in params && params.body.query.term.type === 'config') { if (language === 'missingConfigDoc') { - Promise.resolve({ + return Promise.resolve({ hits: { hits: [], }, @@ -69,7 +73,9 @@ function setupMockCallCluster(optCount, language) { }); } } - }); + + throw new Error('invalid call'); + }) as unknown) as APICaller; } describe('makeKQLUsageCollector', () => { diff --git a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/fetch.js b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts similarity index 89% rename from src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/fetch.js rename to src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts index 0a886d364c6fd..9f3437161541f 100644 --- a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/fetch.js +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts @@ -17,14 +17,14 @@ * under the License. */ -import { getUiSettingDefaults } from '../../../ui_setting_defaults'; import { get } from 'lodash'; +import { APICaller } from 'kibana/server'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../common'; -const uiSettingDefaults = getUiSettingDefaults(); -const defaultSearchQueryLanguageSetting = uiSettingDefaults['search:queryLanguage'].value; +const defaultSearchQueryLanguageSetting = DEFAULT_QUERY_LANGUAGE; -export function fetchProvider(index) { - return async callCluster => { +export function fetchProvider(index: string) { + return async (callCluster: APICaller) => { const [response, config] = await Promise.all([ callCluster('get', { index, diff --git a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/index.js b/src/plugins/data/server/kql_telemetry/usage_collector/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/index.js rename to src/plugins/data/server/kql_telemetry/usage_collector/index.ts diff --git a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.test.js b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.test.ts similarity index 62% rename from src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.test.js rename to src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.test.ts index 34503bf56ad84..97a714f09474b 100644 --- a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.test.js +++ b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.test.ts @@ -18,33 +18,26 @@ */ import { makeKQLUsageCollector } from './make_kql_usage_collector'; +import { UsageCollectionSetup } from '../../../../usage_collection/server'; describe('makeKQLUsageCollector', () => { - let server; - let makeUsageCollectorStub; - let registerStub; - let usageCollection; + let usageCollectionMock: jest.Mocked; beforeEach(() => { - makeUsageCollectorStub = jest.fn(); - registerStub = jest.fn(); - usageCollection = { - makeUsageCollector: makeUsageCollectorStub, - registerCollector: registerStub, - }; - server = { - config: () => ({ get: () => '.kibana' }), - }; + usageCollectionMock = ({ + makeUsageCollector: jest.fn(), + registerCollector: jest.fn(), + } as unknown) as jest.Mocked; }); it('should call registerCollector', () => { - makeKQLUsageCollector(usageCollection, server); - expect(registerStub).toHaveBeenCalledTimes(1); + makeKQLUsageCollector(usageCollectionMock, '.kibana'); + expect(usageCollectionMock.registerCollector).toHaveBeenCalledTimes(1); }); it('should call makeUsageCollector with type = kql', () => { - makeKQLUsageCollector(usageCollection, server); - expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1); - expect(makeUsageCollectorStub.mock.calls[0][0].type).toBe('kql'); + makeKQLUsageCollector(usageCollectionMock, '.kibana'); + expect(usageCollectionMock.makeUsageCollector).toHaveBeenCalledTimes(1); + expect(usageCollectionMock.makeUsageCollector.mock.calls[0][0].type).toBe('kql'); }); }); diff --git a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.js b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts similarity index 81% rename from src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.js rename to src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts index 6d751a9e9ff45..db4c9a8f0b4c7 100644 --- a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.js +++ b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts @@ -18,10 +18,13 @@ */ import { fetchProvider } from './fetch'; +import { UsageCollectionSetup } from '../../../../usage_collection/server'; -export function makeKQLUsageCollector(usageCollection, server) { - const index = server.config().get('kibana.index'); - const fetch = fetchProvider(index); +export async function makeKQLUsageCollector( + usageCollection: UsageCollectionSetup, + kibanaIndex: string +) { + const fetch = fetchProvider(kibanaIndex); const kqlUsageCollector = usageCollection.makeUsageCollector({ type: 'kql', fetch, diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 6df0a11e7538d..591fdb4c4080d 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -21,29 +21,42 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../.. import { IndexPatternsService } from './index_patterns'; import { ISearchSetup } from './search'; import { SearchService } from './search/search_service'; +import { ScriptsService } from './scripts'; +import { KqlTelemetryService } from './kql_telemetry'; +import { UsageCollectionSetup } from '../../usage_collection/server'; import { AutocompleteService } from './autocomplete'; export interface DataPluginSetup { search: ISearchSetup; } +export interface DataPluginSetupDependencies { + usageCollection?: UsageCollectionSetup; +} export class DataServerPlugin implements Plugin { private readonly searchService: SearchService; + private readonly scriptsService: ScriptsService; + private readonly kqlTelemetryService: KqlTelemetryService; private readonly autocompleteService = new AutocompleteService(); private readonly indexPatterns = new IndexPatternsService(); constructor(initializerContext: PluginInitializerContext) { this.searchService = new SearchService(initializerContext); + this.scriptsService = new ScriptsService(); + this.kqlTelemetryService = new KqlTelemetryService(initializerContext); } - public setup(core: CoreSetup) { + public setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies) { this.indexPatterns.setup(core); + this.scriptsService.setup(core); this.autocompleteService.setup(core); + this.kqlTelemetryService.setup(core, { usageCollection }); return { search: this.searchService.setup(core), }; } + public start(core: CoreStart) {} public stop() {} } diff --git a/src/plugins/data/server/scripts/index.ts b/src/plugins/data/server/scripts/index.ts new file mode 100644 index 0000000000000..d4a3e70fdbf53 --- /dev/null +++ b/src/plugins/data/server/scripts/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { ScriptsService } from './scripts_service'; diff --git a/src/plugins/data/server/scripts/route.ts b/src/plugins/data/server/scripts/route.ts new file mode 100644 index 0000000000000..3eb77f09b25cd --- /dev/null +++ b/src/plugins/data/server/scripts/route.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 { IRouter } from 'kibana/server'; + +export function registerScriptsRoute(router: IRouter) { + router.get( + { path: '/api/kibana/scripts/languages', validate: false }, + async (context, request, response) => { + return response.ok({ + body: ['painless', 'expression'], + }); + } + ); +} diff --git a/src/plugins/data/server/scripts/scripts_service.ts b/src/plugins/data/server/scripts/scripts_service.ts new file mode 100644 index 0000000000000..9a3a20a64276d --- /dev/null +++ b/src/plugins/data/server/scripts/scripts_service.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreSetup, Plugin } from 'kibana/server'; +import { registerScriptsRoute } from './route'; + +export class ScriptsService implements Plugin { + public setup({ http }: CoreSetup) { + registerScriptsRoute(http.createRouter()); + } + + public start() {} +} diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts index 5a3f28ed76486..b8f7db1463ab8 100644 --- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts +++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts @@ -19,7 +19,7 @@ import { useEffect, useState, useRef } from 'react'; -import { HttpServiceBase, HttpFetchQuery } from '../../../../../src/core/public'; +import { HttpSetup, HttpFetchQuery } from '../../../../../src/core/public'; export interface SendRequestConfig { path: string; @@ -48,7 +48,7 @@ export interface UseRequestResponse { } export const sendRequest = async ( - httpClient: HttpServiceBase, + httpClient: HttpSetup, { path, method, body, query }: SendRequestConfig ): Promise => { try { @@ -67,7 +67,7 @@ export const sendRequest = async ( }; export const useRequest = ( - httpClient: HttpServiceBase, + httpClient: HttpSetup, { path, method, diff --git a/src/plugins/home/README.md b/src/plugins/home/README.md index 74e12a799b1b7..1ce0ae0bf44d3 100644 --- a/src/plugins/home/README.md +++ b/src/plugins/home/README.md @@ -1,13 +1,13 @@ # home plugin Moves the legacy `ui/registry/feature_catalogue` module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls. -# Feature catalogue (public service) +## Feature catalogue (public service) Replaces the legacy `ui/registry/feature_catalogue` module for registering "features" that should be showed in the home page's feature catalogue. This should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls. -## Example registration +### Example registration ```ts // For legacy plugins @@ -27,3 +27,28 @@ class MyPlugin { ``` Note that the old module supported providing a Angular DI function to receive Angular dependencies. This is no longer supported as we migrate away from Angular and will be removed in 8.0. + +## Sample data + +Replaces the sample data mixin putting functions on the global `server` object. + +### What happens when a user installs a sample data set? +1) Kibana deletes existing Elastic search indicies for the sample data set if they exist from previous installs. +2) Kibana creates Elasticsearch indicies with the provided field mappings. +3) Kibana uses bulk insert to ingest the new-line delimited json into the Elasticsearch index. Kibana migrates timestamps provided in new-line delimited json to the current time frame for any date field defined in `timeFields` +4) Kibana will install all saved objects for sample data set. This will override any saved objects previouslly installed for sample data set. + +Elasticsearch index names are prefixed with `kibana_sample_data_`. For more details see [createIndexName](/src/plugins/home/server/services/sample_data/lib/create_index_name.js) + +Sample data sets typically provide data that spans 5 weeks from the past and 5 weeks into the future so users see data relative to `now` for a few weeks after installing sample data sets. + +### Adding new sample data sets +Use [existing sample data sets](/src/plugins/home/server/services/sample_data/data_sets) as examples. +To avoid bloating the Kibana distribution, keep data set size to a minimum. + +Follow the steps below to add new Sample data sets to Kibana. +1) Create new-line delimited json containing sample data. +2) Create file with Elasticsearch field mappings for sample data indices. +3) Create Kibana saved objects for sample data including index-patterns, visualizations, and dashboards. The best way to extract the saved objects is from the Kibana management -> saved objects [export UI](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html#_export) +4) Define sample data spec conforming to [Data Set Schema](/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts). +5) Register sample data by calling `plguins.home.sampleData.registerSampleDataset(yourSpecProvider)` in your `setup` method where `yourSpecProvider` is a function that returns an object containing your sample data spec from step 4. diff --git a/src/plugins/home/kibana.json b/src/plugins/home/kibana.json index a5c65e3efa597..31e7ebc138dcb 100644 --- a/src/plugins/home/kibana.json +++ b/src/plugins/home/kibana.json @@ -2,5 +2,6 @@ "id": "home", "version": "kibana", "server": true, - "ui": true + "ui": true, + "optionalPlugins": ["usage_collection"] } diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts index be4e20ab63d3c..ed336f4a41d6e 100644 --- a/src/plugins/home/server/index.ts +++ b/src/plugins/home/server/index.ts @@ -19,6 +19,8 @@ export { HomeServerPluginSetup, HomeServerPluginStart } from './plugin'; export { TutorialProvider } from './services'; +export { SampleDatasetProvider, SampleDataRegistrySetup } from './services'; +import { PluginInitializerContext } from 'src/core/server'; import { HomeServerPlugin } from './plugin'; -export const plugin = () => new HomeServerPlugin(); +export const plugin = (initContext: PluginInitializerContext) => new HomeServerPlugin(initContext); diff --git a/src/plugins/home/server/plugin.test.mocks.ts b/src/plugins/home/server/plugin.test.mocks.ts index a5640de579b15..ac11948c040d0 100644 --- a/src/plugins/home/server/plugin.test.mocks.ts +++ b/src/plugins/home/server/plugin.test.mocks.ts @@ -17,8 +17,11 @@ * under the License. */ import { tutorialsRegistryMock } from './services/tutorials/tutorials_registry.mock'; +import { sampleDataRegistryMock } from './services/sample_data/sample_data_registry.mock'; -export const registryMock = tutorialsRegistryMock.create(); +export const registryForTutorialsMock = tutorialsRegistryMock.create(); +export const registryForSampleDataMock = sampleDataRegistryMock.create(); jest.doMock('./services', () => ({ - TutorialsRegistry: jest.fn(() => registryMock), + TutorialsRegistry: jest.fn(() => registryForTutorialsMock), + SampleDataRegistry: jest.fn(() => registryForSampleDataMock), })); diff --git a/src/plugins/home/server/plugin.test.ts b/src/plugins/home/server/plugin.test.ts index eec6501436bf4..33d907315e512 100644 --- a/src/plugins/home/server/plugin.test.ts +++ b/src/plugins/home/server/plugin.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { registryMock } from './plugin.test.mocks'; +import { registryForTutorialsMock, registryForSampleDataMock } from './plugin.test.mocks'; import { HomeServerPlugin } from './plugin'; import { coreMock } from '../../../core/server/mocks'; import { CoreSetup } from '../../../core/server'; @@ -26,26 +26,41 @@ type MockedKeys = { [P in keyof T]: jest.Mocked }; describe('HomeServerPlugin', () => { beforeEach(() => { - registryMock.setup.mockClear(); - registryMock.start.mockClear(); + registryForTutorialsMock.setup.mockClear(); + registryForTutorialsMock.start.mockClear(); + registryForSampleDataMock.setup.mockClear(); + registryForSampleDataMock.start.mockClear(); }); describe('setup', () => { const mockCoreSetup: MockedKeys = coreMock.createSetup(); + const initContext = coreMock.createPluginInitializerContext(); - test('wires up and returns registerTutorial and addScopedTutorialContextFactory', () => { - const setup = new HomeServerPlugin().setup(mockCoreSetup); + test('wires up tutorials provider service and returns registerTutorial and addScopedTutorialContextFactory', () => { + const setup = new HomeServerPlugin(initContext).setup(mockCoreSetup, {}); expect(setup).toHaveProperty('tutorials'); expect(setup.tutorials).toHaveProperty('registerTutorial'); expect(setup.tutorials).toHaveProperty('addScopedTutorialContextFactory'); }); + + test('wires up sample data provider service and returns registerTutorial and addScopedTutorialContextFactory', () => { + const setup = new HomeServerPlugin(initContext).setup(mockCoreSetup, {}); + expect(setup).toHaveProperty('sampleData'); + expect(setup.sampleData).toHaveProperty('registerSampleDataset'); + expect(setup.sampleData).toHaveProperty('getSampleDatasets'); + expect(setup.sampleData).toHaveProperty('addSavedObjectsToSampleDataset'); + expect(setup.sampleData).toHaveProperty('addAppLinksToSampleDataset'); + expect(setup.sampleData).toHaveProperty('replacePanelInSampleDatasetDashboard'); + }); }); describe('start', () => { + const initContext = coreMock.createPluginInitializerContext(); test('is defined', () => { - const start = new HomeServerPlugin().start(); + const start = new HomeServerPlugin(initContext).start(); expect(start).toBeDefined(); expect(start).toHaveProperty('tutorials'); + expect(start).toHaveProperty('sampleData'); }); }); }); diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index 89dda8205ce02..23c236764cddc 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -16,21 +16,37 @@ * specific language governing permissions and limitations * under the License. */ -import { CoreSetup, Plugin } from 'src/core/server'; -import { TutorialsRegistry, TutorialsRegistrySetup, TutorialsRegistryStart } from './services'; +import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/server'; +import { + TutorialsRegistry, + TutorialsRegistrySetup, + TutorialsRegistryStart, + SampleDataRegistry, + SampleDataRegistrySetup, + SampleDataRegistryStart, +} from './services'; +import { UsageCollectionSetup } from '../../usage_collection/server'; + +interface HomeServerPluginSetupDependencies { + usage_collection?: UsageCollectionSetup; +} export class HomeServerPlugin implements Plugin { + constructor(private readonly initContext: PluginInitializerContext) {} private readonly tutorialsRegistry = new TutorialsRegistry(); + private readonly sampleDataRegistry = new SampleDataRegistry(this.initContext); - public setup(core: CoreSetup) { + public setup(core: CoreSetup, plugins: HomeServerPluginSetupDependencies): HomeServerPluginSetup { return { tutorials: { ...this.tutorialsRegistry.setup(core) }, + sampleData: { ...this.sampleDataRegistry.setup(core, plugins.usage_collection) }, }; } - public start() { + public start(): HomeServerPluginStart { return { tutorials: { ...this.tutorialsRegistry.start() }, + sampleData: { ...this.sampleDataRegistry.start() }, }; } } @@ -38,9 +54,11 @@ export class HomeServerPlugin implements Plugin [ +export const getSavedObjects = (): SavedObject[] => [ { id: '37cc8650-b882-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.salesByCategoryTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.salesByCategoryTitle', { defaultMessage: '[eCommerce] Sales by Category', }), visState: @@ -40,15 +44,16 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: 'ed8436b0-b88b-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.salesByGenderTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.salesByGenderTitle', { defaultMessage: '[eCommerce] Sales by Gender', }), visState: @@ -61,15 +66,16 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '09ffee60-b88c-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.markdownTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.markdownTitle', { defaultMessage: '[eCommerce] Markdown', }), visState: @@ -81,15 +87,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '1c389590-b88d-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.controlsTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.controlsTitle', { defaultMessage: '[eCommerce] Controls', }), visState: @@ -101,15 +108,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '45e07720-b890-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:17:30.755Z', - version: 2, + version: '2', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.promotionTrackingTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.promotionTrackingTitle', { defaultMessage: '[eCommerce] Promotion Tracking', }), visState: @@ -121,15 +129,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '10f1a240-b891-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.totalRevenueTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.totalRevenueTitle', { defaultMessage: '[eCommerce] Total Revenue', }), visState: @@ -142,15 +151,16 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: 'b80e6540-b891-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.soldProductsPerDayTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.soldProductsPerDayTitle', { defaultMessage: '[eCommerce] Sold Products per Day', }), visState: @@ -162,15 +172,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '4b3ec120-b892-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.averageSalesPriceTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.averageSalesPriceTitle', { defaultMessage: '[eCommerce] Average Sales Price', }), visState: @@ -184,15 +195,16 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '9ca7aa90-b892-11e8-a6d9-e546fe2bba5f', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.averageSoldQuantityTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.averageSoldQuantityTitle', { defaultMessage: '[eCommerce] Average Sold Quantity', }), visState: @@ -206,15 +218,16 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '3ba638e0-b894-11e8-a6d9-e546fe2bba5f', type: 'search', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.ordersTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.ordersTitle', { defaultMessage: '[eCommerce] Orders', }), description: '', @@ -227,15 +240,16 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","highlightAll":true,"version":true,"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '9c6f83f0-bb4d-11e8-9c84-77068524bcab', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.averageSalesPerRegionTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.averageSalesPerRegionTitle', { defaultMessage: '[eCommerce] Average Sales Per Region', }), visState: @@ -248,15 +262,16 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: 'b72dd430-bb4d-11e8-9c84-77068524bcab', type: 'visualization', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.topSellingProductsTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.topSellingProductsTitle', { defaultMessage: '[eCommerce] Top Selling Products', }), visState: @@ -269,12 +284,13 @@ export const getSavedObjects = () => [ '{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', type: 'index-pattern', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { title: 'kibana_sample_data_ecommerce', @@ -283,12 +299,13 @@ export const getSavedObjects = () => [ '[{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "category"}}},{"name":"currency","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_birth_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_first_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_first_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "customer_first_name"}}},{"name":"customer_full_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_full_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "customer_full_name"}}},{"name":"customer_gender","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_last_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_last_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "customer_last_name"}}},{"name":"customer_phone","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week_i","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"email","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.city_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.continent_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.country_iso_code","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.location","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.region_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "manufacturer"}}},{"name":"order_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"order_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products._id","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products._id.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "products._id"}}},{"name":"products.base_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.base_unit_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "products.category"}}},{"name":"products.created_on","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_percentage","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "products.manufacturer"}}},{"name":"products.min_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_id","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.product_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "products.product_name"}}},{"name":"products.quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.tax_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxful_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxless_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.unit_discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxful_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxless_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_unique_products","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"type","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"user","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', fieldFormatMap: '{"taxful_total_price":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, + references: [], }, { id: '722b74f0-b882-11e8-a6d9-e546fe2bba5f', type: 'dashboard', updated_at: '2018-10-01T15:13:03.270Z', - version: 1, + version: '1', references: [ { name: 'panel_0', @@ -355,11 +372,11 @@ export const getSavedObjects = () => [ dashboard: '7.0.0', }, attributes: { - title: i18n.translate('server.sampleData.ecommerceSpec.revenueDashboardTitle', { + title: i18n.translate('home.sampleData.ecommerceSpec.revenueDashboardTitle', { defaultMessage: '[eCommerce] Revenue Dashboard', }), hits: 0, - description: i18n.translate('server.sampleData.ecommerceSpec.revenueDashboardDescription', { + description: i18n.translate('home.sampleData.ecommerceSpec.revenueDashboardDescription', { defaultMessage: 'Analyze mock eCommerce orders and revenue', }), panelsJSON: diff --git a/src/legacy/server/sample_data/data_sets/flights/field_mappings.js b/src/plugins/home/server/services/sample_data/data_sets/flights/field_mappings.ts similarity index 100% rename from src/legacy/server/sample_data/data_sets/flights/field_mappings.js rename to src/plugins/home/server/services/sample_data/data_sets/flights/field_mappings.ts diff --git a/src/legacy/server/sample_data/data_sets/flights/flights.json.gz b/src/plugins/home/server/services/sample_data/data_sets/flights/flights.json.gz similarity index 100% rename from src/legacy/server/sample_data/data_sets/flights/flights.json.gz rename to src/plugins/home/server/services/sample_data/data_sets/flights/flights.json.gz diff --git a/src/legacy/server/sample_data/data_sets/flights/index.js b/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts similarity index 71% rename from src/legacy/server/sample_data/data_sets/flights/index.js rename to src/plugins/home/server/services/sample_data/data_sets/flights/index.ts index 382f82de88305..d63ea8f7fb493 100644 --- a/src/legacy/server/sample_data/data_sets/flights/index.js +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/index.ts @@ -21,19 +21,25 @@ import path from 'path'; import { i18n } from '@kbn/i18n'; import { getSavedObjects } from './saved_objects'; import { fieldMappings } from './field_mappings'; +import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types'; -export function flightsSpecProvider() { +const flightsName = i18n.translate('home.sampleData.flightsSpecTitle', { + defaultMessage: 'Sample flight data', +}); +const flightsDescription = i18n.translate('home.sampleData.flightsSpecDescription', { + defaultMessage: 'Sample data, visualizations, and dashboards for monitoring flight routes.', +}); +const initialAppLinks = [] as AppLinkSchema[]; + +export const flightsSpecProvider = function(): SampleDatasetSchema { return { id: 'flights', - name: i18n.translate('server.sampleData.flightsSpecTitle', { - defaultMessage: 'Sample flight data', - }), - description: i18n.translate('server.sampleData.flightsSpecDescription', { - defaultMessage: 'Sample data, visualizations, and dashboards for monitoring flight routes.', - }), + name: flightsName, + description: flightsDescription, previewImagePath: '/plugins/kibana/home/sample_data_resources/flights/dashboard.png', darkPreviewImagePath: '/plugins/kibana/home/sample_data_resources/flights/dashboard_dark.png', overviewDashboard: '7adfa750-4c81-11e8-b3d7-01146121b73d', + appLinks: initialAppLinks, defaultIndex: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', savedObjects: getSavedObjects(), dataIndices: [ @@ -46,5 +52,6 @@ export function flightsSpecProvider() { preserveDayOfWeekTimeOfDay: true, }, ], + status: 'not_installed', }; -} +}; diff --git a/src/legacy/server/sample_data/data_sets/flights/saved_objects.js b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts similarity index 94% rename from src/legacy/server/sample_data/data_sets/flights/saved_objects.js rename to src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 69e77e67c5d9c..1cce11aea37fb 100644 --- a/src/legacy/server/sample_data/data_sets/flights/saved_objects.js +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -17,17 +17,21 @@ * under the License. */ +/* eslint max-len: 0 */ +/* eslint-disable */ + import { i18n } from '@kbn/i18n'; +import { SavedObject } from 'kibana/server'; -export const getSavedObjects = () => [ +export const getSavedObjects = (): SavedObject[] => [ { id: 'aeb212e0-4c84-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.controlsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.controlsTitle', { defaultMessage: '[Flights] Controls', }), visState: @@ -39,15 +43,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{}', }, }, + references: [], }, { id: 'c8fc3d30-4c87-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.flightCountAndAverageTicketPriceTitle', { + title: i18n.translate('home.sampleData.flightsSpec.flightCountAndAverageTicketPriceTitle', { defaultMessage: '[Flights] Flight Count and Average Ticket Price', }), visState: @@ -61,15 +66,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '571aaf70-4c88-11e8-b3d7-01146121b73d', type: 'search', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.flightLogTitle', { + title: i18n.translate('home.sampleData.flightsSpec.flightLogTitle', { defaultMessage: '[Flights] Flight Log', }), description: '', @@ -92,15 +98,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","highlightAll":true,"version":true,"query":{"language":"kuery","query":""},"filter":[]}', }, }, + references: [], }, { id: '8f4d0c00-4c86-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.airlineCarrierTitle', { + title: i18n.translate('home.sampleData.flightsSpec.airlineCarrierTitle', { defaultMessage: '[Flights] Airline Carrier', }), visState: @@ -113,15 +120,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: 'f8290060-4c88-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.delayTypeTitle', { + title: i18n.translate('home.sampleData.flightsSpec.delayTypeTitle', { defaultMessage: '[Flights] Delay Type', }), visState: @@ -134,15 +142,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: 'bcb63b50-4c89-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.delaysAndCancellationsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.delaysAndCancellationsTitle', { defaultMessage: '[Flights] Delays & Cancellations', }), visState: @@ -154,15 +163,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{}', }, }, + references: [], }, { id: '9886b410-4c8b-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.delayBucketsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.delayBucketsTitle', { defaultMessage: '[Flights] Delay Buckets', }), visState: @@ -175,15 +185,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[{"meta":{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","negate":true,"disabled":false,"alias":null,"type":"phrase","key":"FlightDelayMin","value":"0","params":{"query":0,"type":"phrase"}},"query":{"match":{"FlightDelayMin":{"query":0,"type":"phrase"}}},"$state":{"store":"appState"}}],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '76e3c090-4c8c-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.flightDelaysTitle', { + title: i18n.translate('home.sampleData.flightsSpec.flightDelaysTitle', { defaultMessage: '[Flights] Flight Delays', }), visState: @@ -196,15 +207,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '707665a0-4c8c-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.flightCancellationsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.flightCancellationsTitle', { defaultMessage: '[Flights] Flight Cancellations', }), visState: @@ -217,15 +229,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '293b5a30-4c8f-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.destinationWeatherTitle', { + title: i18n.translate('home.sampleData.flightsSpec.destinationWeatherTitle', { defaultMessage: '[Flights] Destination Weather', }), visState: @@ -238,15 +251,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '129be430-4c93-11e8-b3d7-01146121b73d', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.markdownInstructionsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.markdownInstructionsTitle', { defaultMessage: '[Flights] Markdown Instructions', }), visState: @@ -258,15 +272,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{}', }, }, + references: [], }, { id: '334084f0-52fd-11e8-a160-89cc2ad9e8e2', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.originCountryTicketPricesTitle', { + title: i18n.translate('home.sampleData.flightsSpec.originCountryTicketPricesTitle', { defaultMessage: '[Flights] Origin Country Ticket Prices', }), visState: @@ -279,15 +294,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: 'f8283bf0-52fd-11e8-a160-89cc2ad9e8e2', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.totalFlightDelaysTitle', { + title: i18n.translate('home.sampleData.flightsSpec.totalFlightDelaysTitle', { defaultMessage: '[Flights] Total Flight Delays', }), visState: @@ -301,15 +317,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[{"meta":{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","negate":false,"disabled":false,"alias":null,"type":"phrase","key":"FlightDelay","value":"true","params":{"query":true,"type":"phrase"}},"query":{"match":{"FlightDelay":{"query":true,"type":"phrase"}}},"$state":{"store":"appState"}}],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '08884800-52fe-11e8-a160-89cc2ad9e8e2', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.totalFlightCancellationsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.totalFlightCancellationsTitle', { defaultMessage: '[Flights] Total Flight Cancellations', }), visState: @@ -323,15 +340,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[{"meta":{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","negate":false,"disabled":false,"alias":null,"type":"phrase","key":"Cancelled","value":"true","params":{"query":true,"type":"phrase"}},"query":{"match":{"Cancelled":{"query":true,"type":"phrase"}}},"$state":{"store":"appState"}}],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: 'e6944e50-52fe-11e8-a160-89cc2ad9e8e2', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.originCountryTitle', { + title: i18n.translate('home.sampleData.flightsSpec.originCountryTitle', { defaultMessage: '[Flights] Origin Country vs. Destination Country', }), visState: @@ -345,15 +363,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '01c413e0-5395-11e8-99bf-1ba7b1bdaa61', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.totalFlightsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.totalFlightsTitle', { defaultMessage: '[Flights] Total Flights', }), visState: @@ -366,15 +385,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '2edf78b0-5395-11e8-99bf-1ba7b1bdaa61', type: 'visualization', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.averageTicketPriceTitle', { + title: i18n.translate('home.sampleData.flightsSpec.averageTicketPriceTitle', { defaultMessage: '[Flights] Average Ticket Price', }), visState: @@ -387,15 +407,16 @@ export const getSavedObjects = () => [ '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: 'ed78a660-53a0-11e8-acbd-0be0ad9d822b', type: 'visualization', updated_at: '2018-05-09T15:55:51.195Z', - version: 3, + version: '3', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.airportConnectionsTitle', { + title: i18n.translate('home.sampleData.flightsSpec.airportConnectionsTitle', { defaultMessage: '[Flights] Airport Connections (Hover Over Airport)', }), visState: @@ -407,12 +428,13 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', type: 'index-pattern', updated_at: '2018-05-09T15:49:03.736Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { title: 'kibana_sample_data_flights', @@ -422,12 +444,13 @@ export const getSavedObjects = () => [ fieldFormatMap: '{"hour_of_day":{"id":"number","params":{"pattern":"00"}},"AvgTicketPrice":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, + references: [], }, { id: '7adfa750-4c81-11e8-b3d7-01146121b73d', type: 'dashboard', updated_at: '2018-05-09T15:59:04.578Z', - version: 4, + version: '4', references: [ { name: 'panel_0', @@ -524,12 +547,12 @@ export const getSavedObjects = () => [ dashboard: '7.0.0', }, attributes: { - title: i18n.translate('server.sampleData.flightsSpec.globalFlightDashboardTitle', { + title: i18n.translate('home.sampleData.flightsSpec.globalFlightDashboardTitle', { defaultMessage: '[Flights] Global Flight Dashboard', }), hits: 0, description: i18n.translate( - 'server.sampleData.flightsSpec.globalFlightDashboardDescription', + 'home.sampleData.flightsSpec.globalFlightDashboardDescription', { defaultMessage: 'Analyze mock flight data for ES-Air, Logstash Airways, Kibana Airlines and JetBeats', diff --git a/src/legacy/server/sample_data/data_sets/index.js b/src/plugins/home/server/services/sample_data/data_sets/index.ts similarity index 100% rename from src/legacy/server/sample_data/data_sets/index.js rename to src/plugins/home/server/services/sample_data/data_sets/index.ts diff --git a/src/legacy/server/sample_data/data_sets/logs/field_mappings.js b/src/plugins/home/server/services/sample_data/data_sets/logs/field_mappings.ts similarity index 100% rename from src/legacy/server/sample_data/data_sets/logs/field_mappings.js rename to src/plugins/home/server/services/sample_data/data_sets/logs/field_mappings.ts diff --git a/src/legacy/server/sample_data/data_sets/logs/index.js b/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts similarity index 72% rename from src/legacy/server/sample_data/data_sets/logs/index.js rename to src/plugins/home/server/services/sample_data/data_sets/logs/index.ts index c6d955eb2ff51..bb6e2982f59a0 100644 --- a/src/legacy/server/sample_data/data_sets/logs/index.js +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/index.ts @@ -21,19 +21,25 @@ import path from 'path'; import { i18n } from '@kbn/i18n'; import { getSavedObjects } from './saved_objects'; import { fieldMappings } from './field_mappings'; +import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types'; -export function logsSpecProvider() { +const logsName = i18n.translate('home.sampleData.logsSpecTitle', { + defaultMessage: 'Sample web logs', +}); +const logsDescription = i18n.translate('home.sampleData.logsSpecDescription', { + defaultMessage: 'Sample data, visualizations, and dashboards for monitoring web logs.', +}); +const initialAppLinks = [] as AppLinkSchema[]; + +export const logsSpecProvider = function(): SampleDatasetSchema { return { id: 'logs', - name: i18n.translate('server.sampleData.logsSpecTitle', { - defaultMessage: 'Sample web logs', - }), - description: i18n.translate('server.sampleData.logsSpecDescription', { - defaultMessage: 'Sample data, visualizations, and dashboards for monitoring web logs.', - }), + name: logsName, + description: logsDescription, previewImagePath: '/plugins/kibana/home/sample_data_resources/logs/dashboard.png', darkPreviewImagePath: '/plugins/kibana/home/sample_data_resources/logs/dashboard_dark.png', overviewDashboard: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b', + appLinks: initialAppLinks, defaultIndex: '90943e30-9a47-11e8-b64d-95841ca0b247', savedObjects: getSavedObjects(), dataIndices: [ @@ -46,5 +52,6 @@ export function logsSpecProvider() { preserveDayOfWeekTimeOfDay: true, }, ], + status: 'not_installed', }; -} +}; diff --git a/src/legacy/server/sample_data/data_sets/logs/logs.json.gz b/src/plugins/home/server/services/sample_data/data_sets/logs/logs.json.gz similarity index 100% rename from src/legacy/server/sample_data/data_sets/logs/logs.json.gz rename to src/plugins/home/server/services/sample_data/data_sets/logs/logs.json.gz diff --git a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts similarity index 96% rename from src/legacy/server/sample_data/data_sets/logs/saved_objects.js rename to src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 9ce6e0d001e9e..883108651bfc5 100644 --- a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -17,17 +17,20 @@ * under the License. */ +/* eslint max-len: 0 */ +/* eslint-disable */ import { i18n } from '@kbn/i18n'; +import { SavedObject } from 'kibana/server'; -export const getSavedObjects = () => [ +export const getSavedObjects = (): SavedObject[] => [ { id: 'e1d0f010-9ee7-11e7-8711-e7a007dcef99', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.uniqueVisitorsTitle', { + title: i18n.translate('home.sampleData.logsSpec.uniqueVisitorsTitle', { defaultMessage: '[Logs] Unique Visitors vs. Average Bytes', }), visState: @@ -40,15 +43,16 @@ export const getSavedObjects = () => [ '{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.uniqueVisitorsByCountryTitle', { + title: i18n.translate('home.sampleData.logsSpec.uniqueVisitorsByCountryTitle', { defaultMessage: '[Logs] Unique Visitors by Country', }), visState: @@ -61,15 +65,16 @@ export const getSavedObjects = () => [ '{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '935afa20-e0cd-11e7-9d07-1398ccfcefa3', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.heatmapTitle', { + title: i18n.translate('home.sampleData.logsSpec.heatmapTitle', { defaultMessage: '[Logs] Heatmap', }), visState: @@ -83,15 +88,16 @@ export const getSavedObjects = () => [ '{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b', type: 'visualization', updated_at: '2018-08-29T13:23:20.897Z', - version: 2, + version: '2', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.hostVisitsBytesTableTitle', { + title: i18n.translate('home.sampleData.logsSpec.hostVisitsBytesTableTitle', { defaultMessage: '[Logs] Host, Visits and Bytes Table', }), visState: @@ -103,15 +109,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '69a34b00-9ee8-11e7-8711-e7a007dcef99', type: 'visualization', updated_at: '2018-08-29T13:24:46.136Z', - version: 2, + version: '2', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.goalsTitle', { + title: i18n.translate('home.sampleData.logsSpec.goalsTitle', { defaultMessage: '[Logs] Goals', }), visState: @@ -125,15 +132,16 @@ export const getSavedObjects = () => [ '{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '42b997f0-0c26-11e8-b0ec-3bb475f6b6ff', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.fileTypeScatterPlotTitle', { + title: i18n.translate('home.sampleData.logsSpec.fileTypeScatterPlotTitle', { defaultMessage: '[Logs] File Type Scatter Plot', }), visState: @@ -145,15 +153,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '7cbd2350-2223-11e8-b802-5bcf64c2cfb4', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle', { + title: i18n.translate('home.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle', { defaultMessage: '[Logs] Source and Destination Sankey Chart', }), visState: @@ -165,15 +174,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '314c6f60-2224-11e8-b802-5bcf64c2cfb4', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.responseCodesOverTimeTitle', { + title: i18n.translate('home.sampleData.logsSpec.responseCodesOverTimeTitle', { defaultMessage: '[Logs] Response Codes Over Time + Annotations', }), visState: @@ -185,15 +195,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '24a3e970-4257-11e8-b3aa-73fdaf54bfc9', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.inputControlsTitle', { + title: i18n.translate('home.sampleData.logsSpec.inputControlsTitle', { defaultMessage: '[Logs] Input Controls', }), visState: @@ -205,15 +216,16 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '14e2e710-4258-11e8-b3aa-73fdaf54bfc9', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.visitorOSTitle', { + title: i18n.translate('home.sampleData.logsSpec.visitorOSTitle', { defaultMessage: '[Logs] Visitors by OS', }), visState: @@ -226,15 +238,16 @@ export const getSavedObjects = () => [ '{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}', }, }, + references: [], }, { id: '47f2c680-a6e3-11e8-94b4-c30c0228351b', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { - title: i18n.translate('server.sampleData.logsSpec.markdownInstructionsTitle', { + title: i18n.translate('home.sampleData.logsSpec.markdownInstructionsTitle', { defaultMessage: '[Logs] Markdown Instructions', }), visState: @@ -246,12 +259,13 @@ export const getSavedObjects = () => [ searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', }, }, + references: [], }, { id: '90943e30-9a47-11e8-b64d-95841ca0b247', type: 'index-pattern', updated_at: '2018-08-29T13:22:17.617Z', - version: 1, + version: '1', migrationVersion: {}, attributes: { title: 'kibana_sample_data_logs', @@ -260,12 +274,13 @@ export const getSavedObjects = () => [ '[{"name":"@timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"agent","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"agent.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "agent"}}},{"name":"bytes","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"clientip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"event.dataset","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"extension","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"extension.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "extension"}}},{"name":"geo.coordinates","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.dest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.src","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.srcdest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"host","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"host.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "host"}}},{"name":"index","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"index.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "index"}}},{"name":"ip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"machine.os","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"machine.os.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "machine.os"}}},{"name":"machine.ram","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"memory","type":"number","esTypes":["double"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"message","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"message.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "message"}}},{"name":"phpmemory","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"referer","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"request","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"request.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "request"}}},{"name":"response","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"response.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "response"}}},{"name":"tags","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"tags.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "tags"}}},{"name":"timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"url","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"url.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "url"}}},{"name":"utc_time","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.getHour()","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', fieldFormatMap: '{"hour_of_day":{}}', }, + references: [], }, { id: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b', type: 'dashboard', updated_at: '2018-08-29T13:26:13.463Z', - version: 3, + version: '3', references: [ { name: 'panel_0', @@ -327,11 +342,11 @@ export const getSavedObjects = () => [ dashboard: '7.0.0', }, attributes: { - title: i18n.translate('server.sampleData.logsSpec.webTrafficTitle', { + title: i18n.translate('home.sampleData.logsSpec.webTrafficTitle', { defaultMessage: '[Logs] Web Traffic', }), hits: 0, - description: i18n.translate('server.sampleData.logsSpec.webTrafficDescription', { + description: i18n.translate('home.sampleData.logsSpec.webTrafficDescription', { defaultMessage: "Analyze mock web traffic log data for Elastic's website", }), panelsJSON: diff --git a/src/legacy/core_plugins/kibana/server/routes/api/scripts/register_languages.js b/src/plugins/home/server/services/sample_data/index.ts similarity index 78% rename from src/legacy/core_plugins/kibana/server/routes/api/scripts/register_languages.js rename to src/plugins/home/server/services/sample_data/index.ts index d28ca50e32088..f9fbee8fc6e81 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/scripts/register_languages.js +++ b/src/plugins/home/server/services/sample_data/index.ts @@ -16,13 +16,10 @@ * specific language governing permissions and limitations * under the License. */ +export { + SampleDataRegistry, + SampleDataRegistrySetup, + SampleDataRegistryStart, +} from './sample_data_registry'; -export function registerLanguages(server) { - server.route({ - path: '/api/kibana/scripts/languages', - method: 'GET', - handler: function() { - return ['painless', 'expression']; - }, - }); -} +export { SampleDatasetSchema, SampleDatasetProvider } from './lib/sample_dataset_registry_types'; diff --git a/src/legacy/server/sample_data/routes/lib/create_index_name.js b/src/plugins/home/server/services/sample_data/lib/create_index_name.ts similarity index 92% rename from src/legacy/server/sample_data/routes/lib/create_index_name.js rename to src/plugins/home/server/services/sample_data/lib/create_index_name.ts index de9dac7c96fa0..9aecef405d7ce 100644 --- a/src/legacy/server/sample_data/routes/lib/create_index_name.js +++ b/src/plugins/home/server/services/sample_data/lib/create_index_name.ts @@ -17,7 +17,7 @@ * under the License. */ -export function createIndexName(sampleDataSetId, dataIndexId) { +export const createIndexName = function(sampleDataSetId: string, dataIndexId: string): string { // Sample data schema was updated to support multiple indices in 6.5. // This if statement ensures that sample data sets that used a single index prior to the schema change // have the same index name to avoid orphaned indices when uninstalling. @@ -25,4 +25,4 @@ export function createIndexName(sampleDataSetId, dataIndexId) { return `kibana_sample_data_${sampleDataSetId}`; } return `kibana_sample_data_${sampleDataSetId}_${dataIndexId}`; -} +}; diff --git a/src/legacy/server/sample_data/routes/lib/load_data.js b/src/plugins/home/server/services/sample_data/lib/load_data.ts similarity index 90% rename from src/legacy/server/sample_data/routes/lib/load_data.js rename to src/plugins/home/server/services/sample_data/lib/load_data.ts index 9e343b644ec7b..481ed8da93dba 100644 --- a/src/legacy/server/sample_data/routes/lib/load_data.js +++ b/src/plugins/home/server/services/sample_data/lib/load_data.ts @@ -19,20 +19,20 @@ import readline from 'readline'; import fs from 'fs'; -import zlib from 'zlib'; +import { createUnzip } from 'zlib'; const BULK_INSERT_SIZE = 500; -export function loadData(path, bulkInsert) { +export function loadData(path: any, bulkInsert: (docs: any[]) => Promise) { return new Promise((resolve, reject) => { - let count = 0; - let docs = []; - let isPaused = false; + let count: number = 0; + let docs: any[] = []; + let isPaused: boolean = false; // pause does not stop lines already in buffer. Use smaller buffer size to avoid bulk inserting to many records const readStream = fs.createReadStream(path, { highWaterMark: 1024 * 4 }); - // eslint-disable-next-line new-cap - const lineStream = readline.createInterface({ input: readStream.pipe(zlib.Unzip()) }); + + const lineStream = readline.createInterface({ input: readStream.pipe(createUnzip()) }); const onClose = async () => { if (docs.length > 0) { try { @@ -46,7 +46,7 @@ export function loadData(path, bulkInsert) { }; lineStream.on('close', onClose); - const closeWithError = err => { + const closeWithError = (err: any) => { lineStream.removeListener('close', onClose); lineStream.close(); reject(err); diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts new file mode 100644 index 0000000000000..29cf2289fc5b3 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts @@ -0,0 +1,92 @@ +/* + * 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 { SavedObject } from 'src/core/server'; + +export enum DatasetStatusTypes { + NOT_INSTALLED = 'not_installed', + INSTALLED = 'installed', + UNKNOWN = 'unknown', +} +export interface SampleDatasetDashboardPanel { + sampleDataId: string; + dashboardId: string; + oldEmbeddableId: string; + embeddableId: string; + embeddableType: EmbeddableTypes; + embeddableConfig: object; +} +export enum EmbeddableTypes { + MAP_SAVED_OBJECT_TYPE = 'map', + SEARCH_EMBEDDABLE_TYPE = 'search', + VISUALIZE_EMBEDDABLE_TYPE = 'visualization', +} +export interface DataIndexSchema { + id: string; + + // path to newline delimented JSON file containing data relative to KIBANA_HOME + dataPath: string; + + // Object defining Elasticsearch field mappings (contents of index.mappings.type.properties) + fields: object; + + // times fields that will be updated relative to now when data is installed + timeFields: string[]; + + // Reference to now in your test data set. + // When data is installed, timestamps are converted to the present time. + // The distance between a timestamp and currentTimeMarker is preserved but the date and time will change. + // For example: + // sample data set: timestamp: 2018-01-01T00:00:00Z, currentTimeMarker: 2018-01-01T12:00:00Z + // installed data set: timestamp: 2018-04-18T20:33:14Z, currentTimeMarker: 2018-04-19T08:33:14Z + currentTimeMarker: string; + + // Set to true to move timestamp to current week, preserving day of week and time of day + // Relative distance from timestamp to currentTimeMarker will not remain the same + preserveDayOfWeekTimeOfDay: boolean; +} + +export interface AppLinkSchema { + path: string; + icon: string; + label: string; +} + +export interface SampleDatasetSchema { + id: string; + name: string; + description: string; + previewImagePath: string; + darkPreviewImagePath: string; + + // saved object id of main dashboard for sample data set + overviewDashboard: string; + appLinks: AppLinkSchema[]; + + // saved object id of default index-pattern for sample data set + defaultIndex: string; + + // Kibana saved objects (index patter, visualizations, dashboard, ...) + // Should provide a nice demo of Kibana's functionality with the sample data set + savedObjects: SavedObject[]; + dataIndices: DataIndexSchema[]; + status?: string | undefined; + statusMsg?: unknown; +} + +export type SampleDatasetProvider = () => SampleDatasetSchema; diff --git a/src/legacy/server/sample_data/data_set_schema.js b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts similarity index 100% rename from src/legacy/server/sample_data/data_set_schema.js rename to src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts diff --git a/src/legacy/server/sample_data/routes/lib/translate_timestamp.js b/src/plugins/home/server/services/sample_data/lib/translate_timestamp.ts similarity index 79% rename from src/legacy/server/sample_data/routes/lib/translate_timestamp.js rename to src/plugins/home/server/services/sample_data/lib/translate_timestamp.ts index 5d98eb34af247..b4e56cdfe7f36 100644 --- a/src/legacy/server/sample_data/routes/lib/translate_timestamp.js +++ b/src/plugins/home/server/services/sample_data/lib/translate_timestamp.ts @@ -19,29 +19,34 @@ const MILLISECONDS_IN_DAY = 86400000; -function iso8601ToDateIgnoringTime(iso8601) { +function iso8601ToDateIgnoringTime(iso8601: string) { const split = iso8601.split('-'); if (split.length < 3) { throw new Error('Unexpected timestamp format, expecting YYYY-MM-DDTHH:mm:ss'); } - const year = parseInt(split[0]); - const month = parseInt(split[1]) - 1; // javascript months are zero-based indexed - const date = parseInt(split[2]); + const year = parseInt(split[0], 10); + const month = parseInt(split[1], 10) - 1; // javascript months are zero-based indexed + const date = parseInt(split[2], 10); return new Date(year, month, date); } -export function dateToIso8601IgnoringTime(date) { +export function dateToIso8601IgnoringTime(date: Date) { // not using "Date.toISOString" because only using Date methods that deal with local time - const year = date.getFullYear(); - const month = date.getMonth() + 1; + const dateItem = new Date(date); + const year = dateItem.getFullYear(); + const month = dateItem.getMonth() + 1; const monthString = month < 10 ? `0${month}` : `${month}`; - const dateString = date.getDate() < 10 ? `0${date.getDate()}` : `${date.getDate()}`; + const dateString = dateItem.getDate() < 10 ? `0${dateItem.getDate()}` : `${dateItem.getDate()}`; return `${year}-${monthString}-${dateString}`; } // Translate source timestamp by targetReference timestamp, // perserving the distance between source and sourceReference -export function translateTimeRelativeToDifference(source, sourceReference, targetReference) { +export function translateTimeRelativeToDifference( + source: string, + sourceReference: any, + targetReference: any +) { const sourceDate = iso8601ToDateIgnoringTime(source); const sourceReferenceDate = iso8601ToDateIgnoringTime(sourceReference); const targetReferenceDate = iso8601ToDateIgnoringTime(targetReference); @@ -54,7 +59,11 @@ export function translateTimeRelativeToDifference(source, sourceReference, targe // Translate source timestamp by targetReference timestamp, // perserving the week distance between source and sourceReference and day of week of the source timestamp -export function translateTimeRelativeToWeek(source, sourceReference, targetReference) { +export function translateTimeRelativeToWeek( + source: string, + sourceReference: any, + targetReference: any +) { const sourceReferenceDate = iso8601ToDateIgnoringTime(sourceReference); const targetReferenceDate = iso8601ToDateIgnoringTime(targetReference); diff --git a/src/legacy/server/sample_data/routes/index.js b/src/plugins/home/server/services/sample_data/routes/index.ts similarity index 99% rename from src/legacy/server/sample_data/routes/index.js rename to src/plugins/home/server/services/sample_data/routes/index.ts index 800c165e9eae6..41b9458b9fbb6 100644 --- a/src/legacy/server/sample_data/routes/index.js +++ b/src/plugins/home/server/services/sample_data/routes/index.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - export { createListRoute } from './list'; export { createInstallRoute } from './install'; export { createUninstallRoute } from './uninstall'; diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts new file mode 100644 index 0000000000000..e2c5ce6883230 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -0,0 +1,186 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { IRouter, Logger, RequestHandlerContext } from 'src/core/server'; +import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; +import { createIndexName } from '../lib/create_index_name'; +import { + dateToIso8601IgnoringTime, + translateTimeRelativeToDifference, + translateTimeRelativeToWeek, +} from '../lib/translate_timestamp'; +import { loadData } from '../lib/load_data'; +import { SampleDataUsageTracker } from '../usage/usage'; + +const insertDataIntoIndex = ( + dataIndexConfig: any, + index: string, + nowReference: string, + context: RequestHandlerContext, + logger: Logger +) => { + function updateTimestamps(doc: any) { + dataIndexConfig.timeFields + .filter((timeFieldName: string) => doc[timeFieldName]) + .forEach((timeFieldName: string) => { + doc[timeFieldName] = dataIndexConfig.preserveDayOfWeekTimeOfDay + ? translateTimeRelativeToWeek( + doc[timeFieldName], + dataIndexConfig.currentTimeMarker, + nowReference + ) + : translateTimeRelativeToDifference( + doc[timeFieldName], + dataIndexConfig.currentTimeMarker, + nowReference + ); + }); + return doc; + } + + const bulkInsert = async (docs: any) => { + const insertCmd = { index: { _index: index } }; + const bulk: any[] = []; + docs.forEach((doc: any) => { + bulk.push(insertCmd); + bulk.push(updateTimestamps(doc)); + }); + const resp = await context.core.elasticsearch.adminClient.callAsCurrentUser('bulk', { + body: bulk, + }); + if (resp.errors) { + const errMsg = `sample_data install errors while bulk inserting. Elasticsearch response: ${JSON.stringify( + resp, + null, + '' + )}`; + logger.warn(errMsg); + return Promise.reject( + new Error(`Unable to load sample data into index "${index}", see kibana logs for details`) + ); + } + }; + return loadData(dataIndexConfig.dataPath, bulkInsert); // this returns a Promise +}; + +export function createInstallRoute( + router: IRouter, + sampleDatasets: SampleDatasetSchema[], + logger: Logger, + usageTracker: SampleDataUsageTracker +): void { + router.post( + { + path: '/api/sample_data/{id}', + validate: { + params: schema.object({ id: schema.string() }), + // TODO validate now as date + query: schema.object({ now: schema.maybe(schema.string()) }), + }, + }, + async (context, req, res) => { + const { params, query } = req; + const sampleDataset = sampleDatasets.find(({ id }) => id === params.id); + if (!sampleDataset) { + return res.notFound(); + } + // @ts-ignore Custom query validation used + const now = query.now ? new Date(query.now) : new Date(); + const nowReference = dateToIso8601IgnoringTime(now); + const counts = {}; + for (let i = 0; i < sampleDataset.dataIndices.length; i++) { + const dataIndexConfig = sampleDataset.dataIndices[i]; + const index = createIndexName(sampleDataset.id, dataIndexConfig.id); + + // clean up any old installation of dataset + try { + await context.core.elasticsearch.dataClient.callAsCurrentUser('indices.delete', { + index, + }); + } catch (err) { + // ignore delete errors + } + + try { + const createIndexParams = { + index, + body: { + settings: { index: { number_of_shards: 1, auto_expand_replicas: '0-1' } }, + mappings: { properties: dataIndexConfig.fields }, + }, + }; + await context.core.elasticsearch.dataClient.callAsCurrentUser( + 'indices.create', + createIndexParams + ); + } catch (err) { + const errMsg = `Unable to create sample data index "${index}", error: ${err.message}`; + logger.warn(errMsg); + return res.customError({ body: errMsg, statusCode: err.status }); + } + + try { + const count = await insertDataIntoIndex( + dataIndexConfig, + index, + nowReference, + context, + logger + ); + (counts as any)[index] = count; + } catch (err) { + const errMsg = `sample_data install errors while loading data. Error: ${err}`; + logger.warn(errMsg); + return res.internalError({ body: errMsg }); + } + } + + let createResults; + try { + createResults = await context.core.savedObjects.client.bulkCreate( + sampleDataset.savedObjects, + { overwrite: true } + ); + } catch (err) { + const errMsg = `bulkCreate failed, error: ${err.message}`; + logger.warn(errMsg); + return res.internalError({ body: errMsg }); + } + const errors = createResults.saved_objects.filter(savedObjectCreateResult => { + return Boolean(savedObjectCreateResult.error); + }); + if (errors.length > 0) { + const errMsg = `sample_data install errors while loading saved objects. Errors: ${errors.join( + ',' + )}`; + logger.warn(errMsg); + return res.customError({ body: errMsg, statusCode: 403 }); + } + usageTracker.addInstall(params.id); + + // FINALLY + return res.ok({ + body: { + elasticsearchIndicesCreated: counts, + kibanaSavedObjectsLoaded: sampleDataset.savedObjects.length, + }, + }); + } + ); +} diff --git a/src/plugins/home/server/services/sample_data/routes/list.ts b/src/plugins/home/server/services/sample_data/routes/list.ts new file mode 100644 index 0000000000000..37ebab1c168d2 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/routes/list.ts @@ -0,0 +1,92 @@ +/* + * 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 { isBoom } from 'boom'; +import { IRouter } from 'src/core/server'; +import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; +import { createIndexName } from '../lib/create_index_name'; + +const NOT_INSTALLED = 'not_installed'; +const INSTALLED = 'installed'; +const UNKNOWN = 'unknown'; + +export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSchema[]) => { + router.get({ path: '/api/sample_data', validate: false }, async (context, req, res) => { + const registeredSampleDatasets = sampleDatasets.map(sampleDataset => { + return { + id: sampleDataset.id, + name: sampleDataset.name, + description: sampleDataset.description, + previewImagePath: sampleDataset.previewImagePath, + darkPreviewImagePath: sampleDataset.darkPreviewImagePath, + overviewDashboard: sampleDataset.overviewDashboard, + appLinks: sampleDataset.appLinks, + defaultIndex: sampleDataset.defaultIndex, + dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })), + status: sampleDataset.status, + statusMsg: sampleDataset.statusMsg, + }; + }); + const isInstalledPromises = registeredSampleDatasets.map(async sampleDataset => { + for (let i = 0; i < sampleDataset.dataIndices.length; i++) { + const dataIndexConfig = sampleDataset.dataIndices[i]; + const index = createIndexName(sampleDataset.id, dataIndexConfig.id); + try { + const indexExists = await context.core.elasticsearch.dataClient.callAsCurrentUser( + 'indices.exists', + { index } + ); + if (!indexExists) { + sampleDataset.status = NOT_INSTALLED; + return; + } + + const { count } = await context.core.elasticsearch.dataClient.callAsCurrentUser('count', { + index, + }); + if (count === 0) { + sampleDataset.status = NOT_INSTALLED; + return; + } + } catch (err) { + sampleDataset.status = UNKNOWN; + sampleDataset.statusMsg = err.message; + return; + } + } + try { + await context.core.savedObjects.client.get('dashboard', sampleDataset.overviewDashboard); + } catch (err) { + // savedObjectClient.get() throws an boom error when object is not found. + if (isBoom(err) && err.output.statusCode === 404) { + sampleDataset.status = NOT_INSTALLED; + return; + } + + sampleDataset.status = UNKNOWN; + sampleDataset.statusMsg = err.message; + return; + } + + sampleDataset.status = INSTALLED; + }); + + await Promise.all(isInstalledPromises); + return res.ok({ body: registeredSampleDatasets }); + }); +}; diff --git a/src/plugins/home/server/services/sample_data/routes/uninstall.ts b/src/plugins/home/server/services/sample_data/routes/uninstall.ts new file mode 100644 index 0000000000000..64fb2b8b3a547 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/routes/uninstall.ts @@ -0,0 +1,96 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import _ from 'lodash'; +import { IRouter } from 'src/core/server'; +import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; +import { createIndexName } from '../lib/create_index_name'; +import { SampleDataUsageTracker } from '../usage/usage'; + +export function createUninstallRoute( + router: IRouter, + sampleDatasets: SampleDatasetSchema[], + usageTracker: SampleDataUsageTracker +): void { + router.delete( + { + path: '/api/sample_data/{id}', + validate: { + params: schema.object({ id: schema.string() }), + }, + }, + async ( + { + core: { + elasticsearch: { + dataClient: { callAsCurrentUser }, + }, + savedObjects: { client: savedObjectsClient }, + }, + }, + request, + response + ) => { + const sampleDataset = sampleDatasets.find(({ id }) => id === request.params.id); + + if (!sampleDataset) { + return response.notFound(); + } + + for (let i = 0; i < sampleDataset.dataIndices.length; i++) { + const dataIndexConfig = sampleDataset.dataIndices[i]; + const index = createIndexName(sampleDataset.id, dataIndexConfig.id); + + try { + await callAsCurrentUser('indices.delete', { index }); + } catch (err) { + return response.customError({ + statusCode: err.status, + body: { + message: `Unable to delete sample data index "${index}", error: ${err.message}`, + }, + }); + } + } + + const deletePromises = sampleDataset.savedObjects.map(({ type, id }) => + savedObjectsClient.delete(type, id) + ); + + try { + await Promise.all(deletePromises); + } catch (err) { + // ignore 404s since users could have deleted some of the saved objects via the UI + if (_.get(err, 'output.statusCode') !== 404) { + return response.customError({ + statusCode: err.status, + body: { + message: `Unable to delete sample dataset saved objects, error: ${err.message}`, + }, + }); + } + } + + // track the usage operation in a non-blocking way + usageTracker.addUninstall(request.params.id); + + return response.noContent(); + } + ); +} diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.mock.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.mock.ts new file mode 100644 index 0000000000000..4d0fb4f96023a --- /dev/null +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.mock.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + SampleDataRegistrySetup, + SampleDataRegistryStart, + SampleDataRegistry, +} from './sample_data_registry'; + +const createSetupMock = (): jest.Mocked => { + const setup = { + registerSampleDataset: jest.fn(), + getSampleDatasets: jest.fn(), + addSavedObjectsToSampleDataset: jest.fn(), + addAppLinksToSampleDataset: jest.fn(), + replacePanelInSampleDatasetDashboard: jest.fn(), + }; + return setup; +}; + +const createStartMock = (): jest.Mocked => { + const start = {}; + return start; +}; + +const createMock = (): jest.Mocked> => { + const service = { + setup: jest.fn(), + start: jest.fn(), + }; + service.setup.mockImplementation(createSetupMock); + service.start.mockImplementation(createStartMock); + return service; +}; + +export const sampleDataRegistryMock = { + createSetup: createSetupMock, + createStart: createStartMock, + create: createMock, +}; diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts new file mode 100644 index 0000000000000..7a4909668fff2 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -0,0 +1,182 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Joi from 'joi'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { SavedObject } from 'src/core/public'; +import { + SampleDatasetProvider, + SampleDatasetSchema, + AppLinkSchema, + SampleDatasetDashboardPanel, +} from './lib/sample_dataset_registry_types'; +import { sampleDataSchema } from './lib/sample_dataset_schema'; + +import { flightsSpecProvider, logsSpecProvider, ecommerceSpecProvider } from './data_sets'; +import { createListRoute, createInstallRoute } from './routes'; +import { UsageCollectionSetup } from '../../../../usage_collection/server'; +import { makeSampleDataUsageCollector, usage } from './usage'; +import { createUninstallRoute } from './routes/uninstall'; + +const flightsSampleDataset = flightsSpecProvider(); +const logsSampleDataset = logsSpecProvider(); +const ecommerceSampleDataset = ecommerceSpecProvider(); + +export class SampleDataRegistry { + constructor(private readonly initContext: PluginInitializerContext) {} + private readonly sampleDatasets: SampleDatasetSchema[] = [ + flightsSampleDataset, + logsSampleDataset, + ecommerceSampleDataset, + ]; + + public setup(core: CoreSetup, usageCollections: UsageCollectionSetup | undefined) { + if (usageCollections) { + makeSampleDataUsageCollector(usageCollections, this.initContext); + } + const usageTracker = usage( + core.savedObjects, + this.initContext.logger.get('sample_data', 'telemetry') + ); + const router = core.http.createRouter(); + createListRoute(router, this.sampleDatasets); + createInstallRoute( + router, + this.sampleDatasets, + this.initContext.logger.get('sampleData'), + usageTracker + ); + createUninstallRoute(router, this.sampleDatasets, usageTracker); + + return { + registerSampleDataset: (specProvider: SampleDatasetProvider) => { + const { error, value } = Joi.validate(specProvider(), sampleDataSchema); + + if (error) { + throw new Error(`Unable to register sample dataset spec because it's invalid. ${error}`); + } + const defaultIndexSavedObjectJson = value.savedObjects.find((savedObjectJson: any) => { + return ( + savedObjectJson.type === 'index-pattern' && savedObjectJson.id === value.defaultIndex + ); + }); + if (!defaultIndexSavedObjectJson) { + throw new Error( + `Unable to register sample dataset spec, defaultIndex: "${value.defaultIndex}" does not exist in savedObjects list.` + ); + } + + const dashboardSavedObjectJson = value.savedObjects.find((savedObjectJson: any) => { + return ( + savedObjectJson.type === 'dashboard' && savedObjectJson.id === value.overviewDashboard + ); + }); + if (!dashboardSavedObjectJson) { + throw new Error( + `Unable to register sample dataset spec, overviewDashboard: "${value.overviewDashboard}" does not exist in savedObject list.` + ); + } + this.sampleDatasets.push(value); + }, + getSampleDatasets: () => this.sampleDatasets, + + addSavedObjectsToSampleDataset: (id: string, savedObjects: SavedObject[]) => { + const sampleDataset = this.sampleDatasets.find(dataset => { + return dataset.id === id; + }); + + if (!sampleDataset) { + throw new Error(`Unable to find sample dataset with id: ${id}`); + } + + sampleDataset.savedObjects = sampleDataset.savedObjects.concat(savedObjects); + }, + + addAppLinksToSampleDataset: (id: string, appLinks: AppLinkSchema[]) => { + const sampleDataset = this.sampleDatasets.find(dataset => { + return dataset.id === id; + }); + + if (!sampleDataset) { + throw new Error(`Unable to find sample dataset with id: ${id}`); + } + + sampleDataset.appLinks = sampleDataset.appLinks + ? sampleDataset.appLinks.concat(appLinks) + : []; + }, + + replacePanelInSampleDatasetDashboard: ({ + sampleDataId, + dashboardId, + oldEmbeddableId, + embeddableId, + embeddableType, + embeddableConfig, + }: SampleDatasetDashboardPanel) => { + const sampleDataset = this.sampleDatasets.find(dataset => { + return dataset.id === sampleDataId; + }); + if (!sampleDataset) { + throw new Error(`Unable to find sample dataset with id: ${sampleDataId}`); + } + + const dashboard = sampleDataset.savedObjects.find((savedObject: SavedObject) => { + return savedObject.id === dashboardId && savedObject.type === 'dashboard'; + }); + if (!dashboard) { + throw new Error(`Unable to find dashboard with id: ${dashboardId}`); + } + try { + const reference = dashboard.references.find((referenceItem: any) => { + return referenceItem.id === oldEmbeddableId; + }); + if (!reference) { + throw new Error(`Unable to find reference for embeddable: ${oldEmbeddableId}`); + } + reference.type = embeddableType; + reference.id = embeddableId; + + const panels = JSON.parse(dashboard.attributes.panelsJSON); + const panel = panels.find((panelItem: any) => { + return panelItem.panelRefName === reference.name; + }); + if (!panel) { + throw new Error(`Unable to find panel for reference: ${reference.name}`); + } + panel.embeddableConfig = embeddableConfig; + dashboard.attributes.panelsJSON = JSON.stringify(panels); + } catch (error) { + throw new Error( + `Unable to replace panel with embeddable ${oldEmbeddableId}, error: ${error}` + ); + } + }, + }; + } + + public start() { + return {}; + } +} +/** @public */ +export type SampleDataRegistrySetup = ReturnType; + +/** @public */ +export type SampleDataRegistryStart = ReturnType; diff --git a/src/legacy/server/sample_data/usage/collector.ts b/src/plugins/home/server/services/sample_data/usage/collector.ts similarity index 75% rename from src/legacy/server/sample_data/usage/collector.ts rename to src/plugins/home/server/services/sample_data/usage/collector.ts index bcb5e7be2597a..19ceceb4cba14 100644 --- a/src/legacy/server/sample_data/usage/collector.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector.ts @@ -17,17 +17,19 @@ * under the License. */ -import { Server } from 'hapi'; +import { PluginInitializerContext } from 'kibana/server'; +import { first } from 'rxjs/operators'; import { fetchProvider } from './collector_fetch'; -import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server'; +import { UsageCollectionSetup } from '../../../../../usage_collection/server'; -export function makeSampleDataUsageCollector( +export async function makeSampleDataUsageCollector( usageCollection: UsageCollectionSetup, - server: Server + context: PluginInitializerContext ) { let index: string; try { - index = server.config().get('kibana.index'); + const config = await context.config.legacy.globalConfig$.pipe(first()).toPromise(); + index = config.kibana.index; } catch (err) { return; // kibana plugin is not enabled (test environment) } diff --git a/src/legacy/server/sample_data/usage/collector_fetch.test.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts similarity index 100% rename from src/legacy/server/sample_data/usage/collector_fetch.test.ts rename to src/plugins/home/server/services/sample_data/usage/collector_fetch.test.ts diff --git a/src/legacy/server/sample_data/usage/collector_fetch.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts similarity index 100% rename from src/legacy/server/sample_data/usage/collector_fetch.ts rename to src/plugins/home/server/services/sample_data/usage/collector_fetch.ts diff --git a/src/legacy/server/sample_data/usage/index.ts b/src/plugins/home/server/services/sample_data/usage/index.ts similarity index 100% rename from src/legacy/server/sample_data/usage/index.ts rename to src/plugins/home/server/services/sample_data/usage/index.ts diff --git a/src/legacy/server/sample_data/usage/usage.ts b/src/plugins/home/server/services/sample_data/usage/usage.ts similarity index 61% rename from src/legacy/server/sample_data/usage/usage.ts rename to src/plugins/home/server/services/sample_data/usage/usage.ts index 0fb17128b102c..a06dde387bb36 100644 --- a/src/legacy/server/sample_data/usage/usage.ts +++ b/src/plugins/home/server/services/sample_data/usage/usage.ts @@ -17,40 +17,39 @@ * under the License. */ -import * as Hapi from 'hapi'; +import { Logger, SavedObjectsServiceSetup } from 'kibana/server'; const SAVED_OBJECT_ID = 'sample-data-telemetry'; -export function usage(request: Hapi.Request) { - const { server } = request; +export interface SampleDataUsageTracker { + addInstall(dataSet: string): void; + addUninstall(dataSet: string): void; +} +export function usage( + savedObjects: SavedObjectsServiceSetup, + logger: Logger +): SampleDataUsageTracker { const handleIncrementError = (err: Error) => { - if (err != null) { - server.log(['debug', 'sample_data', 'telemetry'], err.stack); + if (err && err.stack) { + logger.debug(err.stack); } - server.log( - ['warning', 'sample_data', 'telemetry'], - `saved objects repository incrementCounter encountered an error: ${err}` - ); + logger.warn(`saved objects repository incrementCounter encountered an error: ${err}`); }; - const { - savedObjects: { getSavedObjectsRepository }, - } = server; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); + const internalRepository = savedObjects.createInternalRepository(); return { addInstall: async (dataSet: string) => { try { - internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `installCount`); + await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `installCount`); } catch (err) { handleIncrementError(err); } }, addUninstall: async (dataSet: string) => { try { - internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `unInstallCount`); + await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `unInstallCount`); } catch (err) { handleIncrementError(err); } diff --git a/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts b/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts index d081639e5c7e2..951ce935760a1 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts @@ -94,17 +94,5 @@ export interface TutorialSchema { savedObjectsInstallMsg: string; } export type TutorialProvider = (context: { [key: string]: unknown }) => TutorialSchema; -export type TutorialContextFactory = ( - req: KibanaRequest< - Readonly<{ - [x: string]: any; - }>, - Readonly<{ - [x: string]: any; - }>, - Readonly<{ - [x: string]: any; - }> - > -) => { [key: string]: unknown }; +export type TutorialContextFactory = (req: KibanaRequest) => { [key: string]: unknown }; export type ScopedTutorialContextFactory = (...args: any[]) => any; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx index bab710cdca595..33d4dcfd6606a 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx @@ -39,7 +39,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/kibana/public/visualize/embeddable/constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/kibana/public/visualize_embeddable/constants'; export interface OnSaveProps { newTitle: string; diff --git a/src/plugins/newsfeed/public/lib/api.test.ts b/src/plugins/newsfeed/public/lib/api.test.ts index 4383b9e0f7dab..9d64dd26da047 100644 --- a/src/plugins/newsfeed/public/lib/api.test.ts +++ b/src/plugins/newsfeed/public/lib/api.test.ts @@ -21,7 +21,7 @@ import { take, tap, toArray } from 'rxjs/operators'; import { interval, race } from 'rxjs'; import sinon, { stub } from 'sinon'; import moment from 'moment'; -import { HttpServiceBase } from 'src/core/public'; +import { HttpSetup } from 'src/core/public'; import { NEWSFEED_HASH_SET_STORAGE_KEY, NEWSFEED_LAST_FETCH_STORAGE_KEY } from '../../constants'; import { ApiItem, NewsfeedItem, NewsfeedPluginInjectedConfig } from '../../types'; import { NewsfeedApiDriver, getApi } from './api'; @@ -444,7 +444,7 @@ describe('getApi', () => { const mockHttpGet = jest.fn(); let httpMock = ({ fetch: mockHttpGet, - } as unknown) as HttpServiceBase; + } as unknown) as HttpSetup; const getHttpMockWithItems = (mockApiItems: ApiItem[]) => ( arg1: string, arg2: { method: string } @@ -478,7 +478,7 @@ describe('getApi', () => { }; httpMock = ({ fetch: mockHttpGet, - } as unknown) as HttpServiceBase; + } as unknown) as HttpSetup; }); it('creates a result', done => { diff --git a/src/plugins/newsfeed/public/lib/api.ts b/src/plugins/newsfeed/public/lib/api.ts index 6920dd9b2bccc..bfeff4aa3e37b 100644 --- a/src/plugins/newsfeed/public/lib/api.ts +++ b/src/plugins/newsfeed/public/lib/api.ts @@ -21,7 +21,7 @@ import * as Rx from 'rxjs'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { catchError, filter, mergeMap, tap } from 'rxjs/operators'; -import { HttpServiceBase } from 'src/core/public'; +import { HttpSetup } from 'src/core/public'; import { NEWSFEED_FALLBACK_LANGUAGE, NEWSFEED_LAST_FETCH_STORAGE_KEY, @@ -77,7 +77,7 @@ export class NewsfeedApiDriver { return { previous: old, current: updatedHashes }; } - fetchNewsfeedItems(http: HttpServiceBase, config: ApiConfig): Rx.Observable { + fetchNewsfeedItems(http: HttpSetup, config: ApiConfig): Rx.Observable { const urlPath = config.pathTemplate.replace('{VERSION}', this.kibanaVersion); const fullUrl = config.urlRoot + urlPath; @@ -166,7 +166,7 @@ export class NewsfeedApiDriver { * Computes hasNew value from new item hashes saved in localStorage */ export function getApi( - http: HttpServiceBase, + http: HttpSetup, config: NewsfeedPluginInjectedConfig['newsfeed'], kibanaVersion: string ): Rx.Observable { diff --git a/src/plugins/usage_collection/public/plugin.ts b/src/plugins/usage_collection/public/plugin.ts index 2ecc6c8bc2038..7f80076a483b4 100644 --- a/src/plugins/usage_collection/public/plugin.ts +++ b/src/plugins/usage_collection/public/plugin.ts @@ -25,7 +25,7 @@ import { Plugin, CoreSetup, CoreStart, - HttpServiceBase, + HttpSetup, } from '../../../core/public'; interface PublicConfigType { @@ -41,7 +41,7 @@ export interface UsageCollectionSetup { METRIC_TYPE: typeof METRIC_TYPE; } -export function isUnauthenticated(http: HttpServiceBase) { +export function isUnauthenticated(http: HttpSetup) { const { anonymousPaths } = http; return anonymousPaths.isAnonymous(window.location.pathname); } diff --git a/src/plugins/usage_collection/public/services/create_reporter.ts b/src/plugins/usage_collection/public/services/create_reporter.ts index 6bc35de8972c3..304eb639d62ca 100644 --- a/src/plugins/usage_collection/public/services/create_reporter.ts +++ b/src/plugins/usage_collection/public/services/create_reporter.ts @@ -18,12 +18,12 @@ */ import { Reporter, Storage } from '@kbn/analytics'; -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; interface AnalyicsReporterConfig { localStorage: Storage; debug: boolean; - fetch: HttpServiceBase; + fetch: HttpSetup; } export function createReporter(config: AnalyicsReporterConfig): Reporter { diff --git a/src/plugins/vis_type_timeseries/kibana.json b/src/plugins/vis_type_timeseries/kibana.json new file mode 100644 index 0000000000000..f9a368e85ed49 --- /dev/null +++ b/src/plugins/vis_type_timeseries/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "metrics", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true +} \ No newline at end of file diff --git a/src/plugins/vis_type_timeseries/server/index.ts b/src/plugins/vis_type_timeseries/server/index.ts new file mode 100644 index 0000000000000..599726612a936 --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/index.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'src/core/server'; +import { VisTypeTimeseriesPlugin } from './plugin'; +export { VisTypeTimeseriesSetup, Framework } from './plugin'; + +export const config = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), +}; + +export type VisTypeTimeseriesConfig = TypeOf; + +export function plugin(initializerContext: PluginInitializerContext) { + return new VisTypeTimeseriesPlugin(initializerContext); +} diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts new file mode 100644 index 0000000000000..f508aa250454f --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -0,0 +1,96 @@ +/* + * 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, + RequestHandlerContext, + Logger, +} from 'src/core/server'; +import { Observable } from 'rxjs'; +import { Server } from 'hapi'; +import { once } from 'lodash'; +import { VisTypeTimeseriesConfig } from '.'; +import { + init, + getVisData, + GetVisData, + GetVisDataOptions, +} from '../../../legacy/core_plugins/vis_type_timeseries/server'; + +export interface LegacySetup { + server: Server; +} + +export interface VisTypeTimeseriesSetup { + /** @deprecated */ + __legacy: { + config$: Observable; + registerLegacyAPI: (__LEGACY: LegacySetup) => void; + }; + getVisData: ( + requestContext: RequestHandlerContext, + options: GetVisDataOptions + ) => ReturnType; +} + +export interface Framework { + core: CoreSetup; + plugins: any; + config$: Observable; + globalConfig$: PluginInitializerContext['config']['legacy']['globalConfig$']; + logger: Logger; +} + +export class VisTypeTimeseriesPlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public setup(core: CoreSetup, plugins: any) { + const logger = this.initializerContext.logger.get('visTypeTimeseries'); + const config$ = this.initializerContext.config.create(); + // Global config contains things like the ES shard timeout + const globalConfig$ = this.initializerContext.config.legacy.globalConfig$; + + const framework: Framework = { + core, + plugins, + config$, + globalConfig$, + logger, + }; + + return { + __legacy: { + config$, + registerLegacyAPI: once((__LEGACY: LegacySetup) => { + init(framework, __LEGACY); + }), + }, + getVisData: async (requestContext: RequestHandlerContext, options: GetVisDataOptions) => { + return await getVisData(requestContext, options, framework); + }, + }; + } + + public start(core: CoreStart) {} +} diff --git a/src/test_utils/public/stub_field_formats.ts b/src/test_utils/public/stub_field_formats.ts index da1a31f1cc7a5..ea46710c0dc84 100644 --- a/src/test_utils/public/stub_field_formats.ts +++ b/src/test_utils/public/stub_field_formats.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { IUiSettingsClient } from 'kibana/public'; +import { CoreSetup } from 'kibana/public'; import { FieldFormatRegisty, @@ -37,7 +37,7 @@ import { UrlFormat, } from '../../plugins/data/public/'; -export const getFieldFormatsRegistry = (uiSettings: IUiSettingsClient) => { +export const getFieldFormatsRegistry = (core: CoreSetup) => { const fieldFormats = new FieldFormatRegisty(); fieldFormats.register([ @@ -58,7 +58,7 @@ export const getFieldFormatsRegistry = (uiSettings: IUiSettingsClient) => { UrlFormat, ]); - fieldFormats.init(uiSettings); + fieldFormats.init(core); return fieldFormats; }; diff --git a/src/test_utils/public/stub_index_pattern.js b/src/test_utils/public/stub_index_pattern.js index 14931b938732c..7807821ab09bd 100644 --- a/src/test_utils/public/stub_index_pattern.js +++ b/src/test_utils/public/stub_index_pattern.js @@ -40,8 +40,8 @@ setFieldFormats({ import { getFieldFormatsRegistry } from './stub_field_formats'; -export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { - const registeredFieldFormats = getFieldFormatsRegistry(uiSettings); +export default function StubIndexPattern(pattern, getConfig, timeField, fields, core) { + const registeredFieldFormats = getFieldFormatsRegistry(core); this.id = pattern; this.title = pattern; diff --git a/test/api_integration/apis/general/prototype_pollution.ts b/test/api_integration/apis/general/prototype_pollution.ts index 1b732dc81afa9..3e74480ebe9eb 100644 --- a/test/api_integration/apis/general/prototype_pollution.ts +++ b/test/api_integration/apis/general/prototype_pollution.ts @@ -26,7 +26,7 @@ export default function({ getService }: FtrProviderContext) { describe('prototype pollution smoke test', () => { it('prevents payloads with the "constructor.prototype" pollution vector from being accepted', async () => { await supertest - .post('/api/sample_data/some_data_id') + .post('/api/saved_objects/_log_legacy_import') .send([ { constructor: { @@ -44,7 +44,7 @@ export default function({ getService }: FtrProviderContext) { it('prevents payloads with the "__proto__" pollution vector from being accepted', async () => { await supertest - .post('/api/sample_data/some_data_id') + .post('/api/saved_objects/_log_legacy_import') .send(JSON.parse(`{"foo": { "__proto__": {} } }`)) .expect(400, { statusCode: 400, diff --git a/test/api_integration/apis/home/sample_data.js b/test/api_integration/apis/home/sample_data.js index 4aaf8c75092ae..d5f03cc714f2f 100644 --- a/test/api_integration/apis/home/sample_data.js +++ b/test/api_integration/apis/home/sample_data.js @@ -96,7 +96,7 @@ export default function({ getService }) { await supertest .delete(`/api/sample_data/flights`) .set('kbn-xsrf', 'kibana') - .expect(200); + .expect(204); }); it('should remove elasticsearch index containing sample data', async () => { diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js index 2e0c4d449d447..90f02c36b3b7f 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.js +++ b/test/functional/apps/dashboard/create_and_add_embeddables.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; -import { VisualizeConstants } from '../../../../src/legacy/core_plugins/kibana/public/visualize/visualize_constants'; +import { VisualizeConstants } from '../../../../src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants'; export default function({ getService, getPageObjects }) { const retry = getService('retry'); diff --git a/test/functional/apps/dashboard/empty_dashboard.js b/test/functional/apps/dashboard/empty_dashboard.js index 0f10eabcba434..d46daff183abf 100644 --- a/test/functional/apps/dashboard/empty_dashboard.js +++ b/test/functional/apps/dashboard/empty_dashboard.js @@ -28,8 +28,7 @@ export default function({ getService, getPageObjects }) { const dashboardExpect = getService('dashboardExpect'); const PageObjects = getPageObjects(['common', 'dashboard']); - // FLAKY: https://github.com/elastic/kibana/issues/48236 - describe.skip('empty dashboard', () => { + describe('empty dashboard', () => { before(async () => { await esArchiver.load('dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ @@ -57,11 +56,12 @@ export default function({ getService, getPageObjects }) { }); it('should add new visualization from dashboard', async () => { + await testSubjects.exists('addVisualizationButton'); await testSubjects.click('addVisualizationButton'); - await dashboardVisualizations.createAndAddMarkdown( - { name: 'Dashboard Test Markdown', markdown: 'Markdown text' }, - false - ); + await dashboardVisualizations.createAndAddMarkdown({ + name: 'Dashboard Test Markdown', + markdown: 'Markdown text', + }); await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.markdownWithValuesExists(['Markdown text']); }); diff --git a/test/functional/apps/dashboard/panel_controls.js b/test/functional/apps/dashboard/panel_controls.js index b69e031f84667..683f3683e65e5 100644 --- a/test/functional/apps/dashboard/panel_controls.js +++ b/test/functional/apps/dashboard/panel_controls.js @@ -24,7 +24,7 @@ import { AREA_CHART_VIS_NAME, LINE_CHART_VIS_NAME, } from '../../page_objects/dashboard_page'; -import { VisualizeConstants } from '../../../../src/legacy/core_plugins/kibana/public/visualize/visualize_constants'; +import { VisualizeConstants } from '../../../../src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants'; export default function({ getService, getPageObjects }) { const browser = getService('browser'); diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.js index ec258aa823405..b0f1a3304a9b8 100644 --- a/test/functional/page_objects/dashboard_page.js +++ b/test/functional/page_objects/dashboard_page.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { DashboardConstants } from '../../../src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants'; +import { DashboardConstants } from '../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants'; export const PIE_CHART_VIS_NAME = 'Visualization PieChart'; export const AREA_CHART_VIS_NAME = 'Visualization漢字 AreaChart'; diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js index 7537094bae3dd..7029fbf9e1350 100644 --- a/test/functional/page_objects/discover_page.js +++ b/test/functional/page_objects/discover_page.js @@ -30,7 +30,6 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { const globalNav = getService('globalNav'); const config = getService('config'); const defaultFindTimeout = config.get('timeouts.find'); - const comboBox = getService('comboBox'); const elasticChart = getService('elasticChart'); class DiscoverPage { @@ -279,7 +278,9 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { async selectIndexPattern(indexPattern) { await testSubjects.click('indexPattern-switch-link'); - await comboBox.set('index-pattern-selection', indexPattern); + await find.clickByCssSelector( + `[data-test-subj="indexPattern-switcher"] [title="${indexPattern}*"]` + ); await PageObjects.header.waitUntilLoadingHasFinished(); } diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index 33b7515b58d2d..c1ea8be9be98b 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -17,7 +17,7 @@ * under the License. */ -import { VisualizeConstants } from '../../../src/legacy/core_plugins/kibana/public/visualize/visualize_constants'; +import { VisualizeConstants } from '../../../src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants'; import Bluebird from 'bluebird'; import expect from '@kbn/expect'; diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index ab686f4d5ffec..72bcc07ab98cf 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -30,13 +30,7 @@ import { Browsers } from './remote/browsers'; export async function BrowserProvider({ getService }: FtrProviderContext) { const log = getService('log'); - const { driver, browserType, consoleLog$ } = await getService('__webdriver__').init(); - - consoleLog$.subscribe(({ message, level }) => { - log[level === 'SEVERE' || level === 'error' ? 'error' : 'debug']( - `browser[${level}] ${message}` - ); - }); + const { driver, browserType } = await getService('__webdriver__').init(); const isW3CEnabled = (driver as any).executor_.w3c === true; diff --git a/test/functional/services/dashboard/visualizations.js b/test/functional/services/dashboard/visualizations.js index 1facf49651b19..c4e7fe6ad3bd9 100644 --- a/test/functional/services/dashboard/visualizations.js +++ b/test/functional/services/dashboard/visualizations.js @@ -19,6 +19,8 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { const log = getService('log'); + const find = getService('find'); + const retry = getService('retry'); const queryBar = getService('queryBar'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -72,16 +74,38 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { await dashboardAddPanel.addSavedSearch(name); } - async createAndAddMarkdown({ name, markdown }, checkForAddPanel = true) { + async clickAddVisualizationButton() { + log.debug('DashboardVisualizations.clickAddVisualizationButton'); + await testSubjects.click('addVisualizationButton'); + } + + async isNewVisDialogShowing() { + log.debug('DashboardVisualizations.isNewVisDialogShowing'); + return await find.existsByCssSelector('.visNewVisDialog'); + } + + async ensureNewVisualizationDialogIsShowing() { + let isShowing = await this.isNewVisDialogShowing(); + log.debug(`DashboardVisualizations.ensureNewVisualizationDialogIsShowing:${isShowing}`); + if (!isShowing) { + await retry.try(async () => { + await this.clickAddVisualizationButton(); + isShowing = await this.isNewVisDialogShowing(); + log.debug(`DashboardVisualizations.ensureNewVisualizationDialogIsShowing:${isShowing}`); + if (!isShowing) { + throw new Error('New Vis Dialog still not open, trying again.'); + } + }); + } + } + + async createAndAddMarkdown({ name, markdown }) { log.debug(`createAndAddMarkdown(${markdown})`); const inViewMode = await PageObjects.dashboard.getIsInViewMode(); if (inViewMode) { await PageObjects.dashboard.switchToEditMode(); } - if (checkForAddPanel) { - await dashboardAddPanel.ensureAddPanelIsShowing(); - await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); - } + await this.ensureNewVisualizationDialogIsShowing(); await PageObjects.visualize.clickMarkdownWidget(); await PageObjects.visualize.setMarkdownTxt(markdown); await PageObjects.visualize.clickGo(); diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 90ff55fbebde5..69c2793621095 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -20,14 +20,11 @@ import Fs from 'fs'; import { resolve } from 'path'; -import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; -import { logging } from 'selenium-webdriver'; import { FtrProviderContext } from '../../ftr_provider_context'; import { initWebDriver } from './webdriver'; import { Browsers } from './browsers'; -import { pollForLogEntry$ } from './poll_for_log_entry'; export async function RemoteProvider({ getService }: FtrProviderContext) { const lifecycle = getService('lifecycle'); @@ -37,7 +34,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { const collectCoverage: boolean = !!process.env.CODE_COVERAGE; const coveragePrefix = 'coveragejson:'; const coverageDir = resolve(__dirname, '../../../../target/kibana-coverage/functional'); - let logSubscription: undefined | Rx.Subscription; + let coverageCounter = 1; type BrowserStorage = 'sessionStorage' | 'localStorage'; const clearBrowserStorage = async (storageType: BrowserStorage) => { @@ -50,6 +47,14 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { } }; + const writeCoverage = (coverageJson: string) => { + const id = coverageCounter++; + const timestamp = Date.now(); + const path = resolve(coverageDir, `${id}.${timestamp}.coverage.json`); + log.info('writing coverage to', path); + Fs.writeFileSync(path, JSON.stringify(JSON.parse(coverageJson), null, 2)); + }; + const { driver, By, until, consoleLog$ } = await initWebDriver( log, browserType, @@ -69,46 +74,35 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { caps.get('chrome').chromedriverVersion }, w3c=${isW3CEnabled}, codeCoverage=${collectCoverage}` ); - - if (collectCoverage) { - let coverageCounter = 1; - // We are running xpack tests with different configs and cleanup will delete collected coverage - // del.sync(coverageDir); - Fs.mkdirSync(coverageDir, { recursive: true }); - - logSubscription = pollForLogEntry$( - driver, - logging.Type.BROWSER, - config.get('browser.logPollingMs'), - lifecycle.cleanup.after$ - ) - .pipe( - mergeMap(logEntry => { - if (logEntry.message.includes(coveragePrefix)) { - const id = coverageCounter++; - const timestamp = Date.now(); - const path = resolve(coverageDir, `${id}.${timestamp}.coverage.json`); - const [, coverageJsonBase64] = logEntry.message.split(coveragePrefix); - const coverageJson = Buffer.from(coverageJsonBase64, 'base64').toString('utf8'); - - log.info('writing coverage to', path); - Fs.writeFileSync(path, JSON.stringify(JSON.parse(coverageJson), null, 2)); - - // filter out this message - return []; - } - - return [logEntry]; - }) - ) - .subscribe({ - next({ message, level: { name: level } }) { - const msg = message.replace(/\\n/g, '\n'); - log[level === 'SEVERE' ? 'error' : 'debug'](`browser[${level}] ${msg}`); - }, - }); - } } + // code coverage is supported only in Chrome browser + if (collectCoverage) { + // We are running xpack tests with different configs and cleanup will delete collected coverage + // del.sync(coverageDir); + Fs.mkdirSync(coverageDir, { recursive: true }); + } + + consoleLog$ + .pipe( + mergeMap(logEntry => { + if (collectCoverage && logEntry.message.includes(coveragePrefix)) { + const [, coverageJsonBase64] = logEntry.message.split(coveragePrefix); + const coverageJson = Buffer.from(coverageJsonBase64, 'base64').toString('utf8'); + writeCoverage(coverageJson); + + // filter out this message + return []; + } + + return [logEntry]; + }) + ) + .subscribe({ + next({ message, level }) { + const msg = message.replace(/\\n/g, '\n'); + log[level === 'SEVERE' ? 'error' : 'debug'](`browser[${level}] ${msg}`); + }, + }); lifecycle.beforeTests.add(async () => { // hard coded default, can be overridden per suite using `browser.setWindowSize()` @@ -144,8 +138,15 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { }); lifecycle.cleanup.add(async () => { - if (logSubscription) { - await new Promise(r => logSubscription!.add(r)); + // Getting the last piece of code coverage before closing browser + if (collectCoverage) { + const coverageJson = await driver + .executeScript('return window.__coverage__') + .catch(() => undefined) + .then(coverage => coverage && JSON.stringify(coverage)); + if (coverageJson) { + writeCoverage(coverageJson); + } } await driver.quit(); diff --git a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts index 771e50b22f66b..91a4fe89c1c57 100644 --- a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts @@ -18,6 +18,7 @@ */ import { Plugin, CoreSetup } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; import { PluginARequestContext } from '../../core_plugin_a/server'; declare module 'kibana/server' { @@ -34,6 +35,25 @@ export class CorePluginBPlugin implements Plugin { const response = await context.pluginA.ping(); return res.ok({ body: `Pong via plugin A: ${response}` }); }); + + router.post( + { + path: '/core_plugin_b', + validate: { + query: schema.object({ id: schema.string() }), + body: ({ bar, baz } = {}, { ok, badRequest }) => { + if (typeof bar === 'string' && bar === baz) { + return ok({ bar, baz }); + } else { + return badRequest(`bar: ${bar} !== baz: ${baz} or they are not string`); + } + }, + }, + }, + async (context, req, res) => { + return res.ok({ body: `ID: ${req.query.id} - ${req.body.bar.toUpperCase()}` }); + } + ); } public start() {} diff --git a/test/plugin_functional/test_suites/core_plugins/server_plugins.ts b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts index 3881af5642996..d437912c1b69a 100644 --- a/test/plugin_functional/test_suites/core_plugins/server_plugins.ts +++ b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts @@ -29,5 +29,29 @@ export default function({ getService }: PluginFunctionalProviderContext) { .expect(200) .expect('Pong via plugin A: true'); }); + + it('extend request handler context with validation', async () => { + await supertest + .post('/core_plugin_b') + .set('kbn-xsrf', 'anything') + .query({ id: 'TEST' }) + .send({ bar: 'hi!', baz: 'hi!' }) + .expect(200) + .expect('ID: TEST - HI!'); + }); + + it('extend request handler context with validation (400)', async () => { + await supertest + .post('/core_plugin_b') + .set('kbn-xsrf', 'anything') + .query({ id: 'TEST' }) + .send({ bar: 'hi!', baz: 1234 }) + .expect(400) + .expect({ + error: 'Bad Request', + message: '[request body]: bar: hi! !== baz: 1234 or they are not string', + statusCode: 400, + }); + }); }); } diff --git a/x-pack/legacy/plugins/actions/server/constants/plugin.ts b/x-pack/legacy/plugins/actions/server/constants/plugin.ts new file mode 100644 index 0000000000000..27cf0a8d2bf88 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/constants/plugin.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../common/constants'; + +export const PLUGIN = { + ID: 'actions', + MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + getI18nName: (i18n: any): string => + i18n.translate('xpack.actions.appName', { + defaultMessage: 'Actions', + }), +}; diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts index c6817b3bc12f3..c5496cd92cc9f 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts @@ -138,6 +138,7 @@ describe('execute()', () => { id: '123', params: { baz: false }, spaceId: 'default', + apiKey: null, }); expect(getScopedSavedObjectsClient).toHaveBeenCalledWith({ getBasePath: expect.anything(), diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.ts b/x-pack/legacy/plugins/actions/server/create_execute_function.ts index 451be9354006e..8ff12b8c3fa4b 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.ts +++ b/x-pack/legacy/plugins/actions/server/create_execute_function.ts @@ -18,7 +18,7 @@ export interface ExecuteOptions { id: string; params: Record; spaceId: string; - apiKey?: string; + apiKey: string | null; } export function createExecuteFunction({ diff --git a/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.test.ts b/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.test.ts new file mode 100644 index 0000000000000..186c26f6100e4 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { extendRouteWithLicenseCheck } from './extend_route_with_license_check'; +import { LicenseState } from './lib/license_state'; +jest.mock('./lib/license_state', () => ({ + verifyApiAccessFactory: () => {}, +})); + +describe('extendRouteWithLicenseCheck', () => { + describe('#actionsextendRouteWithLicenseCheck', () => { + let licenseState: jest.Mocked; + + test('extends route object with license, if config property already exists', () => { + const newRoute = extendRouteWithLicenseCheck( + { config: { someTestProperty: 'test' } }, + licenseState + ); + expect(newRoute.config.pre.length > 0); + }); + test('extends route object with license check uder options.pre', () => { + const newRoute = extendRouteWithLicenseCheck( + { options: { someProperty: 'test' } }, + licenseState + ); + expect(newRoute.options.pre.length > 0); + }); + }); +}); diff --git a/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.ts b/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.ts new file mode 100644 index 0000000000000..f39dc125071b4 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.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 { LicenseState, verifyApiAccessFactory } from './lib/license_state'; + +export function extendRouteWithLicenseCheck(route: any, licenseState: LicenseState) { + const verifyApiAccessPreRouting = verifyApiAccessFactory(licenseState); + + const key = route.options ? 'options' : 'config'; + return { + ...route, + [key]: { + ...route[key], + pre: [verifyApiAccessPreRouting], + }, + }; +} diff --git a/x-pack/legacy/plugins/actions/server/lib/license_state.test.ts b/x-pack/legacy/plugins/actions/server/lib/license_state.test.ts new file mode 100644 index 0000000000000..e58c52f63c8cb --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/lib/license_state.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { LicenseState } from './license_state'; +import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; +import { LICENSE_CHECK_STATE } from '../../../../../plugins/licensing/server'; + +describe('license_state', () => { + let getRawLicense: any; + + beforeEach(() => { + getRawLicense = jest.fn(); + }); + + describe('status is LICENSE_STATUS_INVALID', () => { + beforeEach(() => { + const license = licensingMock.createLicense({ license: { status: 'invalid' } }); + license.check = jest.fn(() => ({ + state: LICENSE_CHECK_STATE.Invalid, + })); + getRawLicense.mockReturnValue(license); + }); + + it('check application link should be disabled', () => { + const licensing = licensingMock.createSetup(); + const licenseState = new LicenseState(licensing.license$); + const actionsLicenseInfo = licenseState.checkLicense(getRawLicense()); + expect(actionsLicenseInfo.enableAppLink).to.be(false); + }); + }); + + describe('status is LICENSE_STATUS_VALID', () => { + beforeEach(() => { + const license = licensingMock.createLicense({ license: { status: 'active' } }); + license.check = jest.fn(() => ({ + state: LICENSE_CHECK_STATE.Valid, + })); + getRawLicense.mockReturnValue(license); + }); + + it('check application link should be enabled', () => { + const licensing = licensingMock.createSetup(); + const licenseState = new LicenseState(licensing.license$); + const actionsLicenseInfo = licenseState.checkLicense(getRawLicense()); + expect(actionsLicenseInfo.showAppLink).to.be(true); + }); + }); +}); diff --git a/x-pack/legacy/plugins/actions/server/lib/license_state.ts b/x-pack/legacy/plugins/actions/server/lib/license_state.ts new file mode 100644 index 0000000000000..b4de23ef0a949 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/lib/license_state.ts @@ -0,0 +1,94 @@ +/* + * 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 Boom from 'boom'; +import { i18n } from '@kbn/i18n'; +import { Observable, Subscription } from 'rxjs'; +import { ILicense, LICENSE_CHECK_STATE } from '../../../../../plugins/licensing/common/types'; +import { assertNever } from '../../../../../../src/core/utils'; +import { PLUGIN } from '../constants/plugin'; + +export interface ActionsLicenseInformation { + showAppLink: boolean; + enableAppLink: boolean; + message: string; +} + +export class LicenseState { + private licenseInformation: ActionsLicenseInformation = this.checkLicense(undefined); + private subscription: Subscription; + + constructor(license$: Observable) { + this.subscription = license$.subscribe(this.updateInformation.bind(this)); + } + + private updateInformation(license: ILicense | undefined) { + this.licenseInformation = this.checkLicense(license); + } + + public clean() { + this.subscription.unsubscribe(); + } + + public getLicenseInformation() { + return this.licenseInformation; + } + + public checkLicense(license: ILicense | undefined): ActionsLicenseInformation { + if (!license?.isAvailable) { + return { + showAppLink: true, + enableAppLink: false, + message: i18n.translate( + 'xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage', + { + defaultMessage: + 'Actions is unavailable - license information is not available at this time.', + } + ), + }; + } + + const check = license.check(PLUGIN.ID, PLUGIN.MINIMUM_LICENSE_REQUIRED); + + switch (check.state) { + case LICENSE_CHECK_STATE.Expired: + return { + showAppLink: true, + enableAppLink: false, + message: check.message || '', + }; + case LICENSE_CHECK_STATE.Invalid: + case LICENSE_CHECK_STATE.Unavailable: + return { + showAppLink: false, + enableAppLink: false, + message: check.message || '', + }; + case LICENSE_CHECK_STATE.Valid: + return { + showAppLink: true, + enableAppLink: true, + message: '', + }; + default: + return assertNever(check.state); + } + } +} + +export function verifyApiAccessFactory(licenseState: LicenseState) { + function verifyApiAccess() { + const licenseCheckResults = licenseState.getLicenseInformation(); + + if (licenseCheckResults.showAppLink && licenseCheckResults.enableAppLink) { + return null; + } + + throw Boom.forbidden(licenseCheckResults.message); + } + return verifyApiAccess; +} diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts index 6a41bf9a8b459..d55b30b8e30ea 100644 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ b/x-pack/legacy/plugins/actions/server/plugin.ts @@ -33,6 +33,8 @@ import { listActionTypesRoute, getExecuteActionRoute, } from './routes'; +import { extendRouteWithLicenseCheck } from './extend_route_with_license_check'; +import { LicenseState } from './lib/license_state'; export interface PluginSetupContract { registerType: ActionTypeRegistry['register']; @@ -54,6 +56,7 @@ export class Plugin { private actionTypeRegistry?: ActionTypeRegistry; private actionExecutor?: ActionExecutor; private defaultKibanaIndex?: string; + private licenseState: LicenseState | null = null; constructor(initializerContext: ActionsPluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); @@ -69,6 +72,8 @@ export class Plugin { this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); this.defaultKibanaIndex = (await this.kibana$.pipe(first()).toPromise()).index; + this.licenseState = new LicenseState(plugins.licensing.license$); + // Encrypted attributes // - `secrets` properties will be encrypted // - `config` will be included in AAD @@ -103,13 +108,15 @@ export class Plugin { }); // Routes - core.http.route(createActionRoute); - core.http.route(deleteActionRoute); - core.http.route(getActionRoute); - core.http.route(findActionRoute); - core.http.route(updateActionRoute); - core.http.route(listActionTypesRoute); - core.http.route(getExecuteActionRoute(actionExecutor)); + core.http.route(extendRouteWithLicenseCheck(createActionRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(deleteActionRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(getActionRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(findActionRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(updateActionRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(listActionTypesRoute, this.licenseState)); + core.http.route( + extendRouteWithLicenseCheck(getExecuteActionRoute(actionExecutor), this.licenseState) + ); return { registerType: actionTypeRegistry.register.bind(actionTypeRegistry), @@ -176,4 +183,10 @@ export class Plugin { }, }; } + + public stop() { + if (this.licenseState) { + this.licenseState.clean(); + } + } } diff --git a/x-pack/legacy/plugins/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts index 1fa8f755cc7ee..3887e62c4c40a 100644 --- a/x-pack/legacy/plugins/actions/server/shim.ts +++ b/x-pack/legacy/plugins/actions/server/shim.ts @@ -22,6 +22,7 @@ import { LoggerFactory, SavedObjectsLegacyService, } from '../../../../../src/core/server'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; // Extend PluginProperties to indicate which plugins are guaranteed to exist // due to being marked as dependencies @@ -76,6 +77,7 @@ export interface ActionsPluginsSetup { task_manager: TaskManagerSetupContract; xpack_main: XPackMainPluginSetupContract; encryptedSavedObjects: EncryptedSavedObjectsSetupContract; + licensing: LicensingPluginSetup; } export interface ActionsPluginsStart { security?: SecurityPluginStartContract; @@ -134,6 +136,7 @@ export function shim( xpack_main: server.plugins.xpack_main, encryptedSavedObjects: newPlatform.setup.plugins .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, + licensing: newPlatform.setup.plugins.licensing as LicensingPluginSetup, }; const pluginsStart: ActionsPluginsStart = { diff --git a/x-pack/legacy/plugins/alerting/README.md b/x-pack/legacy/plugins/alerting/README.md index 0b4024be39548..33679cf6fa422 100644 --- a/x-pack/legacy/plugins/alerting/README.md +++ b/x-pack/legacy/plugins/alerting/README.md @@ -200,7 +200,7 @@ Payload: |name|A name to reference and search in the future.|string| |tags|A list of keywords to reference and search in the future.|string[]| |alertTypeId|The id value of the alert type you want to call when the alert is scheduled to execute.|string| -|schedule|The schedule specifying when this alert should run, using one of the available schedule formats specified under _Schedule Formats_ below|object| +|schedule|The schedule specifying when this alert should be run, using one of the available schedule formats specified under _Schedule Formats_ below|object| |params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| |actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): The map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| diff --git a/x-pack/legacy/plugins/alerting/mappings.json b/x-pack/legacy/plugins/alerting/mappings.json index 9536187116031..bc4a7118666ed 100644 --- a/x-pack/legacy/plugins/alerting/mappings.json +++ b/x-pack/legacy/plugins/alerting/mappings.json @@ -20,6 +20,9 @@ } } }, + "consumer": { + "type": "keyword" + }, "actions": { "type": "nested", "properties": { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index b07dad68da72d..d11541e9378bb 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -3,13 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; import { AlertsClient } from './alerts_client'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../task_manager/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; import { TaskStatus } from '../../task_manager'; +import { IntervalSchedule } from './types'; +import { resolvable } from './test_utils'; const taskManager = taskManagerMock.create(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -29,6 +31,7 @@ beforeEach(() => { jest.resetAllMocks(); alertsClientParams.createAPIKey.mockResolvedValue({ created: false }); alertsClientParams.getUserName.mockResolvedValue('elastic'); + taskManager.runNow.mockResolvedValue({ id: '' }); }); const mockedDate = new Date('2019-02-12T21:01:22.479Z'); @@ -47,6 +50,7 @@ function getMockData(overwrites: Record = {}) { name: 'abc', tags: ['foo'], alertTypeId: '123', + consumer: 'bar', schedule: { interval: '10s' }, throttle: null, params: { @@ -182,8 +186,9 @@ describe('create()', () => { }, ], "alertTypeId": "123", - "apiKey": undefined, - "apiKeyOwner": undefined, + "apiKey": null, + "apiKeyOwner": null, + "consumer": "bar", "createdBy": "elastic", "enabled": true, "muteAll": false, @@ -795,6 +800,7 @@ describe('create()', () => { }, ], alertTypeId: '123', + consumer: 'bar', name: 'abc', params: { bar: true }, apiKey: Buffer.from('123:abc').toString('base64'), @@ -1952,6 +1958,228 @@ describe('update()', () => { `"params invalid: [param1]: expected value of type [string] but got [undefined]"` ); }); + + describe('updating an alert schedule', () => { + function mockApiCalls( + alertId: string, + taskId: string, + currentSchedule: IntervalSchedule, + updatedSchedule: IntervalSchedule + ) { + // mock return values from deps + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); + savedObjectsClient.get.mockResolvedValueOnce({ + id: alertId, + type: 'alert', + attributes: { + enabled: true, + alertTypeId: '123', + schedule: currentSchedule, + scheduledTaskId: 'task-123', + }, + references: [], + version: '123', + }); + + taskManager.schedule.mockResolvedValueOnce({ + id: taskId, + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: alertId, + type: 'alert', + attributes: { + enabled: true, + schedule: updatedSchedule, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + scheduledTaskId: taskId, + }, + references: [ + { + name: 'action_0', + type: 'action', + id: alertId, + }, + ], + }); + + taskManager.runNow.mockReturnValueOnce(Promise.resolve({ id: alertId })); + } + + test('updating the alert schedule should rerun the task immediately', async () => { + const alertId = uuid.v4(); + const taskId = uuid.v4(); + const alertsClient = new AlertsClient(alertsClientParams); + + mockApiCalls(alertId, taskId, { interval: '60m' }, { interval: '10s' }); + + await alertsClient.update({ + id: alertId, + data: { + schedule: { interval: '10s' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }); + + expect(taskManager.runNow).toHaveBeenCalledWith(taskId); + }); + + test('updating the alert without changing the schedule should not rerun the task', async () => { + const alertId = uuid.v4(); + const taskId = uuid.v4(); + const alertsClient = new AlertsClient(alertsClientParams); + + mockApiCalls(alertId, taskId, { interval: '10s' }, { interval: '10s' }); + + await alertsClient.update({ + id: alertId, + data: { + schedule: { interval: '10s' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }); + + expect(taskManager.runNow).not.toHaveBeenCalled(); + }); + + test('updating the alert should not wait for the rerun the task to complete', async done => { + const alertId = uuid.v4(); + const taskId = uuid.v4(); + const alertsClient = new AlertsClient(alertsClientParams); + + mockApiCalls(alertId, taskId, { interval: '10s' }, { interval: '30s' }); + + const resolveAfterAlertUpdatedCompletes = resolvable<{ id: string }>(); + resolveAfterAlertUpdatedCompletes.then(() => done()); + + taskManager.runNow.mockReset(); + taskManager.runNow.mockReturnValue(resolveAfterAlertUpdatedCompletes); + + await alertsClient.update({ + id: alertId, + data: { + schedule: { interval: '10s' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }); + + expect(taskManager.runNow).toHaveBeenCalled(); + + resolveAfterAlertUpdatedCompletes.resolve({ id: alertId }); + }); + + test('logs when the rerun of an alerts underlying task fails', async () => { + const alertId = uuid.v4(); + const taskId = uuid.v4(); + const alertsClient = new AlertsClient(alertsClientParams); + + mockApiCalls(alertId, taskId, { interval: '10s' }, { interval: '30s' }); + + taskManager.runNow.mockReset(); + taskManager.runNow.mockRejectedValue(new Error('Failed to run alert')); + + await alertsClient.update({ + id: alertId, + data: { + schedule: { interval: '10s' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }); + + expect(taskManager.runNow).toHaveBeenCalled(); + + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + `Alert update failed to run its underlying task. TaskManager runNow failed with Error: Failed to run alert` + ); + }); + }); }); describe('updateApiKey()', () => { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index 578daa445b6ff..70d2ff8ca3033 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -5,9 +5,14 @@ */ import Boom from 'boom'; -import { omit } from 'lodash'; +import { omit, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { Logger, SavedObjectsClientContract, SavedObjectReference } from 'src/core/server'; +import { + Logger, + SavedObjectsClientContract, + SavedObjectReference, + SavedObject, +} from 'src/core/server'; import { Alert, RawAlert, @@ -126,7 +131,6 @@ export class AlertsClient { // Throws an error if alert type isn't registered const alertType = this.alertTypeRegistry.get(data.alertTypeId); const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); - const apiKey = await this.createAPIKey(); const username = await this.getUserName(); this.validateActions(alertType, data.actions); @@ -134,13 +138,10 @@ export class AlertsClient { const { references, actions } = await this.denormalizeActions(data.actions); const rawAlert: RawAlert = { ...data, + ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), actions, createdBy: username, updatedBy: username, - apiKeyOwner: apiKey.created && username ? username : undefined, - apiKey: apiKey.created - ? Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64') - : undefined, params: validatedAlertTypeParams, muteAll: false, mutedInstanceIds: [], @@ -206,9 +207,28 @@ export class AlertsClient { } public async update({ id, data }: UpdateOptions) { - const { attributes, version } = await this.savedObjectsClient.get('alert', id); + const alert = await this.savedObjectsClient.get('alert', id); + const updateResult = await this.updateAlert({ id, data }, alert); + + if ( + updateResult.scheduledTaskId && + !isEqual(alert.attributes.schedule, updateResult.schedule) + ) { + this.taskManager.runNow(updateResult.scheduledTaskId).catch(err => { + this.logger.error( + `Alert update failed to run its underlying task. TaskManager runNow failed with Error: ${err.message}` + ); + }); + } + + return updateResult; + } + + private async updateAlert( + { id, data }: UpdateOptions, + { attributes, version }: SavedObject + ) { const alertType = this.alertTypeRegistry.get(attributes.alertTypeId); - const apiKey = await this.createAPIKey(); // Validate const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); @@ -216,19 +236,18 @@ export class AlertsClient { const { actions, references } = await this.denormalizeActions(data.actions); const username = await this.getUserName(); - const updatedObject = await this.savedObjectsClient.update( + const apiKeyAttributes = this.apiKeyAsAlertAttributes(await this.createAPIKey(), username); + + const updatedObject = await this.savedObjectsClient.update( 'alert', id, { ...attributes, ...data, + ...apiKeyAttributes, params: validatedAlertTypeParams, actions, updatedBy: username, - apiKeyOwner: apiKey.created ? username : null, - apiKey: apiKey.created - ? Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64') - : null, }, { version, @@ -238,21 +257,32 @@ export class AlertsClient { return this.getAlertFromRaw(id, updatedObject.attributes, updatedObject.references); } + private apiKeyAsAlertAttributes( + apiKey: CreateAPIKeyResult, + username: string | null + ): Pick { + return apiKey.created + ? { + apiKeyOwner: username, + apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'), + } + : { + apiKeyOwner: null, + apiKey: null, + }; + } + public async updateApiKey({ id }: { id: string }) { const { version, attributes } = await this.savedObjectsClient.get('alert', id); - const apiKey = await this.createAPIKey(); const username = await this.getUserName(); await this.savedObjectsClient.update( 'alert', id, { ...attributes, + ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), updatedBy: username, - apiKeyOwner: apiKey.created ? username : null, - apiKey: apiKey.created - ? Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64') - : null, }, { version } ); @@ -261,7 +291,6 @@ export class AlertsClient { public async enable({ id }: { id: string }) { const { attributes, version } = await this.savedObjectsClient.get('alert', id); if (attributes.enabled === false) { - const apiKey = await this.createAPIKey(); const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); const username = await this.getUserName(); await this.savedObjectsClient.update( @@ -270,12 +299,9 @@ export class AlertsClient { { ...attributes, enabled: true, + ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), updatedBy: username, - apiKeyOwner: apiKey.created ? username : null, scheduledTaskId: scheduledTask.id, - apiKey: apiKey.created - ? Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64') - : null, }, { version } ); diff --git a/x-pack/legacy/plugins/alerting/server/constants/plugin.ts b/x-pack/legacy/plugins/alerting/server/constants/plugin.ts new file mode 100644 index 0000000000000..e3435b09829c6 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/constants/plugin.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../common/constants'; + +export const PLUGIN = { + ID: 'alerting', + MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + getI18nName: (i18n: any): string => + i18n.translate('xpack.alerting.appName', { + defaultMessage: 'Alerting', + }), +}; diff --git a/x-pack/legacy/plugins/alerting/server/extend_route_with_license_check.test.ts b/x-pack/legacy/plugins/alerting/server/extend_route_with_license_check.test.ts new file mode 100644 index 0000000000000..05c8424881625 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/extend_route_with_license_check.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { extendRouteWithLicenseCheck } from './extend_route_with_license_check'; +import { LicenseState } from './lib/license_state'; +jest.mock('./lib/license_state', () => ({ + verifyApiAccessFactory: () => {}, +})); + +describe('extendRouteWithLicenseCheck', () => { + describe('#actionsextendRouteWithLicenseCheck', () => { + let licenseState: jest.Mocked; + + test('extends route object with license, if config property already exists', () => { + const newRoute = extendRouteWithLicenseCheck( + { config: { someTestProperty: 'test' } }, + licenseState + ); + expect(newRoute.config.pre.length > 0); + }); + test('extends route object with license check under options.pre', () => { + const newRoute = extendRouteWithLicenseCheck( + { options: { someProperty: 'test' } }, + licenseState + ); + expect(newRoute.options.pre.length > 0); + }); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/extend_route_with_license_check.ts b/x-pack/legacy/plugins/alerting/server/extend_route_with_license_check.ts new file mode 100644 index 0000000000000..f39dc125071b4 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/extend_route_with_license_check.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 { LicenseState, verifyApiAccessFactory } from './lib/license_state'; + +export function extendRouteWithLicenseCheck(route: any, licenseState: LicenseState) { + const verifyApiAccessPreRouting = verifyApiAccessFactory(licenseState); + + const key = route.options ? 'options' : 'config'; + return { + ...route, + [key]: { + ...route[key], + pre: [verifyApiAccessPreRouting], + }, + }; +} diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts b/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts index 5e7c91c560a6f..3fb197ec97f4f 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts @@ -14,7 +14,7 @@ interface CreateExecutionHandlerOptions { executeAction: ActionsPluginStartContract['execute']; actions: AlertAction[]; spaceId: string; - apiKey?: string; + apiKey: string | null; alertType: AlertType; logger: Logger; } diff --git a/x-pack/legacy/plugins/alerting/server/lib/license_state.test.ts b/x-pack/legacy/plugins/alerting/server/lib/license_state.test.ts new file mode 100644 index 0000000000000..484dc49532567 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/lib/license_state.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { LicenseState } from './license_state'; +import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; +import { LICENSE_CHECK_STATE } from '../../../../../plugins/licensing/server'; + +describe('license_state', () => { + let getRawLicense: any; + + beforeEach(() => { + getRawLicense = jest.fn(); + }); + + describe('status is LICENSE_STATUS_INVALID', () => { + beforeEach(() => { + const license = licensingMock.createLicense({ license: { status: 'invalid' } }); + license.check = jest.fn(() => ({ + state: LICENSE_CHECK_STATE.Invalid, + })); + getRawLicense.mockReturnValue(license); + }); + + it('check application link should be disabled', () => { + const licensing = licensingMock.createSetup(); + const licenseState = new LicenseState(licensing.license$); + const alertingLicenseInfo = licenseState.checkLicense(getRawLicense()); + expect(alertingLicenseInfo.enableAppLink).to.be(false); + }); + }); + + describe('status is LICENSE_STATUS_VALID', () => { + beforeEach(() => { + const license = licensingMock.createLicense({ license: { status: 'active' } }); + license.check = jest.fn(() => ({ + state: LICENSE_CHECK_STATE.Valid, + })); + getRawLicense.mockReturnValue(license); + }); + + it('check application link should be enabled', () => { + const licensing = licensingMock.createSetup(); + const licenseState = new LicenseState(licensing.license$); + const alertingLicenseInfo = licenseState.checkLicense(getRawLicense()); + expect(alertingLicenseInfo.enableAppLink).to.be(true); + }); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/lib/license_state.ts b/x-pack/legacy/plugins/alerting/server/lib/license_state.ts new file mode 100644 index 0000000000000..344bc9c409edf --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/lib/license_state.ts @@ -0,0 +1,94 @@ +/* + * 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 Boom from 'boom'; +import { i18n } from '@kbn/i18n'; +import { Observable, Subscription } from 'rxjs'; +import { ILicense, LICENSE_CHECK_STATE } from '../../../../../plugins/licensing/common/types'; +import { assertNever } from '../../../../../../src/core/utils'; +import { PLUGIN } from '../constants/plugin'; + +export interface AlertingLicenseInformation { + showAppLink: boolean; + enableAppLink: boolean; + message: string; +} + +export class LicenseState { + private licenseInformation: AlertingLicenseInformation = this.checkLicense(undefined); + private subscription: Subscription; + + constructor(license$: Observable) { + this.subscription = license$.subscribe(this.updateInformation.bind(this)); + } + + private updateInformation(license: ILicense | undefined) { + this.licenseInformation = this.checkLicense(license); + } + + public clean() { + this.subscription.unsubscribe(); + } + + public getLicenseInformation() { + return this.licenseInformation; + } + + public checkLicense(license: ILicense | undefined): AlertingLicenseInformation { + if (!license || !license.isAvailable) { + return { + showAppLink: true, + enableAppLink: false, + message: i18n.translate( + 'xpack.alerting.serverSideErrors.unavailableLicenseInformationErrorMessage', + { + defaultMessage: + 'Alerting is unavailable - license information is not available at this time.', + } + ), + }; + } + + const check = license.check(PLUGIN.ID, PLUGIN.MINIMUM_LICENSE_REQUIRED); + + switch (check.state) { + case LICENSE_CHECK_STATE.Expired: + return { + showAppLink: true, + enableAppLink: false, + message: check.message || '', + }; + case LICENSE_CHECK_STATE.Invalid: + case LICENSE_CHECK_STATE.Unavailable: + return { + showAppLink: false, + enableAppLink: false, + message: check.message || '', + }; + case LICENSE_CHECK_STATE.Valid: + return { + showAppLink: true, + enableAppLink: true, + message: '', + }; + default: + return assertNever(check.state); + } + } +} + +export function verifyApiAccessFactory(licenseState: LicenseState) { + function verifyApiAccess() { + const licenseCheckResults = licenseState.getLicenseInformation(); + + if (licenseCheckResults.showAppLink && licenseCheckResults.enableAppLink) { + return null; + } + + throw Boom.forbidden(licenseCheckResults.message); + } + return verifyApiAccess; +} diff --git a/x-pack/legacy/plugins/alerting/server/plugin.ts b/x-pack/legacy/plugins/alerting/server/plugin.ts index 32e5753687367..3b17fa066d55a 100644 --- a/x-pack/legacy/plugins/alerting/server/plugin.ts +++ b/x-pack/legacy/plugins/alerting/server/plugin.ts @@ -10,6 +10,7 @@ import { Services } from './types'; import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry } from './alert_type_registry'; import { AlertsClientFactory, TaskRunnerFactory } from './lib'; +import { LicenseState } from './lib/license_state'; import { IClusterClient, KibanaRequest, Logger } from '../../../../../src/core/server'; import { AlertingPluginInitializerContext, @@ -33,6 +34,7 @@ import { muteAlertInstanceRoute, unmuteAlertInstanceRoute, } from './routes'; +import { extendRouteWithLicenseCheck } from './extend_route_with_license_check'; export interface PluginSetupContract { registerType: AlertTypeRegistry['register']; @@ -48,6 +50,7 @@ export class Plugin { private readonly taskRunnerFactory: TaskRunnerFactory; private adminClient?: IClusterClient; private serverBasePath?: string; + private licenseState: LicenseState | null = null; constructor(initializerContext: AlertingPluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); @@ -60,6 +63,8 @@ export class Plugin { ): Promise { this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); + this.licenseState = new LicenseState(plugins.licensing.license$); + // Encrypted attributes plugins.encryptedSavedObjects.registerType({ type: 'alert', @@ -80,19 +85,19 @@ export class Plugin { this.serverBasePath = core.http.basePath.serverBasePath; // Register routes - core.http.route(createAlertRoute); - core.http.route(deleteAlertRoute); - core.http.route(findAlertRoute); - core.http.route(getAlertRoute); - core.http.route(listAlertTypesRoute); - core.http.route(updateAlertRoute); - core.http.route(enableAlertRoute); - core.http.route(disableAlertRoute); - core.http.route(updateApiKeyRoute); - core.http.route(muteAllAlertRoute); - core.http.route(unmuteAllAlertRoute); - core.http.route(muteAlertInstanceRoute); - core.http.route(unmuteAlertInstanceRoute); + core.http.route(extendRouteWithLicenseCheck(createAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(deleteAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(findAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(getAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(listAlertTypesRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(updateAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(enableAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(disableAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(updateApiKeyRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(muteAllAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(unmuteAllAlertRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(muteAlertInstanceRoute, this.licenseState)); + core.http.route(extendRouteWithLicenseCheck(unmuteAlertInstanceRoute, this.licenseState)); return { registerType: alertTypeRegistry.register.bind(alertTypeRegistry), @@ -140,4 +145,10 @@ export class Plugin { alertsClientFactory!.create(KibanaRequest.from(request), request), }; } + + public stop() { + if (this.licenseState) { + this.licenseState.clean(); + } + } } diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index a804aff55ad42..c41e0d068aff2 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -12,6 +12,7 @@ server.route(createAlertRoute); const mockedAlert = { alertTypeId: '1', + consumer: 'bar', name: 'abc', schedule: { interval: '10s' }, tags: ['foo'], @@ -64,6 +65,7 @@ test('creates an alert with proper parameters', async () => { }, ], "alertTypeId": "1", + "consumer": "bar", "id": "123", "name": "abc", "params": Object { @@ -92,6 +94,7 @@ test('creates an alert with proper parameters', async () => { }, ], "alertTypeId": "1", + "consumer": "bar", "enabled": true, "name": "abc", "params": Object { diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.ts b/x-pack/legacy/plugins/alerting/server/routes/create.ts index 417072f978a92..362a23a3fa910 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.ts @@ -15,6 +15,7 @@ interface ScheduleRequest extends Hapi.Request { name: string; tags: string[]; alertTypeId: string; + consumer: string; schedule: IntervalSchedule; actions: Array<{ group: string; @@ -43,6 +44,7 @@ export const createAlertRoute = { .items(Joi.string()) .default([]), alertTypeId: Joi.string().required(), + consumer: Joi.string().required(), throttle: getDurationSchema().default(null), schedule: Joi.object() .keys({ diff --git a/x-pack/legacy/plugins/alerting/server/shim.ts b/x-pack/legacy/plugins/alerting/server/shim.ts index ef1f1b41049e5..a3d6b778b3a1f 100644 --- a/x-pack/legacy/plugins/alerting/server/shim.ts +++ b/x-pack/legacy/plugins/alerting/server/shim.ts @@ -25,6 +25,7 @@ import { PluginSetupContract as ActionsPluginSetupContract, PluginStartContract as ActionsPluginStartContract, } from '../../actions'; +import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; // Extend PluginProperties to indicate which plugins are guaranteed to exist // due to being marked as dependencies @@ -40,7 +41,10 @@ export interface Server extends Legacy.Server { /** * Shim what we're thinking setup and start contracts will look like */ -export type TaskManagerStartContract = Pick; +export type TaskManagerStartContract = Pick< + TaskManager, + 'schedule' | 'fetch' | 'remove' | 'runNow' +>; export type SecurityPluginSetupContract = Pick; export type SecurityPluginStartContract = Pick; export type XPackMainPluginSetupContract = Pick; @@ -73,6 +77,7 @@ export interface AlertingPluginsSetup { actions: ActionsPluginSetupContract; xpack_main: XPackMainPluginSetupContract; encryptedSavedObjects: EncryptedSavedObjectsSetupContract; + licensing: LicensingPluginSetup; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; @@ -121,6 +126,7 @@ export function shim( xpack_main: server.plugins.xpack_main, encryptedSavedObjects: newPlatform.setup.plugins .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, + licensing: newPlatform.setup.plugins.licensing as LicensingPluginSetup, }; const pluginsStart: AlertingPluginsStart = { diff --git a/x-pack/legacy/plugins/alerting/server/test_utils/index.ts b/x-pack/legacy/plugins/alerting/server/test_utils/index.ts new file mode 100644 index 0000000000000..be9c5493ccf2b --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/test_utils/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface Resolvable { + resolve: (arg?: T) => void; +} + +/** + * Creates a promise which can be resolved externally, useful for + * coordinating async tests. + */ +export function resolvable(): Promise & Resolvable { + let resolve: (arg?: T) => void; + const result = new Promise(r => { + resolve = r; + }) as any; + + result.resolve = (arg: T) => resolve(arg); + + return result; +} diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index e06e0c45e20b4..7c2f3dcc918dc 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -69,14 +69,15 @@ export interface Alert { name: string; tags: string[]; alertTypeId: string; + consumer: string; schedule: IntervalSchedule; actions: AlertAction[]; params: Record; scheduledTaskId?: string; createdBy: string | null; updatedBy: string | null; - apiKey?: string; - apiKeyOwner?: string; + apiKey: string | null; + apiKeyOwner: string | null; throttle: string | null; muteAll: boolean; mutedInstanceIds: string[]; @@ -87,14 +88,15 @@ export interface RawAlert extends SavedObjectAttributes { name: string; tags: string[]; alertTypeId: string; + consumer: string; schedule: SavedObjectAttributes; actions: RawAlertAction[]; params: SavedObjectAttributes; scheduledTaskId?: string; createdBy: string | null; updatedBy: string | null; - apiKey?: string; - apiKeyOwner?: string; + apiKey: string | null; + apiKeyOwner: string | null; throttle: string | null; muteAll: boolean; mutedInstanceIds: string[]; 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/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 ( - - {i18n.translate('xpack.apm.loading.prompt', { - defaultMessage: 'Loading...' - })} -
- } - titleSize="s" - /> + + + + + ); } 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; +type HttpMock = HttpSetup & { + get: jest.SpyInstance; }; 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 & { pathname: string; @@ -42,7 +42,7 @@ export function clearCache() { export type CallApi = typeof callApi; export async function callApi( - http: HttpServiceBase, + http: HttpSetup, fetchOptions: FetchOptions ): Promise { 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 & { }; }; -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(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/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 +node x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js --role-suffix ``` 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 ` ' + 'Please specify a unique suffix that will be added to your roles with `--role-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(options: AxiosRequestConfig): Promise { - 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 "` + ); + } + } + 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 ******************************/ // compile typescript on the fly 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/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/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 d347398ed02af..07f4b7d9ac6db 100644 --- a/x-pack/legacy/plugins/canvas/server/plugin.ts +++ b/x-pack/legacy/plugins/canvas/server/plugin.ts @@ -61,8 +61,8 @@ export class Plugin { }); 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/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_management/public/app/components/template_form/steps/step_review.tsx b/x-pack/legacy/plugins/index_management/public/app/components/template_form/steps/step_review.tsx index 8f2d3978a6780..09172bf5cd0ca 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/template_form/steps/step_review.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/template_form/steps/step_review.tsx @@ -163,6 +163,10 @@ export const StepReview: React.FunctionComponent = ({ template, updat const templateString = JSON.stringify(serializedTemplate, null, 2); const request = `${endpoint}\n${templateString}`; + // Beyond a certain point, highlighting the syntax will bog down performance to unacceptable + // levels. This way we prevent that happening for very large requests. + const language = request.length < 60000 ? 'json' : undefined; + return (
@@ -178,7 +182,7 @@ export const StepReview: React.FunctionComponent = ({ template, updat - + {request}
diff --git a/x-pack/legacy/plugins/index_management/public/app/services/http.ts b/x-pack/legacy/plugins/index_management/public/app/services/http.ts index d88bd52405495..34f5abe5983cc 100644 --- a/x-pack/legacy/plugins/index_management/public/app/services/http.ts +++ b/x-pack/legacy/plugins/index_management/public/app/services/http.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HttpServiceBase } from '../../../../../../../src/core/public'; +import { HttpSetup } from '../../../../../../../src/core/public'; class HttpService { private client: any; - public init(httpClient: HttpServiceBase): void { + public init(httpClient: HttpSetup): void { this.client = httpClient; } - public get httpClient(): HttpServiceBase { + public get httpClient(): HttpSetup { return this.client; } } diff --git a/x-pack/legacy/plugins/infra/common/graphql/types.ts b/x-pack/legacy/plugins/infra/common/graphql/types.ts index bd1d3945f35f7..bb089bf8bf8ad 100644 --- a/x-pack/legacy/plugins/infra/common/graphql/types.ts +++ b/x-pack/legacy/plugins/infra/common/graphql/types.ts @@ -35,7 +35,6 @@ export interface InfraSource { /** Sequences of log entries matching sets of highlighting queries within an interval */ logEntryHighlights: InfraLogEntryInterval[]; - logItem: InfraLogItem; /** A snapshot of nodes */ snapshot?: InfraSnapshotResponse | null; @@ -205,24 +204,6 @@ export interface InfraLogEntryFieldColumn { highlights: string[]; } -export interface InfraLogItem { - /** The ID of the document */ - id: string; - /** The index where the document was found */ - index: string; - /** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */ - key: InfraTimeKey; - /** An array of flattened fields and values */ - fields: InfraLogItemField[]; -} - -export interface InfraLogItemField { - /** The flattened field name */ - field: string; - /** The value for the Field as a string */ - value: string; -} - export interface InfraSnapshotResponse { /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ nodes: InfraSnapshotNode[]; @@ -424,9 +405,6 @@ export interface LogEntryHighlightsInfraSourceArgs { /** The highlighting to apply to the log entries */ highlights: InfraLogEntryHighlightInput[]; } -export interface LogItemInfraSourceArgs { - id: string; -} export interface SnapshotInfraSourceArgs { timerange: InfraTimerangeInput; @@ -600,49 +578,6 @@ export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessa // Documents // ==================================================== -export namespace FlyoutItemQuery { - export type Variables = { - sourceId: string; - itemId: string; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - logItem: LogItem; - }; - - export type LogItem = { - __typename?: 'InfraLogItem'; - - id: string; - - index: string; - - key: Key; - - fields: Fields[]; - }; - - export type Key = InfraTimeKeyFields.Fragment; - - export type Fields = { - __typename?: 'InfraLogItemField'; - - field: string; - - value: string; - }; -} - export namespace LogEntryHighlightsQuery { export type Variables = { sourceId?: string | null; diff --git a/x-pack/legacy/plugins/infra/public/apps/testing_app.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/common.ts similarity index 66% rename from x-pack/legacy/plugins/infra/public/apps/testing_app.ts rename to x-pack/legacy/plugins/infra/common/http_api/log_entries/common.ts index bcd7d0e592644..3eb7e278bf99c 100644 --- a/x-pack/legacy/plugins/infra/public/apps/testing_app.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/common.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { compose } from '../lib/compose/testing_compose'; -import { startApp } from './start_app'; -startApp(compose()); +import * as rt from 'io-ts'; + +export const logEntriesCursorRT = rt.type({ + time: rt.number, + tiebreaker: rt.number, +}); diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts index ee2d150fdaac0..8fed914c3dc8c 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './item'; export * from './summary'; export * from './summary_highlights'; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_entries/item.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/item.ts new file mode 100644 index 0000000000000..02335d68402c0 --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/item.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { logEntriesCursorRT } from './common'; + +export const LOG_ENTRIES_ITEM_PATH = '/api/log_entries/item'; + +export const logEntriesItemRequestRT = rt.type({ + sourceId: rt.string, + id: rt.string, +}); + +export type LogEntriesItemRequest = rt.TypeOf; + +const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.string }); +const logEntriesItemRT = rt.type({ + id: rt.string, + index: rt.string, + fields: rt.array(logEntriesItemFieldRT), + key: logEntriesCursorRT, +}); +export const logEntriesItemResponseRT = rt.type({ + data: logEntriesItemRT, +}); + +export type LogEntriesItemField = rt.TypeOf; +export type LogEntriesItem = rt.TypeOf; +export type LogEntriesItemResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_entries/summary_highlights.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/summary_highlights.ts index 56191368dbcdf..30222cd71bbde 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_entries/summary_highlights.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/summary_highlights.ts @@ -6,6 +6,7 @@ import * as rt from 'io-ts'; import { logEntriesSummaryRequestRT, logEntriesSummaryBucketRT } from './summary'; +import { logEntriesCursorRT } from './common'; export const LOG_ENTRIES_SUMMARY_HIGHLIGHTS_PATH = '/api/log_entries/summary_highlights'; @@ -23,10 +24,7 @@ export type LogEntriesSummaryHighlightsRequest = rt.TypeOf< export const logEntriesSummaryHighlightsBucketRT = rt.intersection([ logEntriesSummaryBucketRT, rt.type({ - representativeKey: rt.type({ - time: rt.number, - tiebreaker: rt.number, - }), + representativeKey: logEntriesCursorRT, }), ]); diff --git a/x-pack/legacy/plugins/infra/index.ts b/x-pack/legacy/plugins/infra/index.ts index 38661f430c402..196950b51be3a 100644 --- a/x-pack/legacy/plugins/infra/index.ts +++ b/x-pack/legacy/plugins/infra/index.ts @@ -16,12 +16,10 @@ import { plugin, InfraServerPluginDeps } from './server/new_platform_index'; import { InfraSetup } from '../../../plugins/infra/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../../plugins/features/server'; import { SpacesPluginSetup } from '../../../plugins/spaces/server'; +import { VisTypeTimeseriesSetup } from '../../../../src/plugins/vis_type_timeseries/server'; import { APMPluginContract } from '../../../plugins/apm/server'; -const APP_ID = 'infra'; -const logsSampleDataLinkLabel = i18n.translate('xpack.infra.sampleDataLinkLabel', { - defaultMessage: 'Logs', -}); +export const APP_ID = 'infra'; export function infra(kibana: any) { return new kibana.Plugin({ @@ -88,23 +86,14 @@ export function infra(kibana: any) { } as unknown) as PluginInitializerContext; // NP_TODO: Use real types from the other plugins as they are migrated const pluginDeps: InfraServerPluginDeps = { + home: legacyServer.newPlatform.setup.plugins.home, usageCollection: plugins.usageCollection as UsageCollectionSetup, indexPatterns: { indexPatternsServiceFactory: legacyServer.indexPatternsServiceFactory, }, - metrics: legacyServer.plugins.metrics, + metrics: plugins.metrics as VisTypeTimeseriesSetup, spaces: plugins.spaces as SpacesPluginSetup, features: plugins.features as FeaturesPluginSetup, - // NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that - // originate from the New Platform router (and are very different to the old request object). - // Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just - // the requestContext, this can be removed. - ___legacy: { - tsvb: { - elasticsearch: legacyServer.plugins.elasticsearch, - __internals: legacyServer.newPlatform.__internals, - }, - }, apm: plugins.apm as APMPluginContract, }; @@ -120,15 +109,6 @@ export function infra(kibana: any) { 'defineInternalSourceConfiguration', libs.sources.defineInternalSourceConfiguration.bind(libs.sources) ); - - // NP_TODO: How do we move this to new platform? - legacyServer.addAppLinksToSampleDataset('logs', [ - { - path: `/app/${APP_ID}#/logs`, - label: logsSampleDataLinkLabel, - icon: 'logsApp', - }, - ]); }, }); } diff --git a/x-pack/legacy/plugins/infra/public/app.ts b/x-pack/legacy/plugins/infra/public/app.ts index 255c51c9e48ce..4b14e168eb768 100644 --- a/x-pack/legacy/plugins/infra/public/app.ts +++ b/x-pack/legacy/plugins/infra/public/app.ts @@ -4,4 +4,51 @@ * you may not use this file except in compliance with the Elastic License. */ -import './apps/kibana_app'; +// NP_TODO: This app.ts layer is needed until we migrate 100% to the NP. +// This is so other plugins can import from our public/index file without trying to +// actually mount and run our application. Once in the NP this won't be an issue +// as the NP will look for an export named "plugin" and run that from the index file. + +import { npStart } from 'ui/new_platform'; +import { PluginInitializerContext } from 'kibana/public'; +import chrome from 'ui/chrome'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import uiRoutes from 'ui/routes'; +// @ts-ignore +import { timezoneProvider } from 'ui/vis/lib/timezone'; +import { plugin } from './new_platform_index'; + +const ROOT_ELEMENT_ID = 'react-infra-root'; +export { ROOT_ELEMENT_ID }; + +const { core, plugins } = npStart; +const __LEGACY = { + uiModules, + uiRoutes, + timezoneProvider, +}; +// This will be moved to core.application.register when the new platform +// migration is complete. +// @ts-ignore +chrome.setRootTemplate(` +
+`); + +const checkForRoot = () => { + return new Promise(resolve => { + const ready = !!document.getElementById(ROOT_ELEMENT_ID); + if (ready) { + resolve(); + } else { + setTimeout(() => resolve(checkForRoot()), 10); + } + }); +}; + +checkForRoot().then(() => { + plugin({} as PluginInitializerContext).start(core, plugins, __LEGACY); +}); diff --git a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx index 41479cf6351ec..8ccb051724ede 100644 --- a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx @@ -6,16 +6,15 @@ import { createHashHistory } from 'history'; import React from 'react'; +import ReactDOM from 'react-dom'; import { ApolloProvider } from 'react-apollo'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { BehaviorSubject } from 'rxjs'; import { pluck } from 'rxjs/operators'; +import { CoreStart } from 'kibana/public'; // TODO use theme provided from parentApp when kibana supports it import { EuiErrorBoundary } from '@elastic/eui'; -import { UICapabilitiesProvider } from 'ui/capabilities/react'; -import { I18nContext } from 'ui/i18n'; -import { npStart } from 'ui/new_platform'; import { EuiThemeProvider } from '../../../../common/eui_styled_components'; import { InfraFrontendLibs } from '../lib/lib'; import { PageRouter } from '../routes'; @@ -27,12 +26,10 @@ import { useUiSetting$, KibanaContextProvider, } from '../../../../../../src/plugins/kibana_react/public'; - -const { uiSettings } = npStart.core; - -export async function startApp(libs: InfraFrontendLibs) { +import { ROOT_ELEMENT_ID } from '../app'; +// NP_TODO: Type plugins +export async function startApp(libs: InfraFrontendLibs, core: CoreStart, plugins: any) { const history = createHashHistory(); - const libs$ = new BehaviorSubject(libs); const store = createStore({ apolloClient: libs$.pipe(pluck('apolloClient')), @@ -43,31 +40,35 @@ export async function startApp(libs: InfraFrontendLibs) { const [darkMode] = useUiSetting$('theme:darkMode'); return ( - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + ); }; - libs.framework.render( - + const node = await document.getElementById(ROOT_ELEMENT_ID); + + const App = ( + ); + + if (node) { + ReactDOM.render(App, node); + } } diff --git a/x-pack/legacy/plugins/infra/public/components/header/external_header.tsx b/x-pack/legacy/plugins/infra/public/components/header/external_header.tsx deleted file mode 100644 index 4649241d2a0c8..0000000000000 --- a/x-pack/legacy/plugins/infra/public/components/header/external_header.tsx +++ /dev/null @@ -1,47 +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 isEqual from 'lodash/fp/isEqual'; -import React from 'react'; - -import { Badge } from 'ui/chrome/api/badge'; -import { Breadcrumb } from 'ui/chrome/api/breadcrumbs'; - -interface ExternalHeaderProps { - breadcrumbs?: Breadcrumb[]; - setBreadcrumbs: (breadcrumbs: Breadcrumb[]) => void; - badge: Badge | undefined; - setBadge: (badge: Badge | undefined) => void; -} - -export class ExternalHeader extends React.Component { - public componentDidMount() { - this.setBreadcrumbs(); - this.setBadge(); - } - - public componentDidUpdate(prevProps: ExternalHeaderProps) { - if (!isEqual(this.props.breadcrumbs, prevProps.breadcrumbs)) { - this.setBreadcrumbs(); - } - - if (!isEqual(this.props.badge, prevProps.badge)) { - this.setBadge(); - } - } - - public render() { - return null; - } - - private setBadge = () => { - this.props.setBadge(this.props.badge); - }; - - private setBreadcrumbs = () => { - this.props.setBreadcrumbs(this.props.breadcrumbs || []); - }; -} diff --git a/x-pack/legacy/plugins/infra/public/components/header/header.tsx b/x-pack/legacy/plugins/infra/public/components/header/header.tsx index 5ff8472e94ddc..731e62b927ae4 100644 --- a/x-pack/legacy/plugins/infra/public/components/header/header.tsx +++ b/x-pack/legacy/plugins/infra/public/components/header/header.tsx @@ -4,40 +4,51 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; - +import { useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; - -import { Breadcrumb } from 'ui/chrome/api/breadcrumbs'; -import { WithKibanaChrome } from '../../containers/with_kibana_chrome'; -import { ExternalHeader } from './external_header'; +import { ChromeBreadcrumb } from 'src/core/public'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; interface HeaderProps { - breadcrumbs?: Breadcrumb[]; + breadcrumbs?: ChromeBreadcrumb[]; readOnlyBadge?: boolean; } -export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) => ( - - {({ setBreadcrumbs, setBadge }) => ( - - )} - -); +export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) => { + const chrome = useKibana().services.chrome; + + const badge = readOnlyBadge + ? { + text: i18n.translate('xpack.infra.header.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('xpack.infra.header.badge.readOnly.tooltip', { + defaultMessage: 'Unable to change source configuration', + }), + iconType: 'glasses', + } + : undefined; + + const setBreadcrumbs = useCallback(() => { + return chrome?.setBreadcrumbs(breadcrumbs || []); + }, [breadcrumbs, chrome]); + + const setBadge = useCallback(() => { + return chrome?.setBadge(badge); + }, [badge, chrome]); + + useEffect(() => { + setBreadcrumbs(); + setBadge(); + }, [setBreadcrumbs, setBadge]); + + useEffect(() => { + setBreadcrumbs(); + }, [breadcrumbs, setBreadcrumbs]); + + useEffect(() => { + setBadge(); + }, [badge, setBadge]); + + return null; +}; diff --git a/x-pack/legacy/plugins/infra/public/components/help_center_content.tsx b/x-pack/legacy/plugins/infra/public/components/help_center_content.tsx index 3095230ab8311..4b27c6cfce53f 100644 --- a/x-pack/legacy/plugins/infra/public/components/help_center_content.tsx +++ b/x-pack/legacy/plugins/infra/public/components/help_center_content.tsx @@ -5,7 +5,7 @@ */ import React, { useEffect } from 'react'; -import chrome from 'ui/chrome'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; interface HelpCenterContentProps { feedbackLink: string; @@ -13,8 +13,10 @@ interface HelpCenterContentProps { } export const HelpCenterContent: React.FC = ({ feedbackLink, appName }) => { + const chrome = useKibana().services.chrome; + useEffect(() => { - chrome.helpExtension.set({ + return chrome?.setHelpExtension({ appName, links: [ { @@ -23,7 +25,7 @@ export const HelpCenterContent: React.FC = ({ feedbackLi }, ], }); - }, [feedbackLink, appName]); + }, [feedbackLink, appName, chrome]); return null; }; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx index c5d83e1c205cc..536dd24faa7c1 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx @@ -8,10 +8,9 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { encode } from 'rison-node'; -import chrome from 'ui/chrome'; import { QueryString } from 'ui/utils/query_string'; import url from 'url'; - +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { TimeRange } from '../../../../common/http_api/shared/time_range'; export const AnalyzeInMlButton: React.FunctionComponent<{ @@ -19,7 +18,11 @@ export const AnalyzeInMlButton: React.FunctionComponent<{ partition?: string; timeRange: TimeRange; }> = ({ jobId, partition, timeRange }) => { - const pathname = chrome.addBasePath('/app/ml'); + const prependBasePath = useKibana().services.http?.basePath?.prepend; + if (!prependBasePath) { + return null; + } + const pathname = prependBasePath('/app/ml'); const buttonLabel = ( = ({ logItem }) => { + const prependBasePath = useKibana().services.http?.basePath?.prepend; const { hide, isVisible, show } = useVisibilityState(false); - const uptimeLink = useMemo(() => getUptimeLink(logItem), [logItem]); + const uptimeLink = useMemo(() => { + const link = getUptimeLink(logItem); + return prependBasePath && link ? prependBasePath(link) : link; + }, [logItem, prependBasePath]); - const apmLink = useMemo(() => getAPMLink(logItem), [logItem]); + const apmLink = useMemo(() => { + const link = getAPMLink(logItem); + return prependBasePath && link ? prependBasePath(link) : link; + }, [logItem, prependBasePath]); const menuItems = useMemo( () => [ @@ -56,7 +62,6 @@ export const LogEntryActionsMenu: React.FunctionComponent<{ ); const hasMenuItems = useMemo(() => menuItems.length > 0, [menuItems]); - return ( { +const getUptimeLink = (logItem: LogEntriesItem) => { const searchExpressions = logItem.fields .filter(({ field, value }) => value != null && UPTIME_FIELDS.includes(field)) .map(({ field, value }) => `${field}:${value}`); @@ -94,12 +99,12 @@ const getUptimeLink = (logItem: InfraLogItem) => { } return url.format({ - pathname: chrome.addBasePath('/app/uptime'), + pathname: '/app/uptime', hash: `/?search=(${searchExpressions.join(' OR ')})`, }); }; -const getAPMLink = (logItem: InfraLogItem) => { +const getAPMLink = (logItem: LogEntriesItem) => { const traceIdEntry = logItem.fields.find( ({ field, value }) => value != null && field === 'trace.id' ); @@ -123,7 +128,7 @@ const getAPMLink = (logItem: InfraLogItem) => { : { rangeFrom: 'now-1y', rangeTo: 'now' }; return url.format({ - pathname: chrome.addBasePath('/app/apm'), + pathname: '/app/apm', hash: getTraceUrl({ traceId: traceIdEntry.value, rangeFrom, rangeTo }), }); }; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx index ed61c70afb73f..d2cb9cf9370dd 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx @@ -22,12 +22,12 @@ import React, { useCallback, useMemo } from 'react'; import euiStyled from '../../../../../../common/eui_styled_components'; import { TimeKey } from '../../../../common/time'; -import { InfraLogItem, InfraLogItemField } from '../../../graphql/types'; import { InfraLoadingPanel } from '../../loading'; import { LogEntryActionsMenu } from './log_entry_actions_menu'; +import { LogEntriesItem, LogEntriesItemField } from '../../../../common/http_api'; interface Props { - flyoutItem: InfraLogItem | null; + flyoutItem: LogEntriesItem | null; setFlyoutVisibility: (visible: boolean) => void; setFilter: (filter: string) => void; setTarget: (timeKey: TimeKey, flyoutItemId: string) => void; @@ -43,7 +43,7 @@ export const LogEntryFlyout = ({ setTarget, }: Props) => { const createFilterHandler = useCallback( - (field: InfraLogItemField) => () => { + (field: LogEntriesItemField) => () => { const filter = `${field.field}:"${field.value}"`; setFilter(filter); @@ -80,7 +80,7 @@ export const LogEntryFlyout = ({ defaultMessage: 'Value', }), sortable: true, - render: (_name: string, item: InfraLogItemField) => ( + render: (_name: string, item: LogEntriesItemField) => ( void; - uiCapabilities: UICapabilities; } -export const MetricsExplorerChart = injectUICapabilities( - ({ - source, - options, - chartOptions, - series, - title, - onFilter, - height = 200, - width = '100%', - timeRange, - onTimeChange, - uiCapabilities, - }: Props) => { - const { metrics } = options; - const [dateFormat] = useKibanaUiSetting('dateFormat'); - const handleTimeChange = (from: number, to: number) => { - onTimeChange(moment(from).toISOString(), moment(to).toISOString()); - }; - const dateFormatter = useMemo( - () => - series.rows.length > 0 - ? niceTimeFormatter([first(series.rows).timestamp, last(series.rows).timestamp]) - : (value: number) => `${value}`, - [series.rows] - ); - const tooltipProps = { - headerFormatter: useCallback( - (data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), - [dateFormat] - ), - }; - const yAxisFormater = useCallback(createFormatterForMetric(first(metrics)), [options]); - const dataDomain = calculateDomain(series, metrics, chartOptions.stack); - const domain = - chartOptions.yAxisMode === MetricsExplorerYAxisMode.fromZero - ? { ...dataDomain, min: 0 } - : dataDomain; - return ( -
- {options.groupBy ? ( - - - - - {title} - - - - - - - - ) : ( - +export const MetricsExplorerChart = ({ + source, + options, + chartOptions, + series, + title, + onFilter, + height = 200, + width = '100%', + timeRange, + onTimeChange, +}: Props) => { + const uiCapabilities = useKibana().services.application?.capabilities; + const isDarkMode = useUiSetting('theme:darkMode'); + const { metrics } = options; + const [dateFormat] = useKibanaUiSetting('dateFormat'); + const handleTimeChange = (from: number, to: number) => { + onTimeChange(moment(from).toISOString(), moment(to).toISOString()); + }; + const dateFormatter = useMemo( + () => + series.rows.length > 0 + ? niceTimeFormatter([first(series.rows).timestamp, last(series.rows).timestamp]) + : (value: number) => `${value}`, + [series.rows] + ); + const tooltipProps = { + headerFormatter: useCallback( + (data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), + [dateFormat] + ), + }; + const yAxisFormater = useCallback(createFormatterForMetric(first(metrics)), [options]); + const dataDomain = calculateDomain(series, metrics, chartOptions.stack); + const domain = + chartOptions.yAxisMode === MetricsExplorerYAxisMode.fromZero + ? { ...dataDomain, min: 0 } + : dataDomain; + return ( +
+ {options.groupBy ? ( + + + + + {title} + + - )} -
- {series.rows.length > 0 ? ( - - {metrics.map((metric, id) => ( - - ))} - - - + ) : ( + + + + + + )} +
+ {series.rows.length > 0 ? ( + + {metrics.map((metric, id) => ( + - - ) : options.metrics.length > 0 ? ( - - ) : ( - - )} -
+ ))} + + + +
+ ) : options.metrics.length > 0 ? ( + + ) : ( + + )}
- ); - } -); +
+ ); +}; const ChartTitle = euiStyled.div` - width: 100% - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: left; - flex: 1 1 auto; - margin: 12px; - `; + width: 100% + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + flex: 1 1 auto; + margin: 12px; +`; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx index 2c700d4053489..6c044baa56d7c 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.test.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { MetricsExplorerChartContextMenu, createNodeDetailLink } from './chart_context_menu'; import { mount } from 'enzyme'; import { options, source, timeRange, chartOptions } from '../../utils/fixtures/metrics_explorer'; -import { UICapabilities } from 'ui/capabilities'; import { InfraNodeType } from '../../graphql/types'; import DateMath from '@elastic/datemath'; import { ReactWrapper } from 'enzyme'; +import { Capabilities } from 'src/core/public'; const series = { id: 'exmaple-01', rows: [], columns: [] }; -const uiCapabilities: UICapabilities = { +const uiCapabilities: Capabilities = { navLinks: { show: false }, management: { fake: { show: false } }, catalogue: { show: false }, diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index aa3616c5ecfbe..597386fc24ee8 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -12,8 +12,8 @@ import { EuiContextMenuPanelDescriptor, EuiPopover, } from '@elastic/eui'; -import { UICapabilities } from 'ui/capabilities'; import DateMath from '@elastic/datemath'; +import { Capabilities } from 'src/core/public'; import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/types'; import { MetricsExplorerOptions, @@ -31,7 +31,7 @@ interface Props { series: MetricsExplorerSeries; source?: SourceConfiguration; timeRange: MetricsExplorerTimeOptions; - uiCapabilities: UICapabilities; + uiCapabilities?: Capabilities; chartOptions: MetricsExplorerChartOptions; } @@ -118,7 +118,7 @@ export const MetricsExplorerChartContextMenu = ({ ] : []; - const openInVisualize = uiCapabilities.visualize.show + const openInVisualize = uiCapabilities?.visualize?.show ? [ { name: i18n.translate('xpack.infra.metricsExplorer.openInTSVB', { diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/group_by.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/group_by.tsx index 568d768800259..505966e62e45f 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/group_by.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/group_by.tsx @@ -8,14 +8,14 @@ import { EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useCallback } from 'react'; -import { FieldType } from 'ui/index_patterns'; +import { IFieldType } from 'src/plugins/data/public'; import { MetricsExplorerOptions } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import { isDisplayable } from '../../utils/is_displayable'; interface Props { options: MetricsExplorerOptions; onChange: (groupBy: string | null) => void; - fields: FieldType[]; + fields: IFieldType[]; } export const MetricsExplorerGroupBy = ({ options, onChange, fields }: Props) => { diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/get_chart_theme.ts b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/get_chart_theme.ts index 7be2e54d47d75..42469ffb5ee9a 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/get_chart_theme.ts +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/helpers/get_chart_theme.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts'; -export function getChartTheme(): Theme { - const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode'); +export function getChartTheme(isDarkMode: boolean): Theme { return isDarkMode ? DARK_THEME : LIGHT_THEME; } diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx index 31d301e2164f8..7a8b22467ccd8 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx @@ -8,7 +8,7 @@ import { EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useCallback, useState } from 'react'; -import { FieldType } from 'ui/index_patterns'; +import { IFieldType } from 'src/plugins/data/public'; import { colorTransformer, MetricsExplorerColor } from '../../../common/color_palette'; import { MetricsExplorerMetric } from '../../../server/routes/metrics_explorer/types'; import { MetricsExplorerOptions } from '../../containers/metrics_explorer/use_metrics_explorer_options'; @@ -18,7 +18,7 @@ interface Props { autoFocus?: boolean; options: MetricsExplorerOptions; onChange: (metrics: MetricsExplorerMetric[]) => void; - fields: FieldType[]; + fields: IFieldType[]; } interface SelectedOption { diff --git a/x-pack/legacy/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/legacy/plugins/infra/public/components/saved_views/toolbar_control.tsx index ae04ede9fbe0c..e03b7fcc8ffa4 100644 --- a/x-pack/legacy/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/legacy/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -7,11 +7,12 @@ import { EuiButtonEmpty, EuiFlexGroup } from '@elastic/eui'; import React, { useCallback, useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; import { useSavedView } from '../../hooks/use_saved_view'; import { SavedViewCreateModal } from './create_modal'; import { SavedViewListFlyout } from './view_list_flyout'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; + interface Props { viewType: string; viewState: ViewState; @@ -20,6 +21,7 @@ interface Props { } export function SavedViewsToolbarControls(props: Props) { + const kibana = useKibana(); const { views, saveView, @@ -77,11 +79,11 @@ export function SavedViewsToolbarControls(props: Props) { useEffect(() => { if (errorOnCreate) { - toastNotifications.addWarning(getErrorToast('create', errorOnCreate)!); + kibana.notifications.toasts.warning(getErrorToast('create', errorOnCreate)!); } else if (errorOnFind) { - toastNotifications.addWarning(getErrorToast('find', errorOnFind)!); + kibana.notifications.toasts.warning(getErrorToast('find', errorOnFind)!); } - }, [errorOnCreate, errorOnFind]); + }, [errorOnCreate, errorOnFind, kibana]); return ( <> @@ -119,6 +121,7 @@ export function SavedViewsToolbarControls(props: Props) { const getErrorToast = (type: 'create' | 'find', msg?: string) => { if (type === 'create') { return { + toastLifeTimeMs: 3000, title: msg || i18n.translate('xpack.infra.savedView.errorOnCreate.title', { @@ -127,6 +130,7 @@ const getErrorToast = (type: 'create' | 'find', msg?: string) => { }; } else if (type === 'find') { return { + toastLifeTimeMs: 3000, title: msg || i18n.translate('xpack.infra.savedView.findError.title', { diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx index 1bfbf69b434bd..01bff0b4f96e1 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx @@ -6,12 +6,12 @@ import { EuiButton, EuiComboBox, EuiForm, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - import React from 'react'; -import { FieldType } from 'ui/index_patterns'; +import { IFieldType } from 'src/plugins/data/public'; + interface Props { onSubmit: (field: string) => void; - fields: FieldType[]; + fields: IFieldType[]; } interface SelectedOption { diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx index 43f0afb125d2b..cb894f37c1fce 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -13,13 +13,12 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { InfraNodeType } from '../../graphql/types'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to'; import { createUptimeLink } from './lib/create_uptime_link'; import { findInventoryModel } from '../../../common/inventory_models'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { options: InfraWaffleMapOptions; @@ -29,102 +28,99 @@ interface Props { nodeType: InfraNodeType; isPopoverOpen: boolean; closePopover: () => void; - uiCapabilities: UICapabilities; popoverPosition: EuiPopoverProps['anchorPosition']; } -export const NodeContextMenu = injectUICapabilities( - ({ - options, - currentTime, - children, - node, - isPopoverOpen, - closePopover, - nodeType, - uiCapabilities, - popoverPosition, - }: Props) => { - const inventoryModel = findInventoryModel(nodeType); - // Due to the changing nature of the fields between APM and this UI, - // We need to have some exceptions until 7.0 & ECS is finalized. Reference - // #26620 for the details for these fields. - // TODO: This is tech debt, remove it after 7.0 & ECS migration. - const apmField = nodeType === InfraNodeType.host ? 'host.hostname' : inventoryModel.fields.id; +export const NodeContextMenu = ({ + options, + currentTime, + children, + node, + isPopoverOpen, + closePopover, + nodeType, + popoverPosition, +}: Props) => { + const uiCapabilities = useKibana().services.application?.capabilities; + const inventoryModel = findInventoryModel(nodeType); + // Due to the changing nature of the fields between APM and this UI, + // We need to have some exceptions until 7.0 & ECS is finalized. Reference + // #26620 for the details for these fields. + // TODO: This is tech debt, remove it after 7.0 & ECS migration. + const apmField = nodeType === InfraNodeType.host ? 'host.hostname' : inventoryModel.fields.id; - const nodeLogsMenuItem = { - name: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', { - defaultMessage: 'View logs', - }), - href: getNodeLogsUrl({ - nodeType, - nodeId: node.id, - time: currentTime, - }), - 'data-test-subj': 'viewLogsContextMenuItem', - }; + const nodeLogsMenuItem = { + name: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', { + defaultMessage: 'View logs', + }), + href: getNodeLogsUrl({ + nodeType, + nodeId: node.id, + time: currentTime, + }), + 'data-test-subj': 'viewLogsContextMenuItem', + }; - const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; - const nodeDetailMenuItem = { - name: i18n.translate('xpack.infra.nodeContextMenu.viewMetricsName', { - defaultMessage: 'View metrics', - }), - href: getNodeDetailUrl({ - nodeType, - nodeId: node.id, - from: nodeDetailFrom, - to: currentTime, - }), - }; + const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; + const nodeDetailMenuItem = { + name: i18n.translate('xpack.infra.nodeContextMenu.viewMetricsName', { + defaultMessage: 'View metrics', + }), + href: getNodeDetailUrl({ + nodeType, + nodeId: node.id, + from: nodeDetailFrom, + to: currentTime, + }), + }; - const apmTracesMenuItem = { - name: i18n.translate('xpack.infra.nodeContextMenu.viewAPMTraces', { - defaultMessage: 'View APM traces', - }), - href: `../app/apm#/traces?_g=()&kuery=${apmField}:"${node.id}"`, - 'data-test-subj': 'viewApmTracesContextMenuItem', - }; + const apmTracesMenuItem = { + name: i18n.translate('xpack.infra.nodeContextMenu.viewAPMTraces', { + defaultMessage: 'View APM traces', + }), + href: `../app/apm#/traces?_g=()&kuery=${apmField}:"${node.id}"`, + 'data-test-subj': 'viewApmTracesContextMenuItem', + }; - const uptimeMenuItem = { - name: i18n.translate('xpack.infra.nodeContextMenu.viewUptimeLink', { - defaultMessage: 'View in Uptime', - }), - href: createUptimeLink(options, nodeType, node), - }; + const uptimeMenuItem = { + name: i18n.translate('xpack.infra.nodeContextMenu.viewUptimeLink', { + defaultMessage: 'View in Uptime', + }), + href: createUptimeLink(options, nodeType, node), + }; - const showDetail = inventoryModel.crosslinkSupport.details; - const showLogsLink = - inventoryModel.crosslinkSupport.logs && node.id && uiCapabilities.logs.show; - const showAPMTraceLink = - inventoryModel.crosslinkSupport.apm && uiCapabilities.apm && uiCapabilities.apm.show; - const showUptimeLink = - inventoryModel.crosslinkSupport.uptime && - ([InfraNodeType.pod, InfraNodeType.container].includes(nodeType) || node.ip); + const showDetail = inventoryModel.crosslinkSupport.details; + const showLogsLink = + inventoryModel.crosslinkSupport.logs && node.id && uiCapabilities?.logs?.show; + const showAPMTraceLink = + inventoryModel.crosslinkSupport.apm && uiCapabilities?.apm && uiCapabilities?.apm.show; + const showUptimeLink = + inventoryModel.crosslinkSupport.uptime && + ([InfraNodeType.pod, InfraNodeType.container].includes(nodeType) || node.ip); - const items = [ - ...(showLogsLink ? [nodeLogsMenuItem] : []), - ...(showDetail ? [nodeDetailMenuItem] : []), - ...(showAPMTraceLink ? [apmTracesMenuItem] : []), - ...(showUptimeLink ? [uptimeMenuItem] : []), - ]; - const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, title: '', items }]; + const items = [ + ...(showLogsLink ? [nodeLogsMenuItem] : []), + ...(showDetail ? [nodeDetailMenuItem] : []), + ...(showAPMTraceLink ? [apmTracesMenuItem] : []), + ...(showUptimeLink ? [uptimeMenuItem] : []), + ]; + const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, title: '', items }]; - // If there is nothing to show then we need to return the child as is - if (items.length === 0) { - return <>{children}; - } - - return ( - - - - ); + // If there is nothing to show then we need to return the child as is + if (items.length === 0) { + return <>{children}; } -); + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx index 949896fbb6002..0a9df1f666f3d 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx @@ -16,7 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { FieldType } from 'ui/index_patterns'; +import { IFieldType } from 'src/plugins/data/public'; import { InfraNodeType, InfraSnapshotGroupbyInput } from '../../graphql/types'; import { InfraGroupByOptions } from '../../lib/lib'; import { CustomFieldPanel } from './custom_field_panel'; @@ -28,7 +28,7 @@ interface Props { groupBy: InfraSnapshotGroupbyInput[]; onChange: (groupBy: InfraSnapshotGroupbyInput[]) => void; onChangeCustomOptions: (options: InfraGroupByOptions[]) => void; - fields: FieldType[]; + fields: IFieldType[]; customOptions: InfraGroupByOptions[]; } diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/flyout_item.gql_query.ts b/x-pack/legacy/plugins/infra/public/containers/logs/flyout_item.gql_query.ts deleted file mode 100644 index 2c51bdbb46e89..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/flyout_item.gql_query.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; -import { sharedFragments } from '../../../common/graphql/shared'; - -export const flyoutItemQuery = gql` - query FlyoutItemQuery($sourceId: ID!, $itemId: ID!) { - source(id: $sourceId) { - id - logItem(id: $itemId) { - id - index - key { - ...InfraTimeKeyFields - } - fields { - field - value - } - } - } - } - - ${sharedFragments.InfraTimeKey} -`; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts index 5054f607fa5dc..76e5f210ca238 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts @@ -8,7 +8,7 @@ import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { kfetch } from 'ui/kfetch'; +import { npStart } from 'ui/new_platform'; import { getDatafeedId, getJobId } from '../../../../../common/log_analysis'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; @@ -19,9 +19,8 @@ export const callDeleteJobs = async ( jobTypes: JobType[] ) => { // NOTE: Deleting the jobs via this API will delete the datafeeds at the same time - const deleteJobsResponse = await kfetch({ + const deleteJobsResponse = await npStart.core.http.fetch('/api/ml/jobs/delete_jobs', { method: 'POST', - pathname: '/api/ml/jobs/delete_jobs', body: JSON.stringify( deleteJobsRequestPayloadRT.encode({ jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)), @@ -36,10 +35,9 @@ export const callDeleteJobs = async ( }; export const callGetJobDeletionTasks = async () => { - const jobDeletionTasksResponse = await kfetch({ - method: 'GET', - pathname: '/api/ml/jobs/deleting_jobs_tasks', - }); + const jobDeletionTasksResponse = await npStart.core.http.fetch( + '/api/ml/jobs/deleting_jobs_tasks' + ); return pipe( getJobDeletionTasksResponsePayloadRT.decode(jobDeletionTasksResponse), @@ -53,9 +51,8 @@ export const callStopDatafeeds = async ( jobTypes: JobType[] ) => { // Stop datafeed due to https://github.com/elastic/kibana/issues/44652 - const stopDatafeedResponse = await kfetch({ + const stopDatafeedResponse = await npStart.core.http.fetch('/api/ml/jobs/stop_datafeeds', { method: 'POST', - pathname: '/api/ml/jobs/stop_datafeeds', body: JSON.stringify( stopDatafeedsRequestPayloadRT.encode({ datafeedIds: jobTypes.map(jobType => getDatafeedId(spaceId, sourceId, jobType)), diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts index 2e1ba52353bef..41c155e185c3a 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -8,8 +8,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import * as rt from 'io-ts'; -import { kfetch } from 'ui/kfetch'; - +import { npStart } from 'ui/new_platform'; import { jobCustomSettingsRT } from './ml_api_types'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; import { getJobId } from '../../../../../common/log_analysis'; @@ -19,9 +18,8 @@ export const callJobsSummaryAPI = async ( sourceId: string, jobTypes: JobType[] ) => { - const response = await kfetch({ + const response = await npStart.core.http.fetch('/api/ml/jobs/jobs_summary', { method: 'POST', - pathname: '/api/ml/jobs/jobs_summary', body: JSON.stringify( fetchJobStatusRequestPayloadRT.encode({ jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)), diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts index b58677ffa844e..ae90c7bb84130 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts @@ -8,15 +8,13 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { identity } from 'fp-ts/lib/function'; import * as rt from 'io-ts'; -import { kfetch } from 'ui/kfetch'; - +import { npStart } from 'ui/new_platform'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; import { jobCustomSettingsRT } from './ml_api_types'; export const callGetMlModuleAPI = async (moduleId: string) => { - const response = await kfetch({ + const response = await npStart.core.http.fetch(`/api/ml/modules/get_module/${moduleId}`, { method: 'GET', - pathname: `/api/ml/modules/get_module/${moduleId}`, }); return pipe( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts index 80a4f975cdd57..774a521ff1d34 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts @@ -8,8 +8,7 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { identity } from 'fp-ts/lib/function'; import * as rt from 'io-ts'; -import { kfetch } from 'ui/kfetch'; - +import { npStart } from 'ui/new_platform'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; import { getJobIdPrefix } from '../../../../../common/log_analysis'; @@ -23,9 +22,8 @@ export const callSetupMlModuleAPI = async ( jobOverrides: SetupMlModuleJobOverrides[] = [], datafeedOverrides: SetupMlModuleDatafeedOverrides[] = [] ) => { - const response = await kfetch({ + const response = await npStart.core.http.fetch(`/api/ml/modules/setup/${moduleId}`, { method: 'POST', - pathname: `/api/ml/modules/setup/${moduleId}`, body: JSON.stringify( setupMlModuleRequestPayloadRT.encode({ start, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts index 0d2e9b673488e..480e84ff53e43 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts @@ -7,8 +7,7 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { identity } from 'fp-ts/lib/function'; -import { kfetch } from 'ui/kfetch'; - +import { npStart } from 'ui/new_platform'; import { LOG_ANALYSIS_VALIDATE_INDICES_PATH, ValidationIndicesFieldSpecification, @@ -22,9 +21,8 @@ export const callValidateIndicesAPI = async ( indices: string[], fields: ValidationIndicesFieldSpecification[] ) => { - const response = await kfetch({ + const response = await npStart.core.http.fetch(LOG_ANALYSIS_VALIDATE_INDICES_PATH, { method: 'POST', - pathname: LOG_ANALYSIS_VALIDATE_INDICES_PATH, body: JSON.stringify(validationIndicesRequestPayloadRT.encode({ data: { indices, fields } })), }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx index bb01043b0db6e..bd8be6df8ea69 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx @@ -6,8 +6,7 @@ import createContainer from 'constate'; import { useMemo, useState, useEffect } from 'react'; -import { kfetch } from 'ui/kfetch'; - +import { npStart } from 'ui/new_platform'; import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { identity } from 'fp-ts/lib/function'; @@ -27,10 +26,7 @@ export const useLogAnalysisCapabilities = () => { { cancelPreviousOn: 'resolution', createPromise: async () => { - const rawResponse = await kfetch({ - method: 'GET', - pathname: '/api/ml/ml_capabilities', - }); + const rawResponse = await npStart.core.http.fetch('/api/ml/ml_capabilities'); return pipe( getMlCapabilitiesResponsePayloadRT.decode(rawResponse), diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries_item.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries_item.ts new file mode 100644 index 0000000000000..fd093f20dd490 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/api/fetch_log_entries_item.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import { kfetch } from 'ui/kfetch'; + +import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; + +import { + LOG_ENTRIES_ITEM_PATH, + LogEntriesItemRequest, + logEntriesItemRequestRT, + logEntriesItemResponseRT, +} from '../../../../../common/http_api'; + +export const fetchLogEntriesItem = async (requestArgs: LogEntriesItemRequest) => { + const response = await kfetch({ + method: 'POST', + pathname: LOG_ENTRIES_ITEM_PATH, + body: JSON.stringify(logEntriesItemRequestRT.encode(requestArgs)), + }); + + return pipe( + logEntriesItemResponseRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_flyout.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_flyout.tsx index 53b962c7de5a7..5c1667a4b7680 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_flyout.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_flyout.tsx @@ -8,12 +8,11 @@ import createContainer from 'constate'; import { isString } from 'lodash'; import React, { useContext, useEffect, useMemo, useState } from 'react'; -import { FlyoutItemQuery, InfraLogItem } from '../../graphql/types'; -import { useApolloClient } from '../../utils/apollo_context'; import { UrlStateContainer } from '../../utils/url_state'; import { useTrackedPromise } from '../../utils/use_tracked_promise'; import { Source } from '../source'; -import { flyoutItemQuery } from './flyout_item.gql_query'; +import { fetchLogEntriesItem } from './log_entries/api/fetch_log_entries_item'; +import { LogEntriesItem } from '../../../common/http_api'; export enum FlyoutVisibility { hidden = 'hidden', @@ -30,40 +29,26 @@ export const useLogFlyout = () => { const { sourceId } = useContext(Source.Context); const [flyoutVisible, setFlyoutVisibility] = useState(false); const [flyoutId, setFlyoutId] = useState(null); - const [flyoutItem, setFlyoutItem] = useState(null); + const [flyoutItem, setFlyoutItem] = useState(null); const [surroundingLogsId, setSurroundingLogsId] = useState(null); - const apolloClient = useApolloClient(); - const [loadFlyoutItemRequest, loadFlyoutItem] = useTrackedPromise( { cancelPreviousOn: 'creation', createPromise: async () => { - if (!apolloClient) { - throw new Error('Failed to load flyout item: No apollo client available.'); - } - if (!flyoutId) { return; } - - return await apolloClient.query({ - fetchPolicy: 'no-cache', - query: flyoutItemQuery, - variables: { - itemId: flyoutId, - sourceId, - }, - }); + return await fetchLogEntriesItem({ sourceId, id: flyoutId }); }, onResolve: response => { if (response) { const { data } = response; - setFlyoutItem((data && data.source && data.source.logItem) || null); + setFlyoutItem(data || null); } }, }, - [apolloClient, sourceId, flyoutId] + [sourceId, flyoutId] ); const isLoading = useMemo(() => { diff --git a/x-pack/legacy/plugins/infra/public/containers/with_kibana_chrome.tsx b/x-pack/legacy/plugins/infra/public/containers/with_kibana_chrome.tsx deleted file mode 100644 index 5fc83315d3dfd..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/with_kibana_chrome.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -import chrome from 'ui/chrome'; -import { Badge } from 'ui/chrome/api/badge'; -import { Breadcrumb } from 'ui/chrome/api/breadcrumbs'; -import { RendererFunction } from '../utils/typed_react'; - -interface WithKibanaChromeProps { - children: RendererFunction< - { - setBreadcrumbs: (newBreadcrumbs: Breadcrumb[]) => void; - setBadge: (badge: Badge | undefined) => void; - } & WithKibanaChromeState - >; -} - -interface WithKibanaChromeState { - basePath: string; -} - -export class WithKibanaChrome extends React.Component< - WithKibanaChromeProps, - WithKibanaChromeState -> { - public state: WithKibanaChromeState = { - basePath: chrome.getBasePath(), - }; - - public render() { - return this.props.children({ - ...this.state, - setBreadcrumbs: chrome.breadcrumbs.set, - setBadge: chrome.badge.set, - }); - } -} diff --git a/x-pack/legacy/plugins/infra/public/graphql/introspection.json b/x-pack/legacy/plugins/infra/public/graphql/introspection.json index efb527569b30e..5d351f3259ac5 100644 --- a/x-pack/legacy/plugins/infra/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/infra/public/graphql/introspection.json @@ -286,29 +286,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "logItem", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraLogItem", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "snapshot", "description": "A snapshot of nodes", @@ -1537,108 +1514,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "InfraLogItem", - "description": "", - "fields": [ - { - "name": "id", - "description": "The ID of the document", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "index", - "description": "The index where the document was found", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "key", - "description": "Time key for the document - derived from the source configuration timestamp and tiebreaker settings", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraTimeKey", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fields", - "description": "An array of flattened fields and values", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraLogItemField", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraLogItemField", - "description": "", - "fields": [ - { - "name": "field", - "description": "The flattened field name", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": "The value for the Field as a string", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "InfraTimerangeInput", @@ -1836,9 +1711,24 @@ { "name": "memory", "description": "", "isDeprecated": false, "deprecationReason": null }, { "name": "tx", "description": "", "isDeprecated": false, "deprecationReason": null }, { "name": "rx", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "logRate", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "diskIOReadBytes", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "diskIOWriteBytes", "description": "", "isDeprecated": false, "deprecationReason": null } + { + "name": "logRate", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "diskIOReadBytes", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "diskIOWriteBytes", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } ], "possibleTypes": null }, diff --git a/x-pack/legacy/plugins/infra/public/graphql/types.ts b/x-pack/legacy/plugins/infra/public/graphql/types.ts index 29c849a202930..79351d8dc16cd 100644 --- a/x-pack/legacy/plugins/infra/public/graphql/types.ts +++ b/x-pack/legacy/plugins/infra/public/graphql/types.ts @@ -37,7 +37,6 @@ export interface InfraSource { /** Sequences of log entries matching sets of highlighting queries within an interval */ logEntryHighlights: InfraLogEntryInterval[]; - logItem: InfraLogItem; /** A snapshot of nodes */ snapshot?: InfraSnapshotResponse | null; @@ -207,24 +206,6 @@ export interface InfraLogEntryFieldColumn { highlights: string[]; } -export interface InfraLogItem { - /** The ID of the document */ - id: string; - /** The index where the document was found */ - index: string; - /** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */ - key: InfraTimeKey; - /** An array of flattened fields and values */ - fields: InfraLogItemField[]; -} - -export interface InfraLogItemField { - /** The flattened field name */ - field: string; - /** The value for the Field as a string */ - value: string; -} - export interface InfraSnapshotResponse { /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ nodes: InfraSnapshotNode[]; @@ -426,9 +407,6 @@ export interface LogEntryHighlightsInfraSourceArgs { /** The highlighting to apply to the log entries */ highlights: InfraLogEntryHighlightInput[]; } -export interface LogItemInfraSourceArgs { - id: string; -} export interface SnapshotInfraSourceArgs { timerange: InfraTimerangeInput; @@ -602,49 +580,6 @@ export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessa // Documents // ==================================================== -export namespace FlyoutItemQuery { - export type Variables = { - sourceId: string; - itemId: string; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - logItem: LogItem; - }; - - export type LogItem = { - __typename?: 'InfraLogItem'; - - id: string; - - index: string; - - key: Key; - - fields: Fields[]; - }; - - export type Key = InfraTimeKeyFields.Fragment; - - export type Fields = { - __typename?: 'InfraLogItemField'; - - field: string; - - value: string; - }; -} - export namespace LogEntryHighlightsQuery { export type Variables = { sourceId?: string | null; diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx index a7dff2f11f232..2078c4bdc151d 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx @@ -5,57 +5,69 @@ */ import React, { useMemo, useState } from 'react'; -import { kfetch } from 'ui/kfetch'; -import { toastNotifications } from 'ui/notify'; +import { IHttpFetchError } from 'src/core/public'; import { i18n } from '@kbn/i18n'; -import { KFetchError } from 'ui/kfetch/kfetch_error'; -import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { useTrackedPromise } from '../utils/use_tracked_promise'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + export function useHTTPRequest( pathname: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD', body?: string, decode: (response: any) => Response = response => response ) { + const kibana = useKibana(); + const fetch = kibana.services.http?.fetch; + const toasts = kibana.notifications.toasts; const [response, setResponse] = useState(null); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const [request, makeRequest] = useTrackedPromise( { cancelPreviousOn: 'resolution', - createPromise: () => - kfetch({ + createPromise: () => { + if (!fetch) { + throw new Error('HTTP service is unavailable'); + } + return fetch(pathname, { method, - pathname, body, - }), + }); + }, onResolve: resp => setResponse(decode(resp)), onReject: (e: unknown) => { - const err = e as KFetchError; + const err = e as IHttpFetchError; setError(err); - toastNotifications.addWarning({ + toasts.warning({ + toastLifeTimeMs: 3000, title: i18n.translate('xpack.infra.useHTTPRequest.error.title', { defaultMessage: `Error while fetching resource`, }), - text: toMountPoint( + body: (
-
- {i18n.translate('xpack.infra.useHTTPRequest.error.status', { - defaultMessage: `Error`, - })} -
- {err.res?.statusText} ({err.res?.status}) -
- {i18n.translate('xpack.infra.useHTTPRequest.error.url', { - defaultMessage: `URL`, - })} -
- {err.res?.url} + {err.response ? ( + <> +
+ {i18n.translate('xpack.infra.useHTTPRequest.error.status', { + defaultMessage: `Error`, + })} +
+ {err.response?.statusText} ({err.response?.status}) +
+ {i18n.translate('xpack.infra.useHTTPRequest.error.url', { + defaultMessage: `URL`, + })} +
+ {err.response?.url} + + ) : ( +
{err.message}
+ )}
), }); }, }, - [pathname, body, method] + [pathname, body, method, fetch, toasts] ); const loading = useMemo(() => { diff --git a/x-pack/legacy/plugins/infra/public/index.ts b/x-pack/legacy/plugins/infra/public/index.ts index 7967e640ee9d3..5e68ca85681fe 100644 --- a/x-pack/legacy/plugins/infra/public/index.ts +++ b/x-pack/legacy/plugins/infra/public/index.ts @@ -4,4 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +// NP_NOTE: Whilst we are in the transition period of the NP migration, this index file +// is exclusively for our static code exports that other plugins (e.g. APM) use. +// When we switch over to the real NP, and an export of "plugin" is expected and called, +// we can do away with the middle "app.ts" layer. The "app.ts" layer is needed for now, +// and needs to be situated differently to this index file, so that our code for setting the root template +// and attempting to start the app doesn't try to run just because another plugin is importing from this file. + export { useTrackPageview } from './hooks/use_track_metric'; diff --git a/x-pack/legacy/plugins/infra/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/infra/public/lib/adapters/framework/kibana_framework_adapter.ts deleted file mode 100644 index f91b40815a3ae..0000000000000 --- a/x-pack/legacy/plugins/infra/public/lib/adapters/framework/kibana_framework_adapter.ts +++ /dev/null @@ -1,187 +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. - */ - -/* eslint-disable max-classes-per-file */ - -import { IModule, IScope } from 'angular'; -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; - -import { UIRoutes as KibanaUIRoutes } from 'ui/routes'; - -import { - InfraBufferedKibanaServiceCall, - InfraFrameworkAdapter, - InfraKibanaAdapterServiceRefs, - InfraKibanaUIConfig, - InfraTimezoneProvider, - InfraUiKibanaAdapterScope, -} from '../../lib'; - -const ROOT_ELEMENT_ID = 'react-infra-root'; -const BREADCRUMBS_ELEMENT_ID = 'react-infra-breadcrumbs'; - -export class KibanaFramework implements InfraFrameworkAdapter { - public appState: object; - public kbnVersion?: string; - public timezone?: string; - - private adapterService: KibanaAdapterServiceProvider; - private timezoneProvider: InfraTimezoneProvider; - private rootComponent: React.ReactElement | null = null; - private breadcrumbsComponent: React.ReactElement | null = null; - - constructor( - uiModule: IModule, - uiRoutes: KibanaUIRoutes, - timezoneProvider: InfraTimezoneProvider - ) { - this.adapterService = new KibanaAdapterServiceProvider(); - this.timezoneProvider = timezoneProvider; - this.appState = {}; - this.register(uiModule, uiRoutes); - } - - public setUISettings = (key: string, value: any) => { - this.adapterService.callOrBuffer(({ config }) => { - config.set(key, value); - }); - }; - - public render = (component: React.ReactElement) => { - this.adapterService.callOrBuffer(() => (this.rootComponent = component)); - }; - - public renderBreadcrumbs = (component: React.ReactElement) => { - this.adapterService.callOrBuffer(() => (this.breadcrumbsComponent = component)); - }; - - private register = (adapterModule: IModule, uiRoutes: KibanaUIRoutes) => { - adapterModule.provider('kibanaAdapter', this.adapterService); - - adapterModule.directive('infraUiKibanaAdapter', () => ({ - controller: ($scope: InfraUiKibanaAdapterScope, $element: JQLite) => ({ - $onDestroy: () => { - const targetRootElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`); - const targetBreadcrumbsElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`); - - if (targetRootElement) { - ReactDOM.unmountComponentAtNode(targetRootElement); - } - - if (targetBreadcrumbsElement) { - ReactDOM.unmountComponentAtNode(targetBreadcrumbsElement); - } - }, - $onInit: () => { - $scope.topNavMenu = []; - }, - $postLink: () => { - $scope.$watchGroup( - [ - () => this.breadcrumbsComponent, - () => $element[0].querySelector(`#${BREADCRUMBS_ELEMENT_ID}`), - ], - ([breadcrumbsComponent, targetElement]) => { - if (!targetElement) { - return; - } - - if (breadcrumbsComponent) { - ReactDOM.render(breadcrumbsComponent, targetElement); - } else { - ReactDOM.unmountComponentAtNode(targetElement); - } - } - ); - $scope.$watchGroup( - [() => this.rootComponent, () => $element[0].querySelector(`#${ROOT_ELEMENT_ID}`)], - ([rootComponent, targetElement]) => { - if (!targetElement) { - return; - } - - if (rootComponent) { - ReactDOM.render(rootComponent, targetElement); - } else { - ReactDOM.unmountComponentAtNode(targetElement); - } - } - ); - }, - }), - scope: true, - template: ` -
- `, - })); - - adapterModule.run( - ( - config: InfraKibanaUIConfig, - kbnVersion: string, - Private: (provider: Provider) => Provider, - // @ts-ignore: inject kibanaAdapter to force eager instatiation - kibanaAdapter: any - ) => { - this.timezone = Private(this.timezoneProvider)(); - this.kbnVersion = kbnVersion; - } - ); - - uiRoutes.enable(); - - uiRoutes.otherwise({ - reloadOnSearch: false, - template: - '', - }); - }; -} - -class KibanaAdapterServiceProvider { - public serviceRefs: InfraKibanaAdapterServiceRefs | null = null; - public bufferedCalls: Array> = []; - - public $get($rootScope: IScope, config: InfraKibanaUIConfig) { - this.serviceRefs = { - config, - rootScope: $rootScope, - }; - - this.applyBufferedCalls(this.bufferedCalls); - - return this; - } - - public callOrBuffer(serviceCall: (serviceRefs: InfraKibanaAdapterServiceRefs) => void) { - if (this.serviceRefs !== null) { - this.applyBufferedCalls([serviceCall]); - } else { - this.bufferedCalls.push(serviceCall); - } - } - - public applyBufferedCalls( - bufferedCalls: Array> - ) { - if (!this.serviceRefs) { - return; - } - - this.serviceRefs.rootScope.$apply(() => { - bufferedCalls.forEach(serviceCall => { - if (!this.serviceRefs) { - return; - } - return serviceCall(this.serviceRefs); - }); - }); - } -} diff --git a/x-pack/legacy/plugins/infra/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/legacy/plugins/infra/public/lib/adapters/framework/testing_framework_adapter.ts deleted file mode 100644 index f1f08f6ffbf34..0000000000000 --- a/x-pack/legacy/plugins/infra/public/lib/adapters/framework/testing_framework_adapter.ts +++ /dev/null @@ -1,27 +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 { InfraFrameworkAdapter } from '../../lib'; - -export class InfraTestingFrameworkAdapter implements InfraFrameworkAdapter { - public appState?: object; - public kbnVersion?: string; - public timezone?: string; - - constructor() { - this.appState = {}; - } - - public render() { - return; - } - public renderBreadcrumbs() { - return; - } - public setUISettings() { - return; - } -} diff --git a/x-pack/legacy/plugins/infra/public/lib/adapters/observable_api/kibana_observable_api.ts b/x-pack/legacy/plugins/infra/public/lib/adapters/observable_api/kibana_observable_api.ts index 157a7ebe9fedf..9ae21d96886f3 100644 --- a/x-pack/legacy/plugins/infra/public/lib/adapters/observable_api/kibana_observable_api.ts +++ b/x-pack/legacy/plugins/infra/public/lib/adapters/observable_api/kibana_observable_api.ts @@ -16,13 +16,13 @@ import { export class InfraKibanaObservableApiAdapter implements InfraObservableApi { private basePath: string; private defaultHeaders: { - [headerName: string]: string; + [headerName: string]: boolean | string; }; - constructor({ basePath, xsrfToken }: { basePath: string; xsrfToken: string }) { + constructor({ basePath }: { basePath: string }) { this.basePath = basePath; this.defaultHeaders = { - 'kbn-version': xsrfToken, + 'kbn-xsrf': true, }; } diff --git a/x-pack/legacy/plugins/infra/public/lib/compose/kibana_compose.ts b/x-pack/legacy/plugins/infra/public/lib/compose/kibana_compose.ts deleted file mode 100644 index 9b0beb3ad519c..0000000000000 --- a/x-pack/legacy/plugins/infra/public/lib/compose/kibana_compose.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 'ui/autoload/all'; -// @ts-ignore: path dynamic for kibana -import chrome from 'ui/chrome'; -// @ts-ignore: path dynamic for kibana -import { uiModules } from 'ui/modules'; -import uiRoutes from 'ui/routes'; -// @ts-ignore: path dynamic for kibana -import { timezoneProvider } from 'ui/vis/lib/timezone'; - -import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; -import ApolloClient from 'apollo-client'; -import { ApolloLink } from 'apollo-link'; -import { HttpLink } from 'apollo-link-http'; -import { withClientState } from 'apollo-link-state'; -import { InfraFrontendLibs } from '../lib'; -import introspectionQueryResultData from '../../graphql/introspection.json'; -import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; -import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; - -export function compose(): InfraFrontendLibs { - const cache = new InMemoryCache({ - addTypename: false, - fragmentMatcher: new IntrospectionFragmentMatcher({ - introspectionQueryResultData, - }), - }); - - const observableApi = new InfraKibanaObservableApiAdapter({ - basePath: chrome.getBasePath(), - xsrfToken: chrome.getXsrfToken(), - }); - - const graphQLOptions = { - cache, - link: ApolloLink.from([ - withClientState({ - cache, - resolvers: {}, - }), - new HttpLink({ - credentials: 'same-origin', - headers: { - 'kbn-xsrf': chrome.getXsrfToken(), - }, - uri: `${chrome.getBasePath()}/api/infra/graphql`, - }), - ]), - }; - - const apolloClient = new ApolloClient(graphQLOptions); - - const infraModule = uiModules.get('app/infa'); - - const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider); - - const libs: InfraFrontendLibs = { - apolloClient, - framework, - observableApi, - }; - return libs; -} diff --git a/x-pack/legacy/plugins/infra/public/lib/compose/testing_compose.ts b/x-pack/legacy/plugins/infra/public/lib/compose/testing_compose.ts deleted file mode 100644 index 1e0b2f079497d..0000000000000 --- a/x-pack/legacy/plugins/infra/public/lib/compose/testing_compose.ts +++ /dev/null @@ -1,59 +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 'ui/autoload/all'; -// @ts-ignore: path dynamic for kibana -import chrome from 'ui/chrome'; -// @ts-ignore: path dynamic for kibana -import { uiModules } from 'ui/modules'; -import uiRoutes from 'ui/routes'; -// @ts-ignore: path dynamic for kibana -import { timezoneProvider } from 'ui/vis/lib/timezone'; - -import { InMemoryCache } from 'apollo-cache-inmemory'; -import ApolloClient from 'apollo-client'; -import { SchemaLink } from 'apollo-link-schema'; -import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools'; -import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; -import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; -import { InfraFrontendLibs } from '../lib'; - -export function compose(): InfraFrontendLibs { - const infraModule = uiModules.get('app/infa'); - const observableApi = new InfraKibanaObservableApiAdapter({ - basePath: chrome.getBasePath(), - xsrfToken: chrome.getXsrfToken(), - }); - const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider); - const typeDefs = ` - Query {} -`; - - const mocks = { - Mutation: () => undefined, - Query: () => undefined, - }; - - const schema = makeExecutableSchema({ typeDefs }); - addMockFunctionsToSchema({ - mocks, - schema, - }); - - const cache = new InMemoryCache((window as any).__APOLLO_CLIENT__); - - const apolloClient = new ApolloClient({ - cache, - link: new SchemaLink({ schema }), - }); - - const libs: InfraFrontendLibs = { - apolloClient, - framework, - observableApi, - }; - return libs; -} diff --git a/x-pack/legacy/plugins/infra/public/lib/lib.ts b/x-pack/legacy/plugins/infra/public/lib/lib.ts index 3a9c12f9680e3..4b402ce6beafe 100644 --- a/x-pack/legacy/plugins/infra/public/lib/lib.ts +++ b/x-pack/legacy/plugins/infra/public/lib/lib.ts @@ -20,7 +20,6 @@ import { } from '../graphql/types'; export interface InfraFrontendLibs { - framework: InfraFrameworkAdapter; apolloClient: InfraApolloClient; observableApi: InfraObservableApi; } diff --git a/x-pack/legacy/plugins/siem/public/lib/compose/__mocks__/kibana_plugins.ts b/x-pack/legacy/plugins/infra/public/new_platform_index.ts similarity index 55% rename from x-pack/legacy/plugins/siem/public/lib/compose/__mocks__/kibana_plugins.ts rename to x-pack/legacy/plugins/infra/public/new_platform_index.ts index ab255f7c73a06..33b40da236145 100644 --- a/x-pack/legacy/plugins/siem/public/lib/compose/__mocks__/kibana_plugins.ts +++ b/x-pack/legacy/plugins/infra/public/new_platform_index.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; +import { PluginInitializerContext } from 'kibana/public'; +import { Plugin } from './new_platform_plugin'; -const npStart = createUiNewPlatformMock().npStart; - -export function useKibanaPlugins() { - return npStart.plugins; +export function plugin(context: PluginInitializerContext) { + return new Plugin(context); } diff --git a/x-pack/legacy/plugins/infra/public/new_platform_plugin.ts b/x-pack/legacy/plugins/infra/public/new_platform_plugin.ts new file mode 100644 index 0000000000000..78594afcc8ada --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/new_platform_plugin.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { CoreStart, PluginInitializerContext } from 'kibana/public'; +import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; +import ApolloClient from 'apollo-client'; +import { ApolloLink } from 'apollo-link'; +import { HttpLink } from 'apollo-link-http'; +import { withClientState } from 'apollo-link-state'; +import { startApp } from './apps/start_app'; +import { InfraFrontendLibs } from './lib/lib'; +import introspectionQueryResultData from './graphql/introspection.json'; +import { InfraKibanaObservableApiAdapter } from './lib/adapters/observable_api/kibana_observable_api'; + +type ClientPlugins = any; +type LegacyDeps = any; + +export class Plugin { + constructor(context: PluginInitializerContext) {} + start(core: CoreStart, plugins: ClientPlugins, __LEGACY: LegacyDeps) { + startApp(this.composeLibs(core, plugins, __LEGACY), core, plugins); + } + + composeLibs(core: CoreStart, plugins: ClientPlugins, legacy: LegacyDeps) { + const cache = new InMemoryCache({ + addTypename: false, + fragmentMatcher: new IntrospectionFragmentMatcher({ + introspectionQueryResultData, + }), + }); + + const observableApi = new InfraKibanaObservableApiAdapter({ + basePath: core.http.basePath.get(), + }); + + const graphQLOptions = { + cache, + link: ApolloLink.from([ + withClientState({ + cache, + resolvers: {}, + }), + new HttpLink({ + credentials: 'same-origin', + headers: { + 'kbn-xsrf': true, + }, + uri: `${core.http.basePath.get()}/api/infra/graphql`, + }), + ]), + }; + + const apolloClient = new ApolloClient(graphQLOptions); + + const libs: InfraFrontendLibs = { + apolloClient, + observableApi, + }; + return libs; + } +} diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx index 9efbbe790abc1..dfe4fb05d669a 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx @@ -8,8 +8,6 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; @@ -25,13 +23,11 @@ import { SnapshotPage } from './snapshot'; import { SettingsPage } from '../shared/settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { SourceLoadingPage } from '../../components/source_loading_page'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -interface InfrastructurePageProps extends RouteComponentProps { - uiCapabilities: UICapabilities; -} - -export const InfrastructurePage = injectUICapabilities( - ({ match, uiCapabilities }: InfrastructurePageProps) => ( +export const InfrastructurePage = ({ match }: RouteComponentProps) => { + const uiCapabilities = useKibana().services.application?.capabilities; + return ( - ) -); + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/index.tsx index 612b4a6d91e7f..b9651763e43c0 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/index.tsx @@ -8,8 +8,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { SnapshotPageContent } from './page_content'; import { SnapshotToolbar } from './toolbar'; @@ -27,15 +25,11 @@ import { Source } from '../../../containers/source'; import { WithWaffleFilterUrlState } from '../../../containers/waffle/with_waffle_filters'; import { WithWaffleOptionsUrlState } from '../../../containers/waffle/with_waffle_options'; import { WithWaffleTimeUrlState } from '../../../containers/waffle/with_waffle_time'; -import { WithKibanaChrome } from '../../../containers/with_kibana_chrome'; import { useTrackPageview } from '../../../hooks/use_track_metric'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -interface SnapshotPageProps { - uiCapabilities: UICapabilities; -} - -export const SnapshotPage = injectUICapabilities((props: SnapshotPageProps) => { - const { uiCapabilities } = props; +export const SnapshotPage = () => { + const uiCapabilities = useKibana().services.application?.capabilities; const { createDerivedIndexPattern, hasFailedLoadingSource, @@ -44,7 +38,7 @@ export const SnapshotPage = injectUICapabilities((props: SnapshotPageProps) => { loadSource, metricIndicesExist, } = useContext(Source.Context); - + const basePath = useKibana().services.http?.basePath || ''; useTrackPageview({ app: 'infra_metrics', path: 'inventory' }); useTrackPageview({ app: 'infra_metrics', path: 'inventory', delay: 15000 }); @@ -73,49 +67,44 @@ export const SnapshotPage = injectUICapabilities((props: SnapshotPageProps) => { ) : hasFailedLoadingSource ? ( ) : ( - - {({ basePath }) => ( - - - - {i18n.translate( - 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', - { defaultMessage: 'View setup instructions' } - )} - - - {uiCapabilities.infrastructure.configureSource ? ( - - - {i18n.translate('xpack.infra.configureSourceActionLabel', { - defaultMessage: 'Change source configuration', - })} - - - ) : null} -
- } - data-test-subj="noMetricsIndicesPrompt" - /> - )} - + + + + {i18n.translate('xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', { + defaultMessage: 'View setup instructions', + })} + + + {uiCapabilities?.infrastructure?.configureSource ? ( + + + {i18n.translate('xpack.infra.configureSourceActionLabel', { + defaultMessage: 'Change source configuration', + })} + + + ) : null} + + } + data-test-subj="noMetricsIndicesPrompt" + /> )} ); -}); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index b0619fdcc2acf..a8a75f99253c2 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -7,8 +7,6 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; @@ -27,14 +25,12 @@ import { } from '../../containers/logs/log_analysis'; import { useSourceId } from '../../containers/source_id'; import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params'; +import { useKibana } from '../../../../../../..//src/plugins/kibana_react/public'; import { LogEntryCategoriesPage } from './log_entry_categories'; import { LogEntryRatePage } from './log_entry_rate'; -interface LogsPageProps extends RouteComponentProps { - uiCapabilities: UICapabilities; -} - -export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPageProps) => { +export const LogsPage = ({ match }: RouteComponentProps) => { + const uiCapabilities = useKibana().services.application?.capabilities; const [sourceId] = useSourceId(); const source = useSource({ sourceId }); const logAnalysisCapabilities = useLogAnalysisCapabilities(); @@ -83,7 +79,7 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag text: pageTitle, }, ]} - readOnlyBadge={!uiCapabilities.logs.save} + readOnlyBadge={!uiCapabilities?.logs?.save} /> {source.isLoadingSource || (!source.isLoadingSource && @@ -124,7 +120,7 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag ); -}); +}; const pageTitle = i18n.translate('xpack.infra.header.logsTitle', { defaultMessage: 'Logs', diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts index 471a00d40984c..1df7ef06b9d46 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts @@ -7,8 +7,7 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { identity } from 'fp-ts/lib/function'; -import { kfetch } from 'ui/kfetch'; - +import { npStart } from 'ui/new_platform'; import { getLogEntryRateRequestPayloadRT, getLogEntryRateSuccessReponsePayloadRT, @@ -22,9 +21,8 @@ export const callGetLogEntryRateAPI = async ( endTime: number, bucketDuration: number ) => { - const response = await kfetch({ + const response = await npStart.core.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, { method: 'POST', - pathname: LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, body: JSON.stringify( getLogEntryRateRequestPayloadRT.encode({ data: { diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx index f1bce3da6c876..0fe382bae03b9 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx @@ -9,66 +9,53 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { NoIndices } from '../../../components/empty_states/no_indices'; -import { WithKibanaChrome } from '../../../containers/with_kibana_chrome'; import { ViewSourceConfigurationButton, ViewSourceConfigurationButtonHrefBase, } from '../../../components/source_configuration'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -interface LogsPageNoIndicesContentProps { - uiCapabilities: UICapabilities; -} - -export const LogsPageNoIndicesContent = injectUICapabilities( - (props: LogsPageNoIndicesContentProps) => { - const { uiCapabilities } = props; - - return ( - - {({ basePath }) => ( - - - - {i18n.translate( - 'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel', - { defaultMessage: 'View setup instructions' } - )} - - - {uiCapabilities.logs.configureSource ? ( - - - {i18n.translate('xpack.infra.configureSourceActionLabel', { - defaultMessage: 'Change source configuration', - })} - - - ) : null} - - } - /> - )} - - ); - } -); +export const LogsPageNoIndicesContent = () => { + const basePath = useKibana().services.http?.basePath || ''; + const uiCapabilities = useKibana().services.application?.capabilities; + return ( + + + + {i18n.translate('xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel', { + defaultMessage: 'View setup instructions', + })} + + + {uiCapabilities?.logs?.configureSource ? ( + + + {i18n.translate('xpack.infra.configureSourceActionLabel', { + defaultMessage: 'Change source configuration', + })} + + + ) : null} + + } + /> + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx index 309961cc39025..3d4d656823332 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx @@ -28,6 +28,7 @@ import { } from './helpers'; import { ErrorMessage } from './error_message'; import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; +import { useUiSetting } from '../../../../../../../../src/plugins/kibana_react/public'; import { VisSectionProps } from '../types'; export const ChartSectionVis = ({ @@ -42,6 +43,7 @@ export const ChartSectionVis = ({ seriesOverrides, type, }: VisSectionProps) => { + const isDarkMode = useUiSetting('theme:darkMode'); const [dateFormat] = useKibanaUiSetting('dateFormat'); const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [ formatter, @@ -125,7 +127,7 @@ export const ChartSectionVis = ({ diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/invalid_node.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/invalid_node.tsx index f9e56791746f5..6bda7b7a7cd08 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/invalid_node.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/invalid_node.tsx @@ -9,70 +9,67 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import euiStyled from '../../../../../../common/eui_styled_components'; -import { WithKibanaChrome } from '../../../containers/with_kibana_chrome'; import { ViewSourceConfigurationButton, ViewSourceConfigurationButtonHrefBase, } from '../../../components/source_configuration'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; interface InvalidNodeErrorProps { nodeName: string; } export const InvalidNodeError: React.FunctionComponent = ({ nodeName }) => { + const basePath = useKibana().services.http?.basePath || ''; return ( - - {({ basePath }) => ( - + + + + } + body={ +

+ +

+ } + actions={ + + + - - } - body={ -

+ + + + -

- } - actions={ - - - - - - - - - - - - - } - /> - )} -
+ + + + } + /> ); }; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_error.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_error.tsx index c893d3079364d..e54cdcd151f6f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_error.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_error.tsx @@ -7,7 +7,7 @@ // import { GraphQLFormattedError } from 'graphql'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { KFetchError } from 'ui/kfetch/kfetch_error'; +import { IHttpFetchError } from 'src/core/public'; import { InvalidNodeError } from './invalid_node'; // import { InfraMetricsErrorCodes } from '../../../../common/errors'; import { DocumentTitle } from '../../../components/document_title'; @@ -15,7 +15,7 @@ import { ErrorPageBody } from '../../error'; interface Props { name: string; - error: KFetchError; + error: IHttpFetchError; } export const PageError = ({ error, name }: Props) => { diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx index b330ad02f1022..b7e8274802555 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx @@ -5,8 +5,6 @@ */ import { i18n } from '@kbn/i18n'; import React, { useContext, useState } from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import euiStyled, { EuiTheme, withTheme } from '../../../../../common/eui_styled_components'; import { DocumentTitle } from '../../components/document_title'; import { Header } from '../../components/header'; @@ -20,6 +18,7 @@ import { InfraLoadingPanel } from '../../components/loading'; import { findInventoryModel } from '../../../common/inventory_models'; import { NavItem } from './lib/side_nav_context'; import { NodeDetailsPage } from './components/node_details_page'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; const DetailPageContent = euiStyled(PageContent)` overflow: auto; @@ -34,111 +33,109 @@ interface Props { node: string; }; }; - uiCapabilities: UICapabilities; } export const MetricDetail = withMetricPageProviders( - injectUICapabilities( - withTheme(({ uiCapabilities, match, theme }: Props) => { - const nodeId = match.params.node; - const nodeType = match.params.type as InfraNodeType; - const inventoryModel = findInventoryModel(nodeType); - const { sourceId } = useContext(Source.Context); - const { - name, - filteredRequiredMetrics, - loading: metadataLoading, - cloudId, - metadata, - } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId); + withTheme(({ match, theme }: Props) => { + const uiCapabilities = useKibana().services.application?.capabilities; + const nodeId = match.params.node; + const nodeType = match.params.type as InfraNodeType; + const inventoryModel = findInventoryModel(nodeType); + const { sourceId } = useContext(Source.Context); + const { + name, + filteredRequiredMetrics, + loading: metadataLoading, + cloudId, + metadata, + } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId); - const [sideNav, setSideNav] = useState([]); + const [sideNav, setSideNav] = useState([]); - const addNavItem = React.useCallback( - (item: NavItem) => { - if (!sideNav.some(n => n.id === item.id)) { - setSideNav([item, ...sideNav]); - } - }, - [sideNav] - ); - - const breadcrumbs = [ - { - href: '#/', - text: i18n.translate('xpack.infra.header.infrastructureTitle', { - defaultMessage: 'Metrics', - }), - }, - { text: name }, - ]; + const addNavItem = React.useCallback( + (item: NavItem) => { + if (!sideNav.some(n => n.id === item.id)) { + setSideNav([item, ...sideNav]); + } + }, + [sideNav] + ); - if (metadataLoading && !filteredRequiredMetrics.length) { - return ( - - ); - } + const breadcrumbs = [ + { + href: '#/', + text: i18n.translate('xpack.infra.header.infrastructureTitle', { + defaultMessage: 'Metrics', + }), + }, + { text: name }, + ]; + if (metadataLoading && !filteredRequiredMetrics.length) { return ( - - {({ - timeRange, - parsedTimeRange, - setTimeRange, - refreshInterval, - setRefreshInterval, - isAutoReloading, - setAutoReload, - triggerRefresh, - }) => ( - -
- - - - {metadata ? ( - - ) : null} - - - )} - + ); - }) - ) + } + + return ( + + {({ + timeRange, + parsedTimeRange, + setTimeRange, + refreshInterval, + setRefreshInterval, + isAutoReloading, + setAutoReload, + triggerRefresh, + }) => ( + +
+ + + + {metadata ? ( + + ) : null} + + + )} + + ); + }) ); diff --git a/x-pack/legacy/plugins/infra/public/pages/shared/settings/index.tsx b/x-pack/legacy/plugins/infra/public/pages/shared/settings/index.tsx index daea6cfabdc2a..23ed1b273aa0e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/shared/settings/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/shared/settings/index.tsx @@ -5,14 +5,14 @@ */ import React from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { SourceConfigurationSettings } from '../../../components/source_configuration/source_configuration_settings'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -interface SettingsPageProps { - uiCapabilities: UICapabilities; -} - -export const SettingsPage = injectUICapabilities(({ uiCapabilities }: SettingsPageProps) => ( - -)); +export const SettingsPage = () => { + const uiCapabilities = useKibana().services.application?.capabilities; + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/routes.tsx b/x-pack/legacy/plugins/infra/public/routes.tsx index 9dedc612bbe54..fd69c6e842448 100644 --- a/x-pack/legacy/plugins/infra/public/routes.tsx +++ b/x-pack/legacy/plugins/infra/public/routes.tsx @@ -8,55 +8,54 @@ import { History } from 'history'; import React from 'react'; import { Route, Router, Switch } from 'react-router-dom'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { NotFoundPage } from './pages/404'; import { InfrastructurePage } from './pages/infrastructure'; import { LinkToPage } from './pages/link_to'; import { LogsPage } from './pages/logs'; import { MetricDetail } from './pages/metrics'; import { RedirectWithQueryParams } from './utils/redirect_with_query_params'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; interface RouterProps { history: History; - uiCapabilities: UICapabilities; } -const PageRouterComponent: React.FC = ({ history, uiCapabilities }) => { +export const PageRouter: React.FC = ({ history }) => { + const uiCapabilities = useKibana().services.application?.capabilities; return ( - {uiCapabilities.infrastructure.show && ( + {uiCapabilities?.infrastructure?.show && ( )} - {uiCapabilities.infrastructure.show && ( + {uiCapabilities?.infrastructure?.show && ( )} - {uiCapabilities.infrastructure.show && ( + {uiCapabilities?.infrastructure?.show && ( )} - {uiCapabilities.infrastructure.show && ( + {uiCapabilities?.infrastructure?.show && ( )} - {uiCapabilities.infrastructure.show && ( + {uiCapabilities?.infrastructure?.show && ( )} - {uiCapabilities.infrastructure.show && ( + {uiCapabilities?.infrastructure?.show && ( )} - {uiCapabilities.logs.show && ( + {uiCapabilities?.logs?.show && ( )} - {uiCapabilities.logs.show && } - {uiCapabilities.infrastructure.show && ( + {uiCapabilities?.logs?.show && } + {uiCapabilities?.infrastructure?.show && ( )} @@ -65,5 +64,3 @@ const PageRouterComponent: React.FC = ({ history, uiCapabilities }) ); }; - -export const PageRouter = injectUICapabilities(PageRouterComponent); diff --git a/x-pack/legacy/plugins/infra/public/utils/is_displayable.ts b/x-pack/legacy/plugins/infra/public/utils/is_displayable.ts index 6af6bce315599..534282e807036 100644 --- a/x-pack/legacy/plugins/infra/public/utils/is_displayable.ts +++ b/x-pack/legacy/plugins/infra/public/utils/is_displayable.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FieldType } from 'ui/index_patterns'; +import { IFieldType } from 'src/plugins/data/public'; import { startsWith, uniq } from 'lodash'; import { getAllowedListForPrefix } from '../../common/ecs_allowed_list'; -interface DisplayableFieldType extends FieldType { +interface DisplayableFieldType extends IFieldType { displayable?: boolean; } diff --git a/x-pack/legacy/plugins/infra/server/graphql/log_entries/resolvers.ts b/x-pack/legacy/plugins/infra/server/graphql/log_entries/resolvers.ts index a18ffea3cfd28..edbb736b2c4fd 100644 --- a/x-pack/legacy/plugins/infra/server/graphql/log_entries/resolvers.ts +++ b/x-pack/legacy/plugins/infra/server/graphql/log_entries/resolvers.ts @@ -4,11 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { failure } from 'io-ts/lib/PathReporter'; - -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; import { InfraLogEntryColumn, InfraLogEntryFieldColumn, @@ -20,7 +15,6 @@ import { InfraSourceResolvers, } from '../../graphql/types'; import { InfraLogEntriesDomain } from '../../lib/domains/log_entries_domain'; -import { SourceConfigurationRuntimeType } from '../../lib/sources'; import { parseFilterQuery } from '../../utils/serialized_query'; import { ChildResolverOf, InfraResolverOf } from '../../utils/typed_resolvers'; import { QuerySourceResolver } from '../sources/resolvers'; @@ -40,11 +34,6 @@ export type InfraSourceLogEntryHighlightsResolver = ChildResolverOf< QuerySourceResolver >; -export type InfraSourceLogItem = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - export const createLogEntriesResolvers = (libs: { logEntries: InfraLogEntriesDomain; }): { @@ -52,7 +41,6 @@ export const createLogEntriesResolvers = (libs: { logEntriesAround: InfraSourceLogEntriesAroundResolver; logEntriesBetween: InfraSourceLogEntriesBetweenResolver; logEntryHighlights: InfraSourceLogEntryHighlightsResolver; - logItem: InfraSourceLogItem; }; InfraLogEntryColumn: { __resolveType( @@ -137,16 +125,6 @@ export const createLogEntriesResolvers = (libs: { entries, })); }, - async logItem(source, args, { req }) { - const sourceConfiguration = pipe( - SourceConfigurationRuntimeType.decode(source.configuration), - fold(errors => { - throw new Error(failure(errors).join('\n')); - }, identity) - ); - - return await libs.logEntries.getLogItem(req, args.id, sourceConfiguration); - }, }, InfraLogEntryColumn: { __resolveType(logEntryColumn) { diff --git a/x-pack/legacy/plugins/infra/server/graphql/log_entries/schema.gql.ts b/x-pack/legacy/plugins/infra/server/graphql/log_entries/schema.gql.ts index 4681ce5a49aa9..945f2f85435e5 100644 --- a/x-pack/legacy/plugins/infra/server/graphql/log_entries/schema.gql.ts +++ b/x-pack/legacy/plugins/infra/server/graphql/log_entries/schema.gql.ts @@ -100,24 +100,6 @@ export const logEntriesSchema = gql` entries: [InfraLogEntry!]! } - type InfraLogItemField { - "The flattened field name" - field: String! - "The value for the Field as a string" - value: String! - } - - type InfraLogItem { - "The ID of the document" - id: ID! - "The index where the document was found" - index: String! - "Time key for the document - derived from the source configuration timestamp and tiebreaker settings" - key: InfraTimeKey! - "An array of flattened fields and values" - fields: [InfraLogItemField!]! - } - extend type InfraSource { "A consecutive span of log entries surrounding a point in time" logEntriesAround( @@ -150,6 +132,5 @@ export const logEntriesSchema = gql` "The highlighting to apply to the log entries" highlights: [InfraLogEntryHighlightInput!]! ): [InfraLogEntryInterval!]! - logItem(id: ID!): InfraLogItem! } `; diff --git a/x-pack/legacy/plugins/infra/server/graphql/types.ts b/x-pack/legacy/plugins/infra/server/graphql/types.ts index bb27cc4e21b4e..1d6b03ac7bffb 100644 --- a/x-pack/legacy/plugins/infra/server/graphql/types.ts +++ b/x-pack/legacy/plugins/infra/server/graphql/types.ts @@ -63,7 +63,6 @@ export interface InfraSource { /** Sequences of log entries matching sets of highlighting queries within an interval */ logEntryHighlights: InfraLogEntryInterval[]; - logItem: InfraLogItem; /** A snapshot of nodes */ snapshot?: InfraSnapshotResponse | null; @@ -233,24 +232,6 @@ export interface InfraLogEntryFieldColumn { highlights: string[]; } -export interface InfraLogItem { - /** The ID of the document */ - id: string; - /** The index where the document was found */ - index: string; - /** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */ - key: InfraTimeKey; - /** An array of flattened fields and values */ - fields: InfraLogItemField[]; -} - -export interface InfraLogItemField { - /** The flattened field name */ - field: string; - /** The value for the Field as a string */ - value: string; -} - export interface InfraSnapshotResponse { /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ nodes: InfraSnapshotNode[]; @@ -452,9 +433,6 @@ export interface LogEntryHighlightsInfraSourceArgs { /** The highlighting to apply to the log entries */ highlights: InfraLogEntryHighlightInput[]; } -export interface LogItemInfraSourceArgs { - id: string; -} export interface SnapshotInfraSourceArgs { timerange: InfraTimerangeInput; @@ -513,7 +491,7 @@ export enum InfraNodeType { awsEC2 = 'awsEC2', awsS3 = 'awsS3', awsRDS = 'awsRDS', - awsSQS = 'awsSQS' + awsSQS = 'awsSQS', } export enum InfraSnapshotMetricType { @@ -675,7 +653,6 @@ export namespace InfraSourceResolvers { /** Sequences of log entries matching sets of highlighting queries within an interval */ logEntryHighlights?: LogEntryHighlightsResolver; - logItem?: LogItemResolver; /** A snapshot of nodes */ snapshot?: SnapshotResolver; @@ -758,15 +735,6 @@ export namespace InfraSourceResolvers { highlights: InfraLogEntryHighlightInput[]; } - export type LogItemResolver< - R = InfraLogItem, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export interface LogItemArgs { - id: string; - } - export type SnapshotResolver< R = InfraSnapshotResponse | null, Parent = InfraSource, @@ -1311,60 +1279,6 @@ export namespace InfraLogEntryFieldColumnResolvers { > = Resolver; } -export namespace InfraLogItemResolvers { - export interface Resolvers { - /** The ID of the document */ - id?: IdResolver; - /** The index where the document was found */ - index?: IndexResolver; - /** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */ - key?: KeyResolver; - /** An array of flattened fields and values */ - fields?: FieldsResolver; - } - - export type IdResolver = Resolver< - R, - Parent, - Context - >; - export type IndexResolver = Resolver< - R, - Parent, - Context - >; - export type KeyResolver< - R = InfraTimeKey, - Parent = InfraLogItem, - Context = InfraContext - > = Resolver; - export type FieldsResolver< - R = InfraLogItemField[], - Parent = InfraLogItem, - Context = InfraContext - > = Resolver; -} - -export namespace InfraLogItemFieldResolvers { - export interface Resolvers { - /** The flattened field name */ - field?: FieldResolver; - /** The value for the Field as a string */ - value?: ValueResolver; - } - - export type FieldResolver< - R = string, - Parent = InfraLogItemField, - Context = InfraContext - > = Resolver; - export type ValueResolver< - R = string, - Parent = InfraLogItemField, - Context = InfraContext - > = Resolver; -} - export namespace InfraSnapshotResponseResolvers { export interface Resolvers { /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ diff --git a/x-pack/legacy/plugins/infra/server/infra_server.ts b/x-pack/legacy/plugins/infra/server/infra_server.ts index f5a4bf8e8e054..108e1b1e3f392 100644 --- a/x-pack/legacy/plugins/infra/server/infra_server.ts +++ b/x-pack/legacy/plugins/infra/server/infra_server.ts @@ -22,6 +22,7 @@ import { initNodeDetailsRoute } from './routes/node_details'; import { initLogEntriesSummaryRoute, initLogEntriesSummaryHighlightsRoute, + initLogEntriesItemRoute, } from './routes/log_entries'; import { initInventoryMetaRoute } from './routes/inventory_metadata'; @@ -44,6 +45,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initValidateLogAnalysisIndicesRoute(libs); initLogEntriesSummaryRoute(libs); initLogEntriesSummaryHighlightsRoute(libs); + initLogEntriesItemRoute(libs); initMetricExplorerRoute(libs); initMetadataRoute(libs); initInventoryMetaRoute(libs); diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 28d5f75c3fdaf..b14536275cec3 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -6,26 +6,25 @@ import { SearchResponse, GenericParams } from 'elasticsearch'; import { Lifecycle } from 'hapi'; -import { ObjectType } from '@kbn/config-schema'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { RouteMethod, RouteConfig } from '../../../../../../../../src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server'; import { SpacesPluginSetup } from '../../../../../../../plugins/spaces/server'; +import { VisTypeTimeseriesSetup } from '../../../../../../../../src/plugins/vis_type_timeseries/server'; import { APMPluginContract } from '../../../../../../../plugins/apm/server'; +import { HomeServerPluginSetup } from '../../../../../../../../src/plugins/home/server'; // NP_TODO: Compose real types from plugins we depend on, no "any" export interface InfraServerPluginDeps { + home: HomeServerPluginSetup; spaces: SpacesPluginSetup; usageCollection: UsageCollectionSetup; - metrics: { - getVisData: any; - }; + metrics: VisTypeTimeseriesSetup; indexPatterns: { indexPatternsServiceFactory: any; }; features: FeaturesPluginSetup; apm: APMPluginContract; - ___legacy: any; } export interface CallWithRequestParams extends GenericParams { @@ -168,11 +167,6 @@ export interface InfraTSVBSeries { export type InfraTSVBDataPoint = [number, number]; -export type InfraRouteConfig< - params extends ObjectType, - query extends ObjectType, - body extends ObjectType, - method extends RouteMethod -> = { +export type InfraRouteConfig = { method: RouteMethod; } & RouteConfig; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index b0cdb8389cb29..4409667d8390a 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -10,7 +10,7 @@ import { GenericParams } from 'elasticsearch'; import { GraphQLSchema } from 'graphql'; import { Legacy } from 'kibana'; import { runHttpQuery } from 'apollo-server-core'; -import { schema, TypeOf, ObjectType } from '@kbn/config-schema'; +import { schema, TypeOf } from '@kbn/config-schema'; import { InfraRouteConfig, InfraTSVBResponse, @@ -36,21 +36,14 @@ import { InfraConfig } from '../../../../../../../plugins/infra/server'; export class KibanaFramework { public router: IRouter; - private core: CoreSetup; public plugins: InfraServerPluginDeps; constructor(core: CoreSetup, config: InfraConfig, plugins: InfraServerPluginDeps) { this.router = core.http.createRouter(); - this.core = core; this.plugins = plugins; } - public registerRoute< - params extends ObjectType = any, - query extends ObjectType = any, - body extends ObjectType = any, - method extends RouteMethod = any - >( + public registerRoute( config: InfraRouteConfig, handler: RequestHandler ) { @@ -246,45 +239,21 @@ export class KibanaFramework { } } - // NP_TODO: [TSVB_GROUP] This method needs fixing when the metrics plugin has migrated to the New Platform public async makeTSVBRequest( - request: KibanaRequest, + requestContext: RequestHandlerContext, model: TSVBMetricModel, timerange: { min: number; max: number }, - filters: any[], - requestContext: RequestHandlerContext + filters: any[] ): Promise { const { getVisData } = this.plugins.metrics; if (typeof getVisData !== 'function') { throw new Error('TSVB is not available'); } - const url = this.core.http.basePath.prepend('/api/metrics/vis/data'); - // For the following request we need a copy of the instnace of the internal request - // but modified for our TSVB request. This will ensure all the instance methods - // are available along with our overriden values - const requestCopy = Object.assign({}, request, { - url, - method: 'POST', - payload: { - timerange, - panels: [model], - filters, - }, - // NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that - // originate from the New Platform router (and are very different to the old request object). - // Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just - // the requestContext, this can be removed. - server: { - plugins: { - elasticsearch: this.plugins.___legacy.tsvb.elasticsearch, - }, - newPlatform: { - __internals: this.plugins.___legacy.tsvb.__internals, - }, - }, - getUiSettingsService: () => requestContext.core.uiSettings.client, - getSavedObjectsClient: () => requestContext.core.savedObjects.client, - }); - return getVisData(requestCopy); + const options = { + timerange, + panels: [model], + filters, + }; + return getVisData(requestContext, options); } } diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index c1b567576c214..6acb8afbfb249 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -47,7 +47,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { } const requests = options.metrics.map(metricId => - this.makeTSVBRequest(metricId, options, rawRequest, nodeField, requestContext) + this.makeTSVBRequest(metricId, options, nodeField, requestContext) ); return Promise.all(requests) @@ -89,7 +89,6 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { async makeTSVBRequest( metricId: InfraMetric, options: InfraMetricsRequestOptions, - req: KibanaRequest, nodeField: string, requestContext: RequestHandlerContext ) { @@ -150,6 +149,6 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { ? [{ match: { [model.map_field_to]: id } }] : [{ match: { [nodeField]: id } }]; - return this.framework.makeTSVBRequest(req, model, timerange, filters, requestContext); + return this.framework.makeTSVBRequest(requestContext, model, timerange, filters); } } diff --git a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.ts b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.ts index 37d7a5bd7465c..099e7c3b5038c 100644 --- a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.ts +++ b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.ts @@ -8,7 +8,7 @@ import stringify from 'json-stable-stringify'; import { isArray, isPlainObject } from 'lodash'; import { JsonObject } from '../../../../common/typed_json'; -import { InfraLogItemField } from '../../../graphql/types'; +import { LogEntriesItemField } from '../../../../common/http_api'; const isJsonObject = (subject: any): subject is JsonObject => { return isPlainObject(subject); @@ -24,8 +24,8 @@ const serializeValue = (value: any): string => { export const convertDocumentSourceToLogItemFields = ( source: JsonObject, path: string[] = [], - fields: InfraLogItemField[] = [] -): InfraLogItemField[] => { + fields: LogEntriesItemField[] = [] +): LogEntriesItemField[] => { return Object.keys(source).reduce((acc, key) => { const value = source[key]; const nextPath = [...path, key]; diff --git a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index e3f9d8cef0430..347f0dcf795bc 100644 --- a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -13,8 +13,9 @@ import { JsonObject } from '../../../../common/typed_json'; import { LogEntriesSummaryBucket, LogEntriesSummaryHighlightsBucket, + LogEntriesItem, } from '../../../../common/http_api'; -import { InfraLogEntry, InfraLogItem, InfraLogMessageSegment } from '../../../graphql/types'; +import { InfraLogEntry, InfraLogMessageSegment } from '../../../graphql/types'; import { InfraSourceConfiguration, InfraSources, @@ -282,7 +283,7 @@ export class InfraLogEntriesDomain { requestContext: RequestHandlerContext, id: string, sourceConfiguration: InfraSourceConfiguration - ): Promise { + ): Promise { const document = await this.adapter.getLogItem(requestContext, id, sourceConfiguration); const defaultFields = [ { field: '_index', value: document._index }, diff --git a/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts b/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts index ad26663402adc..147729a1d0b3e 100644 --- a/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts +++ b/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { i18n } from '@kbn/i18n'; import { Server } from 'hapi'; import { InfraConfig } from '../../../../plugins/infra/server'; import { initInfraServer } from './infra_server'; @@ -23,12 +24,17 @@ import { InfraSources } from './lib/sources'; import { InfraServerPluginDeps } from './lib/adapters/framework'; import { METRICS_FEATURE, LOGS_FEATURE } from './features'; import { UsageCollector } from './usage/usage_collector'; +import { APP_ID } from '../index'; import { InfraStaticSourceConfiguration } from './lib/sources/types'; export interface KbnServer extends Server { usage: any; } +const logsSampleDataLinkLabel = i18n.translate('xpack.infra.sampleDataLinkLabel', { + defaultMessage: 'Logs', +}); + export interface InfraPluginSetup { defineInternalSourceConfiguration: ( sourceId: string, @@ -107,6 +113,14 @@ export class InfraServerPlugin { plugins.features.registerFeature(METRICS_FEATURE); plugins.features.registerFeature(LOGS_FEATURE); + plugins.home.sampleData.addAppLinksToSampleDataset('logs', [ + { + path: `/app/${APP_ID}#/logs`, + label: logsSampleDataLinkLabel, + icon: 'logsApp', + }, + ]); + initInfraServer(this.libs); // Telemetry diff --git a/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts b/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts index ee2d150fdaac0..8fed914c3dc8c 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './item'; export * from './summary'; export * from './summary_highlights'; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_entries/item.ts b/x-pack/legacy/plugins/infra/server/routes/log_entries/item.ts new file mode 100644 index 0000000000000..22663cb2001f0 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/routes/log_entries/item.ts @@ -0,0 +1,55 @@ +/* + * 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 Boom from 'boom'; + +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { schema } from '@kbn/config-schema'; + +import { throwErrors } from '../../../common/runtime_types'; + +import { InfraBackendLibs } from '../../lib/infra_types'; +import { + LOG_ENTRIES_ITEM_PATH, + logEntriesItemRequestRT, + logEntriesItemResponseRT, +} from '../../../common/http_api'; + +const escapeHatch = schema.object({}, { allowUnknowns: true }); + +export const initLogEntriesItemRoute = ({ framework, sources, logEntries }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: LOG_ENTRIES_ITEM_PATH, + validate: { body: escapeHatch }, + }, + async (requestContext, request, response) => { + try { + const payload = pipe( + logEntriesItemRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const { id, sourceId } = payload; + const sourceConfiguration = (await sources.getSourceConfiguration(requestContext, sourceId)) + .configuration; + + const logEntry = await logEntries.getLogItem(requestContext, id, sourceConfiguration); + + return response.ok({ + body: logEntriesItemResponseRT.encode({ + data: logEntry, + }), + }); + } catch (error) { + return response.internalError({ body: error.message }); + } + } + ); +}; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts index 3dfbfbb5c3979..17fc46b41278a 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts @@ -76,13 +76,7 @@ export const populateSeriesWithTSVBData = ( } // Get TSVB results using the model, timerange and filters - const tsvbResults = await framework.makeTSVBRequest( - request, - model, - timerange, - filters, - requestContext - ); + const tsvbResults = await framework.makeTSVBRequest(requestContext, model, timerange, filters); // If there is no data `custom` will not exist. if (!tsvbResults.custom) { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index f3e86c5b59214..82e172b6bd7e2 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -17,7 +17,7 @@ import { import { DropHandler, DragContextState } from '../../drag_drop'; import { createMockedDragDropContext } from '../mocks'; import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; -import { IUiSettingsClient, SavedObjectsClientContract, HttpServiceBase } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IndexPatternPrivateState } from '../types'; import { documentField } from '../document_field'; @@ -138,7 +138,7 @@ describe('IndexPatternDimensionPanel', () => { storage: {} as IStorageWrapper, uiSettings: {} as IUiSettingsClient, savedObjectsClient: {} as SavedObjectsClientContract, - http: {} as HttpServiceBase, + http: {} as HttpSetup, }; jest.clearAllMocks(); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx index fded53cd35f59..dfeb8632d096e 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx @@ -8,7 +8,7 @@ import _ from 'lodash'; import React, { memo, useMemo } from 'react'; import { EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient, SavedObjectsClientContract, HttpServiceBase } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { DatasourceDimensionPanelProps, StateSetter } from '../../types'; import { IndexPatternColumn, OperationType } from '../indexpattern'; @@ -29,7 +29,7 @@ export type IndexPatternDimensionPanelProps = DatasourceDimensionPanelProps & { storage: IStorageWrapper; savedObjectsClient: SavedObjectsClientContract; layerId: string; - http: HttpServiceBase; + http: HttpSetup; uniqueLabel: string; dateRange: DateRange; }; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts index 89d4224a7df14..661c627f3454f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts @@ -6,11 +6,7 @@ import _ from 'lodash'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { - SavedObjectsClientContract, - SavedObjectAttributes, - HttpServiceBase, -} from 'src/core/public'; +import { SavedObjectsClientContract, SavedObjectAttributes, HttpSetup } from 'src/core/public'; import { SimpleSavedObject } from 'src/core/public'; import { StateSetter } from '../types'; import { @@ -233,7 +229,7 @@ export async function syncExistingFields({ }: { dateRange: DateRange; indexPatterns: Array<{ title: string; timeFieldName?: string | null }>; - fetchJson: HttpServiceBase['get']; + fetchJson: HttpSetup['get']; setState: SetState; }) { const emptinessInfo = await Promise.all( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx index f7125a1adae52..ea9fa516d4d91 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx @@ -9,7 +9,7 @@ import { DateHistogramIndexPatternColumn } from './date_histogram'; import { dateHistogramOperation } from '.'; import { shallow } from 'enzyme'; import { EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; -import { IUiSettingsClient, SavedObjectsClientContract, HttpServiceBase } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { createMockedIndexPattern } from '../../mocks'; import { IndexPatternPrivateState } from '../../types'; @@ -36,7 +36,7 @@ const defaultOptions = { fromDate: 'now-1y', toDate: 'now', }, - http: {} as HttpServiceBase, + http: {} as HttpSetup, }; describe('date_histogram', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts index 252a3d788fd30..63e4564878e31 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IUiSettingsClient, SavedObjectsClientContract, HttpServiceBase } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { termsOperation } from './terms'; import { cardinalityOperation } from './cardinality'; @@ -47,7 +47,7 @@ export interface ParamEditorProps { uiSettings: IUiSettingsClient; storage: IStorageWrapper; savedObjectsClient: SavedObjectsClientContract; - http: HttpServiceBase; + http: HttpSetup; dateRange: DateRange; } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx index c0d995d420760..a891814bb0496 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { EuiRange, EuiSelect } from '@elastic/eui'; -import { IUiSettingsClient, SavedObjectsClientContract, HttpServiceBase } from 'src/core/public'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { createMockedIndexPattern } from '../../mocks'; import { TermsIndexPatternColumn } from './terms'; @@ -21,7 +21,7 @@ const defaultProps = { uiSettings: {} as IUiSettingsClient, savedObjectsClient: {} as SavedObjectsClientContract, dateRange: { fromDate: 'now-1d', toDate: 'now' }, - http: {} as HttpServiceBase, + http: {} as HttpSetup, }; describe('terms', () => { diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts index 15d36a7c31169..8bb1e086a37c2 100644 --- a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts +++ b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts @@ -12,7 +12,7 @@ import { trackSuggestionEvent, } from './factory'; import { coreMock } from 'src/core/public/mocks'; -import { HttpServiceBase } from 'kibana/public'; +import { HttpSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; jest.useFakeTimers(); @@ -31,7 +31,7 @@ const createMockStorage = () => { describe('Lens UI telemetry', () => { let storage: jest.Mocked; - let http: jest.Mocked; + let http: jest.Mocked; let dateSpy: jest.SpyInstance; beforeEach(() => { diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts index 1a8ec604eda54..babc65969afb5 100644 --- a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts +++ b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { HttpServiceBase } from 'src/core/public'; +import { HttpSetup } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { BASE_API_URL } from '../../common'; @@ -44,10 +44,10 @@ export class LensReportManager { private suggestionEvents: Record> = {}; private storage: IStorageWrapper; - private http: HttpServiceBase; + private http: HttpSetup; private timer: ReturnType; - constructor({ storage, http }: { storage: IStorageWrapper; http: HttpServiceBase }) { + constructor({ storage, http }: { storage: IStorageWrapper; http: HttpSetup }) { this.storage = storage; this.http = http; diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index a6e28c9c29a32..83362e73fb314 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -102,6 +102,7 @@ export function maps(kibana) { const pluginsSetup = { featuresPlugin: newPlatformPlugins.features, licensing: newPlatformPlugins.licensing, + home: newPlatformPlugins.home, }; // legacy dependencies @@ -117,9 +118,6 @@ export function maps(kibana) { savedObjects: { getSavedObjectsRepository: server.savedObjects.getSavedObjectsRepository, }, - addSavedObjectsToSampleDataset: server.addSavedObjectsToSampleDataset, - addAppLinksToSampleDataset: server.addAppLinksToSampleDataset, - replacePanelInSampleDatasetDashboard: server.replacePanelInSampleDatasetDashboard, injectUiAppVars: server.injectUiAppVars, getInjectedUiAppVars: server.getInjectedUiAppVars, }; diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js index eab1df0c3fec7..053b389a59011 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js @@ -70,6 +70,8 @@ export const SET_MAP_INIT_ERROR = 'SET_MAP_INIT_ERROR'; export const SET_INTERACTIVE = 'SET_INTERACTIVE'; export const DISABLE_TOOLTIP_CONTROL = 'DISABLE_TOOLTIP_CONTROL'; export const HIDE_TOOLBAR_OVERLAY = 'HIDE_TOOLBAR_OVERLAY'; +export const HIDE_LAYER_CONTROL = 'HIDE_LAYER_CONTROL'; +export const HIDE_VIEW_CONTROL = 'HIDE_VIEW_CONTROL'; function getLayerLoadingCallbacks(dispatch, layerId) { return { @@ -831,3 +833,10 @@ export function disableTooltipControl() { export function hideToolbarOverlay() { return { type: HIDE_TOOLBAR_OVERLAY, hideToolbarOverlay: true }; } + +export function hideLayerControl() { + return { type: HIDE_LAYER_CONTROL, hideLayerControl: true }; +} +export function hideViewControl() { + return { type: HIDE_VIEW_CONTROL, hideViewControl: true }; +} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js index aad8c79f3a628..0274f849daf3d 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js @@ -23,6 +23,7 @@ import { getScrollZoom, isInteractiveDisabled, isTooltipControlDisabled, + isViewControlHidden, } from '../../../selectors/map_selectors'; import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; @@ -36,6 +37,8 @@ function mapStateToProps(state = {}) { scrollZoom: getScrollZoom(state), disableInteractive: isInteractiveDisabled(state), disableTooltipControl: isTooltipControlDisabled(state), + disableTooltipControl: isTooltipControlDisabled(state), + hideViewControl: isViewControlHidden(state), }; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index 83cebc9e83520..5470d6624916a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -188,18 +188,20 @@ export class MBMapContainer extends React.Component { this.props.extentChanged(this._getMapState()); }, 100) ); - - const throttledSetMouseCoordinates = _.throttle(e => { - this.props.setMouseCoordinates({ - lat: e.lngLat.lat, - lon: e.lngLat.lng, + // Attach event only if view control is visible, which shows lat/lon + if (!this.props.hideViewControl) { + const throttledSetMouseCoordinates = _.throttle(e => { + this.props.setMouseCoordinates({ + lat: e.lngLat.lat, + lon: e.lngLat.lng, + }); + }, 100); + this.state.mbMap.on('mousemove', throttledSetMouseCoordinates); + this.state.mbMap.on('mouseout', () => { + throttledSetMouseCoordinates.cancel(); // cancel any delayed setMouseCoordinates invocations + this.props.clearMouseCoordinates(); }); - }, 100); - this.state.mbMap.on('mousemove', throttledSetMouseCoordinates); - this.state.mbMap.on('mouseout', () => { - throttledSetMouseCoordinates.cancel(); // cancel any delayed setMouseCoordinates invocations - this.props.clearMouseCoordinates(); - }); + } } _initResizerChecker() { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/index.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/index.js index f610070880fc8..ebf9b8a609d2a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/index.js @@ -7,5 +7,14 @@ import { connect } from 'react-redux'; import { WidgetOverlay } from './widget_overlay'; -const connectedWidgetOverlay = connect(null, null)(WidgetOverlay); +import { isLayerControlHidden, isViewControlHidden } from '../../selectors/map_selectors'; + +function mapStateToProps(state = {}) { + return { + hideLayerControl: isLayerControlHidden(state), + hideViewControl: isViewControlHidden(state), + }; +} + +const connectedWidgetOverlay = connect(mapStateToProps, null)(WidgetOverlay); export { connectedWidgetOverlay as WidgetOverlay }; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/widget_overlay.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/widget_overlay.js index ae3deca7a1be4..a37f837874f8f 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/widget_overlay.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/widget_overlay.js @@ -10,7 +10,7 @@ import { LayerControl } from './layer_control'; import { ViewControl } from './view_control'; import { AttributionControl } from './attribution_control'; -export function WidgetOverlay() { +export function WidgetOverlay({ hideLayerControl, hideViewControl }) { return ( - - - - + {!hideLayerControl && } + {!hideViewControl && } diff --git a/x-pack/legacy/plugins/maps/public/embeddable/README.md b/x-pack/legacy/plugins/maps/public/embeddable/README.md index 82f83f1bfcf4a..eb6571a96016c 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/README.md +++ b/x-pack/legacy/plugins/maps/public/embeddable/README.md @@ -7,6 +7,8 @@ - **disableInteractive:** (Boolean) Will disable map interactions, panning, zooming in the map. - **disableTooltipControl:** (Boolean) Will disable tooltip which shows relevant information on hover, like Continent name etc - **hideToolbarOverlay:** (Boolean) Will disable toolbar, which can be used to navigate to coordinate by entering lat/long and zoom values. +- **hideLayerControl:** (Boolean) Will hide useful layer control, which can be used to hide/show a layer to get a refined view of the map. +- **hideViewControl:** (Boolean) Will hide view control at bottom right of the map, which shows lat/lon values based on mouse hover in the map, this is useful to get coordinate value from a particular point in map. ### Creating a Map embeddable from saved object ``` diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js index 2c203ffc8fc63..2ee766f91fbca 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js @@ -30,6 +30,8 @@ import { disableInteractive, disableTooltipControl, hideToolbarOverlay, + hideLayerControl, + hideViewControl, } from '../actions/map_actions'; import { setReadOnly, setIsLayerTOCOpen, setOpenTOCDetails } from '../actions/ui_actions'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors'; @@ -132,6 +134,14 @@ export class MapEmbeddable extends Embeddable { this._store.dispatch(hideToolbarOverlay(this.input.hideToolbarOverlay)); } + if (_.has(this.input, 'hideLayerControl') && this.input.hideLayerControl) { + this._store.dispatch(hideLayerControl(this.input.hideLayerControl)); + } + + if (_.has(this.input, 'hideViewControl') && this.input.hideViewControl) { + this._store.dispatch(hideViewControl(this.input.hideViewControl)); + } + if (this.input.mapCenter) { this._store.dispatch( setGotoWithCenter({ diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index 2890b75172fb0..21c5f15fb6122 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -344,6 +344,10 @@ export class AbstractLayer { return []; } + async getFields() { + return []; + } + syncVisibilityWithMb(mbMap, mbLayerId) { mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none'); } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index d8960ae1f6a04..d852332ac2f84 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -124,6 +124,24 @@ export class ESSearchSource extends AbstractESSource { } } + async getFields() { + try { + const indexPattern = await this.getIndexPattern(); + return indexPattern.fields + .filter(field => { + // Ensure fielddata is enabled for field. + // Search does not request _source + return field.aggregatable; + }) + .map(field => { + return this.createField({ fieldName: field.name }); + }); + } catch (error) { + // failed index-pattern retrieval will show up as error-message in the layer-toc-entry + return []; + } + } + getFieldNames() { return [this._descriptor.geoField]; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js index 814cbffc7bfe3..bf7267e9c5858 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js @@ -103,6 +103,10 @@ export class AbstractVectorSource extends AbstractSource { return []; } + async getFields() { + return [...(await this.getDateFields()), ...(await this.getNumberFields())]; + } + async getLeftJoinFields() { return []; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js index 1ea065c7e9e67..8826c771fab19 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js @@ -92,7 +92,6 @@ describe('getLinearGradient', () => { 'rgb(32,112,180)', 'rgb(7,47,107)', ]; - expect(getLinearGradient(colorRamp)).toBe( 'linear-gradient(to right, rgb(247,250,255) 0%, rgb(221,234,247) 14%, rgb(197,218,238) 28%, rgb(157,201,224) 42%, rgb(106,173,213) 57%, rgb(65,145,197) 71%, rgb(32,112,180) 85%, rgb(7,47,107) 100%)' ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/style_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/style_legend_row.js rename to x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js index 38ffed308a0d1..3eb34ec1406d2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/components/style_legend_row.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiToolTip } from '@elastic/eui'; -export function StyleLegendRow({ header, minLabel, maxLabel, propertyLabel, fieldLabel }) { +export function RangedStyleLegendRow({ header, minLabel, maxLabel, propertyLabel, fieldLabel }) { return (
@@ -39,7 +39,7 @@ export function StyleLegendRow({ header, minLabel, maxLabel, propertyLabel, fiel ); } -StyleLegendRow.propTypes = { +RangedStyleLegendRow.propTypes = { header: PropTypes.node.isRequired, minLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, maxLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js index ce514734b6606..1d8dfe9c7bdbf 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { ColorGradient } from '../../../components/color_gradient'; -import { StyleLegendRow } from '../../../components/style_legend_row'; +import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; import { DEFAULT_RGB_HEATMAP_COLOR_RAMP, DEFAULT_HEATMAP_COLOR_RAMP_NAME, @@ -50,7 +50,7 @@ export class HeatmapLegend extends React.Component { ); return ( - { onChange({ ...styleOptions, field }); }; @@ -32,7 +32,7 @@ export function DynamicColorSelection({ ordinalFields, onChange, styleOptions }) /> { + onChange({ ...styleOptions, field }); + }; + + return ( + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js new file mode 100644 index 0000000000000..ea296a3312799 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFieldText } from '@elastic/eui'; + +export function StaticLabelSelector({ onChange, styleOptions }) { + const onValueChange = event => { + onChange({ value: event.target.value }); + }; + + return ( + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js new file mode 100644 index 0000000000000..6bca56425d38d --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; +import { DynamicLabelSelector } from './dynamic_label_selector'; +import { StaticLabelSelector } from './static_label_selector'; + +export function VectorStyleLabelEditor(props) { + return ( + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js deleted file mode 100644 index 6b31bba85867b..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js +++ /dev/null @@ -1,57 +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 _ from 'lodash'; -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import { getVectorStyleLabel } from '../get_vector_style_label'; -import { StyleLegendRow } from '../../../components/style_legend_row'; - -const EMPTY_VALUE = ''; - -export class StylePropertyLegendRow extends Component { - _formatValue = value => { - if (!this.props.fieldFormatter || value === EMPTY_VALUE) { - return value; - } - - return this.props.fieldFormatter(value); - }; - - render() { - const { meta: range, style } = this.props; - - const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE)); - const minLabel = - this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange - ? `< ${min}` - : min; - - const max = this._formatValue(_.get(range, 'max', EMPTY_VALUE)); - const maxLabel = - this.props.style.isFieldMetaEnabled() && range && range.isMaxOutsideStdRange - ? `> ${max}` - : max; - - return ( - - ); - } -} - -StylePropertyLegendRow.propTypes = { - label: PropTypes.string, - fieldFormatter: PropTypes.func, - meta: PropTypes.object, - style: PropTypes.object, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js index 7ea9b2b04bcfe..9e7afb0aa8873 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js @@ -5,19 +5,16 @@ */ import _ from 'lodash'; -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import { StylePropertyLegendRow } from './style_property_legend_row'; +import React, { Component, Fragment } from 'react'; export class VectorStyleLegend extends Component { state = { - rows: [], + styles: [], }; componentDidMount() { this._isMounted = true; - this._prevRowDescriptors = undefined; + this._prevStyleDescriptors = undefined; this._loadRows(); } @@ -30,27 +27,26 @@ export class VectorStyleLegend extends Component { } _loadRows = _.debounce(async () => { - const rows = await this.props.loadRows(); - const rowDescriptors = rows.map(row => { + const styles = await this.props.getLegendDetailStyleProperties(); + const styleDescriptorPromises = styles.map(async style => { return { - label: row.label, - range: row.meta, - styleOptions: row.style.getOptions(), + type: style.getStyleName(), + options: style.getOptions(), + fieldMeta: style.getFieldMeta(), + label: await style.getField().getLabel(), }; }); - if (this._isMounted && !_.isEqual(rowDescriptors, this._prevRowDescriptors)) { - this._prevRowDescriptors = rowDescriptors; - this.setState({ rows }); + + const styleDescriptors = await Promise.all(styleDescriptorPromises); + if (this._isMounted && !_.isEqual(styleDescriptors, this._prevStyleDescriptors)) { + this._prevStyleDescriptors = styleDescriptors; + this.setState({ styles: styles }); } }, 100); render() { - return this.state.rows.map(rowProps => { - return ; + return this.state.styles.map(style => { + return {style.renderLegendDetailRow()}; }); } } - -VectorStyleLegend.propTypes = { - loadRows: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js index 6df3283737c73..8ad3916ac6509 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js @@ -10,14 +10,14 @@ import PropTypes from 'prop-types'; import { dynamicOrientationShape } from '../style_option_shapes'; import { FieldSelect, fieldShape } from '../field_select'; -export function DynamicOrientationSelection({ ordinalFields, styleOptions, onChange }) { +export function DynamicOrientationSelection({ fields, styleOptions, onChange }) { const onFieldChange = ({ field }) => { onChange({ ...styleOptions, field }); }; return ( { onChange({ ...styleOptions, field }); }; @@ -32,7 +32,7 @@ export function DynamicSizeSelection({ ordinalFields, styleOptions, onChange }) /> 0; + return this.props.fields.length > 0; } _isDynamic() { @@ -78,7 +78,7 @@ export class StaticDynamicStyleRow extends Component { return ( diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 43dd7b1b2d032..44f630db9d890 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -11,6 +11,7 @@ import chrome from 'ui/chrome'; import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; +import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; import { OrientationEditor } from './orientation/orientation_editor'; import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils'; @@ -25,6 +26,7 @@ export class VectorStyleEditor extends Component { state = { dateFields: [], numberFields: [], + fields: [], defaultDynamicProperties: getDefaultDynamicProperties(), defaultStaticProperties: getDefaultStaticProperties(), supportedFeatures: undefined, @@ -37,16 +39,16 @@ export class VectorStyleEditor extends Component { componentDidMount() { this._isMounted = true; - this._loadOrdinalFields(); + this._loadFields(); this._loadSupportedFeatures(); } componentDidUpdate() { - this._loadOrdinalFields(); + this._loadFields(); this._loadSupportedFeatures(); } - async _loadOrdinalFields() { + async _loadFields() { const getFieldMeta = async field => { return { label: await field.getLabel(), @@ -54,21 +56,27 @@ export class VectorStyleEditor extends Component { origin: field.getOrigin(), }; }; + const dateFields = await this.props.layer.getDateFields(); const dateFieldPromises = dateFields.map(getFieldMeta); const dateFieldsArray = await Promise.all(dateFieldPromises); - if (this._isMounted && !_.isEqual(dateFieldsArray, this.state.dateFields)) { this.setState({ dateFields: dateFieldsArray }); } const numberFields = await this.props.layer.getNumberFields(); const numberFieldPromises = numberFields.map(getFieldMeta); - const numberFieldsArray = await Promise.all(numberFieldPromises); if (this._isMounted && !_.isEqual(numberFieldsArray, this.state.numberFields)) { this.setState({ numberFields: numberFieldsArray }); } + + const fields = await this.props.layer.getFields(); + const fieldPromises = fields.map(getFieldMeta); + const fieldsArray = await Promise.all(fieldPromises); + if (this._isMounted && !_.isEqual(fieldsArray, this.state.fields)) { + this.setState({ fields: fieldsArray }); + } } async _loadSupportedFeatures() { @@ -126,7 +134,7 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_FILL_COLORS} handlePropertyChange={this.props.handlePropertyChange} styleProperty={this.props.styleProperties.fillColor} - ordinalFields={this._getOrdinalFields()} + fields={this._getOrdinalFields()} defaultStaticStyleOptions={this.state.defaultStaticProperties.fillColor.options} defaultDynamicStyleOptions={this.state.defaultDynamicProperties.fillColor.options} /> @@ -139,7 +147,7 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_LINE_COLORS} handlePropertyChange={this.props.handlePropertyChange} styleProperty={this.props.styleProperties.lineColor} - ordinalFields={this._getOrdinalFields()} + fields={this._getOrdinalFields()} defaultStaticStyleOptions={this.state.defaultStaticProperties.lineColor.options} defaultDynamicStyleOptions={this.state.defaultDynamicProperties.lineColor.options} /> @@ -151,7 +159,7 @@ export class VectorStyleEditor extends Component { @@ -163,27 +171,58 @@ export class VectorStyleEditor extends Component { ); } + _renderLabelProperties() { + return ( + + + + + + + + + + + ); + } + _renderPointProperties() { let iconOrientation; if (this.props.symbolDescriptor.options.symbolizeAs === SYMBOLIZE_AS_ICON) { iconOrientation = ( - - - - + ); } @@ -207,8 +246,12 @@ export class VectorStyleEditor extends Component { {iconOrientation} + {this._renderSymbolSize()} + + + {this._renderLabelProperties()} ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js new file mode 100644 index 0000000000000..5933bd1575b2a --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import _ from 'lodash'; +import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; +import { getVectorStyleLabel } from '../../components/get_vector_style_label'; + +const EMPTY_VALUE = ''; + +export class DynamicLegendRow extends React.Component { + constructor() { + super(); + this._isMounted = false; + this.state = { + label: EMPTY_VALUE, + }; + } + + async _loadParams() { + const label = await this.props.style.getField().getLabel(); + const newState = { label }; + if (this._isMounted && !_.isEqual(this.state, newState)) { + this.setState(newState); + } + } + + componentDidUpdate() { + this._loadParams(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadParams(); + } + + _formatValue(value) { + if (value === EMPTY_VALUE) { + return value; + } + return this.props.style.formatField(value); + } + + render() { + const fieldMeta = this.props.style.getFieldMeta(); + + let minLabel = EMPTY_VALUE; + let maxLabel = EMPTY_VALUE; + if (fieldMeta) { + const range = { min: fieldMeta.min, max: fieldMeta.max }; + const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE)); + minLabel = + this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange + ? `< ${min}` + : min; + + const max = this._formatValue(_.get(range, 'max', EMPTY_VALUE)); + maxLabel = + this.props.style.isFieldMetaEnabled() && range && range.isMaxOutsideStdRange + ? `> ${max}` + : max; + } + + return ( + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js index fdbb19a60d2e6..3961325c3bd59 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js @@ -46,6 +46,12 @@ export class DynamicColorProperty extends DynamicStyleProperty { mbMap.setPaintProperty(mbLayerId, 'line-opacity', alpha); } + syncLabelColorWithMb(mbLayerId, mbMap, alpha) { + const color = this._getMbColor(); + mbMap.setPaintProperty(mbLayerId, 'text-color', color); + mbMap.setPaintProperty(mbLayerId, 'text-opacity', alpha); + } + isCustomColorRamp() { return this._options.useCustomColorRamp; } @@ -111,7 +117,7 @@ export class DynamicColorProperty extends DynamicStyleProperty { return getColorRampStops(this._options.color); } - renderHeader() { + renderLegendHeader() { if (this._options.color) { return ; } else { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index 78409ef0d488f..78d75dc818d50 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -43,6 +43,10 @@ function getSymbolSizeIcons() { } export class DynamicSizeProperty extends DynamicStyleProperty { + supportsFeatureState() { + return this.getStyleName() !== VECTOR_STYLES.LABEL_SIZE; + } + syncHaloWidthWithMb(mbLayerId, mbMap) { const haloWidth = this._getMbSize(); mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', haloWidth); @@ -89,6 +93,11 @@ export class DynamicSizeProperty extends DynamicStyleProperty { mbMap.setPaintProperty(mbLayerId, 'line-width', lineWidth); } + syncLabelSizeWithMb(mbLayerId, mbMap) { + const lineWidth = this._getMbSize(); + mbMap.setLayoutProperty(mbLayerId, 'text-size', lineWidth); + } + _getMbSize() { if (this._isSizeDynamicConfigComplete(this._options)) { return this._getMbDataDrivenSize({ @@ -101,10 +110,11 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } _getMbDataDrivenSize({ targetName, minSize, maxSize }) { + const lookup = this.supportsFeatureState() ? 'feature-state' : 'get'; return [ 'interpolate', ['linear'], - ['coalesce', ['feature-state', targetName], 0], + ['coalesce', [lookup, targetName], 0], 0, minSize, 1, @@ -121,7 +131,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { ); } - renderHeader() { + renderLegendHeader() { let icons; if (this.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { icons = getLineWidthIcons(); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js index 3b25746717088..8d734a702c8ca 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js @@ -8,13 +8,21 @@ import _ from 'lodash'; import { AbstractStyleProperty } from './style_property'; import { DEFAULT_SIGMA } from '../vector_style_defaults'; import { STYLE_TYPE } from '../../../../../common/constants'; +import { DynamicLegendRow } from './components/dynamic_legend_row'; +import React from 'react'; export class DynamicStyleProperty extends AbstractStyleProperty { static type = STYLE_TYPE.DYNAMIC; - constructor(options, styleName, field) { + constructor(options, styleName, field, getFieldMeta, getFieldFormatter) { super(options, styleName); this._field = field; + this._getFieldMeta = getFieldMeta; + this._getFieldFormatter = getFieldFormatter; + } + + getFieldMeta() { + return this._getFieldMeta && this._field ? this._getFieldMeta(this._field.getName()) : null; } getField() { @@ -25,6 +33,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return true; } + isOrdinal() { + return true; + } + isComplete() { return !!this._field; } @@ -105,4 +117,18 @@ export class DynamicStyleProperty extends AbstractStyleProperty { isMaxOutsideStdRange: stats.max > stdUpperBounds, }; } + + formatField(value) { + if (this.getField()) { + const fieldName = this.getField().getName(); + const fieldFormatter = this._getFieldFormatter(fieldName); + return fieldFormatter ? fieldFormatter(value) : value; + } else { + return value; + } + } + + renderLegendDetailRow() { + return ; + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js new file mode 100644 index 0000000000000..b716030d2f263 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js @@ -0,0 +1,39 @@ +/* + * 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 { DynamicStyleProperty } from './dynamic_style_property'; +import { getComputedFieldName } from '../style_util'; + +export class DynamicTextProperty extends DynamicStyleProperty { + syncTextFieldWithMb(mbLayerId, mbMap) { + if (this._field && this._field.isValid()) { + const targetName = getComputedFieldName(this._styleName, this._options.field.name); + mbMap.setLayoutProperty(mbLayerId, 'text-field', ['coalesce', ['get', targetName], '']); + } else { + mbMap.setLayoutProperty(mbLayerId, 'text-field', null); + } + } + + isOrdinal() { + return false; + } + + supportsFieldMeta() { + return false; + } + + supportsFeatureState() { + return false; + } + + isScaled() { + return false; + } + + renderHeader() { + return null; + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js index a32b17af0be9e..658eb6a164556 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js @@ -30,8 +30,13 @@ export class StaticColorProperty extends StaticStyleProperty { mbMap.setPaintProperty(mbLayerId, 'line-opacity', alpha); } - syncCircleStrokeWithMb(pointLayerId, mbMap, alpha) { - mbMap.setPaintProperty(pointLayerId, 'circle-stroke-color', this._options.color); - mbMap.setPaintProperty(pointLayerId, 'circle-stroke-opacity', alpha); + syncCircleStrokeWithMb(mbLayerId, mbMap, alpha) { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-color', this._options.color); + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-opacity', alpha); + } + + syncLabelColorWithMb(mbLayerId, mbMap, alpha) { + mbMap.setPaintProperty(mbLayerId, 'text-color', this._options.color); + mbMap.setPaintProperty(mbLayerId, 'text-opacity', alpha); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js index d2682a07def62..1584dec998986 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js @@ -43,4 +43,8 @@ export class StaticSizeProperty extends StaticStyleProperty { syncLineWidthWithMb(mbLayerId, mbMap) { mbMap.setPaintProperty(mbLayerId, 'line-width', this._options.size); } + + syncLabelSizeWithMb(mbLayerId, mbMap) { + mbMap.setLayoutProperty(mbLayerId, 'text-size', this._options.size); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js new file mode 100644 index 0000000000000..7a4a4672152c0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { StaticStyleProperty } from './static_style_property'; + +export class StaticTextProperty extends StaticStyleProperty { + isComplete() { + return this.getOptions().value.length > 0; + } + + syncTextFieldWithMb(mbLayerId, mbMap) { + if (this.getOptions().value.length) { + mbMap.setLayoutProperty(mbLayerId, 'text-field', this.getOptions().value); + } else { + mbMap.setLayoutProperty(mbLayerId, 'text-field', null); + } + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js index b0b4bcefd7d2f..8da76a775229e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js @@ -24,6 +24,10 @@ export class AbstractStyleProperty { return true; } + formatField(value) { + return value; + } + getStyleName() { return this._styleName; } @@ -32,7 +36,11 @@ export class AbstractStyleProperty { return this._options || {}; } - renderHeader() { + renderLegendHeader() { + return null; + } + + renderLegendDetailRow() { return null; } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js index fd4794b385bd2..ed59b1d5513a0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js @@ -27,7 +27,6 @@ describe('styleSvg', () => { const unstyledSvgString = ''; const styledSvg = await styleSvg(unstyledSvgString, 'red'); - expect(styledSvg.split('\n')[1]).toBe( '' ); @@ -37,7 +36,6 @@ describe('styleSvg', () => { const unstyledSvgString = ''; const styledSvg = await styleSvg(unstyledSvgString, 'red', 'white'); - expect(styledSvg.split('\n')[1]).toBe( '' ); @@ -47,7 +45,6 @@ describe('styleSvg', () => { const unstyledSvgString = ''; const styledSvg = await styleSvg(unstyledSvgString, 'red', 'white', '2px'); - expect(styledSvg.split('\n')[1]).toBe( '' ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 161c0ea69e86c..9d9764b5fb1fb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -36,6 +36,8 @@ import { StaticColorProperty } from './properties/static_color_property'; import { DynamicColorProperty } from './properties/dynamic_color_property'; import { StaticOrientationProperty } from './properties/static_orientation_property'; import { DynamicOrientationProperty } from './properties/dynamic_orientation_property'; +import { StaticTextProperty } from './properties/static_text_property'; +import { DynamicTextProperty } from './properties/dynamic_text_property'; const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; @@ -81,11 +83,21 @@ export class VectorStyle extends AbstractStyle { this._descriptor.properties[VECTOR_STYLES.ICON_SIZE], VECTOR_STYLES.ICON_SIZE ); - this._iconOrientationProperty = this._makeOrientationProperty( this._descriptor.properties[VECTOR_STYLES.ICON_ORIENTATION], VECTOR_STYLES.ICON_ORIENTATION ); + this._labelStyleProperty = this._makeLabelProperty( + this._descriptor.properties[VECTOR_STYLES.LABEL_TEXT] + ); + this._labelSizeStyleProperty = this._makeSizeProperty( + this._descriptor.properties[VECTOR_STYLES.LABEL_SIZE], + VECTOR_STYLES.LABEL_SIZE + ); + this._labelColorStyleProperty = this._makeColorProperty( + this._descriptor.properties[VECTOR_STYLES.LABEL_COLOR], + VECTOR_STYLES.LABEL_COLOR + ); } _getAllStyleProperties() { @@ -95,6 +107,9 @@ export class VectorStyle extends AbstractStyle { this._lineWidthStyleProperty, this._iconSizeStyleProperty, this._iconOrientationProperty, + this._labelStyleProperty, + this._labelSizeStyleProperty, + this._labelColorStyleProperty, ]; } @@ -115,16 +130,15 @@ export class VectorStyle extends AbstractStyle { return dynamicStyleProp.isFieldMetaEnabled(); }); + const styleProperties = {}; + this._getAllStyleProperties().forEach(styleProperty => { + styleProperties[styleProperty.getStyleName()] = styleProperty; + }); + return ( { const dynamicProp = this._getDynamicPropertyByFieldName(fieldName); if (!dynamicProp) { return null; @@ -366,7 +379,7 @@ export class VectorStyle extends AbstractStyle { const formatters = formattersDataRequest.getData(); return formatters[fieldName]; - } + }; _getStyleMeta = () => { return _.get(this._descriptor, '__styleMeta', {}); @@ -388,22 +401,27 @@ export class VectorStyle extends AbstractStyle { ); }; - async _getLegendDetailStyleProperties() { + _getLegendDetailStyleProperties = async () => { const isLinesOnly = await this._getIsLinesOnly(); const isPolygonsOnly = await this._getIsPolygonsOnly(); return this.getDynamicPropertiesArray().filter(styleProperty => { + const styleName = styleProperty.getStyleName(); + if ([VECTOR_STYLES.ICON_ORIENTATION, VECTOR_STYLES.LABEL_TEXT].includes(styleName)) { + return false; + } + if (isLinesOnly) { - return LINE_STYLES.includes(styleProperty.getStyleName()); + return LINE_STYLES.includes(styleName); } if (isPolygonsOnly) { - return POLYGON_STYLES.includes(styleProperty.getStyleName()); + return POLYGON_STYLES.includes(styleName); } return true; }); - } + }; async hasLegendDetails() { const styles = await this._getLegendDetailStyleProperties(); @@ -411,20 +429,9 @@ export class VectorStyle extends AbstractStyle { } renderLegendDetails() { - const loadRows = async () => { - const styles = await this._getLegendDetailStyleProperties(); - const promises = styles.map(async style => { - return { - label: await style.getField().getLabel(), - fieldFormatter: this._getFieldFormatter(style.getField().getName()), - meta: this._getFieldMeta(style.getField().getName()), - style, - }; - }); - return await Promise.all(promises); - }; - - return ; + return ( + + ); } _getFeatureStyleParams() { @@ -433,6 +440,7 @@ export class VectorStyle extends AbstractStyle { // To work around this limitation, some styling values must fall back to geojson property values. let supportsFeatureState; let isScaled; + // TODO move first check into DynamicSizeProperty.supportsFeatureState if ( styleProperty.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_ICON @@ -448,8 +456,10 @@ export class VectorStyle extends AbstractStyle { return { supportsFeatureState, isScaled, + isOrdinal: styleProperty.isOrdinal(), name: field.getName(), meta: this._getFieldMeta(field.getName()), + formatter: this._getFieldFormatter(field.getName()), computedName: getComputedFieldName(styleProperty.getStyleName(), field.getName()), }; }); @@ -468,6 +478,20 @@ export class VectorStyle extends AbstractStyle { } } + _getOrdinalValue(value, isScaled, range) { + const valueAsFloat = parseFloat(value); + + if (isScaled) { + return scaleValue(valueAsFloat, range); + } + + if (isNaN(valueAsFloat)) { + return 0; + } + + return valueAsFloat; + } + setFeatureStateAndStyleProps(featureCollection, mbMap, mbSourceId) { if (!featureCollection) { return; @@ -492,20 +516,20 @@ export class VectorStyle extends AbstractStyle { const { supportsFeatureState, isScaled, + isOrdinal, name, meta: range, + formatter, computedName, } = featureStateParams[j]; - const value = parseFloat(feature.properties[name]); + let styleValue; - if (isScaled) { - styleValue = scaleValue(value, range); + if (isOrdinal) { + styleValue = this._getOrdinalValue(feature.properties[name], isScaled, range); + } else if (formatter) { + styleValue = formatter(feature.properties[name]); } else { - if (isNaN(value)) { - styleValue = 0; - } else { - styleValue = value; - } + styleValue = feature.properties[name]; } if (supportsFeatureState) { @@ -543,6 +567,14 @@ export class VectorStyle extends AbstractStyle { this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap); } + setMBPropertiesForLabelText({ alpha, mbMap, textLayerId }) { + mbMap.setLayoutProperty(textLayerId, 'icon-allow-overlap', true); + mbMap.setLayoutProperty(textLayerId, 'text-allow-overlap', true); + this._labelStyleProperty.syncTextFieldWithMb(textLayerId, mbMap); + this._labelColorStyleProperty.syncLabelColorWithMb(textLayerId, mbMap, alpha); + this._labelSizeStyleProperty.syncLabelSizeWithMb(textLayerId, mbMap); + } + setMBSymbolPropertiesForPoints({ mbMap, symbolLayerId, alpha }) { const symbolId = this._descriptor.properties.symbol.options.symbolId; mbMap.setLayoutProperty(symbolLayerId, 'icon-ignore-placement', true); @@ -589,7 +621,13 @@ export class VectorStyle extends AbstractStyle { return new StaticSizeProperty(descriptor.options, styleName); } else if (descriptor.type === DynamicStyleProperty.type) { const field = this._makeField(descriptor.options.field); - return new DynamicSizeProperty(descriptor.options, styleName, field); + return new DynamicSizeProperty( + descriptor.options, + styleName, + field, + this._getFieldMeta, + this._getFieldFormatter + ); } else { throw new Error(`${descriptor} not implemented`); } @@ -602,7 +640,13 @@ export class VectorStyle extends AbstractStyle { return new StaticColorProperty(descriptor.options, styleName); } else if (descriptor.type === DynamicStyleProperty.type) { const field = this._makeField(descriptor.options.field); - return new DynamicColorProperty(descriptor.options, styleName, field); + return new DynamicColorProperty( + descriptor.options, + styleName, + field, + this._getFieldMeta, + this._getFieldFormatter + ); } else { throw new Error(`${descriptor} not implemented`); } @@ -620,4 +664,17 @@ export class VectorStyle extends AbstractStyle { throw new Error(`${descriptor} not implemented`); } } + + _makeLabelProperty(descriptor) { + if (!descriptor || !descriptor.options) { + return new StaticTextProperty({ value: '' }, VECTOR_STYLES.LABEL_TEXT); + } else if (descriptor.type === StaticStyleProperty.type) { + return new StaticTextProperty(descriptor.options, VECTOR_STYLES.LABEL_TEXT); + } else if (descriptor.type === DynamicStyleProperty.type) { + const field = this._makeField(descriptor.options.field); + return new DynamicTextProperty(descriptor.options, VECTOR_STYLES.LABEL_TEXT, field); + } else { + throw new Error(`${descriptor} not implemented`); + } + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js index 1242d7307dc48..aa0badd5583d5 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js @@ -96,6 +96,24 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => { }, type: 'DYNAMIC', }, + labelText: { + options: { + value: '', + }, + type: 'STATIC', + }, + labelColor: { + options: { + color: '#000000', + }, + type: 'STATIC', + }, + labelSize: { + options: { + size: 14, + }, + type: 'STATIC', + }, lineColor: { options: {}, type: 'DYNAMIC', diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js index 3f2851fa092cd..4bae90c3165f2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js @@ -7,11 +7,14 @@ import { VectorStyle } from './vector_style'; import { SYMBOLIZE_AS_CIRCLE, DEFAULT_ICON_SIZE } from './vector_constants'; import { COLOR_GRADIENTS, DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../color_utils'; +import chrome from 'ui/chrome'; const DEFAULT_ICON = 'airfield'; export const MIN_SIZE = 1; export const MAX_SIZE = 64; +export const DEFAULT_MIN_SIZE = 4; +export const DEFAULT_MAX_SIZE = 32; export const DEFAULT_SIGMA = 3; export const VECTOR_STYLES = { @@ -21,6 +24,9 @@ export const VECTOR_STYLES = { LINE_WIDTH: 'lineWidth', ICON_SIZE: 'iconSize', ICON_ORIENTATION: 'iconOrientation', + LABEL_TEXT: 'labelText', + LABEL_COLOR: 'labelColor', + LABEL_SIZE: 'labelSize', }; export const LINE_STYLES = [VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; @@ -49,6 +55,8 @@ export function getDefaultStaticProperties(mapColors = []) { const nextFillColor = DEFAULT_FILL_COLORS[nextColorIndex]; const nextLineColor = DEFAULT_LINE_COLORS[nextColorIndex]; + const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode', false); + return { [VECTOR_STYLES.FILL_COLOR]: { type: VectorStyle.STYLE_TYPE.STATIC, @@ -80,6 +88,24 @@ export function getDefaultStaticProperties(mapColors = []) { orientation: 0, }, }, + [VECTOR_STYLES.LABEL_TEXT]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + value: '', + }, + }, + [VECTOR_STYLES.LABEL_COLOR]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + color: isDarkMode ? '#FFFFFF' : '#000000', + }, + }, + [VECTOR_STYLES.LABEL_SIZE]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + size: 14, + }, + }, }; } @@ -122,8 +148,8 @@ export function getDefaultDynamicProperties() { [VECTOR_STYLES.ICON_SIZE]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { - minSize: 4, - maxSize: 32, + minSize: DEFAULT_MIN_SIZE, + maxSize: DEFAULT_MAX_SIZE, field: undefined, fieldMetaOptions: { isEnabled: true, @@ -141,5 +167,34 @@ export function getDefaultDynamicProperties() { }, }, }, + [VECTOR_STYLES.LABEL_TEXT]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + field: undefined, + }, + }, + [VECTOR_STYLES.LABEL_COLOR]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + color: COLOR_GRADIENTS[0].value, + field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + }, + }, + }, + [VECTOR_STYLES.LABEL_SIZE]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + minSize: DEFAULT_MIN_SIZE, + maxSize: DEFAULT_MAX_SIZE, + field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + }, + }, + }, }; } diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 30c47658bb327..6ebc1b3d95250 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -191,24 +191,33 @@ export class VectorLayer extends AbstractLayer { return this._source.getDisplayName(); } + _getJoinFields() { + const joinFields = []; + this.getValidJoins().forEach(join => { + const fields = join.getJoinFields(); + joinFields.push(...fields); + }); + return joinFields; + } + async getDateFields() { return await this._source.getDateFields(); } async getNumberFields() { const numberFieldOptions = await this._source.getNumberFields(); - const joinFields = []; - this.getValidJoins().forEach(join => { - const fields = join.getJoinFields(); - joinFields.push(...fields); - }); - return [...numberFieldOptions, ...joinFields]; + return [...numberFieldOptions, ...this._getJoinFields()]; } async getOrdinalFields() { return [...(await this.getDateFields()), ...(await this.getNumberFields())]; } + async getFields() { + const sourceFields = await this._source.getFields(); + return [...sourceFields, ...this._getJoinFields()]; + } + getIndexPatternIds() { const indexPatternIds = this._source.getIndexPatternIds(); this.getValidJoins().forEach(join => { @@ -621,30 +630,40 @@ export class VectorLayer extends AbstractLayer { const pointLayer = mbMap.getLayer(pointLayerId); const symbolLayer = mbMap.getLayer(symbolLayerId); - let mbLayerId; + // Point layers symbolized as circles require 2 mapbox layers because + // "circle" layers do not support "text" style properties + // Point layers symbolized as icons only contain a single mapbox layer. + let markerLayerId; + let textLayerId; if (this._style.arePointsSymbolizedAsCircles()) { - mbLayerId = pointLayerId; + markerLayerId = pointLayerId; + textLayerId = this._getMbTextLayerId(); if (symbolLayer) { mbMap.setLayoutProperty(symbolLayerId, 'visibility', 'none'); } this._setMbCircleProperties(mbMap); } else { - mbLayerId = symbolLayerId; + markerLayerId = symbolLayerId; + textLayerId = symbolLayerId; if (pointLayer) { mbMap.setLayoutProperty(pointLayerId, 'visibility', 'none'); + mbMap.setLayoutProperty(this._getMbTextLayerId(), 'visibility', 'none'); } this._setMbSymbolProperties(mbMap); } - this.syncVisibilityWithMb(mbMap, mbLayerId); - mbMap.setLayerZoomRange(mbLayerId, this._descriptor.minZoom, this._descriptor.maxZoom); + this.syncVisibilityWithMb(mbMap, markerLayerId); + mbMap.setLayerZoomRange(markerLayerId, this._descriptor.minZoom, this._descriptor.maxZoom); + if (markerLayerId !== textLayerId) { + this.syncVisibilityWithMb(mbMap, textLayerId); + mbMap.setLayerZoomRange(textLayerId, this._descriptor.minZoom, this._descriptor.maxZoom); + } } _setMbCircleProperties(mbMap) { const sourceId = this.getId(); const pointLayerId = this._getMbPointLayerId(); const pointLayer = mbMap.getLayer(pointLayerId); - if (!pointLayer) { mbMap.addLayer({ id: pointLayerId, @@ -654,15 +673,32 @@ export class VectorLayer extends AbstractLayer { }); } + const textLayerId = this._getMbTextLayerId(); + const textLayer = mbMap.getLayer(textLayerId); + if (!textLayer) { + mbMap.addLayer({ + id: textLayerId, + type: 'symbol', + source: sourceId, + }); + } + const filterExpr = getPointFilterExpression(this._hasJoins()); if (filterExpr !== mbMap.getFilter(pointLayerId)) { mbMap.setFilter(pointLayerId, filterExpr); + mbMap.setFilter(textLayerId, filterExpr); } this._style.setMBPaintPropertiesForPoints({ alpha: this.getAlpha(), mbMap, - pointLayerId: pointLayerId, + pointLayerId, + }); + + this._style.setMBPropertiesForLabelText({ + alpha: this.getAlpha(), + mbMap, + textLayerId, }); } @@ -687,7 +723,13 @@ export class VectorLayer extends AbstractLayer { this._style.setMBSymbolPropertiesForPoints({ alpha: this.getAlpha(), mbMap, - symbolLayerId: symbolLayerId, + symbolLayerId, + }); + + this._style.setMBPropertiesForLabelText({ + alpha: this.getAlpha(), + mbMap, + textLayerId: symbolLayerId, }); } @@ -759,6 +801,10 @@ export class VectorLayer extends AbstractLayer { return this.makeMbLayerId('circle'); } + _getMbTextLayerId() { + return this.makeMbLayerId('text'); + } + _getMbSymbolLayerId() { return this.makeMbLayerId('symbol'); } @@ -774,6 +820,7 @@ export class VectorLayer extends AbstractLayer { getMbLayerIds() { return [ this._getMbPointLayerId(), + this._getMbTextLayerId(), this._getMbSymbolLayerId(), this._getMbLineLayerId(), this._getMbPolygonLayerId(), @@ -781,12 +828,7 @@ export class VectorLayer extends AbstractLayer { } ownsMbLayerId(mbLayerId) { - return ( - this._getMbPointLayerId() === mbLayerId || - this._getMbLineLayerId() === mbLayerId || - this._getMbPolygonLayerId() === mbLayerId || - this._getMbSymbolLayerId() === mbLayerId - ); + return this.getMbLayerIds().includes(mbLayerId); } ownsMbSourceId(mbSourceId) { diff --git a/x-pack/legacy/plugins/maps/public/meta.js b/x-pack/legacy/plugins/maps/public/meta.js index d92b8713f0e70..7cdb8d67c057b 100644 --- a/x-pack/legacy/plugins/maps/public/meta.js +++ b/x-pack/legacy/plugins/maps/public/meta.js @@ -42,7 +42,6 @@ export function getEMSClient() { false ); const proxyPath = proxyElasticMapsServiceInMaps ? relativeToAbsolute('..') : ''; - const manifestServiceUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_CATALOGUE_PATH}`) : chrome.getInjected('emsManifestServiceUrl'); diff --git a/x-pack/legacy/plugins/maps/public/reducers/map.js b/x-pack/legacy/plugins/maps/public/reducers/map.js index dc4a56ea91251..7dd60f013cefd 100644 --- a/x-pack/legacy/plugins/maps/public/reducers/map.js +++ b/x-pack/legacy/plugins/maps/public/reducers/map.js @@ -44,6 +44,8 @@ import { SET_INTERACTIVE, DISABLE_TOOLTIP_CONTROL, HIDE_TOOLBAR_OVERLAY, + HIDE_LAYER_CONTROL, + HIDE_VIEW_CONTROL, } from '../actions/map_actions'; import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from './util'; @@ -113,6 +115,8 @@ const INITIAL_STATE = { disableInteractive: false, disableTooltipControl: false, hideToolbarOverlay: false, + hideLayerControl: false, + hideViewControl: false, }, selectedLayerId: null, __transientLayerId: null, @@ -356,6 +360,22 @@ export function map(state = INITIAL_STATE, action) { hideToolbarOverlay: action.hideToolbarOverlay, }, }; + case HIDE_LAYER_CONTROL: + return { + ...state, + mapState: { + ...state.mapState, + hideLayerControl: action.hideLayerControl, + }, + }; + case HIDE_VIEW_CONTROL: + return { + ...state, + mapState: { + ...state.mapState, + hideViewControl: action.hideViewControl, + }, + }; default: return state; } diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index 072ca26c9633e..3d8e6f97ef077 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -71,6 +71,10 @@ export const isTooltipControlDisabled = ({ map }) => map.mapState.disableTooltip export const isToolbarOverlayHidden = ({ map }) => map.mapState.hideToolbarOverlay; +export const isLayerControlHidden = ({ map }) => map.mapState.hideLayerControl; + +export const isViewControlHidden = ({ map }) => map.mapState.hideViewControl; + export const getMapExtent = ({ map }) => (map.mapState.extent ? map.mapState.extent : {}); export const getMapBuffer = ({ map }) => (map.mapState.buffer ? map.mapState.buffer : {}); diff --git a/x-pack/legacy/plugins/maps/server/plugin.js b/x-pack/legacy/plugins/maps/server/plugin.js index e6b474e1c78dd..6009cea330ab0 100644 --- a/x-pack/legacy/plugins/maps/server/plugin.js +++ b/x-pack/legacy/plugins/maps/server/plugin.js @@ -13,7 +13,7 @@ import { initRoutes } from './routes'; export class MapPlugin { setup(core, plugins, __LEGACY) { - const { featuresPlugin, licensing } = plugins; + const { featuresPlugin, home, licensing } = plugins; let routesInitialized = false; featuresPlugin.registerFeature({ @@ -54,66 +54,68 @@ export class MapPlugin { const sampleDataLinkLabel = i18n.translate('xpack.maps.sampleDataLinkLabel', { defaultMessage: 'Map', }); - __LEGACY.addSavedObjectsToSampleDataset('ecommerce', getEcommerceSavedObjects()); + if (home) { + home.sampleData.addSavedObjectsToSampleDataset('ecommerce', getEcommerceSavedObjects()); - __LEGACY.addAppLinksToSampleDataset('ecommerce', [ - { - path: createMapPath('2c9c1f60-1909-11e9-919b-ffe5949a18d2'), - label: sampleDataLinkLabel, - icon: APP_ICON, - }, - ]); + home.sampleData.addAppLinksToSampleDataset('ecommerce', [ + { + path: createMapPath('2c9c1f60-1909-11e9-919b-ffe5949a18d2'), + label: sampleDataLinkLabel, + icon: APP_ICON, + }, + ]); - __LEGACY.replacePanelInSampleDatasetDashboard({ - sampleDataId: 'ecommerce', - dashboardId: '722b74f0-b882-11e8-a6d9-e546fe2bba5f', - oldEmbeddableId: '9c6f83f0-bb4d-11e8-9c84-77068524bcab', - embeddableId: '2c9c1f60-1909-11e9-919b-ffe5949a18d2', - embeddableType: 'map', - embeddableConfig: { - isLayerTOCOpen: false, - }, - }); + home.sampleData.replacePanelInSampleDatasetDashboard({ + sampleDataId: 'ecommerce', + dashboardId: '722b74f0-b882-11e8-a6d9-e546fe2bba5f', + oldEmbeddableId: '9c6f83f0-bb4d-11e8-9c84-77068524bcab', + embeddableId: '2c9c1f60-1909-11e9-919b-ffe5949a18d2', + embeddableType: 'map', + embeddableConfig: { + isLayerTOCOpen: false, + }, + }); - __LEGACY.addSavedObjectsToSampleDataset('flights', getFlightsSavedObjects()); + home.sampleData.addSavedObjectsToSampleDataset('flights', getFlightsSavedObjects()); - __LEGACY.addAppLinksToSampleDataset('flights', [ - { - path: createMapPath('5dd88580-1906-11e9-919b-ffe5949a18d2'), - label: sampleDataLinkLabel, - icon: APP_ICON, - }, - ]); + home.sampleData.addAppLinksToSampleDataset('flights', [ + { + path: createMapPath('5dd88580-1906-11e9-919b-ffe5949a18d2'), + label: sampleDataLinkLabel, + icon: APP_ICON, + }, + ]); - __LEGACY.replacePanelInSampleDatasetDashboard({ - sampleDataId: 'flights', - dashboardId: '7adfa750-4c81-11e8-b3d7-01146121b73d', - oldEmbeddableId: '334084f0-52fd-11e8-a160-89cc2ad9e8e2', - embeddableId: '5dd88580-1906-11e9-919b-ffe5949a18d2', - embeddableType: MAP_SAVED_OBJECT_TYPE, - embeddableConfig: { - isLayerTOCOpen: true, - }, - }); + home.sampleData.replacePanelInSampleDatasetDashboard({ + sampleDataId: 'flights', + dashboardId: '7adfa750-4c81-11e8-b3d7-01146121b73d', + oldEmbeddableId: '334084f0-52fd-11e8-a160-89cc2ad9e8e2', + embeddableId: '5dd88580-1906-11e9-919b-ffe5949a18d2', + embeddableType: MAP_SAVED_OBJECT_TYPE, + embeddableConfig: { + isLayerTOCOpen: true, + }, + }); - __LEGACY.addSavedObjectsToSampleDataset('logs', getWebLogsSavedObjects()); - __LEGACY.addAppLinksToSampleDataset('logs', [ - { - path: createMapPath('de71f4f0-1902-11e9-919b-ffe5949a18d2'), - label: sampleDataLinkLabel, - icon: APP_ICON, - }, - ]); - __LEGACY.replacePanelInSampleDatasetDashboard({ - sampleDataId: 'logs', - dashboardId: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b', - oldEmbeddableId: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', - embeddableId: 'de71f4f0-1902-11e9-919b-ffe5949a18d2', - embeddableType: MAP_SAVED_OBJECT_TYPE, - embeddableConfig: { - isLayerTOCOpen: false, - }, - }); + home.sampleData.addSavedObjectsToSampleDataset('logs', getWebLogsSavedObjects()); + home.sampleData.addAppLinksToSampleDataset('logs', [ + { + path: createMapPath('de71f4f0-1902-11e9-919b-ffe5949a18d2'), + label: sampleDataLinkLabel, + icon: APP_ICON, + }, + ]); + home.sampleData.replacePanelInSampleDatasetDashboard({ + sampleDataId: 'logs', + dashboardId: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b', + oldEmbeddableId: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', + embeddableId: 'de71f4f0-1902-11e9-919b-ffe5949a18d2', + embeddableType: MAP_SAVED_OBJECT_TYPE, + embeddableConfig: { + isLayerTOCOpen: false, + }, + }); + } __LEGACY.injectUiAppVars(APP_ID, async () => { return await __LEGACY.getInjectedUiAppVars('kibana'); diff --git a/x-pack/legacy/plugins/ml/index.ts b/x-pack/legacy/plugins/ml/index.ts index 3078a0c812ff1..9fe55d15d34a7 100755 --- a/x-pack/legacy/plugins/ml/index.ts +++ b/x-pack/legacy/plugins/ml/index.ts @@ -78,17 +78,17 @@ export const ml = (kibana: any) => { }; const core: MlCoreSetup = { - addAppLinksToSampleDataset: server.addAppLinksToSampleDataset, injectUiAppVars: server.injectUiAppVars, http: mlHttpService, savedObjects: server.savedObjects, }; - const { usageCollection, cloud } = kbnServer.newPlatform.setup.plugins; + const { usageCollection, cloud, home } = kbnServer.newPlatform.setup.plugins; const plugins = { elasticsearch: server.plugins.elasticsearch, security: server.plugins.security, xpackMain: server.plugins.xpack_main, spaces: server.plugins.spaces, + home, usageCollection: usageCollection as UsageCollectionSetup, cloud: cloud as CloudSetup, ml: this, diff --git a/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts b/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts index 94a502e6eadde..2a15c597fed81 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts @@ -11,7 +11,7 @@ import { SavedSearchSavedObject } from '../../../../common/types/kibana'; declare const DataRecognizer: FC<{ indexPattern: IndexPattern; - savedSearch?: SavedSearchSavedObject | null; + savedSearch: SavedSearchSavedObject | null; results: { count: number; onChange?: Function; diff --git a/x-pack/legacy/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap b/x-pack/legacy/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap index 9ba92a3aa1677..29d10eb99ff4e 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap @@ -60,7 +60,7 @@ exports[`ValidateJob renders button and modal with a message 1`] = ` values={ Object { "mlJobTipsLink": = ({ indexPattern }) => { - +
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js b/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js index 35e80e14009b0..4cd6dbc787d28 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js +++ b/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js @@ -14,6 +14,8 @@ export const getMessages = () => { return messages; } + const createJobsDocsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/create-jobs.html`; + return (messages = { field_not_aggregatable: { status: 'ERROR', @@ -43,7 +45,7 @@ export const getMessages = () => { fieldName: 'by_field "{{fieldName}}"', }, }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#cardinality', + url: `${createJobsDocsUrl}#cardinality`, }, cardinality_over_field_low: { status: 'WARNING', @@ -57,7 +59,7 @@ export const getMessages = () => { }, } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#cardinality', + url: `${createJobsDocsUrl}#cardinality`, }, cardinality_over_field_high: { status: 'WARNING', @@ -71,7 +73,7 @@ export const getMessages = () => { }, } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#cardinality', + url: `${createJobsDocsUrl}#cardinality`, }, cardinality_partition_field: { status: 'WARNING', @@ -85,7 +87,7 @@ export const getMessages = () => { }, } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#cardinality', + url: `${createJobsDocsUrl}#cardinality`, }, cardinality_model_plot_high: { status: 'WARNING', @@ -155,7 +157,7 @@ export const getMessages = () => { }, } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#bucket-span', + url: `${createJobsDocsUrl}#bucket-span`, }, bucket_span_high: { status: 'INFO', @@ -166,7 +168,7 @@ export const getMessages = () => { defaultMessage: 'Bucket span is 1 day or more. Be aware that days are considered as UTC days, not local days.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#bucket-span', + url: `${createJobsDocsUrl}#bucket-span`, }, bucket_span_valid: { status: 'SUCCESS', @@ -209,21 +211,21 @@ export const getMessages = () => { partitionFieldNameParam: `'partition_field_name'`, }, }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#detectors', + url: `${createJobsDocsUrl}#detectors`, }, detectors_empty: { status: 'ERROR', text: i18n.translate('xpack.ml.models.jobValidation.messages.detectorsEmptyMessage', { defaultMessage: 'No detectors were found. At least one detector must be specified.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#detectors', + url: `${createJobsDocsUrl}#detectors`, }, detectors_function_empty: { status: 'ERROR', text: i18n.translate('xpack.ml.models.jobValidation.messages.detectorsFunctionEmptyMessage', { defaultMessage: 'One of the detector functions is empty.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#detectors', + url: `${createJobsDocsUrl}#detectors`, }, detectors_function_not_empty: { status: 'SUCCESS', @@ -239,7 +241,7 @@ export const getMessages = () => { defaultMessage: 'Presence of detector functions validated in all detectors.', } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#detectors', + url: `${createJobsDocsUrl}#detectors`, }, index_fields_invalid: { status: 'ERROR', @@ -260,7 +262,7 @@ export const getMessages = () => { 'The job configuration includes more than 3 influencers. ' + 'Consider using fewer influencers or creating multiple jobs.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#influencers', + url: 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-influencers.html', }, influencer_low: { status: 'WARNING', @@ -268,7 +270,7 @@ export const getMessages = () => { defaultMessage: 'No influencers have been configured. Picking an influencer is strongly recommended.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#influencers', + url: 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-influencers.html', }, influencer_low_suggestion: { status: 'WARNING', @@ -280,7 +282,7 @@ export const getMessages = () => { values: { influencerSuggestion: '{{influencerSuggestion}}' }, } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#influencers', + url: 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-influencers.html', }, influencer_low_suggestions: { status: 'WARNING', @@ -292,7 +294,7 @@ export const getMessages = () => { values: { influencerSuggestion: '{{influencerSuggestion}}' }, } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#influencers', + url: 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-influencers.html', }, job_id_empty: { status: 'ERROR', @@ -401,7 +403,7 @@ export const getMessages = () => { text: i18n.translate('xpack.ml.models.jobValidation.messages.successCardinalityMessage', { defaultMessage: 'Cardinality of detector fields is within recommended bounds.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#cardinality', + url: `${createJobsDocsUrl}#cardinality`, }, success_bucket_span: { status: 'SUCCESS', @@ -412,14 +414,14 @@ export const getMessages = () => { defaultMessage: 'Format of {bucketSpan} is valid and passed validation checks.', values: { bucketSpan: '"{{bucketSpan}}"' }, }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#bucket-span', + url: `${createJobsDocsUrl}#bucket-span`, }, success_influencers: { status: 'SUCCESS', text: i18n.translate('xpack.ml.models.jobValidation.messages.successInfluencersMessage', { defaultMessage: 'Influencer configuration passed the validation checks.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#influencers', + url: 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-influencers.html', }, estimated_mml_greater_than_max_mml: { status: 'WARNING', @@ -446,7 +448,7 @@ export const getMessages = () => { '1MB and should be specified in bytes e.g. 10MB.', values: { mml: '{{mml}}' }, }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#model-memory-limits', + url: `${createJobsDocsUrl}#model-memory-limits`, }, half_estimated_mml_greater_than_mml: { status: 'WARNING', @@ -458,7 +460,7 @@ export const getMessages = () => { 'memory limit and will likely hit the hard limit.', } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#model-memory-limits', + url: `${createJobsDocsUrl}#model-memory-limits`, }, estimated_mml_greater_than_mml: { status: 'INFO', @@ -469,7 +471,7 @@ export const getMessages = () => { 'The estimated model memory limit is greater than the model memory limit you have configured.', } ), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#model-memory-limits', + url: `${createJobsDocsUrl}#model-memory-limits`, }, success_mml: { status: 'SUCCESS', @@ -479,7 +481,7 @@ export const getMessages = () => { text: i18n.translate('xpack.ml.models.jobValidation.messages.successMmlMessage', { defaultMessage: 'Valid and within the estimated model memory limit.', }), - url: 'https://www.elastic.co/guide/en/kibana/{{version}}/job-tips.html#model-memory-limits', + url: `${createJobsDocsUrl}#model-memory-limits`, }, success_time_range: { status: 'SUCCESS', diff --git a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts index c468c87d7abc8..7e22a9a5a4c8b 100644 --- a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts +++ b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts @@ -55,6 +55,7 @@ import { jobAuditMessagesRoutes } from '../routes/job_audit_messages'; // @ts-ignore: could not find declaration file for module import { fileDataVisualizerRoutes } from '../routes/file_data_visualizer'; import { initMlServerLog, LogInitialization } from '../client/log'; +import { HomeServerPluginSetup } from '../../../../../../src/plugins/home/server'; type CoreHttpSetup = CoreSetup['http']; export interface MlHttpServiceSetup extends CoreHttpSetup { @@ -66,7 +67,6 @@ export interface MlXpackMainPlugin extends XPackMainPlugin { } export interface MlCoreSetup { - addAppLinksToSampleDataset: () => any; injectUiAppVars: (id: string, callback: () => {}) => any; http: MlHttpServiceSetup; savedObjects: SavedObjectsLegacyService; @@ -82,6 +82,7 @@ export interface PluginsSetup { spaces: any; usageCollection?: UsageCollectionSetup; cloud?: CloudSetup; + home?: HomeServerPluginSetup; // TODO: this is temporary for `mirrorPluginStatus` ml: any; } @@ -112,7 +113,7 @@ export class Plugin { public setup(core: MlCoreSetup, plugins: PluginsSetup) { const xpackMainPlugin: MlXpackMainPlugin = plugins.xpackMain; - const { addAppLinksToSampleDataset, http, injectUiAppVars } = core; + const { http, injectUiAppVars } = core; const pluginId = this.pluginId; mirrorPluginStatus(xpackMainPlugin, plugins.ml); @@ -124,10 +125,12 @@ export class Plugin { // Add links to the Kibana sample data sets if ml is enabled // and there is a full license (trial or platinum). - if (mlFeature.isEnabled() === true) { + if (mlFeature.isEnabled() === true && plugins.home) { const licenseCheckResults = mlFeature.getLicenseCheckResults(); if (licenseCheckResults.licenseType === LICENSE_TYPE.FULL) { - addLinksToSampleDatasets({ addAppLinksToSampleDataset }); + addLinksToSampleDatasets({ + addAppLinksToSampleDataset: plugins.home.sampleData.addAppLinksToSampleDataset, + }); } } }); diff --git a/x-pack/legacy/plugins/monitoring/index.js b/x-pack/legacy/plugins/monitoring/index.js index f78f8d45d6440..3d98b11c2045b 100644 --- a/x-pack/legacy/plugins/monitoring/index.js +++ b/x-pack/legacy/plugins/monitoring/index.js @@ -68,12 +68,13 @@ export const monitoring = kibana => _hapi: server, _kbnServer: this.kbnServer, }; - const { usageCollection } = server.newPlatform.setup.plugins; + const { usageCollection, licensing } = server.newPlatform.setup.plugins; const plugins = { xpack_main: server.plugins.xpack_main, elasticsearch: server.plugins.elasticsearch, infra: server.plugins.infra, usageCollection, + licensing, }; new Plugin().setup(serverFacade, plugins); diff --git a/x-pack/legacy/plugins/monitoring/server/__tests__/check_license.js b/x-pack/legacy/plugins/monitoring/server/__tests__/check_license.js new file mode 100644 index 0000000000000..60b27cae30878 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/__tests__/check_license.js @@ -0,0 +1,87 @@ +/* + * 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 { BehaviorSubject } from 'rxjs'; +import expect from '@kbn/expect'; +import sinon from 'sinon'; + +import { XPackInfo } from '../../../xpack_main/server/lib/xpack_info'; +import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; + +const createLicense = (type = 'basic') => { + return licensingMock.createLicense({ + license: { + uid: 'custom-uid', + type, + mode: 'basic', + status: 'active', + expiryDateInMillis: 1286575200000, + }, + features: { + monitoring: { + description: '...', + isAvailable: true, + isEnabled: true, + }, + }, + }); +}; + +describe('XPackInfo', () => { + let mockServer; + let mockElasticsearchPlugin; + + beforeEach(() => { + mockServer = sinon.stub({ + plugins: { elasticsearch: mockElasticsearchPlugin }, + events: { on() {} }, + newPlatform: { + setup: { + plugins: { + licensing: {}, + }, + }, + }, + }); + }); + + describe('refreshNow()', () => { + it('check new platform licensing plugin', async () => { + const refresh = sinon.spy(); + const license$ = new BehaviorSubject(createLicense()); + const xPackInfo = new XPackInfo(mockServer, { + licensing: { + license$, + refresh, + }, + }); + + let changed = false; + license$.subscribe(() => (changed = true)); + await xPackInfo.refreshNow(); + expect(changed).to.be(true); + sinon.assert.calledOnce(refresh); + }); + }); + + describe('Change type', () => { + it('trigger event when license type changes', async () => { + const license$ = new BehaviorSubject(createLicense()); + const refresh = () => void 0; + const xPackInfo = new XPackInfo(mockServer, { + licensing: { + license$, + refresh, + }, + }); + let changed = false; + license$.subscribe(() => (changed = true)); + await license$.next(createLicense('gold')); + expect(xPackInfo.license.getType()).to.be('gold'); + expect(changed).to.be(true); + }); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index 695e23b022b49..73c37dc7f3f28 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -348,7 +348,7 @@ export const getCollectionStatus = async ( }, }; } - console.log('OKOKOKOK'); + const liveClusterUuid = skipLiveData ? null : await getLiveElasticsearchClusterUuid(req); const isLiveCluster = !clusterUuid || liveClusterUuid === clusterUuid; diff --git a/x-pack/legacy/plugins/monitoring/server/plugin.js b/x-pack/legacy/plugins/monitoring/server/plugin.js index 013f8ae681bad..9ce4b355d9c0d 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -17,6 +17,7 @@ export class Plugin { const kbnServer = core._kbnServer; const config = core.config(); const usageCollection = plugins.usageCollection; + const licensing = plugins.licensing; registerMonitoringCollection(); /* * Register collector objects for stats to show up in the APIs @@ -91,17 +92,16 @@ export class Plugin { kbnServerVersion: kbnServer.version, }); const kibanaCollectionEnabled = config.get('xpack.monitoring.kibana.collection.enabled'); - const { info: xpackMainInfo } = xpackMainPlugin; if (kibanaCollectionEnabled) { /* * Bulk uploading of Kibana stats */ - xpackMainInfo.onLicenseInfoChange(() => { + licensing.license$.subscribe(license => { // use updated xpack license info to start/stop bulk upload - const mainMonitoring = xpackMainInfo.feature('monitoring'); + const mainMonitoring = license.getFeature('monitoring'); const monitoringBulkEnabled = - mainMonitoring && mainMonitoring.isAvailable() && mainMonitoring.isEnabled(); + mainMonitoring && mainMonitoring.isAvailable && mainMonitoring.isEnabled; if (monitoringBulkEnabled) { bulkUploader.start(usageCollection); } else { diff --git a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index ebb57d34c01a1..95ca1792a7eb1 100644 --- a/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/legacy/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -19,8 +19,8 @@ import { IEmbeddable, CONTEXT_MENU_TRIGGER, } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; -import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/constants'; -import { ISearchEmbeddable } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/types'; +import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants'; +import { ISearchEmbeddable } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/types'; import { API_BASE_URL_V1 } from '../../common/constants'; diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index e505a8fb55d90..6ee8b5f8b2b10 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -130,7 +130,6 @@ export const security = kibana => const config = server.config(); const xpackInfo = server.plugins.xpack_main.info; securityPlugin.__legacyCompat.registerLegacyAPI({ - savedObjects: server.savedObjects, auditLogger: new AuditLogger(server, 'security', config, xpackInfo), isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx index 7637d28dd4229..2ba012afa689d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx @@ -86,7 +86,7 @@ class EditRolePageUI extends Component { }; } - public UNSAFE_componentWillMount() { + public componentDidMount() { if (this.props.action === 'clone' && isReservedRole(this.props.role)) { this.backToRoleList(); } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index 1f29f774fd6cc..f97fa93294ff5 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -46,6 +46,7 @@ const buildProps = (customProps: any = {}) => { id: 'feature1', name: 'Feature 1', app: ['app'], + icon: 'spacesApp', privileges: { all: { app: ['app'], diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 3b5cdec4dc4d9..4383329fea072 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -11,6 +11,7 @@ export const DEFAULT_DATE_FORMAT = 'dateFormat'; export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz'; export const DEFAULT_DARK_MODE = 'theme:darkMode'; export const DEFAULT_INDEX_KEY = 'siem:defaultIndex'; +export const DEFAULT_NUMBER_FORMAT = 'format:number:defaultPattern'; export const DEFAULT_TIME_RANGE = 'timepicker:timeDefaults'; export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults'; export const DEFAULT_SIEM_TIME_RANGE = 'siem:timeDefaults'; @@ -65,3 +66,8 @@ export const SIGNALS_INDEX_KEY = 'signalsIndex'; export const DETECTION_ENGINE_SIGNALS_URL = `${DETECTION_ENGINE_URL}/signals`; export const DETECTION_ENGINE_SIGNALS_STATUS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/status`; export const DETECTION_ENGINE_QUERY_SIGNALS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/search`; + +/** + * Common naming convention for an unauthenticated user + */ +export const UNAUTHENTICATED_USER = 'Unauthenticated'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/selectors.ts index 08ac78f73603f..039e38aaf3ee7 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/selectors.ts @@ -30,6 +30,10 @@ export const FIELDS_BROWSER_FILTER_INPUT = '[data-test-subj="field-search"]'; */ export const FIELDS_BROWSER_CATEGORIES_COUNT = '[data-test-subj="categories-count"]'; +export const FIELDS_BROWSER_HOST_CATEGORIES_COUNT = '[data-test-subj="host-category-count"]'; + +export const FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT = '[data-test-subj="system-category-count"]'; + /** * This label displays a count of the fields that match the filter criteria */ diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts index 57a1f318a7e31..85878d8225609 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts @@ -26,6 +26,7 @@ import { LOAD_MORE, LOCAL_EVENTS_COUNT, } from '../../lib/events_viewer/selectors'; +import { SERVER_SIDE_EVENT_COUNT } from '../../lib/timeline/selectors'; import { clickEventsTab } from '../../lib/hosts/helpers'; const defaultHeadersInDefaultEcsCategory = [ @@ -106,7 +107,7 @@ describe('Events Viewer', () => { .invoke('text') .then(text1 => { cy.get(HEADER_SUBTITLE) - .invoke('text') + .invoke('text', { timeout: DEFAULT_TIMEOUT }) .should('not.equal', 'Showing: 0 events'); filterSearchBar(filterInput); @@ -141,7 +142,7 @@ describe('Events Viewer', () => { }); it('loads more events when the load more button is clicked', () => { - cy.get(LOCAL_EVENTS_COUNT) + cy.get(LOCAL_EVENTS_COUNT, { timeout: DEFAULT_TIMEOUT }) .invoke('text') .then(text1 => { cy.get(LOCAL_EVENTS_COUNT) @@ -160,11 +161,13 @@ describe('Events Viewer', () => { it('launches the inspect query modal when the inspect button is clicked', () => { // wait for data to load - cy.get(HEADER_SUBTITLE) - .invoke('text') - .should('not.equal', 'Showing: 0 events'); + cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT }) + .should('exist') + .invoke('text', { timeout: DEFAULT_TIMEOUT }) + .should('not.equal', '0'); - cy.get(INSPECT_QUERY) + cy.get(INSPECT_QUERY, { timeout: DEFAULT_TIMEOUT }) + .should('exist') .trigger('mousemove', { force: true }) .click({ force: true }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts index 8f5c6e6f660cc..dfc5e10893ebb 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts @@ -15,8 +15,11 @@ import { FIELDS_BROWSER_CATEGORIES_COUNT, FIELDS_BROWSER_CONTAINER, FIELDS_BROWSER_FIELDS_COUNT, + FIELDS_BROWSER_FILTER_INPUT, + FIELDS_BROWSER_HOST_CATEGORIES_COUNT, FIELDS_BROWSER_SELECTED_CATEGORY_COUNT, FIELDS_BROWSER_SELECTED_CATEGORY_TITLE, + FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT, FIELDS_BROWSER_TITLE, } from '../../lib/fields_browser/selectors'; import { logout } from '../../lib/logout'; @@ -138,9 +141,22 @@ describe('Fields Browser', () => { filterFieldsBrowser(filterInput); - cy.get(FIELDS_BROWSER_FIELDS_COUNT) + cy.get(FIELDS_BROWSER_FILTER_INPUT, { timeout: DEFAULT_TIMEOUT }).should( + 'not.have.class', + 'euiFieldSearch-isLoading' + ); + + cy.get(FIELDS_BROWSER_HOST_CATEGORIES_COUNT) .invoke('text') - .should('eq', '2 fields'); + .then(hostCategoriesCount => { + cy.get(FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT) + .invoke('text') + .then(systemCategoriesCount => { + cy.get(FIELDS_BROWSER_FIELDS_COUNT) + .invoke('text') + .should('eq', `${+hostCategoriesCount + +systemCategoriesCount} fields`); + }); + }); }); it('selects a search results label with the expected count of categories matching the filter input', () => { diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts index eca1887ddaf9b..54207966fd36f 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts @@ -45,7 +45,7 @@ describe('Inspect', () => { toggleTimelineVisibility(); executeKQL(hostExistsQuery); cy.get(TIMELINE_SETTINGS_ICON).trigger('click', { force: true }); - cy.get(TIMELINE_INSPECT_BUTTON).should('not.be.disabled', { timeout: DEFAULT_TIMEOUT }); + cy.get(TIMELINE_INSPECT_BUTTON, { timeout: DEFAULT_TIMEOUT }).should('not.be.disabled'); cy.get(TIMELINE_INSPECT_BUTTON).trigger('click', { force: true }); cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('be.visible'); }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts index 21829a0c20228..dba5099a93c5a 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts @@ -60,11 +60,14 @@ describe('url state', () => { .first() .click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_INPUT, { timeout: 5000 }).type( + cy.get(DATE_PICKER_ABSOLUTE_INPUT, { timeout: DEFAULT_TIMEOUT }).type( `{selectall}{backspace}${ABSOLUTE_DATE_RANGE.newStartTimeTyped}` ); - cy.get(DATE_PICKER_APPLY_BUTTON, { timeout: 5000 }).click(); + cy.get(DATE_PICKER_APPLY_BUTTON, { timeout: DEFAULT_TIMEOUT }) + .click({ force: true }) + .invoke('text') + .should('not.equal', 'Updating'); cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON).click({ force: true }); @@ -72,11 +75,14 @@ describe('url state', () => { .first() .click({ force: true }); - cy.get(DATE_PICKER_ABSOLUTE_INPUT, { timeout: 5000 }).type( + cy.get(DATE_PICKER_ABSOLUTE_INPUT, { timeout: DEFAULT_TIMEOUT }).type( `{selectall}{backspace}${ABSOLUTE_DATE_RANGE.newEndTimeTyped}` ); - cy.get(DATE_PICKER_APPLY_BUTTON, { timeout: 5000 }).click(); + cy.get(DATE_PICKER_APPLY_BUTTON, { timeout: DEFAULT_TIMEOUT }) + .click({ force: true }) + .invoke('text') + .should('not.equal', 'Updating'); cy.url().should( 'include', diff --git a/x-pack/legacy/plugins/siem/public/apps/plugin.tsx b/x-pack/legacy/plugins/siem/public/apps/plugin.tsx index 1f19841788ddb..700fec2557b1b 100644 --- a/x-pack/legacy/plugins/siem/public/apps/plugin.tsx +++ b/x-pack/legacy/plugins/siem/public/apps/plugin.tsx @@ -15,6 +15,12 @@ import template from './template.html'; export const ROOT_ELEMENT_ID = 'react-siem-root'; +export type StartCore = LegacyCoreStart; +export type StartPlugins = Required< + Pick +>; +export type StartServices = StartCore & StartPlugins; + export class Plugin { constructor( // @ts-ignore this is added to satisfy the New Platform typing constraint, @@ -25,7 +31,7 @@ export class Plugin { this.chrome = chrome; } - public start(core: LegacyCoreStart, plugins: PluginsStart) { + public start(core: StartCore, plugins: StartPlugins) { // @ts-ignore improper type description this.chrome.setRootTemplate(template); const checkForRoot = () => { diff --git a/x-pack/legacy/plugins/siem/public/apps/start_app.tsx b/x-pack/legacy/plugins/siem/public/apps/start_app.tsx index 4549db946b815..6b7e3a7b8084f 100644 --- a/x-pack/legacy/plugins/siem/public/apps/start_app.tsx +++ b/x-pack/legacy/plugins/siem/public/apps/start_app.tsx @@ -9,8 +9,6 @@ import React, { memo, FC } from 'react'; import { ApolloProvider } from 'react-apollo'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { ThemeProvider } from 'styled-components'; -import { LegacyCoreStart } from 'kibana/public'; -import { PluginsStart } from 'ui/new_platform/new_platform'; import { EuiErrorBoundary } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; @@ -19,16 +17,14 @@ import { BehaviorSubject } from 'rxjs'; import { pluck } from 'rxjs/operators'; import { I18nContext } from 'ui/i18n'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { KibanaContextProvider, useUiSetting$ } from '../lib/kibana'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { DEFAULT_DARK_MODE } from '../../common/constants'; import { ErrorToastDispatcher } from '../components/error_toast_dispatcher'; import { compose } from '../lib/compose/kibana_compose'; import { AppFrontendLibs } from '../lib/lib'; -import { KibanaCoreContextProvider } from '../lib/compose/kibana_core'; -import { KibanaPluginsContextProvider } from '../lib/compose/kibana_plugins'; -import { useKibanaUiSetting } from '../lib/settings/use_kibana_ui_setting'; +import { StartCore, StartPlugins } from './plugin'; import { PageRouter } from '../routes'; import { createStore } from '../store'; import { GlobalToaster, ManageGlobalToaster } from '../components/toasters'; @@ -44,7 +40,7 @@ const StartApp: FC = memo(libs => { const store = createStore(undefined, libs$.pipe(pluck('apolloClient'))); const AppPluginRoot = memo(() => { - const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE); + const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); return ( @@ -77,21 +73,15 @@ const StartApp: FC = memo(libs => { export const ROOT_ELEMENT_ID = 'react-siem-root'; -export const SiemApp = memo<{ core: LegacyCoreStart; plugins: PluginsStart }>( - ({ core, plugins }) => ( - - - - - - - - ) -); +export const SiemApp = memo<{ core: StartCore; plugins: StartPlugins }>(({ core, plugins }) => ( + + + +)); diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx index e0101dc3ab74b..8fa4f3625c34f 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx @@ -64,7 +64,7 @@ export const AlertsTable = React.memo( const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); return ( ({ documentType: i18n.ALERTS_DOCUMENT_TYPE, footerText: i18n.TOTAL_COUNT_OF_ALERTS, - showCheckboxes: false, - showRowRenderers: false, title: i18n.ALERTS_TABLE_TITLE, }), [] diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/default_headers.ts b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/default_headers.ts index 52990f521b58d..936d43fff0b48 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/default_headers.ts +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/default_headers.ts @@ -65,4 +65,5 @@ export const alertsHeaders: ColumnHeader[] = [ export const alertsDefaultModel: SubsetTimelineModel = { ...timelineDefaults, columns: alertsHeaders, + showRowRenderers: false, }; diff --git a/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx index 2321b06c07cc0..6816bff24f1cd 100644 --- a/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx @@ -12,8 +12,6 @@ import { useMountAppended } from '../../utils/use_mount_appended'; import { Bytes } from '.'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('Bytes', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 25bd2a9d56059..2b99efc05fd8c 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx @@ -11,6 +11,8 @@ import { AreaChartBaseComponent, AreaChartComponent } from './areachart'; import { ChartSeriesData } from './common'; import { ScaleType, AreaSeries, Axis } from '@elastic/charts'; +jest.mock('../../lib/kibana'); + const customHeight = '100px'; const customWidth = '120px'; const chartDataSets = [ diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index c644d148cc1c3..297563c8e31cf 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -21,7 +21,6 @@ import { getOr, get, isNull, isNumber } from 'lodash/fp'; import { AutoSizer } from '../auto_sizer'; import { ChartPlaceHolder } from './chart_place_holder'; import { - browserTimezone, chartDefaultSettings, ChartSeriesConfigs, ChartSeriesData, @@ -29,6 +28,8 @@ import { getChartWidth, getSeriesStyle, WrappedByAutoSizer, + useTheme, + useBrowserTimeZone, } from './common'; // custom series styles: https://ela.st/areachart-styling @@ -72,12 +73,15 @@ export const AreaChartBaseComponent = ({ height: string | null | undefined; configs?: ChartSeriesConfigs | undefined; }) => { + const theme = useTheme(); + const timeZone = useBrowserTimeZone(); const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const xAxisId = getAxisId(`group-${data[0].key}-x`); const yAxisId = getAxisId(`group-${data[0].key}-y`); const settings = { ...chartDefaultSettings, + theme, ...get('configs.settings', chartConfigs), }; return chartConfigs.width && chartConfigs.height ? ( @@ -95,7 +99,7 @@ export const AreaChartBaseComponent = ({ data={series.value || undefined} xScaleType={getOr(ScaleType.Linear, 'configs.series.xScaleType', chartConfigs)} yScaleType={getOr(ScaleType.Linear, 'configs.series.yScaleType', chartConfigs)} - timeZone={browserTimezone} + timeZone={timeZone} xAccessor="x" yAccessors={['y']} areaSeriesStyle={getSeriesLineStyle()} diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx index e28d330d31ba9..506b1ceb5ed83 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx @@ -11,6 +11,8 @@ import { BarChartBaseComponent, BarChartComponent } from './barchart'; import { ChartSeriesData } from './common'; import { BarSeries, ScaleType, Axis } from '@elastic/charts'; +jest.mock('../../lib/kibana'); + const customHeight = '100px'; const customWidth = '120px'; const chartDataSets = [ diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index 99ad995e48852..ee8b4eaf6b08c 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -19,16 +19,17 @@ import { getOr, get, isNumber } from 'lodash/fp'; import { AutoSizer } from '../auto_sizer'; import { ChartPlaceHolder } from './chart_place_holder'; import { - browserTimezone, chartDefaultSettings, ChartSeriesConfigs, ChartSeriesData, checkIfAllValuesAreZero, - getSeriesStyle, getChartHeight, getChartWidth, + getSeriesStyle, SeriesType, WrappedByAutoSizer, + useBrowserTimeZone, + useTheme, } from './common'; const checkIfAllTheDataInTheSeriesAreValid = (series: ChartSeriesData): series is ChartSeriesData => @@ -53,6 +54,8 @@ export const BarChartBaseComponent = ({ height: string | null | undefined; configs?: ChartSeriesConfigs | undefined; }) => { + const theme = useTheme(); + const timeZone = useBrowserTimeZone(); const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const tickSize = getOr(0, 'configs.axis.tickSize', chartConfigs); @@ -60,6 +63,7 @@ export const BarChartBaseComponent = ({ const yAxisId = getAxisId(`stat-items-barchart-${data[0].key}-y`); const settings = { ...chartDefaultSettings, + theme, ...get('configs.settings', chartConfigs), }; @@ -79,7 +83,7 @@ export const BarChartBaseComponent = ({ yScaleType={getOr(ScaleType.Linear, 'configs.series.yScaleType', chartConfigs)} xAccessor="x" yAccessors={['y']} - timeZone={browserTimezone} + timeZone={timeZone} splitSeriesAccessors={['g']} data={series.value!} stackAccessors={get('configs.series.stackAccessors', chartConfigs)} diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx index 0bb4da69a978a..e9df0d3885a18 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx @@ -5,23 +5,27 @@ */ import { shallow } from 'enzyme'; import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; + +import { useUiSetting } from '../../lib/kibana'; import { checkIfAllValuesAreZero, defaultChartHeight, getChartHeight, getChartWidth, getSeriesStyle, - getTheme, SeriesType, WrappedByAutoSizer, ChartSeriesData, + useTheme, } from './common'; -import { mergeWithDefaultTheme, LIGHT_THEME } from '@elastic/charts'; + +jest.mock('../../lib/kibana'); jest.mock('@elastic/charts', () => { return { + ...jest.requireActual('@elastic/charts'), getSpecId: jest.fn(() => {}), - mergeWithDefaultTheme: jest.fn(), }; }); @@ -57,21 +61,6 @@ describe('getSeriesStyle', () => { }); }); -describe('getTheme', () => { - it('should merge custom theme with default theme', () => { - const defaultTheme = { - chartMargins: { bottom: 0, left: 0, right: 0, top: 4 }, - chartPaddings: { bottom: 0, left: 0, right: 0, top: 0 }, - scales: { - barsPadding: 0.05, - }, - }; - getTheme(); - expect((mergeWithDefaultTheme as jest.Mock).mock.calls[0][0]).toMatchObject(defaultTheme); - expect((mergeWithDefaultTheme as jest.Mock).mock.calls[0][1]).toEqual(LIGHT_THEME); - }); -}); - describe('getChartHeight', () => { it('should render customHeight', () => { const height = getChartHeight(10, 100); @@ -197,4 +186,23 @@ describe('checkIfAllValuesAreZero', () => { expect(result).toBeTruthy(); }); }); + + describe('useTheme', () => { + it('merges our spacing with the default theme', () => { + const { result } = renderHook(() => useTheme()); + + expect(result.current).toEqual( + expect.objectContaining({ chartMargins: expect.objectContaining({ top: 4, bottom: 0 }) }) + ); + }); + + it('returns a different theme depending on user settings', () => { + const { result: defaultResult } = renderHook(() => useTheme()); + (useUiSetting as jest.Mock).mockImplementation(() => true); + + const { result: darkResult } = renderHook(() => useTheme()); + + expect(defaultResult.current).not.toMatchObject(darkResult.current); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx index 7ac91437920e5..dfb201fc3d927 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; import { CustomSeriesColorsMap, DARK_THEME, @@ -21,6 +20,7 @@ import { } from '@elastic/charts'; import moment from 'moment-timezone'; import styled from 'styled-components'; +import { useUiSetting } from '../../lib/kibana'; import { DEFAULT_DATE_FORMAT_TZ, DEFAULT_DARK_MODE } from '../../../common/constants'; export const defaultChartHeight = '100%'; @@ -95,27 +95,28 @@ export const getSeriesStyle = ( }; // Apply margins and paddings: https://ela.st/charts-spacing -export const getTheme = () => { - const theme: PartialTheme = { - chartMargins: { - left: 0, - right: 0, - // Apply some paddings to the top to avoid chopping the y tick https://ela.st/chopping-edge - top: 4, - bottom: 0, - }, - chartPaddings: { - left: 0, - right: 0, - top: 0, - bottom: 0, - }, - scales: { - barsPadding: 0.05, - }, - }; - const isDarkMode: boolean = chrome.getUiSettingsClient().get(DEFAULT_DARK_MODE); +const theme: PartialTheme = { + chartMargins: { + left: 0, + right: 0, + // Apply some paddings to the top to avoid chopping the y tick https://ela.st/chopping-edge + top: 4, + bottom: 0, + }, + chartPaddings: { + left: 0, + right: 0, + top: 0, + bottom: 0, + }, + scales: { + barsPadding: 0.05, + }, +}; +export const useTheme = () => { + const isDarkMode = useUiSetting(DEFAULT_DARK_MODE); const defaultTheme = isDarkMode ? DARK_THEME : LIGHT_THEME; + return mergeWithDefaultTheme(theme, defaultTheme); }; @@ -126,11 +127,12 @@ export const chartDefaultSettings = { showLegend: false, showLegendDisplayValue: false, debug: false, - theme: getTheme(), }; -const kibanaTimezone: string = chrome.getUiSettingsClient().get(DEFAULT_DATE_FORMAT_TZ); -export const browserTimezone = kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone; +export const useBrowserTimeZone = () => { + const kibanaTimezone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); + return kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone; +}; export const getChartHeight = (customHeight?: number, autoSizerHeight?: number): string => { const height = customHeight || autoSizerHeight; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx index 68522377bd847..a5eac381f9215 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx @@ -9,7 +9,6 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../../mock/ui_settings'; import { TestProviders } from '../../../mock'; import { UtilityBar, @@ -19,8 +18,6 @@ import { UtilityBarText, } from './index'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('UtilityBar', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx index 7921c1ef42200..2610fb44532f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx @@ -8,12 +8,9 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../../mock/ui_settings'; import { TestProviders } from '../../../mock'; import { UtilityBarAction } from './index'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('UtilityBarAction', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx index 294d27fa95b3d..59ef7021d4049 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx @@ -8,12 +8,9 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../../mock/ui_settings'; import { TestProviders } from '../../../mock'; import { UtilityBarGroup, UtilityBarText } from './index'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('UtilityBarGroup', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx index e0e0acc3a71c9..baa4331ced8f8 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx @@ -8,12 +8,9 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../../mock/ui_settings'; import { TestProviders } from '../../../mock'; import { UtilityBarGroup, UtilityBarSection, UtilityBarText } from './index'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('UtilityBarSection', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx index 29e1844bb2d4f..794f207fd88e3 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx @@ -8,12 +8,9 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../../mock/ui_settings'; import { TestProviders } from '../../../mock'; import { UtilityBarText } from './index'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('UtilityBarText', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap index 2444fd0bc2b7d..d5de7ab508a73 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap @@ -9,7 +9,7 @@ exports[`EmbeddedMapComponent renders correctly against snapshot 1`] = ` size="xs" > Map configuration help diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap index 6794aab205703..2eefdf767dce1 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap @@ -21,7 +21,7 @@ exports[`IndexPatternsMissingPrompt renders correctly against snapshot 1`] = ` values={ Object { "beats": diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx index c0d70754e78bd..884d5bc348d6f 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx @@ -8,11 +8,8 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../mock/ui_settings'; import { Embeddable } from './embeddable'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('Embeddable', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx index 6387de30aa265..aa247b69eb4eb 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx @@ -8,12 +8,9 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../mock/ui_settings'; import { TestProviders } from '../../mock'; import { EmbeddableHeader } from './embeddable_header'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('EmbeddableHeader', () => { test('it renders', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx index 1ed1075542f71..007916595fd6a 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx @@ -7,10 +7,10 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; + +import { useIndexPatterns } from '../../hooks/use_index_patterns'; import { EmbeddedMapComponent } from './embedded_map'; import { SetQuery } from './types'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; -import { useIndexPatterns } from '../../hooks/use_index_patterns'; jest.mock('../search_bar', () => ({ siemFilterManager: { @@ -22,22 +22,7 @@ const mockUseIndexPatterns = useIndexPatterns as jest.Mock; jest.mock('../../hooks/use_index_patterns'); mockUseIndexPatterns.mockImplementation(() => [true, []]); -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: { - get$: () => 'world', - }, - injectedMetadata: { - getKibanaVersion: () => '8.0.0', - }, -})); - -jest.mock('../../lib/compose/kibana_plugins'); - -jest.mock('ui/vis/lib/timezone', () => ({ - timezoneProvider: () => () => 'America/New_York', -})); +jest.mock('../../lib/kibana'); describe('EmbeddedMapComponent', () => { let setQuery: SetQuery; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index e33539af0bfe8..b39d43cc01b42 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -8,16 +8,12 @@ import { EuiLink, EuiText } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; import { createPortalNode, InPortal } from 'react-reverse-portal'; import styled, { css } from 'styled-components'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import { EmbeddablePanel } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { getIndexPatternTitleIdMapping } from '../../hooks/api/helpers'; import { useIndexPatterns } from '../../hooks/use_index_patterns'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; -import { useKibanaPlugins } from '../../lib/compose/kibana_plugins'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; import { Loader } from '../loader'; import { useStateToaster } from '../toasters'; import { Embeddable } from './embeddable'; @@ -28,6 +24,7 @@ import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; import { MapEmbeddable, SetQuery } from './types'; import { Query, esFilters } from '../../../../../../../src/plugins/data/public'; +import { useKibana, useUiSetting$ } from '../../lib/kibana'; import { SavedObjectFinderProps, SavedObjectFinderUi, @@ -96,7 +93,7 @@ export const EmbeddedMapComponent = ({ const [, dispatchToaster] = useStateToaster(); const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns(); - const [siemDefaultIndices] = useKibanaUiSetting(DEFAULT_INDEX_KEY); + const [siemDefaultIndices] = useUiSetting$(DEFAULT_INDEX_KEY); // This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our // own component tree instead of the embeddables (default). This is necessary to have access to @@ -104,8 +101,7 @@ export const EmbeddedMapComponent = ({ // Search InPortal/OutPortal for implementation touch points const portalNode = React.useMemo(() => createPortalNode(), []); - const plugins = useKibanaPlugins(); - const core = useKibanaCore(); + const { services } = useKibana(); // Initial Load useEffect useEffect(() => { @@ -131,7 +127,7 @@ export const EmbeddedMapComponent = ({ endDate, setQuery, portalNode, - plugins.embeddable + services.embeddable ); if (isSubscribed) { setEmbeddable(embeddableObject); @@ -180,7 +176,11 @@ export const EmbeddedMapComponent = ({ }, [startDate, endDate]); const SavedObjectFinder = (props: SavedObjectFinderProps) => ( - + ); return isError ? null : ( @@ -188,7 +188,7 @@ export const EmbeddedMapComponent = ({ {i18n.EMBEDDABLE_HEADER_HELP} @@ -205,12 +205,12 @@ export const EmbeddedMapComponent = ({ ) : !isLoading && isIndexError ? ( diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx index b4b2b98ddb8d6..4e5fcee439827 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx @@ -9,7 +9,6 @@ import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; import { createPortalNode } from 'react-reverse-portal'; jest.mock('ui/new_platform'); -jest.mock('../../lib/settings/use_kibana_ui_setting'); jest.mock('uuid', () => { return { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx index d32b62900431c..d04329edff475 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx @@ -7,12 +7,10 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; + import { IndexPatternsMissingPromptComponent } from './index_patterns_missing_prompt'; -jest.mock('ui/documentation_links', () => ({ - ELASTIC_WEBSITE_URL: 'https://www.elastic.co', - DOC_LINK_VERSION: 'current', -})); +jest.mock('../../lib/kibana'); describe('IndexPatternsMissingPrompt', () => { test('renders correctly against snapshot', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx index 6533be49c3430..798e3d2c10f97 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx @@ -8,66 +8,70 @@ import { EuiButton, EuiCode, EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import * as React from 'react'; import chrome from 'ui/chrome'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { useKibana } from '../../lib/kibana'; import * as i18n from './translations'; -export const IndexPatternsMissingPromptComponent = () => ( - {i18n.ERROR_TITLE}} - titleSize="xs" - body={ - <> -

- - {'siem:defaultIndex'} - - ), - beats: ( - - {'beats'} - - ), - setup: {'setup'}, - example: {'./packetbeat setup'}, - }} - /> -

+export const IndexPatternsMissingPromptComponent = () => { + const docLinks = useKibana().services.docLinks; -

- -

- - } - actions={ - - {i18n.ERROR_BUTTON} - - } - /> -); + return ( + {i18n.ERROR_TITLE}} + titleSize="xs" + body={ + <> +

+ + {'siem:defaultIndex'} + + ), + beats: ( + + {'beats'} + + ), + setup: {'setup'}, + example: {'./packetbeat setup'}, + }} + /> +

+ +

+ +

+ + } + actions={ + + {i18n.ERROR_BUTTON} + + } + /> + ); +}; IndexPatternsMissingPromptComponent.displayName = 'IndexPatternsMissingPromptComponent'; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx index f1e96392d6afc..d97da7797bb45 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx @@ -16,8 +16,6 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { defaultHeaders } from '../../mock/header'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('EventDetails', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx index 2c28ab8696f0e..25f95bfa1d383 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx @@ -14,7 +14,7 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { defaultHeaders } from '../../mock/header'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../lib/kibana'); describe('EventFieldsBrowser', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx index 5ddf17bd77d74..b44d83c27a60d 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx @@ -8,25 +8,17 @@ import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { mockIndexPattern, TestProviders } from '../../mock'; -import { mockUiSettings } from '../../mock/ui_settings'; import { wait } from '../../lib/helpers'; import { mockEventViewerResponse } from './mock'; import { StatefulEventsViewer } from '.'; import { defaultHeaders } from './default_headers'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns'; import { mockBrowserFields } from '../../containers/source/mock'; import { eventsDefaultModel } from './default_model'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); +jest.mock('../../lib/kibana'); const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock; jest.mock('../../containers/detection_engine/rules/fetch_index_patterns'); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx index 9878194a17826..aaf88e68684ca 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx @@ -5,14 +5,14 @@ */ import { EuiPanel } from '@elastic/eui'; -import { getOr, isEmpty, isEqual } from 'lodash/fp'; -import React from 'react'; +import { getOr, isEmpty, isEqual, union } from 'lodash/fp'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; import { TimelineQuery } from '../../containers/timeline'; import { Direction } from '../../graphql/types'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; +import { useKibana } from '../../lib/kibana'; import { KqlMode } from '../../store/timeline/model'; import { AutoSizer } from '../auto_sizer'; import { HeaderSection } from '../header_section'; @@ -46,6 +46,7 @@ interface Props { browserFields: BrowserFields; columns: ColumnHeader[]; dataProviders: DataProvider[]; + deletedEventIds: Readonly; end: number; filters: esFilters.Filter[]; headerFilterGroup?: React.ReactNode; @@ -71,6 +72,7 @@ export const EventsViewer = React.memo( browserFields, columns, dataProviders, + deletedEventIds, end, filters, headerFilterGroup, @@ -91,9 +93,9 @@ export const EventsViewer = React.memo( utilityBar, }) => { const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; - const core = useKibanaCore(); + const kibana = useKibana(); const combinedQueries = combineQueries({ - config: esQuery.getEsQueryConfig(core.uiSettings), + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), dataProviders, indexPattern, browserFields, @@ -104,6 +106,14 @@ export const EventsViewer = React.memo( end, isEventViewer: true, }); + const queryFields = useMemo( + () => + union( + columnsHeader.map(c => c.id), + timelineTypeContext.queryFields ?? [] + ), + [columnsHeader, timelineTypeContext.queryFields] + ); return ( @@ -119,7 +129,7 @@ export const EventsViewer = React.memo( {combinedQueries != null ? ( c.id)} + fields={queryFields} filterQuery={combinedQueries.filterQuery} id={id} indexPattern={indexPattern} @@ -139,73 +149,81 @@ export const EventsViewer = React.memo( pageInfo, refetch, totalCount = 0, - }) => ( - <> - - {headerFilterGroup} - + }) => { + const totalCountMinusDeleted = + totalCount > 0 ? totalCount - deletedEventIds.length : 0; + + // TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt) + return ( + <> + + {headerFilterGroup} + - {utilityBar?.(totalCount)} + {utilityBar?.(totalCountMinusDeleted)} -
- - + width={width} + type={timelineTypeContext} + > + - + !deletedEventIds.includes(e._id))} + id={id} + isEventViewer={true} + height={height} + sort={sort} + toggleColumn={toggleColumn} + /> -
- -
- - )} +
+ +
+ + ); + }} ) : null} @@ -218,6 +236,7 @@ export const EventsViewer = React.memo( prevProps.browserFields === nextProps.browserFields && prevProps.columns === nextProps.columns && prevProps.dataProviders === nextProps.dataProviders && + prevProps.deletedEventIds === nextProps.deletedEventIds && prevProps.end === nextProps.end && isEqual(prevProps.filters, nextProps.filters) && prevProps.height === nextProps.height && @@ -230,6 +249,8 @@ export const EventsViewer = React.memo( isEqual(prevProps.query, nextProps.query) && prevProps.showInspect === nextProps.showInspect && prevProps.start === nextProps.start && - prevProps.sort === nextProps.sort + prevProps.sort === nextProps.sort && + isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && + prevProps.utilityBar === nextProps.utilityBar ); EventsViewer.displayName = 'EventsViewer'; diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx index e46153c18c2b5..27c3abf7f6824 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx @@ -7,10 +7,8 @@ import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; import { wait } from '../../lib/helpers'; import { mockIndexPattern, TestProviders } from '../../mock'; -import { mockUiSettings } from '../../mock/ui_settings'; import { useMountAppended } from '../../utils/use_mount_appended'; import { mockEventViewerResponse } from './mock'; @@ -19,13 +17,7 @@ import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/f import { mockBrowserFields } from '../../containers/source/mock'; import { eventsDefaultModel } from './default_model'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); +jest.mock('../../lib/kibana'); const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock; jest.mock('../../containers/detection_engine/rules/fetch_index_patterns'); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx index b614776cd90cf..9b8ec243d5f38 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx @@ -5,10 +5,9 @@ */ import { isEqual } from 'lodash/fp'; -import React, { useCallback, useEffect, useState, useMemo } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; -import chrome from 'ui/chrome'; import { inputsModel, inputsSelectors, State, timelineSelectors } from '../../store'; import { inputsActions, timelineActions } from '../../store/actions'; import { KqlMode, SubsetTimelineModel, TimelineModel } from '../../store/timeline/model'; @@ -18,15 +17,16 @@ import { Sort } from '../timeline/body/sort'; import { OnChangeItemsPerPage } from '../timeline/events'; import { esFilters, Query } from '../../../../../../../src/plugins/data/public'; +import { useUiSetting } from '../../lib/kibana'; import { EventsViewer } from './events_viewer'; import { InputsModelId } from '../../store/inputs/constants'; import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns'; import { TimelineTypeContextProps } from '../timeline/timeline_context'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; +import * as i18n from './translations'; export interface OwnProps { defaultIndices?: string[]; - defaultFilters?: esFilters.Filter[]; defaultModel: SubsetTimelineModel; end: number; id: string; @@ -38,7 +38,6 @@ export interface OwnProps { } interface StateReduxProps { - activePage?: number; columns: ColumnHeader[]; dataProviders?: DataProvider[]; filters: esFilters.Filter[]; @@ -46,9 +45,12 @@ interface StateReduxProps { itemsPerPage?: number; itemsPerPageOptions?: number[]; kqlMode: KqlMode; + deletedEventIds: Readonly; query: Query; pageCount?: number; sort?: Sort; + showCheckboxes: boolean; + showRowRenderers: boolean; } interface DispatchProps { @@ -57,6 +59,8 @@ interface DispatchProps { columns: ColumnHeader[]; itemsPerPage?: number; sort?: Sort; + showCheckboxes?: boolean; + showRowRenderers?: boolean; }>; deleteEventQuery: ActionCreator<{ id: string; @@ -84,8 +88,8 @@ const StatefulEventsViewerComponent = React.memo( createTimeline, columns, dataProviders, - defaultFilters = [], defaultModel, + deletedEventIds, defaultIndices, deleteEventQuery, end, @@ -96,13 +100,15 @@ const StatefulEventsViewerComponent = React.memo( itemsPerPage, itemsPerPageOptions, kqlMode, + pageFilters = [], query, removeColumn, start, + showCheckboxes, + showRowRenderers, sort, timelineTypeContext = { - showCheckboxes: false, - showRowRenderers: true, + loadingText: i18n.LOADING_EVENTS, }, updateItemsPerPage, upsertColumn, @@ -110,12 +116,12 @@ const StatefulEventsViewerComponent = React.memo( }) => { const [showInspect, setShowInspect] = useState(false); const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( - defaultIndices ?? chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY) + defaultIndices ?? useUiSetting(DEFAULT_INDEX_KEY) ); useEffect(() => { if (createTimeline != null) { - createTimeline({ id, columns, sort, itemsPerPage }); + createTimeline({ id, columns, sort, itemsPerPage, showCheckboxes, showRowRenderers }); } return () => { deleteEventQuery({ id, inputId: 'global' }); @@ -151,7 +157,7 @@ const StatefulEventsViewerComponent = React.memo( const handleOnMouseEnter = useCallback(() => setShowInspect(true), []); const handleOnMouseLeave = useCallback(() => setShowInspect(false), []); - const eventsFilter = useMemo(() => [...filters], [defaultFilters]); + return (
( columns={columns} id={id} dataProviders={dataProviders!} + deletedEventIds={deletedEventIds} end={end} - filters={eventsFilter} + filters={filters} headerFilterGroup={headerFilterGroup} indexPattern={indexPatterns ?? { fields: [], title: '' }} isLive={isLive} @@ -181,9 +188,9 @@ const StatefulEventsViewerComponent = React.memo( }, (prevProps, nextProps) => prevProps.id === nextProps.id && - prevProps.activePage === nextProps.activePage && isEqual(prevProps.columns, nextProps.columns) && isEqual(prevProps.dataProviders, nextProps.dataProviders) && + prevProps.deletedEventIds === nextProps.deletedEventIds && prevProps.end === nextProps.end && isEqual(prevProps.filters, nextProps.filters) && prevProps.isLive === nextProps.isLive && @@ -194,7 +201,12 @@ const StatefulEventsViewerComponent = React.memo( prevProps.pageCount === nextProps.pageCount && isEqual(prevProps.sort, nextProps.sort) && prevProps.start === nextProps.start && - isEqual(prevProps.defaultFilters, nextProps.defaultFilters) + isEqual(prevProps.pageFilters, nextProps.pageFilters) && + prevProps.showCheckboxes === nextProps.showCheckboxes && + prevProps.showRowRenderers === nextProps.showRowRenderers && + prevProps.start === nextProps.start && + isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && + prevProps.utilityBar === nextProps.utilityBar ); StatefulEventsViewerComponent.displayName = 'StatefulEventsViewerComponent'; @@ -204,15 +216,26 @@ const makeMapStateToProps = () => { const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); const getEvents = timelineSelectors.getEventsByIdSelector(); - const mapStateToProps = (state: State, { id, defaultFilters = [], defaultModel }: OwnProps) => { + const mapStateToProps = (state: State, { id, pageFilters = [], defaultModel }: OwnProps) => { const input: inputsModel.InputsRange = getInputsTimeline(state); const events: TimelineModel = getEvents(state, id) ?? defaultModel; - const { columns, dataProviders, itemsPerPage, itemsPerPageOptions, kqlMode, sort } = events; + const { + columns, + dataProviders, + deletedEventIds, + itemsPerPage, + itemsPerPageOptions, + kqlMode, + sort, + showCheckboxes, + showRowRenderers, + } = events; return { columns, dataProviders, - filters: [...getGlobalFiltersQuerySelector(state), ...defaultFilters], + deletedEventIds, + filters: [...getGlobalFiltersQuerySelector(state), ...pageFilters], id, isLive: input.policy.kind === 'interval', itemsPerPage, @@ -220,6 +243,8 @@ const makeMapStateToProps = () => { kqlMode, query: getGlobalQuerySelector(state), sort, + showCheckboxes, + showRowRenderers, }; }; return mapStateToProps; @@ -231,5 +256,4 @@ export const StatefulEventsViewer = connect(makeMapStateToProps, { updateItemsPerPage: timelineActions.updateItemsPerPage, removeColumn: timelineActions.removeColumn, upsertColumn: timelineActions.upsertColumn, - setSearchBarFilter: inputsActions.setSearchBarFilter, })(StatefulEventsViewerComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts b/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts index f1a844f84ada1..6e6be02a6085d 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/translations.ts @@ -14,6 +14,13 @@ export const EVENTS = i18n.translate('xpack.siem.eventsViewer.eventsLabel', { defaultMessage: 'Events', }); +export const LOADING_EVENTS = i18n.translate( + 'xpack.siem.eventsViewer.footer.loadingEventsDataLabel', + { + defaultMessage: 'Loading Events', + } +); + export const UNIT = (totalCount: number) => i18n.translate('xpack.siem.eventsViewer.unit', { values: { totalCount }, diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx index 86a8952a10efa..be7e8fac70bf5 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx @@ -20,8 +20,6 @@ import { FlyoutButton } from './button'; const testFlyoutHeight = 980; const usersViewing = ['elastic']; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('Flyout', () => { const state: State = mockGlobalState; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx index 66e9bc700b3a1..246261035508b 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx @@ -8,26 +8,15 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { flyoutHeaderHeight } from '../'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { TestProviders } from '../../../mock'; -import { mockUiSettings } from '../../../mock/ui_settings'; +import { flyoutHeaderHeight } from '..'; import { Pane } from '.'; const testFlyoutHeight = 980; const testWidth = 640; const usersViewing = ['elastic']; -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('ui/new_platform'); -jest.mock('../../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); - -jest.mock('ui/vis/lib/timezone', () => ({ - timezoneProvider: () => () => 'America/New_York', -})); +jest.mock('../../../lib/kibana'); describe('Pane', () => { test('renders correctly against snapshot', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx index a517820361f9f..8c27a55d3a6b0 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx @@ -8,27 +8,25 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; import { mockFrameworks, getMockKibanaUiSetting } from '../../mock'; +import { useUiSetting$ } from '../../lib/kibana'; import { PreferenceFormattedBytesComponent } from '.'; -const mockUseKibanaUiSetting: jest.Mock = useKibanaUiSetting as jest.Mock; -jest.mock('../../lib/settings/use_kibana_ui_setting', () => ({ - useKibanaUiSetting: jest.fn(), -})); +jest.mock('../../lib/kibana'); +const mockUseUiSetting$ = useUiSetting$ as jest.Mock; describe('formatted_bytes', () => { describe('PreferenceFormattedBytes', () => { describe('rendering', () => { beforeEach(() => { - mockUseKibanaUiSetting.mockClear(); + mockUseUiSetting$.mockClear(); }); const bytes = '2806422'; test('renders correctly against snapshot', () => { - mockUseKibanaUiSetting.mockImplementation( + mockUseUiSetting$.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); const wrapper = shallow(); @@ -36,13 +34,13 @@ describe('formatted_bytes', () => { }); test('it renders bytes to hardcoded format when no configuration exists', () => { - mockUseKibanaUiSetting.mockImplementation(() => [null]); + mockUseUiSetting$.mockImplementation(() => [null]); const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); test('it renders bytes according to the default format', () => { - mockUseKibanaUiSetting.mockImplementation( + mockUseUiSetting$.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); const wrapper = mount(); @@ -50,7 +48,7 @@ describe('formatted_bytes', () => { }); test('it renders bytes supplied as a number according to the default format', () => { - mockUseKibanaUiSetting.mockImplementation( + mockUseUiSetting$.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); const wrapper = mount(); @@ -58,9 +56,7 @@ describe('formatted_bytes', () => { }); test('it renders bytes according to new format', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.bytes_short) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.bytes_short)); const wrapper = mount(); expect(wrapper.text()).toEqual('3MB'); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx index 408e8d7ad4d80..003ce0879b7b5 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx @@ -8,10 +8,10 @@ import * as React from 'react'; import numeral from '@elastic/numeral'; import { DEFAULT_BYTES_FORMAT } from '../../../common/constants'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../lib/kibana'; export const PreferenceFormattedBytesComponent = ({ value }: { value: string | number }) => { - const [bytesFormat] = useKibanaUiSetting(DEFAULT_BYTES_FORMAT); + const [bytesFormat] = useUiSetting$(DEFAULT_BYTES_FORMAT); return ( <>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[0]b')} ); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx index df361a06d3805..dad1d5feb5c6e 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx @@ -9,24 +9,18 @@ import toJson from 'enzyme-to-json'; import moment from 'moment-timezone'; import * as React from 'react'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../lib/kibana'; import { mockFrameworks, TestProviders, MockFrameworks, getMockKibanaUiSetting } from '../../mock'; - -import { PreferenceFormattedDate, FormattedDate, FormattedRelativePreferenceDate } from '.'; import { getEmptyString, getEmptyValue } from '../empty_value'; +import { PreferenceFormattedDate, FormattedDate, FormattedRelativePreferenceDate } from '.'; -const mockUseKibanaUiSetting: jest.Mock = useKibanaUiSetting as jest.Mock; -jest.mock('../../lib/settings/use_kibana_ui_setting', () => ({ - useKibanaUiSetting: jest.fn(), -})); +jest.mock('../../lib/kibana'); +const mockUseUiSetting$ = useUiSetting$ as jest.Mock; describe('formatted_date', () => { describe('PreferenceFormattedDate', () => { describe('rendering', () => { - beforeEach(() => { - mockUseKibanaUiSetting.mockClear(); - }); const isoDateString = '2019-02-25T22:27:05.000Z'; const isoDate = new Date(isoDateString); const configFormattedDateString = (dateString: string, config: MockFrameworks): string => @@ -38,21 +32,19 @@ describe('formatted_date', () => { .format(config.dateFormat); test('renders correctly against snapshot', () => { - mockUseKibanaUiSetting.mockImplementation(() => [null]); + mockUseUiSetting$.mockImplementation(() => [null]); const wrapper = mount(); expect(toJson(wrapper)).toMatchSnapshot(); }); test('it renders the UTC ISO8601 date string supplied when no configuration exists', () => { - mockUseKibanaUiSetting.mockImplementation(() => [null]); + mockUseUiSetting$.mockImplementation(() => [null]); const wrapper = mount(); expect(wrapper.text()).toEqual(isoDateString); }); test('it renders the UTC ISO8601 date supplied when the default configuration exists', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount(); expect(wrapper.text()).toEqual( @@ -61,7 +53,7 @@ describe('formatted_date', () => { }); test('it renders the correct tz when the default browser configuration exists', () => { - mockUseKibanaUiSetting.mockImplementation( + mockUseUiSetting$.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); const wrapper = mount(); @@ -71,9 +63,7 @@ describe('formatted_date', () => { }); test('it renders the correct tz when a non-UTC configuration exists', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_MT) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_MT)); const wrapper = mount(); expect(wrapper.text()).toEqual( configFormattedDateString(isoDateString, mockFrameworks.default_MT) @@ -84,30 +74,20 @@ describe('formatted_date', () => { describe('FormattedDate', () => { describe('rendering', () => { - beforeEach(() => { - mockUseKibanaUiSetting.mockClear(); - }); - test('it renders against a numeric epoch', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount(); expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); }); test('it renders against a string epoch', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount(); expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); }); test('it renders against a ISO string', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount( ); @@ -115,9 +95,7 @@ describe('formatted_date', () => { }); test('it renders against an empty string as an empty string placeholder', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount( @@ -127,9 +105,7 @@ describe('formatted_date', () => { }); test('it renders against an null as a EMPTY_VALUE', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount( @@ -139,9 +115,7 @@ describe('formatted_date', () => { }); test('it renders against an undefined as a EMPTY_VALUE', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount( @@ -151,9 +125,7 @@ describe('formatted_date', () => { }); test('it renders against an invalid date time as just the string its self', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_UTC) - ); + mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); const wrapper = mount( diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx index 37bf3653f3b62..19e8ec3f95d26 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx @@ -8,20 +8,21 @@ import moment from 'moment-timezone'; import * as React from 'react'; import { FormattedRelative } from '@kbn/i18n/react'; +import { useUiSetting$ } from '../../lib/kibana'; + import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ, DEFAULT_TIMEZONE_BROWSER, } from '../../../common/constants'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; import { getOrEmptyTagFromValue } from '../empty_value'; import { LocalizedDateTooltip } from '../localized_date_tooltip'; import { getMaybeDate } from './maybe_date'; export const PreferenceFormattedDate = React.memo<{ value: Date }>(({ value }) => { - const [dateFormat] = useKibanaUiSetting(DEFAULT_DATE_FORMAT); - const [dateFormatTz] = useKibanaUiSetting(DEFAULT_DATE_FORMAT_TZ); - const [timezone] = useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER); + const [dateFormat] = useUiSetting$(DEFAULT_DATE_FORMAT); + const [dateFormatTz] = useUiSetting$(DEFAULT_DATE_FORMAT_TZ); + const [timezone] = useUiSetting$(DEFAULT_TIMEZONE_BROWSER); return ( <> diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx index b3eb599af9407..a45bed87829bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -9,10 +9,9 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import '../../mock/match_media'; -import '../../mock/ui_settings'; import { HeaderGlobal } from './index'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); +jest.mock('ui/new_platform'); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx index 5644a344f91d6..633ff90524de6 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx @@ -10,12 +10,9 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; -import '../../mock/ui_settings'; import { HeaderPage } from './index'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('HeaderPage', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx index 8606758c68d2c..fbd8642c01fac 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx @@ -10,11 +10,8 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; -import '../../mock/ui_settings'; import { HeaderSection } from './index'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('HeaderSection', () => { test('it renders', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx index e2ada4682fdec..dcecc636d9f0f 100644 --- a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx @@ -13,7 +13,6 @@ import { mockLastEventTimeQuery } from '../../containers/events/last_event_time/ import { useMountAppended } from '../../utils/use_mount_appended'; import { useLastEventTimeQuery } from '../../containers/events/last_event_time'; import { TestProviders } from '../../mock'; -import '../../mock/ui_settings'; import { LastEventTime } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx index 7f9133a0de7c0..87761a51a431f 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx @@ -9,11 +9,8 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; -import '../../mock/ui_settings'; import { LinkIcon } from './index'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('LinkIcon', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx index bdd8a0c544ed8..87d4e072e4299 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx @@ -9,12 +9,7 @@ import * as React from 'react'; import { MatrixHistogram } from '.'; -jest.mock('@elastic/eui', () => { - return { - EuiPanel: (children: JSX.Element) => <>{children}, - EuiLoadingContent: () =>
, - }; -}); +jest.mock('../../lib/kibana'); jest.mock('../loader', () => { return { @@ -22,10 +17,6 @@ jest.mock('../loader', () => { }; }); -jest.mock('../../lib/settings/use_kibana_ui_setting', () => { - return { useKibanaUiSetting: () => [false] }; -}); - jest.mock('../header_section', () => { return { HeaderSection: () =>
, diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx index e1ccfd79a89a0..c29b5282e13af 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx @@ -14,7 +14,7 @@ import { BarChart } from '../charts/barchart'; import { HeaderSection } from '../header_section'; import { ChartSeriesData } from '../charts/common'; import { DEFAULT_DARK_MODE } from '../../../common/constants'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../lib/kibana'; import { Loader } from '../loader'; import { Panel } from '../panel'; import { getBarchartConfigs, getCustomChartData } from './utils'; @@ -45,7 +45,7 @@ export const MatrixHistogram = ({ showLegend, }); const [showInspect, setShowInspect] = useState(false); - const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE); + const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); const [loadingInitial, setLoadingInitial] = useState(false); const barChartData: ChartSeriesData[] = getCustomChartData(data, mapping); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts index 130c326339b1b..bce99c943c7a5 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts @@ -14,7 +14,7 @@ import { useStateToaster } from '../../toasters'; import { errorToToaster } from '../api/error_to_toaster'; import * as i18n from './translations'; -import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_ANOMALY_SCORE, DEFAULT_TIMEZONE_BROWSER, @@ -67,9 +67,9 @@ export const useAnomaliesTableData = ({ const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); const [, dispatchToaster] = useStateToaster(); - const [timezone] = useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER); - const [anomalyScore] = useKibanaUiSetting(DEFAULT_ANOMALY_SCORE); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [timezone] = useUiSetting$(DEFAULT_TIMEZONE_BROWSER); + const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const siemJobIds = siemJobs.filter(job => job.isInstalled).map(job => job.id); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx index 352f39a75ded1..b8d6908df464e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx @@ -11,7 +11,7 @@ import { getMlCapabilities } from '../api/get_ml_capabilities'; import { emptyMlCapabilities } from '../empty_ml_capabilities'; import { errorToToaster } from '../api/error_to_toaster'; import { useStateToaster } from '../../toasters'; -import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import * as i18n from './translations'; @@ -36,7 +36,7 @@ export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ c emptyMlCapabilitiesProvider ); const [, dispatchToaster] = useStateToaster(); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx index f7d9052b19a2f..cf24d6c02a138 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx @@ -17,8 +17,6 @@ import { Anomalies } from '../types'; const endDate: number = new Date('3000-01-01T00:00:00.000Z').valueOf(); const narrowDateRange = jest.fn(); -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('anomaly_scores', () => { let anomalies: Anomalies = cloneDeep(mockAnomalies); const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx index f01df38138456..759e84e36f4ac 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx @@ -18,8 +18,6 @@ import { useMountAppended } from '../../../utils/use_mount_appended'; const endDate: number = new Date('3000-01-01T00:00:00.000Z').valueOf(); const narrowDateRange = jest.fn(); -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('anomaly_scores', () => { let anomalies: Anomalies = cloneDeep(mockAnomalies); const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx index d55c6972d5cac..f00fb62d74ac3 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx @@ -12,7 +12,7 @@ import { createDescriptionList } from './create_description_list'; import { EuiDescriptionList } from '@elastic/eui'; import { Anomaly } from '../types'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); const endDate: number = new Date('3000-01-01T00:00:00.000Z').valueOf(); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx index 0bef34a7edc44..f9d110d711d07 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx @@ -12,7 +12,7 @@ import { hasMlUserPermissions } from '../../ml/permissions/has_ml_user_permissio import { MlCapabilitiesContext } from '../../ml/permissions/ml_capabilities_provider'; import { useStateToaster } from '../../toasters'; import { errorToToaster } from '../../ml/api/error_to_toaster'; -import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_INDEX_KEY, DEFAULT_KBN_VERSION } from '../../../../common/constants'; import * as i18n from './translations'; @@ -33,8 +33,8 @@ export const useSiemJobs = (refetchData: boolean): Return => { const [loading, setLoading] = useState(true); const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); - const [siemDefaultIndex] = useKibanaUiSetting(DEFAULT_INDEX_KEY); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [siemDefaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx index 1a8360fe82c58..987c63be3f7be 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx @@ -9,7 +9,8 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { MlPopover } from './ml_popover'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); +jest.mock('ui/new_platform'); +jest.mock('../../lib/kibana'); jest.mock('../ml/permissions/has_ml_admin_permissions', () => ({ hasMlAdminPermissions: () => true, diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx index 0b33ab83d2a47..c34ed51d22994 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx @@ -9,10 +9,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; import React, { useContext, useReducer, useState } from 'react'; import styled from 'styled-components'; -import { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } from 'ui/documentation_links'; import { DEFAULT_KBN_VERSION } from '../../../common/constants'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; +import { useKibana, useUiSetting$ } from '../../lib/kibana'; import { METRIC_TYPE, TELEMETRY_EVENT, trackUiAction as track } from '../../lib/track_usage'; import { errorToToaster } from '../ml/api/error_to_toaster'; import { hasMlAdminPermissions } from '../ml/permissions/has_ml_admin_permissions'; @@ -98,10 +97,11 @@ export const MlPopover = React.memo(() => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [filterProperties, setFilterProperties] = useState(defaultFilterProps); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [isLoadingSiemJobs, siemJobs] = useSiemJobs(refreshToggle); const [, dispatchToaster] = useStateToaster(); const capabilities = useContext(MlCapabilitiesContext); + const docLinks = useKibana().services.docLinks; // Enable/Disable Job & Datafeed -- passed to JobsTable for use as callback on JobSwitch const enableDatafeed = async (job: SiemJob, latestTimestampMs: number, enable: boolean) => { @@ -226,7 +226,7 @@ export const MlPopover = React.memo(() => { values={{ mlDocs: ( diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts index 88481e140cb03..f8794c1963961 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts @@ -13,7 +13,7 @@ export interface Group { } export interface CheckRecognizerProps { - indexPatternName: string; + indexPatternName: string[]; kbnVersion: string; signal: AbortSignal; } diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx index 2d8c201e41462..22531983b2399 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx @@ -57,8 +57,6 @@ import { } from '../source_destination/field_names'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - const getNetflowInstance = () => ( { start: 0, }, description: '', + deletedEventIds: [], eventIdToNoteIds: {}, filters: [], highlightedDropAndProviderId: '', @@ -241,6 +242,7 @@ describe('helpers', () => { id: 'savedObject-1', isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, isSaving: false, itemsPerPage: 25, @@ -250,11 +252,15 @@ describe('helpers', () => { filterQuery: null, filterQueryDraft: null, }, + loadingEventIds: [], noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, savedObjectId: 'savedObject-1', + selectedEventIds: {}, show: false, + showCheckboxes: false, + showRowRenderers: true, sort: { columnId: '@timestamp', sortDirection: 'desc', @@ -321,6 +327,7 @@ describe('helpers', () => { start: 0, }, description: '', + deletedEventIds: [], eventIdToNoteIds: {}, filters: [], highlightedDropAndProviderId: '', @@ -328,6 +335,7 @@ describe('helpers', () => { id: 'savedObject-1', isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, isSaving: false, itemsPerPage: 25, @@ -337,11 +345,15 @@ describe('helpers', () => { filterQuery: null, filterQueryDraft: null, }, + loadingEventIds: [], noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, savedObjectId: 'savedObject-1', + selectedEventIds: {}, show: false, + showCheckboxes: false, + showRowRenderers: true, sort: { columnId: '@timestamp', sortDirection: 'desc', @@ -401,12 +413,14 @@ describe('helpers', () => { version: '1', dataProviders: [], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, filters: [], highlightedDropAndProviderId: '', historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, isSaving: false, itemsPerPage: 25, @@ -416,6 +430,7 @@ describe('helpers', () => { filterQuery: null, filterQueryDraft: null, }, + loadingEventIds: [], title: '', noteIds: [], pinnedEventIds: {}, @@ -424,7 +439,10 @@ describe('helpers', () => { start: 0, end: 0, }, + selectedEventIds: {}, show: false, + showCheckboxes: false, + showRowRenderers: true, sort: { columnId: '@timestamp', sortDirection: 'desc', @@ -516,6 +534,7 @@ describe('helpers', () => { version: '1', dataProviders: [], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, filters: [ { @@ -565,6 +584,7 @@ describe('helpers', () => { historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, isSaving: false, itemsPerPage: 25, @@ -574,6 +594,7 @@ describe('helpers', () => { filterQuery: null, filterQueryDraft: null, }, + loadingEventIds: [], title: '', noteIds: [], pinnedEventIds: {}, @@ -582,7 +603,10 @@ describe('helpers', () => { start: 0, end: 0, }, + selectedEventIds: {}, show: false, + showCheckboxes: false, + showRowRenderers: true, sort: { columnId: '@timestamp', sortDirection: 'desc', diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx index 0f5aafe661d0e..c9f52d9d204ae 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx @@ -19,7 +19,7 @@ import { StatefulOpenTimeline } from '.'; import { NotePreviews } from './note_previews'; import { OPEN_TIMELINE_CLASS_NAME } from './helpers'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../lib/kibana'); describe('StatefulOpenTimeline', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx index 690e8877b5019..dbc7199aac725 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx @@ -17,7 +17,7 @@ import { mockTimelineResults } from '../../mock/timeline_results'; import { OpenTimeline } from './open_timeline'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from './constants'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../lib/kibana'); describe('OpenTimeline', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx index 03383a6cb6612..e3dc6d974b5e0 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx @@ -16,7 +16,7 @@ import { mockOpenTimelineQueryResults } from '../../../mock/timeline_results'; import { OpenTimelineModal } from '.'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); jest.mock('../../../utils/apollo_context', () => ({ useApolloClient: () => ({}), })); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx index 4237caf8f3c51..a5abb42c2e3b6 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx @@ -17,7 +17,7 @@ import { mockTimelineResults } from '../../../mock/timeline_results'; import { OpenTimelineModalBody } from './open_timeline_modal_body'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); describe('OpenTimelineModal', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx index a5e436c73f93b..9a70fd476e89e 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx @@ -17,8 +17,6 @@ import * as i18n from '../translations'; import { OpenTimelineModalButton } from './open_timeline_modal_button'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - describe('OpenTimelineModalButton', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx index 30a02a974ba92..749ba8672abea 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx @@ -17,7 +17,7 @@ import { OpenTimelineResult } from '../types'; import { TimelinesTable } from '.'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); describe('#getActionsColumns', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx index ae68019c2fe69..fa08df1df4785 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx @@ -21,7 +21,7 @@ import { TimelinesTable } from '.'; import * as i18n from '../translations'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); describe('#getCommonColumns', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx index bf5adc8aca533..13362e0f43a28 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx @@ -20,7 +20,7 @@ import { TimelinesTable } from '.'; import * as i18n from '../translations'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); describe('#getExtendedColumns', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx index e5047662eef67..b6048b85eea75 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx @@ -16,7 +16,7 @@ import { TimelinesTable } from '.'; import { OpenTimelineResult } from '../types'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); describe('#getActionsColumns', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx index 6cf56ad6a770f..d75863d1ccb8b 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx @@ -18,7 +18,7 @@ import { TimelinesTable, TimelinesTableProps } from '.'; import * as i18n from '../translations'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../lib/kibana'); describe('TimelinesTable', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx index c0d0feca5d53c..ad1d80a761854 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx @@ -8,12 +8,9 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../../../mock/ui_settings'; import { TestProviders } from '../../../../mock'; import { HistogramSignals } from './index'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); - describe('HistogramSignals', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx index 6c3ab04849236..35c1eded18f15 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx @@ -12,26 +12,25 @@ import { render, act } from '@testing-library/react'; import { mockFirstLastSeenHostQuery } from '../../../../containers/hosts/first_last_seen/mock'; import { wait } from '../../../../lib/helpers'; import { TestProviders } from '../../../../mock'; -import '../../../../mock/ui_settings'; import { FirstLastSeenHost, FirstLastSeenHostType } from '.'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); - -// Suppress warnings about "react-apollo" until we migrate to apollo@3 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); +jest.mock('../../../../lib/kibana'); describe('FirstLastSeen Component', () => { const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; const lastSeen = 'Apr 8, 2019 @ 18:35:45.064'; + // Suppress warnings about "react-apollo" until we migrate to apollo@3 + /* eslint-disable no-console */ + const originalError = console.error; + beforeAll(() => { + console.error = jest.fn(); + }); + afterAll(() => { + console.error = originalError; + }); + test('Loading', async () => { const { container } = render( diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx index 437d14edeb5c8..9e3f8f91d5cf7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx @@ -12,7 +12,7 @@ import React, { useContext, useState, useCallback } from 'react'; import { DEFAULT_DARK_MODE } from '../../../../../common/constants'; import { DescriptionList } from '../../../../../common/utility_types'; -import { useKibanaUiSetting } from '../../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../../lib/kibana'; import { getEmptyTagValue } from '../../../empty_value'; import { DefaultFieldRenderer, hostIdRenderer } from '../../../field_renderers/field_renderers'; import { InspectButton } from '../../../inspect'; @@ -59,7 +59,7 @@ export const HostOverview = React.memo( const [showInspect, setShowInspect] = useState(false); const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); - const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE); + const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); const getDefaultRenderer = (fieldName: string, fieldData: HostItem) => ( ({ - uiSettings: mockUiSettings, -})); - // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar jest.mock('../../../search_bar', () => ({ diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx index 0dcf81fd26310..79bdf5c4b8315 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx @@ -140,7 +140,7 @@ const HostsTableComponent = React.memo( if (criteria.sort != null) { const sort: HostsSortField = { field: getSortField(criteria.sort.field), - direction: criteria.sort.direction, + direction: criteria.sort.direction as Direction, }; if (sort.direction !== direction || sort.field !== sortField) { updateHostsSort({ diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx index 8cb55f0d0fb58..0c4e594399517 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx @@ -11,7 +11,7 @@ import React, { useContext, useState, useCallback } from 'react'; import { DEFAULT_DARK_MODE } from '../../../../../common/constants'; import { DescriptionList } from '../../../../../common/utility_types'; -import { useKibanaUiSetting } from '../../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../../lib/kibana'; import { FlowTarget, IpOverviewData, Overview } from '../../../../graphql/types'; import { networkModel } from '../../../../store'; import { getEmptyTagValue } from '../../../empty_value'; @@ -74,7 +74,7 @@ export const IpOverview = React.memo( const [showInspect, setShowInspect] = useState(false); const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); - const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE); + const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); const typeData: Overview = data[flowTarget]!; const column: DescriptionList[] = [ { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx index 0537b95ca6cf7..b88653bcadde8 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx @@ -18,8 +18,6 @@ import { useMountAppended } from '../../../../utils/use_mount_appended'; import { NetworkDnsTable } from '.'; import { mockData } from './mock'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); - describe('NetworkTopNFlow Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx index 34fad309f2346..e316f951a0363 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx @@ -10,7 +10,12 @@ import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { networkActions } from '../../../../store/actions'; -import { NetworkDnsEdges, NetworkDnsFields, NetworkDnsSortField } from '../../../../graphql/types'; +import { + Direction, + NetworkDnsEdges, + NetworkDnsFields, + NetworkDnsSortField, +} from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; import { Criteria, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; @@ -102,7 +107,7 @@ export const NetworkDnsTableComponent = React.memo( if (criteria.sort != null) { const newDnsSortField: NetworkDnsSortField = { field: criteria.sort.field.split('.')[1] as NetworkDnsFields, - direction: criteria.sort.direction, + direction: criteria.sort.direction as Direction, }; if (!isEqual(newDnsSortField, sort)) { updateNetworkTable({ diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx index 50d64817f81f8..81e0c7fad7b39 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx @@ -18,8 +18,6 @@ import { createStore, networkModel, State } from '../../../../store'; import { NetworkHttpTable } from '.'; import { mockData } from './mock'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); - describe('NetworkHttp Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx index eb4179a040431..8fd245b077243 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx @@ -24,7 +24,6 @@ import { createStore, networkModel, State } from '../../../../store'; import { NetworkTopCountriesTable } from '.'; import { mockData } from './mock'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); describe('NetworkTopCountries Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx index 8fd5cc9b3f3c0..6d14b52d3586d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.tsx @@ -141,7 +141,7 @@ const NetworkTopCountriesTableComponent = React.memo { const loadPage = jest.fn(); const state: State = mockGlobalState; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx index c38d809458163..6cf3401031fa7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.tsx @@ -116,7 +116,7 @@ const NetworkTopNFlowTableComponent = React.memo( const newSortDirection = field !== sort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click const newTopNFlowSort: NetworkTopTablesSortField = { field: field as NetworkTopTablesFields, - direction: newSortDirection, + direction: newSortDirection as Direction, }; if (!isEqual(newTopNFlowSort, sort)) { updateNetworkTable({ diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx index 4313c455a0df1..920d1cd8210e5 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx @@ -18,8 +18,6 @@ import { createStore, networkModel, State } from '../../../../store'; import { TlsTable } from '.'; import { mockTlsData } from './mock'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); - describe('Tls Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx index 026ab9537d3d7..95c0fff054440 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx @@ -11,7 +11,7 @@ import { compose } from 'redux'; import { ActionCreator } from 'typescript-fsa'; import { networkActions } from '../../../../store/network'; -import { TlsEdges, TlsSortField, TlsFields } from '../../../../graphql/types'; +import { TlsEdges, TlsSortField, TlsFields, Direction } from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; import { Criteria, ItemsPerRow, PaginatedTable, SortingBasicTable } from '../../../paginated_table'; import { getTlsColumns } from './columns'; @@ -105,7 +105,7 @@ const TlsTableComponent = React.memo( const splitField = criteria.sort.field.split('.'); const newTlsSort: TlsSortField = { field: getSortFromString(splitField[splitField.length - 1]), - direction: criteria.sort.direction, + direction: criteria.sort.direction as Direction, }; if (!isEqual(newTlsSort, sort)) { updateNetworkTable({ diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx index d6b9ec24de0aa..d01923f01543f 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx @@ -19,8 +19,6 @@ import { createStore, networkModel, State } from '../../../../store'; import { UsersTable } from '.'; import { mockUsersData } from './mock'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); - jest.mock('../../../search_bar', () => ({ siemFilterManager: { addFilters: jest.fn(), diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx index 14fbf4860f7c0..f4f14c7c009dc 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.tsx @@ -10,7 +10,13 @@ import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { networkActions } from '../../../../store/network'; -import { FlowTarget, UsersEdges, UsersFields, UsersSortField } from '../../../../graphql/types'; +import { + Direction, + FlowTarget, + UsersEdges, + UsersFields, + UsersSortField, +} from '../../../../graphql/types'; import { networkModel, networkSelectors, State } from '../../../../store'; import { Criteria, ItemsPerRow, PaginatedTable, SortingBasicTable } from '../../../paginated_table'; @@ -104,7 +110,7 @@ const UsersTableComponent = React.memo( const splitField = criteria.sort.field.split('.'); const newUsersSort: UsersSortField = { field: getSortFromString(splitField[splitField.length - 1]), - direction: criteria.sort.direction, + direction: criteria.sort.direction as Direction, }; if (!isEqual(newUsersSort, sort)) { updateNetworkTable({ diff --git a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx index d99a4201c4c72..8ecc50402cef1 100644 --- a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx @@ -8,11 +8,8 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../mock/ui_settings'; import { ProgressInline } from './index'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('ProgressInline', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx index 10b769e2a791c..e403963cbbe20 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx @@ -7,23 +7,16 @@ import { mount } from 'enzyme'; import React from 'react'; -import { FilterManager, SearchBar } from '../../../../../../../src/plugins/data/public'; -import { uiSettingsServiceMock } from '../../../../../../../src/core/public/ui_settings/ui_settings_service.mock'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; import { TestProviders, mockIndexPattern } from '../../mock'; -import { QueryBar, QueryBarComponentProps } from '.'; +import { createKibanaCoreStartMock } from '../../mock/kibana_core'; import { DEFAULT_FROM, DEFAULT_TO } from '../../../common/constants'; -import { mockUiSettings } from '../../mock/ui_settings'; +import { FilterManager, SearchBar } from '../../../../../../../src/plugins/data/public'; +import { QueryBar, QueryBarComponentProps } from '.'; +import { createKibanaContextProviderMock } from '../../mock/kibana_react'; -jest.mock('ui/new_platform'); +jest.mock('../../lib/kibana'); -const mockUseKibanaCore = useKibanaCore as jest.Mock; -const mockUiSettingsForFilterManager = uiSettingsServiceMock.createSetupContract(); -jest.mock('../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, - savedObjects: {}, -})); +const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings; describe('QueryBar ', () => { // We are doing that because we need to wrapped this component with redux @@ -196,9 +189,13 @@ describe('QueryBar ', () => { describe('#onQueryChange', () => { test(' is the only reference that changed when filterQueryDraft props get updated', () => { + const KibanaWithStorageProvider = createKibanaContextProviderMock(); + const Proxy = (props: QueryBarComponentProps) => ( - + + + ); diff --git a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx index 9f706790bec67..b2843348cc2e3 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx @@ -6,7 +6,6 @@ import { isEqual } from 'lodash/fp'; import React, { memo, useState, useEffect, useMemo, useCallback } from 'react'; -import { IndexPattern } from 'ui/index_patterns'; import { esFilters, @@ -118,7 +117,7 @@ export const QueryBar = memo( ); const CustomButton = <>{null}; - const indexPatterns = useMemo(() => [indexPattern as IndexPattern], [indexPattern]); + const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); const searchBarProps = savedQuery != null ? { savedQuery } : {}; diff --git a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx index 3d02cff7b72e8..089bade4a4144 100644 --- a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx @@ -10,7 +10,6 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; -import { IndexPattern } from 'ui/index_patterns'; import { IIndexPattern } from 'src/plugins/data/public'; import { SavedQuery } from 'src/legacy/core_plugins/data/public'; @@ -254,7 +253,7 @@ const SearchBarComponent = memo [indexPattern as IndexPattern], [indexPattern]); + const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); return ( ( { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx index b54f3133de472..3424c05f32d63 100644 --- a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx @@ -8,12 +8,9 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import React from 'react'; -import '../../mock/ui_settings'; import { TestProviders } from '../../mock'; import { Subtitle } from './index'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('Subtitle', () => { test('it renders', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx index 011e2ddcdbde7..013104da7c612 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx @@ -8,13 +8,69 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; +import { useUiSetting$ } from '../../lib/kibana'; import { apolloClientObservable, mockGlobalState } from '../../mock'; +import { createUseUiSetting$Mock } from '../../mock/kibana_react'; import { createStore, State } from '../../store'; import { SuperDatePicker, makeMapStateToProps } from '.'; import { cloneDeep } from 'lodash/fp'; - -jest.mock('../../lib/settings/use_kibana_ui_setting'); +import { DEFAULT_TIMEPICKER_QUICK_RANGES } from '../../../common/constants'; + +jest.mock('../../lib/kibana'); +const mockUseUiSetting$ = useUiSetting$ as jest.Mock; +const timepickerRanges = [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, +]; describe('SIEM Super Date Picker', () => { describe('#SuperDatePicker', () => { @@ -24,6 +80,13 @@ describe('SIEM Super Date Picker', () => { beforeEach(() => { jest.clearAllMocks(); store = createStore(state, apolloClientObservable); + mockUseUiSetting$.mockImplementation((key, defaultValue) => { + const useUiSetting$Mock = createUseUiSetting$Mock(); + + return key === DEFAULT_TIMEPICKER_QUICK_RANGES + ? [timepickerRanges, jest.fn()] + : useUiSetting$Mock(key, defaultValue); + }); }); describe('Pick Relative Date', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx index a2e190da0f7bc..0877906c721ce 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx @@ -18,7 +18,7 @@ import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { DEFAULT_TIMEPICKER_QUICK_RANGES } from '../../../common/constants'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../lib/kibana'; import { inputsModel, State } from '../../store'; import { inputsActions, timelineActions } from '../../store/actions'; import { InputsModelId } from '../../store/inputs/constants'; @@ -38,6 +38,12 @@ import { InputsRange, Policy } from '../../store/inputs/model'; const MAX_RECENTLY_USED_RANGES = 9; +interface Range { + from: string; + to: string; + display: string; +} + interface SuperDatePickerStateRedux { duration: number; end: number; @@ -196,10 +202,10 @@ export const SuperDatePickerComponent = React.memo( const endDate = kind === 'relative' ? toStr : new Date(end).toISOString(); const startDate = kind === 'relative' ? fromStr : new Date(start).toISOString(); - const [quickRanges] = useKibanaUiSetting(DEFAULT_TIMEPICKER_QUICK_RANGES); + const [quickRanges] = useUiSetting$(DEFAULT_TIMEPICKER_QUICK_RANGES); const commonlyUsedRanges = isEmpty(quickRanges) ? [] - : quickRanges.map(({ from, to, display }: { from: string; to: string; display: string }) => ({ + : quickRanges.map(({ from, to, display }) => ({ start: from, end: to, label: display, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx index 12141d0b5555b..a9628ebbd183f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx @@ -24,9 +24,11 @@ describe('Actions', () => { eventIsPinned={false} getNotesByIds={jest.fn()} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={jest.fn()} onPinClicked={jest.fn()} + onRowSelected={jest.fn()} showCheckboxes={true} showNotes={false} toggleShowNotes={jest.fn()} @@ -50,9 +52,11 @@ describe('Actions', () => { eventIsPinned={false} getNotesByIds={jest.fn()} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={jest.fn()} onPinClicked={jest.fn()} + onRowSelected={jest.fn()} showCheckboxes={false} showNotes={false} toggleShowNotes={jest.fn()} @@ -76,9 +80,11 @@ describe('Actions', () => { eventIsPinned={false} getNotesByIds={jest.fn()} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={jest.fn()} onPinClicked={jest.fn()} + onRowSelected={jest.fn()} showCheckboxes={false} showNotes={false} toggleShowNotes={jest.fn()} @@ -104,9 +110,11 @@ describe('Actions', () => { eventIsPinned={false} getNotesByIds={jest.fn()} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={onEventToggled} onPinClicked={jest.fn()} + onRowSelected={jest.fn()} showCheckboxes={false} showNotes={false} toggleShowNotes={jest.fn()} @@ -138,9 +146,11 @@ describe('Actions', () => { getNotesByIds={jest.fn()} isEventViewer={true} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={jest.fn()} onPinClicked={jest.fn()} + onRowSelected={jest.fn()} showCheckboxes={false} showNotes={false} toggleShowNotes={toggleShowNotes} @@ -166,9 +176,11 @@ describe('Actions', () => { eventIsPinned={false} getNotesByIds={jest.fn()} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={jest.fn()} onPinClicked={jest.fn()} + onRowSelected={jest.fn()} showCheckboxes={false} showNotes={false} toggleShowNotes={toggleShowNotes} @@ -200,9 +212,11 @@ describe('Actions', () => { getNotesByIds={jest.fn()} isEventViewer={true} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={jest.fn()} onPinClicked={onPinClicked} + onRowSelected={jest.fn()} showCheckboxes={false} showNotes={false} toggleShowNotes={jest.fn()} @@ -228,9 +242,11 @@ describe('Actions', () => { eventIsPinned={false} getNotesByIds={jest.fn()} loading={false} + loadingEventIds={[]} noteIds={[]} onEventToggled={jest.fn()} onPinClicked={onPinClicked} + onRowSelected={jest.fn()} showCheckboxes={false} showNotes={false} toggleShowNotes={jest.fn()} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx index 2de60fdf548b1..54b1fb0893c83 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx @@ -3,8 +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 { EuiButtonIcon, EuiCheckbox, EuiToolTip } from '@elastic/eui'; -import { noop } from 'lodash/fp'; +import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; import * as React from 'react'; import { Note } from '../../../../lib/note'; @@ -14,17 +13,33 @@ import { NotesButton } from '../../properties/helpers'; import { EventsLoading, EventsTd, EventsTdContent, EventsTdGroupActions } from '../../styles'; import { eventHasNotes, getPinTooltip } from '../helpers'; import * as i18n from '../translations'; +import { OnRowSelected } from '../../events'; +import { TimelineNonEcsData } from '../../../../graphql/types'; + +export interface TimelineActionProps { + eventId: string; + data: TimelineNonEcsData[]; +} + +export interface TimelineAction { + getAction: ({ eventId, data }: TimelineActionProps) => JSX.Element; + width: number; + id: string; +} interface Props { actionsColumnWidth: number; + additionalActions?: JSX.Element[]; associateNote: AssociateNote; checked: boolean; + onRowSelected: OnRowSelected; expanded: boolean; eventId: string; eventIsPinned: boolean; getNotesByIds: (noteIds: string[]) => Note[]; isEventViewer?: boolean; loading: boolean; + loadingEventIds: Readonly; noteIds: string[]; onEventToggled: () => void; onPinClicked: () => void; @@ -39,6 +54,7 @@ const emptyNotes: string[] = []; export const Actions = React.memo( ({ actionsColumnWidth, + additionalActions, associateNote, checked, expanded, @@ -47,9 +63,11 @@ export const Actions = React.memo( getNotesByIds, isEventViewer = false, loading = false, + loadingEventIds, noteIds, onEventToggled, onPinClicked, + onRowSelected, showCheckboxes, showNotes, toggleShowNotes, @@ -62,16 +80,27 @@ export const Actions = React.memo( {showCheckboxes && ( - + {loadingEventIds.includes(eventId) ? ( + + ) : ( + ) => { + onRowSelected({ + eventIds: [eventId], + isSelected: event.currentTarget.checked, + }); + }} + /> + )} )} + <>{additionalActions} + {loading && } @@ -137,7 +166,9 @@ export const Actions = React.memo( prevProps.eventId === nextProps.eventId && prevProps.eventIsPinned === nextProps.eventIsPinned && prevProps.loading === nextProps.loading && + prevProps.loadingEventIds === nextProps.loadingEventIds && prevProps.noteIds === nextProps.noteIds && + prevProps.onRowSelected === nextProps.onRowSelected && prevProps.showCheckboxes === nextProps.showCheckboxes && prevProps.showNotes === nextProps.showNotes ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 65818b697e0b3..1b66a130c3550 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -8,6 +8,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` { actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH} browserFields={mockBrowserFields} columnHeaders={defaultHeaders} + isSelectAllChecked={false} onColumnSorted={jest.fn()} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} + onSelectAll={jest.fn} onUpdateColumns={jest.fn()} showEventsSelect={false} + showSelectAllCheckbox={false} sort={sort} timelineId={'test'} toggleColumn={jest.fn()} @@ -61,11 +64,14 @@ describe('ColumnHeaders', () => { actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH} browserFields={mockBrowserFields} columnHeaders={defaultHeaders} + isSelectAllChecked={false} onColumnSorted={jest.fn()} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} + onSelectAll={jest.fn} onUpdateColumns={jest.fn()} showEventsSelect={false} + showSelectAllCheckbox={false} sort={sort} timelineId={'test'} toggleColumn={jest.fn()} @@ -88,11 +94,14 @@ describe('ColumnHeaders', () => { actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH} browserFields={mockBrowserFields} columnHeaders={defaultHeaders} + isSelectAllChecked={false} onColumnSorted={jest.fn()} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} + onSelectAll={jest.fn} onUpdateColumns={jest.fn()} showEventsSelect={false} + showSelectAllCheckbox={false} sort={sort} timelineId={'test'} toggleColumn={jest.fn()} @@ -117,11 +126,14 @@ describe('ColumnHeaders', () => { actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH} browserFields={mockBrowserFields} columnHeaders={defaultHeaders} + isSelectAllChecked={false} onColumnSorted={jest.fn()} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} + onSelectAll={jest.fn} onUpdateColumns={jest.fn()} showEventsSelect={false} + showSelectAllCheckbox={false} sort={sort} timelineId={'test'} toggleColumn={jest.fn()} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx index f6562b4db064d..95a7ae52b0f23 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiCheckbox } from '@elastic/eui'; import { noop } from 'lodash/fp'; import * as React from 'react'; import { Draggable, Droppable } from 'react-beautiful-dnd'; @@ -11,9 +12,9 @@ import { Draggable, Droppable } from 'react-beautiful-dnd'; import { BrowserFields } from '../../../../containers/source'; import { DragEffects } from '../../../drag_and_drop/draggable_wrapper'; import { + DRAG_TYPE_FIELD, droppableTimelineColumnsPrefix, getDraggableFieldId, - DRAG_TYPE_FIELD, } from '../../../drag_and_drop/helpers'; import { DraggableFieldBadge } from '../../../draggables/field_badge'; import { StatefulFieldsBrowser } from '../../../fields_browser'; @@ -24,6 +25,7 @@ import { OnColumnResized, OnColumnSorted, OnFilterChange, + OnSelectAll, OnUpdateColumns, } from '../../events'; import { @@ -44,12 +46,15 @@ interface Props { browserFields: BrowserFields; columnHeaders: ColumnHeader[]; isEventViewer?: boolean; + isSelectAllChecked: boolean; onColumnRemoved: OnColumnRemoved; onColumnResized: OnColumnResized; onColumnSorted: OnColumnSorted; onFilterChange?: OnFilterChange; + onSelectAll: OnSelectAll; onUpdateColumns: OnUpdateColumns; showEventsSelect: boolean; + showSelectAllCheckbox: boolean; sort: Sort; timelineId: string; toggleColumn: (column: ColumnHeader) => void; @@ -61,12 +66,15 @@ export const ColumnHeadersComponent = ({ browserFields, columnHeaders, isEventViewer = false, + isSelectAllChecked, onColumnRemoved, onColumnResized, onColumnSorted, + onSelectAll, onUpdateColumns, onFilterChange = noop, showEventsSelect, + showSelectAllCheckbox, sort, timelineId, toggleColumn, @@ -78,6 +86,7 @@ export const ColumnHeadersComponent = ({ {showEventsSelect && ( @@ -88,8 +97,23 @@ export const ColumnHeadersComponent = ({ )} + {showSelectAllCheckbox && ( + + + ) => { + onSelectAll({ isSelected: event.currentTarget.checked }); + }} + /> + + + )} + - + ; onColumnResized: OnColumnResized; onEventToggled: () => void; onPinEvent: OnPinEvent; + onRowSelected: OnRowSelected; onUnPinEvent: OnUnPinEvent; + selectedEventIds: Readonly>; + showCheckboxes: boolean; showNotes: boolean; timelineId: string; toggleShowNotes: () => void; @@ -60,10 +64,14 @@ export const EventColumnView = React.memo( isEventPinned = false, isEventViewer = false, loading, + loadingEventIds, onColumnResized, onEventToggled, onPinEvent, + onRowSelected, onUnPinEvent, + selectedEventIds, + showCheckboxes, showNotes, timelineId, toggleShowNotes, @@ -71,12 +79,24 @@ export const EventColumnView = React.memo( }) => { const timelineTypeContext = useTimelineTypeContext(); + const additionalActions = useMemo(() => { + return ( + timelineTypeContext.timelineActions?.map(action => ( + + {action.getAction({ eventId: id, data })} + + )) ?? [] + ); + }, [data, timelineTypeContext.timelineActions]); + return ( ( getNotesByIds={getNotesByIds} isEventViewer={isEventViewer} loading={loading} + loadingEventIds={loadingEventIds} noteIds={eventIdToNoteIds[id] || emptyNotes} onEventToggled={onEventToggled} onPinClicked={getPinOnClick({ @@ -93,7 +114,7 @@ export const EventColumnView = React.memo( onUnPinEvent, isEventPinned, })} - showCheckboxes={timelineTypeContext.showCheckboxes} + showCheckboxes={showCheckboxes} showNotes={showNotes} toggleShowNotes={toggleShowNotes} updateNote={updateNote} @@ -120,7 +141,11 @@ export const EventColumnView = React.memo( prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds && prevProps.expanded === nextProps.expanded && prevProps.loading === nextProps.loading && + prevProps.loadingEventIds === nextProps.loadingEventIds && prevProps.isEventPinned === nextProps.isEventPinned && + prevProps.onRowSelected === nextProps.onRowSelected && + prevProps.selectedEventIds === nextProps.selectedEventIds && + prevProps.showCheckboxes === nextProps.showCheckboxes && prevProps.showNotes === nextProps.showNotes && prevProps.timelineId === nextProps.timelineId ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx index 34281219bcc0c..9361a46dddff4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx @@ -7,11 +7,17 @@ import React from 'react'; import { BrowserFields } from '../../../../containers/source'; -import { TimelineItem } from '../../../../graphql/types'; +import { TimelineItem, TimelineNonEcsData } from '../../../../graphql/types'; import { maxDelay } from '../../../../lib/helpers/scheduler'; import { Note } from '../../../../lib/note'; import { AddNoteToEvent, UpdateNote } from '../../../notes/helpers'; -import { OnColumnResized, OnPinEvent, OnUnPinEvent, OnUpdateColumns } from '../../events'; +import { + OnColumnResized, + OnPinEvent, + OnRowSelected, + OnUnPinEvent, + OnUpdateColumns, +} from '../../events'; import { EventsTbody } from '../../styles'; import { ColumnHeader } from '../column_headers/column_header'; import { ColumnRenderer } from '../renderers/column_renderer'; @@ -30,12 +36,16 @@ interface Props { getNotesByIds: (noteIds: string[]) => Note[]; id: string; isEventViewer?: boolean; + loadingEventIds: Readonly; onColumnResized: OnColumnResized; onPinEvent: OnPinEvent; + onRowSelected: OnRowSelected; onUpdateColumns: OnUpdateColumns; onUnPinEvent: OnUnPinEvent; pinnedEventIds: Readonly>; rowRenderers: RowRenderer[]; + selectedEventIds: Readonly>; + showCheckboxes: boolean; toggleColumn: (column: ColumnHeader) => void; updateNote: UpdateNote; } @@ -55,12 +65,16 @@ export const Events = React.memo( getNotesByIds, id, isEventViewer = false, + loadingEventIds, onColumnResized, onPinEvent, + onRowSelected, onUpdateColumns, onUnPinEvent, pinnedEventIds, rowRenderers, + selectedEventIds, + showCheckboxes, toggleColumn, updateNote, }) => ( @@ -78,12 +92,16 @@ export const Events = React.memo( isEventPinned={eventIsPinned({ eventId: event._id, pinnedEventIds })} isEventViewer={isEventViewer} key={event._id} + loadingEventIds={loadingEventIds} maxDelay={maxDelay(i)} onColumnResized={onColumnResized} onPinEvent={onPinEvent} + onRowSelected={onRowSelected} onUnPinEvent={onUnPinEvent} onUpdateColumns={onUpdateColumns} rowRenderers={rowRenderers} + selectedEventIds={selectedEventIds} + showCheckboxes={showCheckboxes} timelineId={id} toggleColumn={toggleColumn} updateNote={updateNote} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx index b3ef4b7b39466..baa5c35880d68 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx @@ -10,12 +10,18 @@ import VisibilitySensor from 'react-visibility-sensor'; import { BrowserFields } from '../../../../containers/source'; import { TimelineDetailsComponentQuery } from '../../../../containers/timeline/details'; -import { TimelineItem, DetailItem } from '../../../../graphql/types'; +import { TimelineItem, DetailItem, TimelineNonEcsData } from '../../../../graphql/types'; import { requestIdleCallbackViaScheduler } from '../../../../lib/helpers/scheduler'; import { Note } from '../../../../lib/note'; import { AddNoteToEvent, UpdateNote } from '../../../notes/helpers'; import { SkeletonRow } from '../../../skeleton_row'; -import { OnColumnResized, OnPinEvent, OnUnPinEvent, OnUpdateColumns } from '../../events'; +import { + OnColumnResized, + OnPinEvent, + OnRowSelected, + OnUnPinEvent, + OnUpdateColumns, +} from '../../events'; import { ExpandableEvent } from '../../expandable_event'; import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers'; import { EventsTrGroup, EventsTrSupplement, OFFSET_SCROLLBAR } from '../../styles'; @@ -36,13 +42,17 @@ interface Props { eventIdToNoteIds: Readonly>; getNotesByIds: (noteIds: string[]) => Note[]; isEventViewer?: boolean; + loadingEventIds: Readonly; maxDelay?: number; onColumnResized: OnColumnResized; onPinEvent: OnPinEvent; + onRowSelected: OnRowSelected; onUnPinEvent: OnUnPinEvent; onUpdateColumns: OnUpdateColumns; isEventPinned: boolean; rowRenderers: RowRenderer[]; + selectedEventIds: Readonly>; + showCheckboxes: boolean; timelineId: string; toggleColumn: (column: ColumnHeader) => void; updateNote: UpdateNote; @@ -110,12 +120,16 @@ export const StatefulEvent = React.memo( getNotesByIds, isEventViewer = false, isEventPinned = false, + loadingEventIds, maxDelay = 0, onColumnResized, onPinEvent, + onRowSelected, onUnPinEvent, onUpdateColumns, rowRenderers, + selectedEventIds, + showCheckboxes, timelineId, toggleColumn, updateNote, @@ -224,11 +238,15 @@ export const StatefulEvent = React.memo( isEventPinned={isEventPinned} isEventViewer={isEventViewer} loading={loading} + loadingEventIds={loadingEventIds} onColumnResized={onColumnResized} onPinEvent={onPinEvent} + onRowSelected={onRowSelected} onToggleExpanded={onToggleExpanded} onToggleShowNotes={onToggleShowNotes} onUnPinEvent={onUnPinEvent} + selectedEventIds={selectedEventIds} + showCheckboxes={showCheckboxes} showNotes={!!showNotes[event._id]} timelineId={timelineId} updateNote={updateNote} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx index 668139349a377..9ea1bbb1e8430 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx @@ -11,7 +11,7 @@ import { TimelineNonEcsData } from '../../../../graphql/types'; import { Note } from '../../../../lib/note'; import { AddNoteToEvent, UpdateNote } from '../../../notes/helpers'; import { NoteCards } from '../../../notes/note_cards'; -import { OnPinEvent, OnColumnResized, OnUnPinEvent } from '../../events'; +import { OnPinEvent, OnColumnResized, OnUnPinEvent, OnRowSelected } from '../../events'; import { EventsTrSupplement, OFFSET_SCROLLBAR } from '../../styles'; import { useTimelineWidthContext } from '../../timeline_context'; import { ColumnHeader } from '../column_headers/column_header'; @@ -31,8 +31,12 @@ interface Props { isEventViewer?: boolean; isEventPinned: boolean; loading: boolean; + loadingEventIds: Readonly; onColumnResized: OnColumnResized; + onRowSelected: OnRowSelected; onUnPinEvent: OnUnPinEvent; + selectedEventIds: Readonly>; + showCheckboxes: boolean; showNotes: boolean; timelineId: string; updateNote: UpdateNote; @@ -62,9 +66,13 @@ export const StatefulEventChild = React.memo( isEventViewer = false, isEventPinned = false, loading, + loadingEventIds, onColumnResized, + onRowSelected, onToggleExpanded, onUnPinEvent, + selectedEventIds, + showCheckboxes, showNotes, timelineId, onToggleShowNotes, @@ -90,10 +98,14 @@ export const StatefulEventChild = React.memo( isEventPinned={isEventPinned} isEventViewer={isEventViewer} loading={loading} + loadingEventIds={loadingEventIds} onColumnResized={onColumnResized} onEventToggled={onToggleExpanded} onPinEvent={onPinEvent} + onRowSelected={onRowSelected} onUnPinEvent={onUnPinEvent} + selectedEventIds={selectedEventIds} + showCheckboxes={showCheckboxes} showNotes={showNotes} timelineId={timelineId} toggleShowNotes={onToggleShowNotes} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts index 5fdc2fddfbfb1..c11b884f8a80a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts @@ -6,7 +6,7 @@ import { get, isEmpty, noop } from 'lodash/fp'; import { BrowserFields } from '../../../containers/source'; -import { Ecs } from '../../../graphql/types'; +import { Ecs, TimelineItem, TimelineNonEcsData } from '../../../graphql/types'; import { OnPinEvent, OnUnPinEvent } from '../events'; import { ColumnHeader } from './column_headers/column_header'; import * as i18n from './translations'; @@ -95,6 +95,34 @@ export const getColumnHeaders = ( }; /** Returns the (fixed) width of the Actions column */ -export const getActionsColumnWidth = (isEventViewer: boolean, showCheckboxes = false): number => +export const getActionsColumnWidth = ( + isEventViewer: boolean, + showCheckboxes = false, + additionalActionWidth = 0 +): number => (showCheckboxes ? SHOW_CHECK_BOXES_COLUMN_WIDTH : 0) + - (isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH); + (isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH) + + additionalActionWidth; + +/** + * Creates mapping of eventID -> fieldData for given fieldsToKeep. Used to store additional field + * data necessary for custom timeline actions in conjunction with selection state + * @param timelineData + * @param eventIds + * @param fieldsToKeep + */ +export const getEventIdToDataMapping = ( + timelineData: TimelineItem[], + eventIds: string[], + fieldsToKeep: string[] +): Record => { + return timelineData.reduce((acc, v) => { + const fvm = eventIds.includes(v._id) + ? { [v._id]: v.data.filter(ti => fieldsToKeep.includes(ti.field)) } + : {}; + return { + ...acc, + ...fvm, + }; + }, {}); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx index a4ed5571bb0da..5c6a0872ce340 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx @@ -17,8 +17,6 @@ import { Sort } from './sort'; import { wait } from '../../../lib/helpers'; import { useMountAppended } from '../../../utils/use_mount_appended'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - const testBodyHeight = 700; const mockGetNotesByIds = (eventId: string[]) => []; const mockSort: Sort = { @@ -55,18 +53,24 @@ describe('Body', () => { eventIdToNoteIds={{}} height={testBodyHeight} id={'timeline-test'} + isSelectAllChecked={false} getNotesByIds={mockGetNotesByIds} + loadingEventIds={[]} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} onColumnSorted={jest.fn()} onFilterChange={jest.fn()} onPinEvent={jest.fn()} + onRowSelected={jest.fn()} + onSelectAll={jest.fn()} onUnPinEvent={jest.fn()} onUpdateColumns={jest.fn()} pinnedEventIds={{}} range={'1 Day'} rowRenderers={rowRenderers} + selectedEventIds={{}} sort={mockSort} + showCheckboxes={false} toggleColumn={jest.fn()} updateNote={jest.fn()} /> @@ -93,18 +97,24 @@ describe('Body', () => { eventIdToNoteIds={{}} height={testBodyHeight} id={'timeline-test'} + isSelectAllChecked={false} getNotesByIds={mockGetNotesByIds} + loadingEventIds={[]} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} onColumnSorted={jest.fn()} onFilterChange={jest.fn()} onPinEvent={jest.fn()} + onRowSelected={jest.fn()} + onSelectAll={jest.fn()} onUnPinEvent={jest.fn()} onUpdateColumns={jest.fn()} pinnedEventIds={{}} range={'1 Day'} rowRenderers={rowRenderers} + selectedEventIds={{}} sort={mockSort} + showCheckboxes={false} toggleColumn={jest.fn()} updateNote={jest.fn()} /> @@ -131,18 +141,24 @@ describe('Body', () => { eventIdToNoteIds={{}} height={testBodyHeight} id={'timeline-test'} + isSelectAllChecked={false} getNotesByIds={mockGetNotesByIds} + loadingEventIds={[]} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} onColumnSorted={jest.fn()} onFilterChange={jest.fn()} onPinEvent={jest.fn()} + onRowSelected={jest.fn()} + onSelectAll={jest.fn()} onUnPinEvent={jest.fn()} onUpdateColumns={jest.fn()} pinnedEventIds={{}} range={'1 Day'} rowRenderers={rowRenderers} + selectedEventIds={{}} sort={mockSort} + showCheckboxes={false} toggleColumn={jest.fn()} updateNote={jest.fn()} /> @@ -171,18 +187,24 @@ describe('Body', () => { eventIdToNoteIds={{}} height={testBodyHeight} id={'timeline-test'} + isSelectAllChecked={false} getNotesByIds={mockGetNotesByIds} + loadingEventIds={[]} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} onColumnSorted={jest.fn()} onFilterChange={jest.fn()} onPinEvent={jest.fn()} + onRowSelected={jest.fn()} + onSelectAll={jest.fn()} onUnPinEvent={jest.fn()} onUpdateColumns={jest.fn()} pinnedEventIds={{}} range={'1 Day'} rowRenderers={rowRenderers} + selectedEventIds={{}} sort={mockSort} + showCheckboxes={false} toggleColumn={jest.fn()} updateNote={jest.fn()} /> @@ -258,18 +280,24 @@ describe('Body', () => { eventIdToNoteIds={{}} height={testBodyHeight} id={'timeline-test'} + isSelectAllChecked={false} getNotesByIds={mockGetNotesByIds} + loadingEventIds={[]} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} onColumnSorted={jest.fn()} onFilterChange={jest.fn()} onPinEvent={dispatchOnPinEvent} + onRowSelected={jest.fn()} + onSelectAll={jest.fn()} onUnPinEvent={jest.fn()} onUpdateColumns={jest.fn()} pinnedEventIds={{}} range={'1 Day'} rowRenderers={rowRenderers} + selectedEventIds={{}} sort={mockSort} + showCheckboxes={false} toggleColumn={jest.fn()} updateNote={jest.fn()} /> @@ -298,18 +326,24 @@ describe('Body', () => { eventIdToNoteIds={{}} height={testBodyHeight} id={'timeline-test'} + isSelectAllChecked={false} getNotesByIds={mockGetNotesByIds} + loadingEventIds={[]} onColumnRemoved={jest.fn()} onColumnResized={jest.fn()} onColumnSorted={jest.fn()} onFilterChange={jest.fn()} onPinEvent={dispatchOnPinEvent} + onRowSelected={jest.fn()} + onSelectAll={jest.fn()} onUnPinEvent={jest.fn()} onUpdateColumns={jest.fn()} pinnedEventIds={{}} range={'1 Day'} rowRenderers={rowRenderers} + selectedEventIds={{}} sort={mockSort} + showCheckboxes={false} toggleColumn={jest.fn()} updateNote={jest.fn()} /> diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx index 0aed68a0e4ad7..23406f1b5f35f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React, { useMemo } from 'react'; import { BrowserFields } from '../../../containers/source'; -import { TimelineItem } from '../../../graphql/types'; +import { TimelineItem, TimelineNonEcsData } from '../../../graphql/types'; import { Note } from '../../../lib/note'; import { AddNoteToEvent, UpdateNote } from '../../notes/helpers'; import { @@ -16,6 +16,8 @@ import { OnColumnSorted, OnFilterChange, OnPinEvent, + OnRowSelected, + OnSelectAll, OnUnPinEvent, OnUpdateColumns, } from '../events'; @@ -39,10 +41,14 @@ export interface BodyProps { height: number; id: string; isEventViewer?: boolean; + isSelectAllChecked: boolean; eventIdToNoteIds: Readonly>; + loadingEventIds: Readonly; onColumnRemoved: OnColumnRemoved; onColumnResized: OnColumnResized; onColumnSorted: OnColumnSorted; + onRowSelected: OnRowSelected; + onSelectAll: OnSelectAll; onFilterChange: OnFilterChange; onPinEvent: OnPinEvent; onUpdateColumns: OnUpdateColumns; @@ -50,6 +56,8 @@ export interface BodyProps { pinnedEventIds: Readonly>; range: string; rowRenderers: RowRenderer[]; + selectedEventIds: Readonly>; + showCheckboxes: boolean; sort: Sort; toggleColumn: (column: ColumnHeader) => void; updateNote: UpdateNote; @@ -68,24 +76,38 @@ export const Body = React.memo( height, id, isEventViewer = false, + isSelectAllChecked, + loadingEventIds, onColumnRemoved, onColumnResized, onColumnSorted, + onRowSelected, + onSelectAll, onFilterChange, onPinEvent, onUpdateColumns, onUnPinEvent, pinnedEventIds, rowRenderers, + selectedEventIds, + showCheckboxes, sort, toggleColumn, updateNote, }) => { const timelineTypeContext = useTimelineTypeContext(); + const additionalActionWidth = + timelineTypeContext.timelineActions?.reduce((acc, v) => acc + v.width, 0) ?? 0; - const columnWidths = columnHeaders.reduce( - (totalWidth, header) => totalWidth + header.width, - getActionsColumnWidth(isEventViewer, timelineTypeContext.showCheckboxes) + const actionsColumnWidth = useMemo( + () => getActionsColumnWidth(isEventViewer, showCheckboxes, additionalActionWidth), + [isEventViewer, showCheckboxes, additionalActionWidth] + ); + + const columnWidths = useMemo( + () => + columnHeaders.reduce((totalWidth, header) => totalWidth + header.width, actionsColumnWidth), + [actionsColumnWidth, columnHeaders] ); return ( @@ -97,29 +119,26 @@ export const Body = React.memo( style={{ minWidth: columnWidths + 'px' }} > ( getNotesByIds={getNotesByIds} id={id} isEventViewer={isEventViewer} + loadingEventIds={loadingEventIds} onColumnResized={onColumnResized} onPinEvent={onPinEvent} + onRowSelected={onRowSelected} onUpdateColumns={onUpdateColumns} onUnPinEvent={onUnPinEvent} pinnedEventIds={pinnedEventIds} rowRenderers={rowRenderers} + selectedEventIds={selectedEventIds} + showCheckboxes={showCheckboxes} toggleColumn={toggleColumn} updateNote={updateNote} /> diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx index 3f27abfd2176c..5c54e5be3374c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx @@ -18,7 +18,7 @@ import { useMountAppended } from '../../../../utils/use_mount_appended'; import { FormattedFieldValue } from './formatted_field'; import { HOST_NAME_FIELD_NAME } from './constants'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../../lib/kibana'); describe('Events', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx index 9f73d86dec402..6ba8f3f28dae8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx @@ -25,8 +25,6 @@ export const justIdAndTimestamp: Ecs = { timestamp: '2018-11-12T19:03:25.936Z', }; -jest.mock('../../../../../lib/settings/use_kibana_ui_setting'); - describe('netflowRowRenderer', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx index 96865fd928216..008885b5264c8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx @@ -18,7 +18,7 @@ import { useMountAppended } from '../../../../utils/use_mount_appended'; import { plainColumnRenderer } from './plain_column_renderer'; import { getValues, deleteItemIdx, findItem } from './helpers'; -jest.mock('../../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../../lib/kibana'); const mockFramework = mockFrameworks.default_UTC; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx index 3727e82c1d2a0..b2dbdb6b0e45c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx @@ -49,7 +49,7 @@ import { } from './generic_row_renderer'; import * as i18n from './translations'; -jest.mock('../../../../../lib/settings/use_kibana_ui_setting'); +jest.mock('../../../../../lib/kibana'); describe('GenericRowRenderer', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx index cd5a677dbb393..8fe6759acf52d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx @@ -6,12 +6,12 @@ import { noop } from 'lodash/fp'; import memoizeOne from 'memoize-one'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { BrowserFields } from '../../../containers/source'; -import { TimelineItem } from '../../../graphql/types'; +import { TimelineItem, TimelineNonEcsData } from '../../../graphql/types'; import { Note } from '../../../lib/note'; import { appModel, appSelectors, State, timelineSelectors } from '../../../store'; import { AddNoteToEvent, UpdateNote } from '../../notes/helpers'; @@ -20,12 +20,14 @@ import { OnColumnResized, OnColumnSorted, OnPinEvent, + OnRowSelected, + OnSelectAll, OnUnPinEvent, OnUpdateColumns, } from '../events'; import { ColumnHeader } from './column_headers/column_header'; -import { getColumnHeaders } from './helpers'; +import { getColumnHeaders, getEventIdToDataMapping } from './helpers'; import { Body } from './index'; import { columnRenderers, rowRenderers } from './renderers'; import { Sort } from './sort'; @@ -47,9 +49,14 @@ interface OwnProps { interface ReduxProps { columnHeaders: ColumnHeader[]; eventIdToNoteIds: Readonly>; + isSelectAllChecked: boolean; + loadingEventIds: Readonly; notesById: appModel.NotesById; pinnedEventIds: Readonly>; range?: string; + selectedEventIds: Readonly>; + showCheckboxes: boolean; + showRowRenderers: boolean; } interface DispatchProps { @@ -59,6 +66,9 @@ interface DispatchProps { columnId: string; delta: number; }>; + clearSelected?: ActionCreator<{ + id: string; + }>; pinEvent?: ActionCreator<{ id: string; eventId: string; @@ -67,6 +77,12 @@ interface DispatchProps { id: string; columnId: string; }>; + setSelected?: ActionCreator<{ + id: string; + eventIds: Record; + isSelected: boolean; + isSelectAllChecked: boolean; + }>; unPinEvent?: ActionCreator<{ id: string; eventId: string; @@ -97,11 +113,18 @@ const StatefulBodyComponent = React.memo( height, id, isEventViewer = false, + isSelectAllChecked, + loadingEventIds, notesById, pinEvent, pinnedEventIds, range, removeColumn, + selectedEventIds, + setSelected, + clearSelected, + showCheckboxes, + showRowRenderers, sort, toggleColumn, unPinEvent, @@ -122,6 +145,36 @@ const StatefulBodyComponent = React.memo( [id] ); + const onRowSelected: OnRowSelected = useCallback( + ({ eventIds, isSelected }: { eventIds: string[]; isSelected: boolean }) => { + setSelected!({ + id, + eventIds: getEventIdToDataMapping(data, eventIds, timelineTypeContext.queryFields ?? []), + isSelected, + isSelectAllChecked: + isSelected && Object.keys(selectedEventIds).length + 1 === data.length, + }); + }, + [id, data, selectedEventIds, timelineTypeContext.queryFields] + ); + + const onSelectAll: OnSelectAll = useCallback( + ({ isSelected }: { isSelected: boolean }) => + isSelected + ? setSelected!({ + id, + eventIds: getEventIdToDataMapping( + data, + data.map(event => event._id), + timelineTypeContext.queryFields ?? [] + ), + isSelected, + isSelectAllChecked: isSelected, + }) + : clearSelected!({ id }), + [id, data, timelineTypeContext.queryFields] + ); + const onColumnSorted: OnColumnSorted = useCallback( sorted => { updateSort!({ id, sort: sorted }); @@ -150,6 +203,13 @@ const StatefulBodyComponent = React.memo( [id] ); + // Sync to timelineTypeContext.selectAll so parent components can select all events + useEffect(() => { + if (timelineTypeContext.selectAll) { + onSelectAll({ isSelected: true }); + } + }, [timelineTypeContext.selectAll]); // onSelectAll dependency not necessary + return ( ( height={height} id={id} isEventViewer={isEventViewer} + isSelectAllChecked={isSelectAllChecked} + loadingEventIds={loadingEventIds} onColumnRemoved={onColumnRemoved} onColumnResized={onColumnResized} onColumnSorted={onColumnSorted} + onRowSelected={onRowSelected} + onSelectAll={onSelectAll} onFilterChange={noop} // TODO: this is the callback for column filters, which is out scope for this phase of delivery onPinEvent={onPinEvent} onUnPinEvent={onUnPinEvent} onUpdateColumns={onUpdateColumns} pinnedEventIds={pinnedEventIds} range={range!} - rowRenderers={timelineTypeContext.showRowRenderers ? rowRenderers : [plainRowRenderer]} + rowRenderers={showRowRenderers ? rowRenderers : [plainRowRenderer]} + selectedEventIds={selectedEventIds} + showCheckboxes={showCheckboxes} sort={sort} toggleColumn={toggleColumn} updateNote={onUpdateNote} @@ -188,7 +254,12 @@ const StatefulBodyComponent = React.memo( prevProps.height === nextProps.height && prevProps.id === nextProps.id && prevProps.isEventViewer === nextProps.isEventViewer && + prevProps.isSelectAllChecked === nextProps.isSelectAllChecked && + prevProps.loadingEventIds === nextProps.loadingEventIds && prevProps.pinnedEventIds === nextProps.pinnedEventIds && + prevProps.selectedEventIds === nextProps.selectedEventIds && + prevProps.showCheckboxes === nextProps.showCheckboxes && + prevProps.showRowRenderers === nextProps.showRowRenderers && prevProps.range === nextProps.range && prevProps.sort === nextProps.sort ); @@ -207,14 +278,28 @@ const makeMapStateToProps = () => { const getNotesByIds = appSelectors.notesByIdsSelector(); const mapStateToProps = (state: State, { browserFields, id }: OwnProps) => { const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; - const { columns, eventIdToNoteIds, pinnedEventIds } = timeline; + const { + columns, + eventIdToNoteIds, + isSelectAllChecked, + loadingEventIds, + pinnedEventIds, + selectedEventIds, + showCheckboxes, + showRowRenderers, + } = timeline; return { columnHeaders: memoizedColumnHeaders(columns, browserFields), eventIdToNoteIds, + isSelectAllChecked, + loadingEventIds, notesById: getNotesByIds(state), id, pinnedEventIds, + selectedEventIds, + showCheckboxes, + showRowRenderers, }; }; return mapStateToProps; @@ -223,9 +308,11 @@ const makeMapStateToProps = () => { export const StatefulBody = connect(makeMapStateToProps, { addNoteToEvent: timelineActions.addNoteToEvent, applyDeltaToColumnWidth: timelineActions.applyDeltaToColumnWidth, + clearSelected: timelineActions.clearSelected, pinEvent: timelineActions.pinEvent, removeColumn: timelineActions.removeColumn, removeProvider: timelineActions.removeProvider, + setSelected: timelineActions.setSelected, unPinEvent: timelineActions.unPinEvent, updateColumns: timelineActions.updateColumns, updateNote: appActions.updateNote, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/events.ts b/x-pack/legacy/plugins/siem/public/components/timeline/events.ts index 253be41a009c8..b54ed52bb9f18 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/events.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/events.ts @@ -72,6 +72,18 @@ export type OnChangeDroppableAndProvider = (providerId: string) => void; /** Invoked when a user pins an event */ export type OnPinEvent = (eventId: string) => void; +/** Invoked when a user checks/un-checks a row */ +export type OnRowSelected = ({ + eventIds, + isSelected, +}: { + eventIds: string[]; + isSelected: boolean; +}) => void; + +/** Invoked when a user checks/un-checks the select all checkbox */ +export type OnSelectAll = ({ isSelected }: { isSelected: boolean }) => void; + /** Invoked when columns are updated */ export type OnUpdateColumns = (columns: ColumnHeader[]) => void; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx index 978386e611808..1fcc4382c1798 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx @@ -211,6 +211,7 @@ export const FooterComponent = ({ const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [paginationLoading, setPaginationLoading] = useState(false); const [updatedAt, setUpdatedAt] = useState(null); + const timelineTypeContext = useTimelineTypeContext(); const loadMore = useCallback(() => { setPaginationLoading(true); @@ -239,7 +240,7 @@ export const FooterComponent = ({ data-test-subj="LoadingPanelTimeline" height="35px" showBorder={false} - text={isEventViewer ? `${i18n.LOADING_EVENTS}...` : `${i18n.LOADING_TIMELINE_DATA}...`} + text={`${timelineTypeContext.loadingText ?? i18n.LOADING_TIMELINE_DATA}...`} width="100%" /> diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/footer/translations.ts index e7f5ba4026ac7..886866ce1b0c2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/translations.ts @@ -6,10 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const LOADING_EVENTS = i18n.translate('xpack.siem.footer.loadingEventsData', { - defaultMessage: 'Loading Events', -}); - export const LOADING_TIMELINE_DATA = i18n.translate('xpack.siem.footer.loadingTimelineData', { defaultMessage: 'Loading Timeline data', }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx index 977764803acbb..4527e39128f89 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx @@ -9,21 +9,14 @@ import toJson from 'enzyme-to-json'; import * as React from 'react'; import { Direction } from '../../../graphql/types'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { mockIndexPattern } from '../../../mock'; import { TestProviders } from '../../../mock/test_providers'; -import { mockUiSettings } from '../../../mock/ui_settings'; import { mockDataProviders } from '../data_providers/mock/mock_data_providers'; import { useMountAppended } from '../../../utils/use_mount_appended'; import { TimelineHeaderComponent } from '.'; -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, - savedObjects: {}, -})); +jest.mock('../../../lib/kibana'); describe('Header', () => { const indexPattern = mockIndexPattern; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx index eb82241b04124..bc05204cc47fe 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx @@ -8,22 +8,12 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { mockGlobalState, apolloClientObservable } from '../../../mock'; -import { mockUiSettings } from '../../../mock/ui_settings'; import { createStore, State } from '../../../store'; import { Properties, showDescriptionThreshold, showNotesThreshold } from '.'; -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); - -jest.mock('ui/vis/lib/timezone', () => ({ - timezoneProvider: () => () => 'America/New_York', -})); +jest.mock('../../../lib/kibana'); describe('Properties', () => { const usersViewing = ['elastic']; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.test.tsx index b78691fabdcbf..b978ef3d478d8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.test.tsx @@ -9,22 +9,15 @@ import React from 'react'; import { DEFAULT_FROM, DEFAULT_TO } from '../../../../common/constants'; import { mockBrowserFields } from '../../../containers/source/mock'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { convertKueryToElasticSearchQuery } from '../../../lib/keury'; import { mockIndexPattern, TestProviders } from '../../../mock'; -import { mockUiSettings } from '../../../mock/ui_settings'; import { QueryBar } from '../../query_bar'; import { mockDataProviders } from '../data_providers/mock/mock_data_providers'; import { buildGlobalQuery } from '../helpers'; import { QueryBarTimeline, QueryBarTimelineComponentProps, getDataProviderFilter } from './index'; -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, - savedObjects: {}, -})); +jest.mock('../../../lib/kibana'); describe('Timeline QueryBar ', () => { // We are doing that because we need to wrapped this component with redux diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx index c55ead5e2d5d5..c3b46c6cd0f72 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx @@ -19,7 +19,7 @@ import { import { BrowserFields } from '../../../containers/source'; import { convertKueryToElasticSearchQuery } from '../../../lib/keury'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; +import { useKibana } from '../../../lib/kibana'; import { KueryFilterQuery, KueryFilterQueryKind } from '../../../store'; import { KqlMode } from '../../../store/timeline/model'; import { useSavedQueryServices } from '../../../utils/saved_query_services'; @@ -92,8 +92,8 @@ export const QueryBarTimeline = memo( const [dataProvidersDsl, setDataProvidersDsl] = useState( convertKueryToElasticSearchQuery(buildGlobalQuery(dataProviders, browserFields), indexPattern) ); - const core = useKibanaCore(); - const [filterManager] = useState(new FilterManager(core.uiSettings)); + const kibana = useKibana(); + const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); const savedQueryServices = useSavedQueryServices(); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx index a138a94e57093..db5d27626fc6d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx @@ -87,10 +87,10 @@ EventsTrHeader.displayName = 'EventsTrHeader'; export const EventsThGroupActions = styled.div.attrs(({ className }) => ({ className: `siemEventsTable__thGroupActions ${className}`, -}))<{ actionsColumnWidth: number }>` +}))<{ actionsColumnWidth: number; justifyContent: string }>` display: flex; flex: 0 0 ${({ actionsColumnWidth }) => actionsColumnWidth + 'px'}; - justify-content: space-between; + justify-content: ${({ justifyContent }) => justifyContent}; min-width: 0; `; EventsThGroupActions.displayName = 'EventsThGroupActions'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx index 180af88f21e4d..bb500de239da7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx @@ -12,10 +12,8 @@ import { MockedProvider } from 'react-apollo/test-utils'; import { timelineQuery } from '../../containers/timeline/index.gql_query'; import { mockBrowserFields } from '../../containers/source/mock'; import { Direction } from '../../graphql/types'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; import { defaultHeaders, mockTimelineData, mockIndexPattern } from '../../mock'; import { TestProviders } from '../../mock/test_providers'; -import { mockUiSettings } from '../../mock/ui_settings'; import { flyoutHeaderHeight } from '../flyout'; import { @@ -30,12 +28,7 @@ import { useMountAppended } from '../../utils/use_mount_appended'; const testFlyoutHeight = 980; -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, - savedObjects: {}, -})); +jest.mock('../../lib/kibana'); describe('Timeline', () => { const sort: Sort = { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index ec7d10d2373d4..5646b26428bf8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -12,7 +12,7 @@ import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; import { TimelineQuery } from '../../containers/timeline'; import { Direction } from '../../graphql/types'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; +import { useKibana } from '../../lib/kibana'; import { KqlMode } from '../../store/timeline/model'; import { AutoSizer } from '../auto_sizer'; import { ColumnHeader } from './body/column_headers/column_header'; @@ -113,9 +113,9 @@ export const TimelineComponent = ({ sort, toggleColumn, }: Props) => { - const core = useKibanaCore(); + const kibana = useKibana(); const combinedQueries = combineQueries({ - config: esQuery.getEsQueryConfig(core.uiSettings), + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), dataProviders, indexPattern, browserFields, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx index 584fe03d2149b..d3251e1d331e3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx @@ -5,6 +5,7 @@ */ import React, { createContext, memo, useContext, useEffect, useState } from 'react'; +import { TimelineAction } from './body/actions'; const initTimelineContext = false; export const TimelineContext = createContext(initTimelineContext); @@ -17,15 +18,19 @@ export const useTimelineWidthContext = () => useContext(TimelineWidthContext); export interface TimelineTypeContextProps { documentType?: string; footerText?: string; - showCheckboxes: boolean; - showRowRenderers: boolean; + loadingText?: string; + queryFields?: string[]; + selectAll?: boolean; + timelineActions?: TimelineAction[]; title?: string; } const initTimelineType: TimelineTypeContextProps = { documentType: undefined, footerText: undefined, - showCheckboxes: false, - showRowRenderers: true, + loadingText: undefined, + queryFields: [], + selectAll: false, + timelineActions: [], title: undefined, }; export const TimelineTypeContext = createContext(initTimelineType); diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx index 3ea243fe5cfe7..5d73e9bcf8e71 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx @@ -9,11 +9,8 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; -import '../../mock/ui_settings'; import { WrapperPage } from './index'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - describe('WrapperPage', () => { test('it renders', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/containers/alerts/alerts_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/alerts/alerts_over_time/index.tsx index 98dcef51292ae..9541e6d39d669 100644 --- a/x-pack/legacy/plugins/siem/public/containers/alerts/alerts_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/alerts/alerts_over_time/index.tsx @@ -8,10 +8,11 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State, inputsSelectors, hostsModel } from '../../../store'; +import { withKibana, WithKibanaProps } from '../../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../../helpers'; import { QueryTemplate, QueryTemplateProps } from '../../query_template'; @@ -40,7 +41,7 @@ export interface AlertsOverTimeComponentReduxProps { isInspected: boolean; } -type AlertsOverTimeProps = OwnProps & AlertsOverTimeComponentReduxProps; +type AlertsOverTimeProps = OwnProps & AlertsOverTimeComponentReduxProps & WithKibanaProps; class AlertsOverTimeComponentQuery extends QueryTemplate< AlertsOverTimeProps, @@ -54,6 +55,7 @@ class AlertsOverTimeComponentQuery extends QueryTemplate< filterQuery, id = ID, isInspected, + kibana, sourceId, startDate, } = this.props; @@ -70,7 +72,7 @@ class AlertsOverTimeComponentQuery extends QueryTemplate< from: startDate!, to: endDate!, }, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), inspect: isInspected, }} > @@ -105,4 +107,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const AlertsOverTimeQuery = connect(makeMapStateToProps)(AlertsOverTimeComponentQuery); +export const AlertsOverTimeQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(AlertsOverTimeComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx index 917f4dbcc211b..f2c00cbae1a74 100644 --- a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx @@ -12,7 +12,7 @@ import { AnomaliesOverTimeHistogram } from '../../../components/anomalies_over_t import { AnomaliesOverTimeQuery } from '../anomalies_over_time'; import { getAnomaliesFilterQuery } from './utils'; import { useSiemJobs } from '../../../components/ml_popover/hooks/use_siem_jobs'; -import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_ANOMALY_SCORE } from '../../../../common/constants'; const AnomaliesOverTimeManage = manageQuery(AnomaliesOverTimeHistogram); @@ -33,7 +33,7 @@ export const AnomaliesQueryTabBody = ({ ip, }: AnomaliesQueryTabBodyProps) => { const [siemJobsLoading, siemJobs] = useSiemJobs(true); - const [anomalyScore] = useKibanaUiSetting(DEFAULT_ANOMALY_SCORE); + const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); const mergedFilterQuery = getAnomaliesFilterQuery( filterQuery, diff --git a/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx index 1d6d96869b6a9..8f363d49f1851 100644 --- a/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx @@ -8,10 +8,11 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State, inputsSelectors, hostsModel } from '../../../store'; +import { withKibana, WithKibanaProps } from '../../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../../helpers'; import { QueryTemplate, QueryTemplateProps } from '../../query_template'; @@ -43,7 +44,9 @@ export interface AuthenticationsOverTimeComponentReduxProps { isInspected: boolean; } -type AuthenticationsOverTimeProps = OwnProps & AuthenticationsOverTimeComponentReduxProps; +type AuthenticationsOverTimeProps = OwnProps & + AuthenticationsOverTimeComponentReduxProps & + WithKibanaProps; class AuthenticationsOverTimeComponentQuery extends QueryTemplate< AuthenticationsOverTimeProps, @@ -56,6 +59,7 @@ class AuthenticationsOverTimeComponentQuery extends QueryTemplate< filterQuery, id = ID, isInspected, + kibana, sourceId, startDate, endDate, @@ -73,7 +77,7 @@ class AuthenticationsOverTimeComponentQuery extends QueryTemplate< from: startDate!, to: endDate!, }, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), inspect: isInspected, }} > @@ -108,6 +112,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const AuthenticationsOverTimeQuery = connect(makeMapStateToProps)( - AuthenticationsOverTimeComponentQuery -); +export const AuthenticationsOverTimeQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(AuthenticationsOverTimeComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx b/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx index 6f896cfd95901..6d4a88c45a768 100644 --- a/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/authentications/index.tsx @@ -8,8 +8,8 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { AuthenticationsEdges, @@ -19,6 +19,7 @@ import { import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; import { authenticationsQuery } from './index.gql_query'; @@ -48,7 +49,7 @@ export interface AuthenticationsComponentReduxProps { limit: number; } -type AuthenticationsProps = OwnProps & AuthenticationsComponentReduxProps; +type AuthenticationsProps = OwnProps & AuthenticationsComponentReduxProps & WithKibanaProps; class AuthenticationsComponentQuery extends QueryTemplatePaginated< AuthenticationsProps, @@ -63,6 +64,7 @@ class AuthenticationsComponentQuery extends QueryTemplatePaginated< filterQuery, id = ID, isInspected, + kibana, limit, skip, sourceId, @@ -77,7 +79,7 @@ class AuthenticationsComponentQuery extends QueryTemplatePaginated< }, pagination: generateTablePaginationOptions(activePage, limit), filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), inspect: isInspected, }; return ( @@ -142,4 +144,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const AuthenticationsQuery = connect(makeMapStateToProps)(AuthenticationsComponentQuery); +export const AuthenticationsQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(AuthenticationsComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index a26f376f962d7..a3ee878e305b4 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -14,6 +14,7 @@ import { FetchRulesResponse, NewRule, Rule, + FetchRuleProps, } from './types'; import { throwIfNotOk } from '../../../hooks/api/api'; import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; @@ -27,7 +28,7 @@ import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; */ export const addRule = async ({ rule, kbnVersion, signal }: AddRulesProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { - method: 'POST', + method: rule.id != null ? 'PUT' : 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', @@ -96,6 +97,28 @@ export const fetchRules = async ({ : response.json(); }; +/** + * Fetch a Rule by providing a Rule ID + * + * @param id Rule ID's (not rule_id) + * @param kbnVersion current Kibana Version to use for headers + */ +export const fetchRuleById = async ({ id, kbnVersion, signal }: FetchRuleProps): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-version': kbnVersion, + 'kbn-xsrf': kbnVersion, + }, + signal, + }); + await throwIfNotOk(response); + const rule: Rule = await response.json(); + return rule; +}; + /** * Enables/Disables provided Rule ID's * @@ -177,11 +200,14 @@ export const duplicateRules = async ({ body: JSON.stringify({ ...rule, name: `${rule.name} [Duplicate]`, + created_at: undefined, created_by: undefined, id: undefined, rule_id: undefined, + updated_at: undefined, updated_by: undefined, enabled: rule.enabled, + immutable: false, }), }) ); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx index dd744f4d7ecd5..88a1333c82a45 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty, get } from 'lodash/fp'; +import { isEmpty, isEqual, get } from 'lodash/fp'; import { useEffect, useState, Dispatch, SetStateAction } from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; import { BrowserFields, getBrowserFields, @@ -40,6 +40,12 @@ export const useFetchIndexPatterns = (defaultIndices: string[] = []): Return => const [isLoading, setIsLoading] = useState(false); const [, dispatchToaster] = useStateToaster(); + useEffect(() => { + if (!isEqual(defaultIndices, indices)) { + setIndices(defaultIndices); + } + }, [defaultIndices, indices]); + useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts new file mode 100644 index 0000000000000..a61cbabd80626 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './api'; +export * from './fetch_index_patterns'; +export * from './persist_rule'; +export * from './types'; +export * from './use_rule'; +export * from './use_rules'; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx index 371d28aebf7f7..82490991236de 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx @@ -6,7 +6,7 @@ import { useEffect, useState, Dispatch } from 'react'; -import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { useStateToaster } from '../../../components/toasters'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; @@ -26,7 +26,7 @@ export const usePersistRule = (): Return => { const [rule, setRule] = useState(null); const [isSaved, setIsSaved] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index fe6fb04800adc..0885e541cead5 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -10,11 +10,13 @@ export const NewRuleSchema = t.intersection([ t.type({ description: t.string, enabled: t.boolean, + filters: t.array(t.unknown), index: t.array(t.string), interval: t.string, language: t.string, name: t.string, query: t.string, + risk_score: t.number, severity: t.string, type: t.union([t.literal('query'), t.literal('saved_query')]), }), @@ -26,7 +28,9 @@ export const NewRuleSchema = t.intersection([ max_signals: t.number, references: t.array(t.string), rule_id: t.string, + saved_id: t.string, tags: t.array(t.string), + threats: t.array(t.unknown), to: t.string, updated_by: t.string, }), @@ -41,29 +45,41 @@ export interface AddRulesProps { signal: AbortSignal; } +const MetaRule = t.type({ + from: t.string, +}); + export const RuleSchema = t.intersection([ t.type({ + created_at: t.string, created_by: t.string, description: t.string, enabled: t.boolean, + false_positives: t.array(t.string), + filters: t.array(t.unknown), + from: t.string, id: t.string, index: t.array(t.string), interval: t.string, + immutable: t.boolean, language: t.string, name: t.string, + max_signals: t.number, + meta: MetaRule, query: t.string, + references: t.array(t.string), + risk_score: t.number, rule_id: t.string, severity: t.string, type: t.string, + tags: t.array(t.string), + to: t.string, + threats: t.array(t.unknown), + updated_at: t.string, updated_by: t.string, }), t.partial({ - false_positives: t.array(t.string), - from: t.string, - max_signals: t.number, - references: t.array(t.string), - tags: t.array(t.string), - to: t.string, + saved_id: t.string, }), ]); @@ -99,6 +115,12 @@ export interface FetchRulesResponse { data: Rule[]; } +export interface FetchRuleProps { + id: string; + kbnVersion: string; + signal: AbortSignal; +} + export interface EnableRulesProps { ids: string[]; enabled: boolean; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx new file mode 100644 index 0000000000000..ad0b87385ee79 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx @@ -0,0 +1,67 @@ +/* + * 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 { useEffect, useState } from 'react'; + +import { useUiSetting$ } from '../../../lib/kibana'; +import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; +import { useStateToaster } from '../../../components/toasters'; +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { fetchRuleById } from './api'; +import * as i18n from './translations'; +import { Rule } from './types'; + +type Return = [boolean, Rule | null]; + +/** + * Hook for using to get a Rule from the Detection Engine API + * + * @param id desired Rule ID's (not rule_id) + * + */ +export const useRule = (id: string | undefined): Return => { + const [rule, setRule] = useState(null); + const [loading, setLoading] = useState(true); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + async function fetchData(idToFetch: string) { + try { + setLoading(true); + const ruleResponse = await fetchRuleById({ + id: idToFetch, + kbnVersion, + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setRule(ruleResponse); + } + } catch (error) { + if (isSubscribed) { + setRule(null); + errorToToaster({ title: i18n.RULE_FETCH_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoading(false); + } + } + if (id != null) { + fetchData(id); + } + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [id]); + + return [loading, rule]; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index 2b8bb986a296a..66285c804aa28 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react'; -import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { FetchRulesResponse, FilterOptions, PaginationOptions } from './types'; import { useStateToaster } from '../../../components/toasters'; @@ -35,7 +35,7 @@ export const useRules = ( data: [], }); const [loading, setLoading] = useState(true); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts new file mode 100644 index 0000000000000..f6274db4a9dae --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import chrome from 'ui/chrome'; + +import { throwIfNotOk } from '../../../hooks/api/api'; +import { + DETECTION_ENGINE_QUERY_SIGNALS_URL, + DETECTION_ENGINE_SIGNALS_STATUS_URL, +} from '../../../../common/constants'; +import { QuerySignals, SignalSearchResponse, UpdateSignalStatusProps } from './types'; + +/** + * Fetch Signals by providing a query + * + * @param query String to match a dsl + * @param kbnVersion current Kibana Version to use for headers + */ +export const fetchQuerySignals = async ({ + query, + kbnVersion, + signal, +}: QuerySignals): Promise> => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_QUERY_SIGNALS_URL}`, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-version': kbnVersion, + 'kbn-xsrf': kbnVersion, + }, + body: query, + signal, + }); + await throwIfNotOk(response); + const signals = await response.json(); + return signals; +}; + +/** + * Update signal status by query + * + * @param query of signals to update + * @param status to update to('open' / 'closed') + * @param kbnVersion current Kibana Version to use for headers + * @param signal to cancel request + */ +export const updateSignalStatus = async ({ + query, + status, + kbnVersion, + signal, +}: UpdateSignalStatusProps): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_SIGNALS_STATUS_URL}`, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-version': kbnVersion, + 'kbn-xsrf': kbnVersion, + }, + body: JSON.stringify({ status, ...query }), + signal, + }); + + await throwIfNotOk(response); + return response.json(); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts similarity index 59% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts rename to x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts index cc2e2565eb8d0..5b5dc9e9699fe 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts @@ -6,6 +6,9 @@ import { i18n } from '@kbn/i18n'; -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.editRule.pageTitle', { - defaultMessage: 'Edit rule settings', -}); +export const SIGNAL_FETCH_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', + { + defaultMessage: 'Failed to query signals', + } +); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts new file mode 100644 index 0000000000000..5846f3275c0fd --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface QuerySignals { + query: string; + kbnVersion: string; + signal: AbortSignal; +} + +export interface SignalsResponse { + took: number; + timeout: boolean; +} + +export interface SignalSearchResponse extends SignalsResponse { + _shards: { + total: number; + successful: number; + skipped: number; + failed: number; + }; + aggregations?: Aggregations; + hits: { + total: { + value: number; + relation: string; + }; + hits: Hit[]; + }; +} + +export interface UpdateSignalStatusProps { + query: object; + status: 'open' | 'closed'; + kbnVersion: string; + signal?: AbortSignal; // TODO: implement cancelling +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx new file mode 100644 index 0000000000000..d865625288550 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx @@ -0,0 +1,67 @@ +/* + * 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 { useEffect, useState } from 'react'; + +import { useUiSetting$ } from '../../../lib/kibana'; +import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { useStateToaster } from '../../../components/toasters'; + +import { fetchQuerySignals } from './api'; +import * as i18n from './translations'; +import { SignalSearchResponse } from './types'; + +type Return = [boolean, SignalSearchResponse | null]; + +/** + * Hook for using to get a Signals from the Detection Engine API + * + * @param query convert a dsl into string + * + */ +export const useQuerySignals = (query: string): Return => { + const [signals, setSignals] = useState | null>(null); + const [loading, setLoading] = useState(true); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function fetchData() { + try { + const signalResponse = await fetchQuerySignals({ + query, + kbnVersion, + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setSignals(signalResponse); + } + } catch (error) { + if (isSubscribed) { + setSignals(null); + errorToToaster({ title: i18n.SIGNAL_FETCH_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoading(false); + } + } + + fetchData(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [query]); + + return [loading, signals]; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx index 77ce98e180ab0..e102cd11f108e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx @@ -8,12 +8,13 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State, inputsSelectors, hostsModel } from '../../../store'; import { createFilter, getDefaultFetchPolicy } from '../../helpers'; import { QueryTemplate, QueryTemplateProps } from '../../query_template'; +import { withKibana, WithKibanaProps } from '../../../lib/kibana'; import { EventsOverTimeGqlQuery } from './events_over_time.gql_query'; import { GetEventsOverTimeQuery, MatrixOverTimeHistogramData } from '../../../graphql/types'; @@ -40,7 +41,7 @@ export interface EventsOverTimeComponentReduxProps { isInspected: boolean; } -type EventsOverTimeProps = OwnProps & EventsOverTimeComponentReduxProps; +type EventsOverTimeProps = OwnProps & EventsOverTimeComponentReduxProps & WithKibanaProps; class EventsOverTimeComponentQuery extends QueryTemplate< EventsOverTimeProps, @@ -54,6 +55,7 @@ class EventsOverTimeComponentQuery extends QueryTemplate< filterQuery, id = ID, isInspected, + kibana, sourceId, startDate, } = this.props; @@ -70,7 +72,7 @@ class EventsOverTimeComponentQuery extends QueryTemplate< from: startDate!, to: endDate!, }, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), inspect: isInspected, }} > @@ -105,4 +107,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const EventsOverTimeQuery = connect(makeMapStateToProps)(EventsOverTimeComponentQuery); +export const EventsOverTimeQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(EventsOverTimeComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts b/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts index 3bfdbae8d30f7..9cae503d30940 100644 --- a/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts @@ -7,11 +7,11 @@ import { get } from 'lodash/fp'; import React, { useEffect, useState } from 'react'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { GetLastEventTimeQuery, LastEventIndexKey, LastTimeDetails } from '../../../graphql/types'; import { inputsModel } from '../../../store'; import { QueryTemplateProps } from '../../query_template'; +import { useUiSetting$ } from '../../../lib/kibana'; import { LastEventTimeGqlQuery } from './last_event_time.gql_query'; import { useApolloClient } from '../../../utils/apollo_context'; @@ -38,6 +38,7 @@ export function useLastEventTimeQuery( const [lastSeen, updateLastSeen] = useState(null); const [errorMessage, updateErrorMessage] = useState(null); const [currentIndexKey, updateCurrentIndexKey] = useState(null); + const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); const apolloClient = useApolloClient(); async function fetchLastEventTime(signal: AbortSignal) { updateLoading(true); @@ -50,7 +51,7 @@ export function useLastEventTimeQuery( sourceId, indexKey, details, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex, }, context: { fetchOptions: { diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts b/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts index 042de56fbd99d..e36da5bfbe4ee 100644 --- a/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts @@ -8,7 +8,7 @@ import ApolloClient from 'apollo-client'; import { get } from 'lodash/fp'; import React, { useEffect, useState } from 'react'; -import chrome from 'ui/chrome'; +import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { GetHostFirstLastSeenQuery } from '../../../graphql/types'; import { inputsModel } from '../../../store'; @@ -39,6 +39,7 @@ export function useFirstLastSeenHostQuery( const [firstSeen, updateFirstSeen] = useState(null); const [lastSeen, updateLastSeen] = useState(null); const [errorMessage, updateErrorMessage] = useState(null); + const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); async function fetchFirstLastSeenHost(signal: AbortSignal) { updateLoading(true); @@ -49,7 +50,7 @@ export function useFirstLastSeenHostQuery( variables: { sourceId, hostName, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex, }, context: { fetchOptions: { diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx b/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx index d2be29e3e9e29..733c2224d840a 100644 --- a/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/hosts/index.tsx @@ -9,8 +9,8 @@ import memoizeOne from 'memoize-one'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { Direction, @@ -22,6 +22,7 @@ import { import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { HostsTableQuery } from './hosts_table.gql_query'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; @@ -57,7 +58,7 @@ export interface HostsComponentReduxProps { direction: Direction; } -type HostsProps = OwnProps & HostsComponentReduxProps; +type HostsProps = OwnProps & HostsComponentReduxProps & WithKibanaProps; class HostsComponentQuery extends QueryTemplatePaginated< HostsProps, @@ -83,12 +84,14 @@ class HostsComponentQuery extends QueryTemplatePaginated< direction, filterQuery, endDate, + kibana, limit, startDate, skip, sourceId, sortField, } = this.props; + const defaultIndex = kibana.services.uiSettings.get(DEFAULT_INDEX_KEY); const variables: GetHostsTableQuery.Variables = { sourceId, @@ -103,7 +106,7 @@ class HostsComponentQuery extends QueryTemplatePaginated< }, pagination: generateTablePaginationOptions(activePage, limit), filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex, inspect: isInspected, }; return ( @@ -174,4 +177,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const HostsQuery = connect(makeMapStateToProps)(HostsComponentQuery); +export const HostsQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(HostsComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/overview/index.tsx b/x-pack/legacy/plugins/siem/public/containers/hosts/overview/index.tsx index a9223143462fd..5057e872b5313 100644 --- a/x-pack/legacy/plugins/siem/public/containers/hosts/overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/hosts/overview/index.tsx @@ -7,12 +7,14 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; -import chrome from 'ui/chrome'; import { connect } from 'react-redux'; +import { compose } from 'redux'; + import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, inputsSelectors, State } from '../../../store'; import { getDefaultFetchPolicy } from '../../helpers'; import { QueryTemplate, QueryTemplateProps } from '../../query_template'; +import { withKibana, WithKibanaProps } from '../../../lib/kibana'; import { HostOverviewQuery } from './host_overview.gql_query'; import { GetHostOverviewQuery, HostItem } from '../../../graphql/types'; @@ -40,8 +42,10 @@ export interface OwnProps extends QueryTemplateProps { endDate: number; } +type HostsOverViewProps = OwnProps & HostOverviewReduxProps & WithKibanaProps; + class HostOverviewByNameComponentQuery extends QueryTemplate< - OwnProps & HostOverviewReduxProps, + HostsOverViewProps, GetHostOverviewQuery.Query, GetHostOverviewQuery.Variables > { @@ -51,6 +55,7 @@ class HostOverviewByNameComponentQuery extends QueryTemplate< isInspected, children, hostName, + kibana, skip, sourceId, startDate, @@ -70,7 +75,7 @@ class HostOverviewByNameComponentQuery extends QueryTemplate< from: startDate, to: endDate, }, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), inspect: isInspected, }} > @@ -102,6 +107,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const HostOverviewByNameQuery = connect(makeMapStateToProps)( - HostOverviewByNameComponentQuery -); +export const HostOverviewByNameQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(HostOverviewByNameComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx b/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx index 003032493fca2..9576c66c4c9a5 100644 --- a/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/ip_overview/index.tsx @@ -9,10 +9,10 @@ import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetIpOverviewQuery, IpOverviewData } from '../../graphql/types'; import { networkModel, inputsModel, inputsSelectors, State } from '../../store'; +import { useUiSetting } from '../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplateProps } from '../query_template'; @@ -49,7 +49,7 @@ const IpOverviewComponentQuery = React.memo(DEFAULT_INDEX_KEY), inspect: isInspected, }} > diff --git a/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx index 20ed7fa991d15..501bc8472b5e2 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kpi_host_details/index.tsx @@ -8,11 +8,11 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { KpiHostDetailsData, GetKpiHostDetailsQuery } from '../../graphql/types'; import { inputsModel, inputsSelectors, State } from '../../store'; +import { useUiSetting } from '../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplateProps } from '../query_template'; @@ -51,7 +51,7 @@ const KpiHostDetailsComponentQuery = React.memo(DEFAULT_INDEX_KEY), inspect: isInspected, }} > diff --git a/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx index f8aa8aa38e8e1..32472ba6deedf 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kpi_hosts/index.tsx @@ -8,11 +8,11 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetKpiHostsQuery, KpiHostsData } from '../../graphql/types'; import { inputsModel, inputsSelectors, State } from '../../store'; +import { useUiSetting } from '../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplateProps } from '../query_template'; @@ -51,7 +51,7 @@ const KpiHostsComponentQuery = React.memo( to: endDate!, }, filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), inspect: isInspected, }} > diff --git a/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx index 269c3593d4d73..52b8814958ba0 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kpi_network/index.tsx @@ -8,11 +8,11 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetKpiNetworkQuery, KpiNetworkData } from '../../graphql/types'; import { inputsModel, inputsSelectors, State } from '../../store'; +import { useUiSetting } from '../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplateProps } from '../query_template'; @@ -51,7 +51,7 @@ const KpiNetworkComponentQuery = React.memo to: endDate!, }, filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), inspect: isInspected, }} > diff --git a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx index d06f4f6fbbbfa..6361f7abcf977 100644 --- a/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/kuery_autocompletion/index.tsx @@ -9,7 +9,7 @@ import { AutocompleteSuggestion, IIndexPattern, } from '../../../../../../../src/plugins/data/public'; -import { useKibanaPlugins } from '../../lib/compose/kibana_plugins'; +import { useKibana } from '../../lib/kibana'; type RendererResult = React.ReactElement | null; type RendererFunction = (args: RenderArgs) => Result; @@ -34,13 +34,13 @@ export const KueryAutocompletion = React.memo null ); const [suggestions, setSuggestions] = useState([]); - const plugins = useKibanaPlugins(); + const kibana = useKibana(); const loadSuggestions = async ( expression: string, cursorPosition: number, maxSuggestions?: number ) => { - const autocompletionProvider = plugins.data.autocomplete.getProvider('kuery'); + const autocompletionProvider = kibana.services.data.autocomplete.getProvider('kuery'); const config = { get: () => true, }; diff --git a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx index 592fe43b9873f..b5ebf3deacd0a 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_dns/index.tsx @@ -8,8 +8,8 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetNetworkDnsQuery, @@ -19,6 +19,7 @@ import { MatrixOverOrdinalHistogramData, } from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; @@ -53,7 +54,7 @@ export interface NetworkDnsComponentReduxProps { limit: number; } -type NetworkDnsProps = OwnProps & NetworkDnsComponentReduxProps; +type NetworkDnsProps = OwnProps & NetworkDnsComponentReduxProps & WithKibanaProps; export class NetworkDnsComponentQuery extends QueryTemplatePaginated< NetworkDnsProps, @@ -70,13 +71,14 @@ export class NetworkDnsComponentQuery extends QueryTemplatePaginated< id = ID, isInspected, isPtrIncluded, + kibana, limit, skip, sourceId, startDate, } = this.props; const variables: GetNetworkDnsQuery.Variables = { - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), inspect: isInspected, isPtrIncluded, @@ -172,7 +174,12 @@ const makeMapHistogramStateToProps = () => { return mapStateToProps; }; -export const NetworkDnsQuery = connect(makeMapStateToProps)(NetworkDnsComponentQuery); -export const NetworkDnsHistogramQuery = connect(makeMapHistogramStateToProps)( - NetworkDnsComponentQuery -); +export const NetworkDnsQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(NetworkDnsComponentQuery); + +export const NetworkDnsHistogramQuery = compose>( + connect(makeMapHistogramStateToProps), + withKibana +)(NetworkDnsComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/network_http/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_http/index.tsx index d76ab53b2de3a..bf4e64f63d559 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_http/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_http/index.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { @@ -19,6 +18,7 @@ import { PageInfoPaginated, } from '../../graphql/types'; import { inputsModel, inputsSelectors, networkModel, networkSelectors, State } from '../../store'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; @@ -52,7 +52,7 @@ export interface NetworkHttpComponentReduxProps { sort: NetworkHttpSortField; } -type NetworkHttpProps = OwnProps & NetworkHttpComponentReduxProps; +type NetworkHttpProps = OwnProps & NetworkHttpComponentReduxProps & WithKibanaProps; class NetworkHttpComponentQuery extends QueryTemplatePaginated< NetworkHttpProps, @@ -68,6 +68,7 @@ class NetworkHttpComponentQuery extends QueryTemplatePaginated< id = ID, ip, isInspected, + kibana, limit, skip, sourceId, @@ -75,7 +76,7 @@ class NetworkHttpComponentQuery extends QueryTemplatePaginated< startDate, } = this.props; const variables: GetNetworkHttpQuery.Variables = { - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), inspect: isInspected, ip, @@ -150,5 +151,6 @@ const makeMapStateToProps = () => { }; export const NetworkHttpQuery = compose>( - connect(makeMapStateToProps) + connect(makeMapStateToProps), + withKibana )(NetworkHttpComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx index b179745dafa51..bd1e1a002bbcd 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_countries/index.tsx @@ -10,7 +10,6 @@ import { Query } from 'react-apollo'; import { connect } from 'react-redux'; import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { FlowTargetSourceDest, @@ -20,6 +19,7 @@ import { PageInfoPaginated, } from '../../graphql/types'; import { inputsModel, inputsSelectors, networkModel, networkSelectors, State } from '../../store'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; @@ -54,7 +54,7 @@ export interface NetworkTopCountriesComponentReduxProps { sort: NetworkTopTablesSortField; } -type NetworkTopCountriesProps = OwnProps & NetworkTopCountriesComponentReduxProps; +type NetworkTopCountriesProps = OwnProps & NetworkTopCountriesComponentReduxProps & WithKibanaProps; class NetworkTopCountriesComponentQuery extends QueryTemplatePaginated< NetworkTopCountriesProps, @@ -68,6 +68,7 @@ class NetworkTopCountriesComponentQuery extends QueryTemplatePaginated< endDate, flowTarget, filterQuery, + kibana, id = `${ID}-${flowTarget}`, ip, isInspected, @@ -78,7 +79,7 @@ class NetworkTopCountriesComponentQuery extends QueryTemplatePaginated< sort, } = this.props; const variables: GetNetworkTopCountriesQuery.Variables = { - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), flowTarget, inspect: isInspected, @@ -154,5 +155,6 @@ const makeMapStateToProps = () => { }; export const NetworkTopCountriesQuery = compose>( - connect(makeMapStateToProps) + connect(makeMapStateToProps), + withKibana )(NetworkTopCountriesComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx index 239576a48c49f..f0f1f8257f29f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/network_top_n_flow/index.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { @@ -19,6 +18,7 @@ import { NetworkTopTablesSortField, PageInfoPaginated, } from '../../graphql/types'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { inputsModel, inputsSelectors, networkModel, networkSelectors, State } from '../../store'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; @@ -54,7 +54,7 @@ export interface NetworkTopNFlowComponentReduxProps { sort: NetworkTopTablesSortField; } -type NetworkTopNFlowProps = OwnProps & NetworkTopNFlowComponentReduxProps; +type NetworkTopNFlowProps = OwnProps & NetworkTopNFlowComponentReduxProps & WithKibanaProps; class NetworkTopNFlowComponentQuery extends QueryTemplatePaginated< NetworkTopNFlowProps, @@ -68,6 +68,7 @@ class NetworkTopNFlowComponentQuery extends QueryTemplatePaginated< endDate, flowTarget, filterQuery, + kibana, id = `${ID}-${flowTarget}`, ip, isInspected, @@ -78,7 +79,7 @@ class NetworkTopNFlowComponentQuery extends QueryTemplatePaginated< sort, } = this.props; const variables: GetNetworkTopNFlowQuery.Variables = { - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), flowTarget, inspect: isInspected, @@ -154,5 +155,6 @@ const makeMapStateToProps = () => { }; export const NetworkTopNFlowQuery = compose>( - connect(makeMapStateToProps) + connect(makeMapStateToProps), + withKibana )(NetworkTopNFlowComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx b/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx index 6ef0ee7b33589..36cadd7872cc8 100644 --- a/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/overview/overview_host/index.tsx @@ -8,10 +8,10 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { GetOverviewHostQuery, OverviewHostData } from '../../../graphql/types'; +import { useUiSetting } from '../../../lib/kibana'; import { inputsModel, inputsSelectors } from '../../../store/inputs'; import { State } from '../../../store'; import { createFilter, getDefaultFetchPolicy } from '../../helpers'; @@ -53,7 +53,7 @@ const OverviewHostComponentQuery = React.memo(DEFAULT_INDEX_KEY), inspect: isInspected, }} > diff --git a/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx b/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx index 677d96c10eee2..9e7d59de0e546 100644 --- a/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/overview/overview_network/index.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { GetOverviewNetworkQuery, OverviewNetworkData } from '../../../graphql/types'; +import { useUiSetting } from '../../../lib/kibana'; import { State } from '../../../store'; import { inputsModel, inputsSelectors } from '../../../store/inputs'; import { createFilter, getDefaultFetchPolicy } from '../../helpers'; @@ -55,7 +55,7 @@ export const OverviewNetworkComponentQuery = React.memo< to: endDate, }, filterQuery: createFilter(filterQuery), - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), inspect: isInspected, }} > diff --git a/x-pack/legacy/plugins/siem/public/containers/source/index.test.tsx b/x-pack/legacy/plugins/siem/public/containers/source/index.test.tsx index 7d6ac73357b15..d1a183a402e37 100644 --- a/x-pack/legacy/plugins/siem/public/containers/source/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/source/index.test.tsx @@ -11,11 +11,11 @@ import { MockedProvider } from 'react-apollo/test-utils'; import { wait } from '../../lib/helpers'; -import '../../mock/ui_settings'; - import { WithSource, indicesExistOrDataTemporarilyUnavailable } from '.'; import { mockBrowserFields, mockIndexFields, mocksSource } from './mock'; +jest.mock('../../lib/kibana'); + describe('Index Fields & Browser Fields', () => { test('Index Fields', async () => { mount( diff --git a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx index 8bddbd14a367c..94524dedbcd59 100644 --- a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx @@ -10,7 +10,7 @@ import { Query } from 'react-apollo'; import React, { useEffect, useState } from 'react'; import memoizeOne from 'memoize-one'; import { IIndexPattern } from 'src/plugins/data/public'; -import chrome from 'ui/chrome'; +import { useUiSetting$ } from '../../lib/kibana'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { IndexField, SourceQuery } from '../../graphql/types'; @@ -82,6 +82,7 @@ export const getBrowserFields = memoizeOne( ); export const WithSource = React.memo(({ children, sourceId }) => { + const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); return ( query={sourceQuery} @@ -89,20 +90,14 @@ export const WithSource = React.memo(({ children, sourceId }) = notifyOnNetworkStatusChange variables={{ sourceId, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex, }} > {({ data }) => children({ indicesExist: get('source.status.indicesExist', data), browserFields: getBrowserFields(get('source.status.indexFields', data)), - indexPattern: getIndexFields( - chrome - .getUiSettingsClient() - .get(DEFAULT_INDEX_KEY) - .join(), - get('source.status.indexFields', data) - ), + indexPattern: getIndexFields(defaultIndex.join(), get('source.status.indexFields', data)), }) } diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx index cfb3f8bd8dc77..721cfefe01780 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx @@ -8,10 +8,10 @@ import { getOr } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import React from 'react'; import { Query } from 'react-apollo'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { DetailItem, GetTimelineDetailsQuery } from '../../../graphql/types'; +import { useUiSetting } from '../../../lib/kibana'; import { timelineDetailsQuery } from './index.gql_query'; @@ -38,7 +38,7 @@ export const TimelineDetailsComponentQuery = React.memo( sourceId, indexName, eventId, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), }; return executeQuery ? ( diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx index 40ed3b3747c10..f7c2d067a29f5 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx @@ -8,9 +8,9 @@ import { getOr } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import React from 'react'; import { Query } from 'react-apollo'; - -import chrome from 'ui/chrome'; +import { compose } from 'redux'; import { connect } from 'react-redux'; + import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetTimelineQuery, @@ -20,6 +20,7 @@ import { TimelineItem, } from '../../graphql/types'; import { inputsModel, inputsSelectors, State } from '../../store'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { createFilter } from '../helpers'; import { QueryTemplate, QueryTemplateProps } from '../query_template'; @@ -50,7 +51,7 @@ export interface OwnProps extends QueryTemplateProps { sortField: SortField; fields: string[]; } -type TimelineQueryProps = OwnProps & TimelineQueryReduxProps; +type TimelineQueryProps = OwnProps & TimelineQueryReduxProps & WithKibanaProps; class TimelineQueryComponent extends QueryTemplate< TimelineQueryProps, @@ -71,6 +72,7 @@ class TimelineQueryComponent extends QueryTemplate< id, indexPattern, isInspected, + kibana, limit, fields, filterQuery, @@ -84,7 +86,8 @@ class TimelineQueryComponent extends QueryTemplate< pagination: { limit, cursor: null, tiebreaker: null }, sortField, defaultIndex: - indexPattern?.title.split(',') ?? chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + indexPattern?.title.split(',') ?? + kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), inspect: isInspected, }; return ( @@ -158,4 +161,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const TimelineQuery = connect(makeMapStateToProps)(TimelineQueryComponent); +export const TimelineQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(TimelineQueryComponent); diff --git a/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx b/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx index 7abe14ae745c5..3738355c8846e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx @@ -10,7 +10,6 @@ import { Query } from 'react-apollo'; import { connect } from 'react-redux'; import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { PageInfoPaginated, @@ -20,6 +19,7 @@ import { FlowTargetSourceDest, } from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; @@ -53,7 +53,7 @@ export interface TlsComponentReduxProps { sort: TlsSortField; } -type TlsProps = OwnProps & TlsComponentReduxProps; +type TlsProps = OwnProps & TlsComponentReduxProps & WithKibanaProps; class TlsComponentQuery extends QueryTemplatePaginated< TlsProps, @@ -70,6 +70,7 @@ class TlsComponentQuery extends QueryTemplatePaginated< id = ID, ip, isInspected, + kibana, limit, skip, sourceId, @@ -77,7 +78,7 @@ class TlsComponentQuery extends QueryTemplatePaginated< sort, } = this.props; const variables: GetTlsQuery.Variables = { - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), flowTarget, inspect: isInspected, @@ -152,6 +153,7 @@ const makeMapStateToProps = () => { }; }; -export const TlsQuery = compose>(connect(makeMapStateToProps))( - TlsComponentQuery -); +export const TlsQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(TlsComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx b/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx index e623110fb7178..520ade954eb5c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/uncommon_processes/index.tsx @@ -8,8 +8,8 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetUncommonProcessesQuery, @@ -17,6 +17,7 @@ import { UncommonProcessesEdges, } from '../../graphql/types'; import { hostsModel, hostsSelectors, inputsModel, State, inputsSelectors } from '../../store'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; @@ -48,7 +49,7 @@ export interface UncommonProcessesComponentReduxProps { limit: number; } -type UncommonProcessesProps = OwnProps & UncommonProcessesComponentReduxProps; +type UncommonProcessesProps = OwnProps & UncommonProcessesComponentReduxProps & WithKibanaProps; class UncommonProcessesComponentQuery extends QueryTemplatePaginated< UncommonProcessesProps, @@ -63,13 +64,14 @@ class UncommonProcessesComponentQuery extends QueryTemplatePaginated< filterQuery, id = ID, isInspected, + kibana, limit, skip, sourceId, startDate, } = this.props; const variables: GetUncommonProcessesQuery.Variables = { - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), inspect: isInspected, pagination: generateTablePaginationOptions(activePage, limit), @@ -142,4 +144,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const UncommonProcessesQuery = connect(makeMapStateToProps)(UncommonProcessesComponentQuery); +export const UncommonProcessesQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(UncommonProcessesComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/containers/users/index.tsx b/x-pack/legacy/plugins/siem/public/containers/users/index.tsx index ea6453b5c5dd4..ece73b7b10ff0 100644 --- a/x-pack/legacy/plugins/siem/public/containers/users/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/users/index.tsx @@ -8,8 +8,8 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { connect } from 'react-redux'; +import { compose } from 'redux'; -import chrome from 'ui/chrome'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetUsersQuery, @@ -19,6 +19,7 @@ import { UsersSortField, } from '../../graphql/types'; import { inputsModel, networkModel, networkSelectors, State, inputsSelectors } from '../../store'; +import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../helpers'; import { generateTablePaginationOptions } from '../../components/paginated_table/helpers'; import { QueryTemplatePaginated, QueryTemplatePaginatedProps } from '../query_template_paginated'; @@ -53,7 +54,7 @@ export interface UsersComponentReduxProps { sort: UsersSortField; } -type UsersProps = OwnProps & UsersComponentReduxProps; +type UsersProps = OwnProps & UsersComponentReduxProps & WithKibanaProps; class UsersComponentQuery extends QueryTemplatePaginated< UsersProps, @@ -70,6 +71,7 @@ class UsersComponentQuery extends QueryTemplatePaginated< id = ID, ip, isInspected, + kibana, limit, skip, sourceId, @@ -77,7 +79,7 @@ class UsersComponentQuery extends QueryTemplatePaginated< sort, } = this.props; const variables: GetUsersQuery.Variables = { - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), filterQuery: createFilter(filterQuery), flowTarget, inspect: isInspected, @@ -154,4 +156,7 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const UsersQuery = connect(makeMapStateToProps)(UsersComponentQuery); +export const UsersQuery = compose>( + connect(makeMapStateToProps), + withKibana +)(UsersComponentQuery); diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts index a1a608c6b5ffa..6dfde08058f7c 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts @@ -6,8 +6,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Direction as EuiDirection } from '@elastic/eui'; - export type Maybe = T | null; export interface PageInfoNote { @@ -54,7 +52,7 @@ export interface PaginationInput { export interface SortField { sortFieldId: string; - direction: Direction | EuiDirection; + direction: Direction; } export interface LastTimeDetails { @@ -66,25 +64,25 @@ export interface LastTimeDetails { export interface HostsSortField { field: HostsFields; - direction: Direction | EuiDirection; + direction: Direction; } export interface UsersSortField { field: UsersFields; - direction: Direction | EuiDirection; + direction: Direction; } export interface NetworkTopTablesSortField { field: NetworkTopTablesFields; - direction: Direction | EuiDirection; + direction: Direction; } export interface NetworkDnsSortField { field: NetworkDnsFields; - direction: Direction | EuiDirection; + direction: Direction; } export interface NetworkHttpSortField { @@ -94,7 +92,7 @@ export interface NetworkHttpSortField { export interface TlsSortField { field: TlsFields; - direction: Direction | EuiDirection; + direction: Direction; } export interface PageInfoTimeline { diff --git a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx index 091315df314d9..f5b595b0d01c6 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx @@ -10,7 +10,7 @@ import { DEFAULT_KBN_VERSION } from '../../common/constants'; import { useStateToaster } from '../components/toasters'; import { errorToToaster } from '../components/ml/api/error_to_toaster'; import { IndexPatternSavedObject } from '../components/ml_popover/types'; -import { useKibanaUiSetting } from '../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../lib/kibana'; import { getIndexPatterns } from './api/api'; import * as i18n from './translations'; @@ -21,7 +21,7 @@ export const useIndexPatterns = (refreshToggle = false): Return => { const [indexPatterns, setIndexPatterns] = useState([]); const [isLoading, setIsLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; diff --git a/x-pack/legacy/plugins/siem/public/lib/compose/__mocks__/kibana_core.ts b/x-pack/legacy/plugins/siem/public/lib/compose/__mocks__/kibana_core.ts deleted file mode 100644 index 7511f65dfb309..0000000000000 --- a/x-pack/legacy/plugins/siem/public/lib/compose/__mocks__/kibana_core.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; - -const npStart = createUiNewPlatformMock().npStart; - -export function useKibanaCore() { - return npStart.core; -} diff --git a/x-pack/legacy/plugins/siem/public/lib/compose/kibana_core.tsx b/x-pack/legacy/plugins/siem/public/lib/compose/kibana_core.tsx deleted file mode 100644 index f2fa261bf3eb4..0000000000000 --- a/x-pack/legacy/plugins/siem/public/lib/compose/kibana_core.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { createContext, useContext } from 'react'; -import { LegacyCoreStart } from 'src/core/public'; - -interface CoreMountContext { - core: LegacyCoreStart; -} - -// TODO: Replace CoreStart/CoreSetup with AppMountContext -// see: https://github.com/elastic/kibana/pull/41007 - -export const KibanaCoreContext = createContext({} as CoreMountContext['core']); - -export const KibanaCoreContextProvider: React.FC<{ core: CoreMountContext['core'] }> = props => ( - -); - -export function useKibanaCore() { - return useContext(KibanaCoreContext); -} diff --git a/x-pack/legacy/plugins/siem/public/lib/compose/kibana_plugins.tsx b/x-pack/legacy/plugins/siem/public/lib/compose/kibana_plugins.tsx deleted file mode 100644 index 7d1f1bc01edd3..0000000000000 --- a/x-pack/legacy/plugins/siem/public/lib/compose/kibana_plugins.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { createContext, useContext } from 'react'; -import { PluginsStart } from 'ui/new_platform/new_platform'; - -interface PluginsMountContext { - plugins: PluginsStart; -} - -// TODO: Replace CoreStart/CoreSetup with AppMountContext -// see: https://github.com/elastic/kibana/pull/41007 - -export const KibanaPluginsContext = createContext({} as PluginsMountContext['plugins']); - -export const KibanaPluginsContextProvider: React.FC<{ - plugins: PluginsMountContext['plugins']; -}> = props => ( - -); - -export function useKibanaPlugins() { - return useContext(KibanaPluginsContext); -} diff --git a/x-pack/legacy/plugins/siem/public/lib/kibana/__mocks__/index.ts b/x-pack/legacy/plugins/siem/public/lib/kibana/__mocks__/index.ts new file mode 100644 index 0000000000000..93fd37c4d14cb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/lib/kibana/__mocks__/index.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 { + createKibanaContextProviderMock, + createUseUiSettingMock, + createUseUiSetting$Mock, + createUseKibanaMock, + createWithKibanaMock, +} from '../../../mock/kibana_react'; + +export const useKibana = jest.fn(createUseKibanaMock()); +export const useUiSetting = jest.fn(createUseUiSettingMock()); +export const useUiSetting$ = jest.fn(createUseUiSetting$Mock()); +export const withKibana = jest.fn(createWithKibanaMock()); +export const KibanaContextProvider = jest.fn(createKibanaContextProviderMock()); diff --git a/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts b/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts new file mode 100644 index 0000000000000..96d9c8330d265 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + KibanaContextProvider, + KibanaReactContextValue, + useKibana, + useUiSetting, + useUiSetting$, + withKibana, +} from '../../../../../../../src/plugins/kibana_react/public'; +import { StartServices } from '../../apps/plugin'; + +export type KibanaContext = KibanaReactContextValue; +export interface WithKibanaProps { + kibana: KibanaContext; +} + +// eslint-disable-next-line react-hooks/rules-of-hooks +const typedUseKibana = () => useKibana(); + +export { + KibanaContextProvider, + typedUseKibana as useKibana, + useUiSetting, + useUiSetting$, + withKibana, +}; diff --git a/x-pack/legacy/plugins/siem/public/lib/settings/__mocks__/use_kibana_ui_setting.ts b/x-pack/legacy/plugins/siem/public/lib/settings/__mocks__/use_kibana_ui_setting.ts deleted file mode 100644 index 9f5639ce19997..0000000000000 --- a/x-pack/legacy/plugins/siem/public/lib/settings/__mocks__/use_kibana_ui_setting.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mockFrameworks, getMockKibanaUiSetting } from '../../../mock'; - -type GenericValue = string | boolean | number; - -export const useKibanaUiSetting = (key: string, defaultValue?: GenericValue) => { - return getMockKibanaUiSetting(mockFrameworks.default_UTC)(key); -}; diff --git a/x-pack/legacy/plugins/siem/public/lib/settings/use_kibana_ui_setting.ts b/x-pack/legacy/plugins/siem/public/lib/settings/use_kibana_ui_setting.ts deleted file mode 100644 index 0a89edb85e6ea..0000000000000 --- a/x-pack/legacy/plugins/siem/public/lib/settings/use_kibana_ui_setting.ts +++ /dev/null @@ -1,55 +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 { useCallback, useMemo } from 'react'; -// @ts-ignore: path dynamic for kibana -import { timezoneProvider } from 'ui/vis/lib/timezone'; - -import { DEFAULT_KBN_VERSION, DEFAULT_TIMEZONE_BROWSER } from '../../../common/constants'; -import { useKibanaCore } from '../compose/kibana_core'; -import { useObservable } from './use_observable'; - -type GenericValue = string | boolean | number; - -/** - * This hook behaves like a `useState` hook in that it provides a requested - * setting value (with an optional default) from the Kibana UI settings (also - * known as "advanced settings") and a setter to change that setting: - * - * ``` - * const [darkMode, setDarkMode] = useKibanaUiSetting('theme:darkMode'); - * ``` - * - * This is not just a static consumption of the value, but will reactively - * update when the underlying setting subscription of the `UiSettingsClient` - * notifies of a change. - * - * Unlike the `useState`, it doesn't give type guarantees for the value, - * because the underlying `UiSettingsClient` doesn't support that. - */ -export const useKibanaUiSetting = (key: string, defaultValue?: GenericValue) => { - const core = useKibanaCore(); - const uiSettingsClient = core.uiSettings; - const uiInjectedMetadata = core.injectedMetadata; - - if (key === DEFAULT_KBN_VERSION) { - return [uiInjectedMetadata.getKibanaVersion()]; - } - - /* eslint-disable react-hooks/rules-of-hooks */ - if (key === DEFAULT_TIMEZONE_BROWSER) { - return [useMemo(() => timezoneProvider(uiSettingsClient)(), [uiSettingsClient])]; - } - - const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [uiSettingsClient]); - const uiSetting = useObservable(uiSetting$); - const setUiSetting = useCallback((value: GenericValue) => uiSettingsClient.set(key, value), [ - uiSettingsClient, - ]); - /* eslint-enable react-hooks/rules-of-hooks */ - - return [uiSetting, setUiSetting]; -}; diff --git a/x-pack/legacy/plugins/siem/public/lib/settings/use_kibana_ui_settings.test.tsx b/x-pack/legacy/plugins/siem/public/lib/settings/use_kibana_ui_settings.test.tsx deleted file mode 100644 index 9af4759b25608..0000000000000 --- a/x-pack/legacy/plugins/siem/public/lib/settings/use_kibana_ui_settings.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import * as React from 'react'; - -import { DEFAULT_KBN_VERSION, DEFAULT_TIMEZONE_BROWSER } from '../../../common/constants'; -import { HookWrapper } from '../../mock/hook_wrapper'; -import { useKibanaCore } from '../compose/kibana_core'; -import { useKibanaUiSetting } from './use_kibana_ui_setting'; -import { mount } from 'enzyme'; - -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - injectedMetadata: { - getKibanaVersion: () => '8.0.0', - }, - uiSettings: { - get$: () => 'world', - }, -})); - -jest.mock('ui/vis/lib/timezone', () => ({ - timezoneProvider: () => () => 'America/New_York', -})); - -jest.mock('./use_observable', () => ({ - useObservable: (val: string) => val, -})); - -describe('useKibanaUiSetting', () => { - test('getKibanaVersion', () => { - const wrapper = mount( useKibanaUiSetting(DEFAULT_KBN_VERSION)} />); - expect(wrapper.text()).toEqual('["8.0.0"]'); - }); - - test('getTimezone', () => { - const wrapper = mount( - useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER)} /> - ); - expect(wrapper.text()).toEqual('["America/New_York"]'); - }); - - test('get any ui settings', () => { - const wrapper = mount( useKibanaUiSetting('hello')} />); - expect(wrapper.text()).toEqual('["world",null]'); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/lib/settings/use_observable.ts b/x-pack/legacy/plugins/siem/public/lib/settings/use_observable.ts deleted file mode 100644 index 536b6b2723ae0..0000000000000 --- a/x-pack/legacy/plugins/siem/public/lib/settings/use_observable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useEffect, useState } from 'react'; -import { Observable } from 'rxjs'; - -export function useObservable(observable$: Observable): T | undefined; -export function useObservable(observable$: Observable, initialValue: T): T; -export function useObservable(observable$: Observable, initialValue?: T): T | undefined { - const [value, update] = useState(initialValue); - - useEffect(() => { - const s = observable$.subscribe(update); - return () => s.unsubscribe(); - }, [observable$]); - - return value; -} diff --git a/x-pack/legacy/plugins/siem/public/lib/theme/use_eui_theme.tsx b/x-pack/legacy/plugins/siem/public/lib/theme/use_eui_theme.tsx index b1defcb34066d..1696001203bc8 100644 --- a/x-pack/legacy/plugins/siem/public/lib/theme/use_eui_theme.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/theme/use_eui_theme.tsx @@ -8,9 +8,9 @@ import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { DEFAULT_DARK_MODE } from '../../../common/constants'; -import { useKibanaUiSetting } from '../settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../kibana'; export const useEuiTheme = () => { - const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE); + const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); return darkMode ? darkTheme : lightTheme; }; diff --git a/x-pack/legacy/plugins/siem/public/mock/global_state.ts b/x-pack/legacy/plugins/siem/public/mock/global_state.ts index 750d5292950be..31e203d080322 100644 --- a/x-pack/legacy/plugins/siem/public/mock/global_state.ts +++ b/x-pack/legacy/plugins/siem/public/mock/global_state.ts @@ -183,6 +183,7 @@ export const mockGlobalState: State = { }, timelineById: { test: { + deletedEventIds: [], id: 'test', savedObjectId: null, columns: defaultHeaders, @@ -194,16 +195,21 @@ export const mockGlobalState: State = { historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: false, + showRowRenderers: true, + showCheckboxes: false, pinnedEventIds: {}, pinnedEventsSaveObject: {}, itemsPerPageOptions: [5, 10, 20], diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_core.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_core.ts new file mode 100644 index 0000000000000..cf3523a6260bb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/mock/kibana_core.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; + +export const createKibanaCoreSetupMock = () => createUiNewPlatformMock().npSetup.core; +export const createKibanaPluginsSetupMock = () => createUiNewPlatformMock().npSetup.plugins; + +export const createKibanaCoreStartMock = () => createUiNewPlatformMock().npStart.core; +export const createKibanaPluginsStartMock = () => createUiNewPlatformMock().npStart.plugins; diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts new file mode 100644 index 0000000000000..15944df1822b3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts @@ -0,0 +1,95 @@ +/* + * 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 { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; + +import { + DEFAULT_SIEM_TIME_RANGE, + DEFAULT_SIEM_REFRESH_INTERVAL, + DEFAULT_INDEX_KEY, + DEFAULT_DATE_FORMAT, + DEFAULT_DATE_FORMAT_TZ, + DEFAULT_DARK_MODE, + DEFAULT_TIME_RANGE, + DEFAULT_REFRESH_RATE_INTERVAL, + DEFAULT_FROM, + DEFAULT_TO, + DEFAULT_INTERVAL_PAUSE, + DEFAULT_INTERVAL_VALUE, + DEFAULT_TIMEZONE_BROWSER, +} from '../../common/constants'; +import { defaultIndexPattern } from '../../default_index_pattern'; +import { createKibanaCoreStartMock, createKibanaPluginsStartMock } from './kibana_core'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const mockUiSettings: Record = { + [DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' }, + [DEFAULT_REFRESH_RATE_INTERVAL]: { pause: false, value: 0 }, + [DEFAULT_SIEM_TIME_RANGE]: { + from: DEFAULT_FROM, + to: DEFAULT_TO, + }, + [DEFAULT_SIEM_REFRESH_INTERVAL]: { + pause: DEFAULT_INTERVAL_PAUSE, + value: DEFAULT_INTERVAL_VALUE, + }, + [DEFAULT_INDEX_KEY]: defaultIndexPattern, + [DEFAULT_DATE_FORMAT_TZ]: 'UTC', + [DEFAULT_TIMEZONE_BROWSER]: 'America/New_York', + [DEFAULT_DATE_FORMAT]: 'MMM D, YYYY @ HH:mm:ss.SSS', + [DEFAULT_DARK_MODE]: false, +}; + +export const createUseUiSettingMock = () => ( + key: string, + defaultValue?: T +): T => { + const result = mockUiSettings[key]; + + if (typeof result != null) return result; + + if (defaultValue != null) { + return defaultValue; + } + + throw new Error(`Unexpected config key: ${key}`); +}; + +export const createUseUiSetting$Mock = () => { + const useUiSettingMock = createUseUiSettingMock(); + + return ( + key: string, + defaultValue?: T + ): [T, () => void] | undefined => [useUiSettingMock(key, defaultValue), jest.fn()]; +}; + +export const createUseKibanaMock = () => { + const services = { ...createKibanaCoreStartMock(), ...createKibanaPluginsStartMock() }; + + return () => ({ services }); +}; + +export const createWithKibanaMock = () => { + const kibana = createUseKibanaMock()(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (Component: any) => (props: any) => { + return React.createElement(Component, { ...props, kibana }); + }; +}; + +export const createKibanaContextProviderMock = () => { + const kibana = createUseKibanaMock()(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return ({ services, ...rest }: any) => + React.createElement(KibanaContextProvider, { + ...rest, + services: { ...kibana.services, ...services }, + }); +}; diff --git a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx index d4c06d998c5a2..6c0a85e3ef778 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -17,12 +17,9 @@ import { Store } from 'redux'; import { BehaviorSubject } from 'rxjs'; import { ThemeProvider } from 'styled-components'; -import { CoreStart } from 'src/core/public'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; - import { createStore, State } from '../store'; import { mockGlobalState } from './global_state'; -import { mockUiSettings } from './ui_settings'; +import { createKibanaContextProviderMock } from './kibana_react'; jest.mock('ui/new_platform'); @@ -41,29 +38,6 @@ export const apolloClient = new ApolloClient({ export const apolloClientObservable = new BehaviorSubject(apolloClient); -const services = { - uiSettings: mockUiSettings, - savedObjects: {} as CoreStart['savedObjects'], - notifications: {} as CoreStart['notifications'], - docLinks: { - links: { - query: { - kueryQuerySyntax: '', - }, - }, - } as CoreStart['docLinks'], - http: {} as CoreStart['http'], - overlays: {} as CoreStart['overlays'], - storage: { - get: () => {}, - }, - data: { - query: { - savedQueries: {}, - }, - }, -}; - const localStorageMock = () => { let store: Record = {}; @@ -84,11 +58,13 @@ Object.defineProperty(window, 'localStorage', { value: localStorageMock(), }); +const MockKibanaContextProvider = createKibanaContextProviderMock(); + /** A utility for wrapping children in the providers required to run most tests */ export const TestProviders = React.memo( ({ children, store = createStore(state, apolloClientObservable), onDragEnd = jest.fn() }) => ( - + ({ eui: euiDarkVars, darkMode: true })}> @@ -96,7 +72,7 @@ export const TestProviders = React.memo( - + ) ); diff --git a/x-pack/legacy/plugins/siem/public/mock/ui_settings.ts b/x-pack/legacy/plugins/siem/public/mock/ui_settings.ts deleted file mode 100644 index 6c6411c6bda53..0000000000000 --- a/x-pack/legacy/plugins/siem/public/mock/ui_settings.ts +++ /dev/null @@ -1,75 +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 chrome from 'ui/chrome'; -import { - DEFAULT_SIEM_TIME_RANGE, - DEFAULT_SIEM_REFRESH_INTERVAL, - DEFAULT_INDEX_KEY, - DEFAULT_DATE_FORMAT, - DEFAULT_DATE_FORMAT_TZ, - DEFAULT_DARK_MODE, - DEFAULT_TIME_RANGE, - DEFAULT_REFRESH_RATE_INTERVAL, - DEFAULT_FROM, - DEFAULT_TO, - DEFAULT_INTERVAL_PAUSE, - DEFAULT_INTERVAL_VALUE, -} from '../../common/constants'; -import { defaultIndexPattern } from '../../default_index_pattern'; - -chrome.getUiSettingsClient().get.mockImplementation((key: string) => { - switch (key) { - case DEFAULT_TIME_RANGE: - return { from: 'now-15m', to: 'now', mode: 'quick' }; - case DEFAULT_REFRESH_RATE_INTERVAL: - return { pause: false, value: 0 }; - case DEFAULT_SIEM_TIME_RANGE: - return { - from: DEFAULT_FROM, - to: DEFAULT_TO, - }; - case DEFAULT_SIEM_REFRESH_INTERVAL: - return { - pause: DEFAULT_INTERVAL_PAUSE, - value: DEFAULT_INTERVAL_VALUE, - }; - case DEFAULT_INDEX_KEY: - return defaultIndexPattern; - case DEFAULT_DATE_FORMAT_TZ: - return 'Asia/Taipei'; - case DEFAULT_DATE_FORMAT: - return 'MMM D, YYYY @ HH:mm:ss.SSS'; - case DEFAULT_DARK_MODE: - return false; - default: - throw new Error(`Unexpected config key: ${key}`); - } -}); - -export interface MockNpSetUp { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - core: { uiSettings: any }; -} - -type Config = - | 'query:allowLeadingWildcards' - | 'query:queryString:options' - | 'courier:ignoreFilterIfFieldNotInIndex' - | 'dateFormat:tz'; - -export const mockUiSettings = { - get: (item: Config) => { - return mockUiSettings[item]; - }, - get$: () => ({ - subscribe: jest.fn(), - }), - 'query:allowLeadingWildcards': true, - 'query:queryString:options': {}, - 'courier:ignoreFilterIfFieldNotInIndex': true, - 'dateFormat:tz': 'Browser', -}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/activity_monitor/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/columns.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/activity_monitor/columns.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/columns.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/activity_monitor/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/activity_monitor/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx new file mode 100644 index 0000000000000..c0ed58daeca7f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; + +import { updateSignalStatus } from '../../../../containers/detection_engine/signals/api'; +import { SendSignalsToTimelineActionProps, UpdateSignalStatusActionProps } from './types'; +import { TimelineNonEcsData } from '../../../../graphql/types'; + +export const getUpdateSignalsQuery = (eventIds: Readonly) => { + return { + query: { + bool: { + filter: { + terms: { + _id: [...eventIds], + }, + }, + }, + }, + }; +}; + +export const getFilterAndRuleBounds = ( + data: TimelineNonEcsData[][] +): [string[], number, number] => { + const stringFilter = data?.[0].filter(d => d.field === 'signal.rule.filters')?.[0]?.value ?? []; + + const eventTimes = data + .flatMap(signal => signal.filter(d => d.field === 'signal.original_time')?.[0]?.value ?? []) + .map(d => moment(d)); + + return [stringFilter, moment.min(eventTimes).valueOf(), moment.max(eventTimes).valueOf()]; +}; + +export const updateSignalStatusAction = async ({ + query, + signalIds, + status, + setEventsLoading, + setEventsDeleted, + kbnVersion, +}: UpdateSignalStatusActionProps) => { + try { + setEventsLoading({ eventIds: signalIds, isLoading: true }); + + const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); + + await updateSignalStatus({ + query: queryObject, + status, + kbnVersion, + }); + // TODO: Only delete those that were successfully updated from updatedRules + setEventsDeleted({ eventIds: signalIds, isDeleted: true }); + } catch (e) { + // TODO: Show error toasts + } finally { + setEventsLoading({ eventIds: signalIds, isLoading: false }); + } +}; + +export const sendSignalsToTimelineAction = async ({ + createTimeline, + data, +}: SendSignalsToTimelineActionProps) => { + const stringFilter = data[0].filter(d => d.field === 'signal.rule.filters')?.[0]?.value ?? []; + + // TODO: Switch to using from/to when adding dateRange + // const [stringFilters, from, to] = getFilterAndRuleBounds(data); + const parsedFilter = stringFilter.map(sf => JSON.parse(sf)); + createTimeline({ + id: 'timeline-1', + filters: parsedFilter, + dateRange: undefined, // TODO + kqlQuery: undefined, // TODO + }); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx new file mode 100644 index 0000000000000..ea9a3ccef05b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx @@ -0,0 +1,223 @@ +/* + * 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 { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import React from 'react'; + +import { esFilters } from '../../../../../../../../../src/plugins/data/common/es_query'; +import { ColumnHeader } from '../../../../components/timeline/body/column_headers/column_header'; +import { TimelineAction, TimelineActionProps } from '../../../../components/timeline/body/actions'; +import { defaultColumnHeaderType } from '../../../../components/timeline/body/column_headers/default_headers'; +import { + DEFAULT_COLUMN_MIN_WIDTH, + DEFAULT_DATE_COLUMN_MIN_WIDTH, +} from '../../../../components/timeline/body/helpers'; +import { SubsetTimelineModel, timelineDefaults } from '../../../../store/timeline/model'; + +import { FILTER_OPEN } from './signals_filter_group'; +import { sendSignalsToTimelineAction, updateSignalStatusAction } from './actions'; +import * as i18n from './translations'; +import { CreateTimeline, SetEventsDeletedProps, SetEventsLoadingProps } from './types'; + +export const signalsOpenFilters: esFilters.Filter[] = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.status', + params: { + query: 'open', + }, + }, + query: { + match_phrase: { + 'signal.status': 'open', + }, + }, + }, +]; + +export const signalsClosedFilters: esFilters.Filter[] = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.status', + params: { + query: 'closed', + }, + }, + query: { + match_phrase: { + 'signal.status': 'closed', + }, + }, + }, +]; + +export const buildSignalsRuleIdFilter = (ruleId: string): esFilters.Filter[] => [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.rule.id', + params: { + query: ruleId, + }, + }, + query: { + match_phrase: { + 'signal.rule.id': ruleId, + }, + }, + }, +]; + +export const signalsHeaders: ColumnHeader[] = [ + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.name', + label: i18n.SIGNALS_HEADERS_RULE, + width: DEFAULT_COLUMN_MIN_WIDTH, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.type', + label: i18n.SIGNALS_HEADERS_METHOD, + width: 80, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.severity', + label: i18n.SIGNALS_HEADERS_SEVERITY, + width: 80, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.risk_score', + label: i18n.SIGNALS_HEADERS_RISK_SCORE, + width: 120, + }, + { + category: 'event', + columnHeaderType: defaultColumnHeaderType, + id: 'event.action', + type: 'string', + aggregatable: true, + width: 140, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'event.category', + width: 150, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'host.name', + width: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'user.name', + width: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'source.ip', + width: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'destination.ip', + width: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: '@timestamp', + width: DEFAULT_DATE_COLUMN_MIN_WIDTH, + }, +]; + +export const signalsDefaultModel: SubsetTimelineModel = { + ...timelineDefaults, + columns: signalsHeaders, + showCheckboxes: true, + showRowRenderers: false, +}; + +export const requiredFieldsForActions = [ + '@timestamp', + 'signal.original_time', + 'signal.rule.filters', + 'signal.rule.from', + 'signal.rule.language', + 'signal.rule.query', + 'signal.rule.to', + 'signal.rule.id', +]; + +export const getSignalsActions = ({ + setEventsLoading, + setEventsDeleted, + createTimeline, + status, + kbnVersion, +}: { + setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; + setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + createTimeline: CreateTimeline; + status: 'open' | 'closed'; + kbnVersion: string; +}): TimelineAction[] => [ + { + getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => ( + + sendSignalsToTimelineAction({ createTimeline, data: [data] })} + iconType="tableDensityNormal" + aria-label="Next" + /> + + ), + id: 'sendSignalToTimeline', + width: 26, + }, + { + getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => ( + + + updateSignalStatusAction({ + signalIds: [eventId], + status, + setEventsLoading, + setEventsDeleted, + kbnVersion, + }) + } + iconType={status === FILTER_OPEN ? 'indexOpen' : 'indexClose'} + aria-label="Next" + /> + + ), + id: 'updateSignalStatus', + width: 26, + }, +]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx new file mode 100644 index 0000000000000..aeb5e677374fc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -0,0 +1,344 @@ +/* + * 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, { useCallback, useEffect, useMemo, useState } from 'react'; +import { connect } from 'react-redux'; +import { ActionCreator } from 'typescript-fsa'; +import { SignalsUtilityBar } from './signals_utility_bar'; +import { StatefulEventsViewer } from '../../../../components/events_viewer'; +import * as i18n from './translations'; +import { + getSignalsActions, + requiredFieldsForActions, + signalsClosedFilters, + signalsDefaultModel, + signalsOpenFilters, +} from './default_config'; +import { timelineActions, timelineSelectors } from '../../../../store/timeline'; +import { timelineDefaults, TimelineModel } from '../../../../store/timeline/model'; +import { + FILTER_CLOSED, + FILTER_OPEN, + SignalFilterOption, + SignalsTableFilterGroup, +} from './signals_filter_group'; +import { useKibana, useUiSetting$ } from '../../../../lib/kibana'; +import { DEFAULT_KBN_VERSION, DEFAULT_SIGNALS_INDEX } from '../../../../../common/constants'; +import { defaultHeaders } from '../../../../components/timeline/body/column_headers/default_headers'; +import { ColumnHeader } from '../../../../components/timeline/body/column_headers/column_header'; +import { esFilters, esQuery } from '../../../../../../../../../src/plugins/data/common/es_query'; +import { TimelineNonEcsData } from '../../../../graphql/types'; +import { inputsSelectors, SerializedFilterQuery, State } from '../../../../store'; +import { sendSignalsToTimelineAction, updateSignalStatusAction } from './actions'; +import { + CreateTimelineProps, + SendSignalsToTimeline, + SetEventsDeletedProps, + SetEventsLoadingProps, + UpdateSignalsStatus, + UpdateSignalsStatusProps, +} from './types'; +import { inputsActions } from '../../../../store/inputs'; +import { combineQueries } from '../../../../components/timeline/helpers'; +import { useFetchIndexPatterns } from '../../../../containers/detection_engine/rules/fetch_index_patterns'; +import { InputsRange } from '../../../../store/inputs/model'; +import { Query } from '../../../../../../../../../src/plugins/data/common/query'; + +const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; + +interface ReduxProps { + globalQuery: Query; + globalFilters: esFilters.Filter[]; + deletedEventIds: string[]; + isSelectAllChecked: boolean; + loadingEventIds: string[]; + selectedEventIds: Readonly>; +} + +interface DispatchProps { + createTimeline: ActionCreator<{ + dateRange?: { + start: number; + end: number; + }; + filters?: esFilters.Filter[]; + id: string; + kqlQuery?: { + filterQuery: SerializedFilterQuery | null; + }; + columns: ColumnHeader[]; + show?: boolean; + }>; + clearEventsDeleted?: ActionCreator<{ id: string }>; + clearEventsLoading?: ActionCreator<{ id: string }>; + clearSelected?: ActionCreator<{ id: string }>; + removeTimelineLinkTo: ActionCreator<{}>; + setEventsDeleted?: ActionCreator<{ + id: string; + eventIds: string[]; + isDeleted: boolean; + }>; + setEventsLoading?: ActionCreator<{ + id: string; + eventIds: string[]; + isLoading: boolean; + }>; +} + +interface OwnProps { + defaultFilters?: esFilters.Filter[]; + from: number; + to: number; +} + +type SignalsTableComponentProps = OwnProps & ReduxProps & DispatchProps; + +export const SignalsTableComponent = React.memo( + ({ + createTimeline, + clearEventsDeleted, + clearEventsLoading, + clearSelected, + defaultFilters = [], + from, + globalFilters, + globalQuery, + isSelectAllChecked, + loadingEventIds, + removeTimelineLinkTo, + selectedEventIds, + setEventsDeleted, + setEventsLoading, + to, + }) => { + const [selectAll, setSelectAll] = useState(false); + + const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([ + `${DEFAULT_SIGNALS_INDEX}-default`, + ]); // TODO Get from new FrankInspired XavierHook + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + const kibana = useKibana(); + + const getGlobalQuery = useCallback(() => { + if (browserFields != null && indexPatterns != null) { + return combineQueries({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + dataProviders: [], + indexPattern: indexPatterns, + browserFields, + filters: globalFilters, + kqlQuery: globalQuery, + kqlMode: globalQuery.language, + start: from, + end: to, + isEventViewer: true, + }); + } + return null; + }, [browserFields, globalFilters, globalQuery, indexPatterns, to, from]); + + // Callback for creating a new timeline -- utilized by row/batch actions + const createTimelineCallback = useCallback( + ({ id, kqlQuery, filters, dateRange }: CreateTimelineProps) => { + removeTimelineLinkTo({}); + createTimeline({ id, columns: defaultHeaders, show: true, filters, dateRange, kqlQuery }); + }, + [createTimeline, removeTimelineLinkTo] + ); + + const setEventsLoadingCallback = useCallback( + ({ eventIds, isLoading }: SetEventsLoadingProps) => { + setEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isLoading }); + }, + [setEventsLoading, SIGNALS_PAGE_TIMELINE_ID] + ); + + const setEventsDeletedCallback = useCallback( + ({ eventIds, isDeleted }: SetEventsDeletedProps) => { + setEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isDeleted }); + }, + [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] + ); + + // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar + useEffect(() => { + if (!isSelectAllChecked) { + setShowClearSelectionAction(false); + } else { + setSelectAll(false); + } + }, [isSelectAllChecked]); + + // Callback for when open/closed filter changes + const onFilterGroupChangedCallback = useCallback( + (newFilterGroup: SignalFilterOption) => { + clearEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID }); + clearEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID }); + clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + setFilterGroup(newFilterGroup); + }, + [setFilterGroup] + ); + + // Callback for clearing entire selection from utility bar + const clearSelectionCallback = useCallback(() => { + clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + setSelectAll(false); + setShowClearSelectionAction(false); + }, [clearSelected, setShowClearSelectionAction]); + + // Callback for selecting all events on all pages from utility bar + // Dispatches to stateful_body's selectAll via TimelineTypeContext props + // as scope of response data required to actually set selectedEvents + const selectAllCallback = useCallback(() => { + setSelectAll(true); + setShowClearSelectionAction(true); + }, [setShowClearSelectionAction]); + + const updateSignalsStatusCallback: UpdateSignalsStatus = useCallback( + async ({ signalIds, status }: UpdateSignalsStatusProps) => { + await updateSignalStatusAction({ + query: showClearSelectionAction ? getGlobalQuery()?.filterQuery : undefined, + signalIds: Object.keys(selectedEventIds), + status, + setEventsDeleted: setEventsDeletedCallback, + setEventsLoading: setEventsLoadingCallback, + kbnVersion, + }); + }, + [ + getGlobalQuery, + selectedEventIds, + setEventsDeletedCallback, + setEventsLoadingCallback, + showClearSelectionAction, + ] + ); + const sendSignalsToTimelineCallback: SendSignalsToTimeline = useCallback(async () => { + await sendSignalsToTimelineAction({ + createTimeline: createTimelineCallback, + data: Object.values(selectedEventIds), + }); + }, [selectedEventIds, setEventsDeletedCallback, setEventsLoadingCallback]); + + // Callback for creating the SignalUtilityBar which receives totalCount from EventsViewer component + const utilityBarCallback = useCallback( + (totalCount: number) => { + return ( + 0} + clearSelection={clearSelectionCallback} + isFilteredToOpen={filterGroup === FILTER_OPEN} + selectAll={selectAllCallback} + selectedEventIds={selectedEventIds} + sendSignalsToTimeline={sendSignalsToTimelineCallback} + showClearSelection={showClearSelectionAction} + totalCount={totalCount} + updateSignalsStatus={updateSignalsStatusCallback} + /> + ); + }, + [ + clearSelectionCallback, + filterGroup, + loadingEventIds.length, + selectAllCallback, + selectedEventIds, + showClearSelectionAction, + ] + ); + + // Send to Timeline / Update Signal Status Actions for each table row + const additionalActions = useMemo( + () => + getSignalsActions({ + createTimeline: createTimelineCallback, + setEventsLoading: setEventsLoadingCallback, + setEventsDeleted: setEventsDeletedCallback, + status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, + kbnVersion, + }), + [createTimelineCallback, filterGroup, kbnVersion] + ); + + const defaultIndices = useMemo(() => [`${DEFAULT_SIGNALS_INDEX}-default`], [ + `${DEFAULT_SIGNALS_INDEX}-default`, + ]); + const defaultFiltersMemo = useMemo( + () => [ + ...defaultFilters, + ...(filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters), + ], + [defaultFilters, filterGroup] + ); + + const timelineTypeContext = useMemo( + () => ({ + documentType: i18n.SIGNALS_DOCUMENT_TYPE, + footerText: i18n.TOTAL_COUNT_OF_SIGNALS, + loadingText: i18n.LOADING_SIGNALS, + queryFields: requiredFieldsForActions, + timelineActions: additionalActions, + title: i18n.SIGNALS_TABLE_TITLE, + selectAll, + }), + [additionalActions, selectAll] + ); + + return ( + + } + id={SIGNALS_PAGE_TIMELINE_ID} + start={from} + timelineTypeContext={timelineTypeContext} + utilityBar={utilityBarCallback} + /> + ); + } +); + +SignalsTableComponent.displayName = 'SignalsTableComponent'; + +const makeMapStateToProps = () => { + const getTimeline = timelineSelectors.getTimelineByIdSelector(); + const getGlobalInputs = inputsSelectors.globalSelector(); + const mapStateToProps = (state: State) => { + const timeline: TimelineModel = + getTimeline(state, SIGNALS_PAGE_TIMELINE_ID) ?? timelineDefaults; + const { deletedEventIds, isSelectAllChecked, loadingEventIds, selectedEventIds } = timeline; + + const globalInputs: InputsRange = getGlobalInputs(state); + const { query, filters } = globalInputs; + + return { + globalQuery: query, + globalFilters: filters, + deletedEventIds, + isSelectAllChecked, + loadingEventIds, + selectedEventIds, + }; + }; + return mapStateToProps; +}; + +export const SignalsTable = connect(makeMapStateToProps, { + removeTimelineLinkTo: inputsActions.removeTimelineLinkTo, + clearSelected: timelineActions.clearSelected, + setEventsLoading: timelineActions.setEventsLoading, + clearEventsLoading: timelineActions.clearEventsLoading, + setEventsDeleted: timelineActions.setEventsDeleted, + clearEventsDeleted: timelineActions.clearEventsDeleted, + createTimeline: timelineActions.createTimeline, +})(SignalsTableComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.tsx new file mode 100644 index 0000000000000..2a47cb5f1b055 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import * as i18n from '../translations'; + +export const FILTER_OPEN = 'open'; +export const FILTER_CLOSED = 'closed'; +export type SignalFilterOption = typeof FILTER_OPEN | typeof FILTER_CLOSED; + +export const SignalsTableFilterGroup = React.memo( + ({ + onFilterGroupChanged, + }: { + onFilterGroupChanged: (filterGroup: SignalFilterOption) => void; + }) => { + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + + const onClickOpenFilterCallback = useCallback(() => { + setFilterGroup(FILTER_OPEN); + onFilterGroupChanged(FILTER_OPEN); + }, [setFilterGroup, onFilterGroupChanged]); + + const onClickCloseFilterCallback = useCallback(() => { + setFilterGroup(FILTER_CLOSED); + onFilterGroupChanged(FILTER_CLOSED); + }, [setFilterGroup, onFilterGroupChanged]); + + return ( + + + {i18n.OPEN_SIGNALS} + + + + {i18n.CLOSED_SIGNALS} + + + ); + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx new file mode 100644 index 0000000000000..bbbc7728e36a5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiContextMenuItem } from '@elastic/eui'; +import React from 'react'; +import * as i18n from './translations'; +import { TimelineNonEcsData } from '../../../../../graphql/types'; +import { SendSignalsToTimeline, UpdateSignalsStatus } from '../types'; +import { FILTER_CLOSED, FILTER_OPEN } from '../signals_filter_group'; + +/** + * Returns ViewInTimeline / UpdateSignalStatus actions to be display within an EuiContextMenuPanel + * + * @param areEventsLoading are any events loading + * @param allEventsSelected are all events on all pages selected + * @param selectedEventIds + * @param updateSignalsStatus function for updating signal status + * @param sendSignalsToTimeline function for sending signals to timeline + * @param closePopover + * @param isFilteredToOpen currently selected filter options + */ +export const getBatchItems = ( + areEventsLoading: boolean, + allEventsSelected: boolean, + selectedEventIds: Readonly>, + updateSignalsStatus: UpdateSignalsStatus, + sendSignalsToTimeline: SendSignalsToTimeline, + closePopover: () => void, + isFilteredToOpen: boolean +) => { + const allDisabled = areEventsLoading || Object.keys(selectedEventIds).length === 0; + const sendToTimelineDisabled = allEventsSelected || uniqueRuleCount(selectedEventIds) > 1; + const filterString = isFilteredToOpen + ? i18n.BATCH_ACTION_CLOSE_SELECTED + : i18n.BATCH_ACTION_OPEN_SELECTED; + + return [ + { + closePopover(); + sendSignalsToTimeline(); + }} + > + {i18n.BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE} + , + + { + closePopover(); + await updateSignalsStatus({ + signalIds: Object.keys(selectedEventIds), + status: isFilteredToOpen ? FILTER_CLOSED : FILTER_OPEN, + }); + }} + > + {filterString} + , + ]; +}; + +/** + * Returns the number of unique rules for a given list of signals + * + * @param signals + */ +export const uniqueRuleCount = ( + signals: Readonly> +): number => { + const ruleIds = Object.values(signals).flatMap( + data => data.find(d => d.field === 'signal.rule.id')?.value + ); + + return Array.from(new Set(ruleIds)).length; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx new file mode 100644 index 0000000000000..72b250470d19b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx @@ -0,0 +1,135 @@ +/* + * 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, { useCallback } from 'react'; +import { EuiContextMenuPanel } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../../../components/detection_engine/utility_bar'; +import * as i18n from './translations'; +import { getBatchItems } from './batch_actions'; +import { useUiSetting$ } from '../../../../../lib/kibana'; +import { DEFAULT_NUMBER_FORMAT } from '../../../../../../common/constants'; +import { TimelineNonEcsData } from '../../../../../graphql/types'; +import { SendSignalsToTimeline, UpdateSignalsStatus } from '../types'; + +interface SignalsUtilityBarProps { + areEventsLoading: boolean; + clearSelection: () => void; + isFilteredToOpen: boolean; + selectAll: () => void; + selectedEventIds: Readonly>; + sendSignalsToTimeline: SendSignalsToTimeline; + showClearSelection: boolean; + totalCount: number; + updateSignalsStatus: UpdateSignalsStatus; +} + +export const SignalsUtilityBar = React.memo( + ({ + areEventsLoading, + clearSelection, + totalCount, + selectedEventIds, + isFilteredToOpen, + selectAll, + showClearSelection, + updateSignalsStatus, + sendSignalsToTimeline, + }) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + + const getBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [ + areEventsLoading, + selectedEventIds, + updateSignalsStatus, + sendSignalsToTimeline, + isFilteredToOpen, + ] + ); + + const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat); + const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format( + defaultNumberFormat + ); + + return ( + <> + + + + + {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} + + + + + {totalCount > 0 && ( + <> + + {i18n.SELECTED_SIGNALS( + showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, + showClearSelection ? totalCount : Object.keys(selectedEventIds).length + )} + + + + {i18n.BATCH_ACTIONS} + + + { + if (!showClearSelection) { + selectAll(); + } else { + clearSelection(); + } + }} + > + {showClearSelection + ? i18n.CLEAR_SELECTION + : i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)} + + + )} + + + + + ); + }, + (prevProps, nextProps) => { + return ( + prevProps.selectedEventIds === nextProps.selectedEventIds && + prevProps.totalCount === nextProps.totalCount && + prevProps.showClearSelection === nextProps.showClearSelection + ); + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/translations.ts new file mode 100644 index 0000000000000..b876177d5e4d1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/translations.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SHOWING_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => + i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.showingSignalsTitle', { + values: { totalSignalsFormatted, totalSignals }, + defaultMessage: + 'Showing {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', + }); + +export const SELECTED_SIGNALS = (selectedSignalsFormatted: string, selectedSignals: number) => + i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectedSignalsTitle', { + values: { selectedSignalsFormatted, selectedSignals }, + defaultMessage: + 'Selected {selectedSignalsFormatted} {selectedSignals, plural, =1 {signal} other {signals}}', + }); + +export const SELECT_ALL_SIGNALS = (totalSignalsFormatted: string, totalSignals: number) => + i18n.translate('xpack.siem.detectionEngine.signals.utilityBar.selectAllSignalsTitle', { + values: { totalSignalsFormatted, totalSignals }, + defaultMessage: + 'Select all {totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', + }); + +export const CLEAR_SELECTION = i18n.translate( + 'xpack.siem.detectionEngine.signals.utilityBar.clearSelectionTitle', + { + defaultMessage: 'Clear selection', + } +); + +export const BATCH_ACTIONS = i18n.translate( + 'xpack.siem.detectionEngine.signals.utilityBar.batchActionsTitle', + { + defaultMessage: 'Batch actions', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_HOSTS = i18n.translate( + 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInHostsTitle', + { + defaultMessage: 'View selected in hosts', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_NETWORK = i18n.translate( + 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInNetworkTitle', + { + defaultMessage: 'View selected in network', + } +); + +export const BATCH_ACTION_VIEW_SELECTED_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.viewSelectedInTimelineTitle', + { + defaultMessage: 'View selected in timeline', + } +); + +export const BATCH_ACTION_OPEN_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.openSelectedTitle', + { + defaultMessage: 'Open selected', + } +); + +export const BATCH_ACTION_CLOSE_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.signals.utilityBar.batchActions.closeSelectedTitle', + { + defaultMessage: 'Close selected', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts similarity index 62% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/translations.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts index 1806ba85f8b55..d1ba946be41de 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts @@ -10,13 +10,6 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', defaultMessage: 'Detection engine', }); -export const PANEL_SUBTITLE_SHOWING = i18n.translate( - 'xpack.siem.detectionEngine.panelSubtitleShowing', - { - defaultMessage: 'Showing', - } -); - export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', { defaultMessage: 'All signals', }); @@ -28,6 +21,24 @@ export const SIGNALS_DOCUMENT_TYPE = i18n.translate( } ); +export const OPEN_SIGNALS = i18n.translate('xpack.siem.detectionEngine.signals.openSignalsTitle', { + defaultMessage: 'Open signals', +}); + +export const CLOSED_SIGNALS = i18n.translate( + 'xpack.siem.detectionEngine.signals.closedSignalsTitle', + { + defaultMessage: 'Closed signals', + } +); + +export const LOADING_SIGNALS = i18n.translate( + 'xpack.siem.detectionEngine.signals.loadingSignalsTitle', + { + defaultMessage: 'Loading Signals', + } +); + export const TOTAL_COUNT_OF_SIGNALS = i18n.translate( 'xpack.siem.detectionEngine.signals.totalCountOfSignalsTitle', { @@ -62,3 +73,24 @@ export const SIGNALS_HEADERS_RISK_SCORE = i18n.translate( defaultMessage: 'Risk Score', } ); + +export const ACTION_OPEN_SIGNAL = i18n.translate( + 'xpack.siem.detectionEngine.signals.actions.openSignalTitle', + { + defaultMessage: 'Open signal', + } +); + +export const ACTION_CLOSE_SIGNAL = i18n.translate( + 'xpack.siem.detectionEngine.signals.actions.closeSignalTitle', + { + defaultMessage: 'Close signal', + } +); + +export const ACTION_VIEW_IN_TIMELINE = i18n.translate( + 'xpack.siem.detectionEngine.signals.actions.viewInTimelineTitle', + { + defaultMessage: 'View in timeline', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts new file mode 100644 index 0000000000000..b02b8eb0ef976 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { esFilters } from '../../../../../../../../../src/plugins/data/common/es_query'; +import { TimelineNonEcsData } from '../../../../graphql/types'; +import { KueryFilterQuery, SerializedFilterQuery } from '../../../../store'; + +export interface SetEventsLoadingProps { + eventIds: string[]; + isLoading: boolean; +} + +export interface SetEventsDeletedProps { + eventIds: string[]; + isDeleted: boolean; +} + +export interface UpdateSignalsStatusProps { + signalIds: string[]; + status: 'open' | 'closed'; +} + +export type UpdateSignalsStatus = ({ signalIds, status }: UpdateSignalsStatusProps) => void; + +export interface UpdateSignalStatusActionProps { + query?: string; + signalIds: string[]; + status: 'open' | 'closed'; + setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; + setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; + kbnVersion: string; +} + +export type SendSignalsToTimeline = () => void; + +export interface SendSignalsToTimelineActionProps { + createTimeline: CreateTimeline; + data: TimelineNonEcsData[][]; +} + +export interface CreateTimelineProps { + id: string; + kqlQuery?: { + filterQuery: SerializedFilterQuery | null; + filterQueryDraft: KueryFilterQuery | null; + }; + filters?: esFilters.Filter[]; + dateRange?: { start: number; end: number }; +} + +export type CreateTimeline = ({ id, kqlQuery, filters, dateRange }: CreateTimelineProps) => void; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_chart/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_chart/index.tsx new file mode 100644 index 0000000000000..094f17922af1a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_chart/index.tsx @@ -0,0 +1,39 @@ +/* + * 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 { EuiPanel, EuiSelect } from '@elastic/eui'; +import { noop } from 'lodash/fp'; +import React, { memo } from 'react'; + +import { HeaderSection } from '../../../../components/header_section'; +import { HistogramSignals } from '../../../../components/page/detection_engine/histogram_signals'; + +export const sampleChartOptions = [ + { text: 'Risk scores', value: 'risk_scores' }, + { text: 'Severities', value: 'severities' }, + { text: 'Top destination IPs', value: 'destination_ips' }, + { text: 'Top event actions', value: 'event_actions' }, + { text: 'Top event categories', value: 'event_categories' }, + { text: 'Top host names', value: 'host_names' }, + { text: 'Top rule types', value: 'rule_types' }, + { text: 'Top rules', value: 'rules' }, + { text: 'Top source IPs', value: 'source_ips' }, + { text: 'Top users', value: 'users' }, +]; + +export const SignalsCharts = memo(() => ( + + + noop} + prepend="Stack by" + value={sampleChartOptions[0].value} + /> + + + + +)); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/index.tsx new file mode 100644 index 0000000000000..7f285a4a708c9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/index.tsx @@ -0,0 +1,53 @@ +/* + * 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 { EuiLoadingSpinner } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import React, { useState, useEffect } from 'react'; + +import { useQuerySignals } from '../../../../containers/detection_engine/signals/use_query'; +import { buildlastSignalsQuery } from './query.dsl'; +import { Aggs } from './types'; + +interface SignalInfo { + ruleId?: string | null; +} + +type Return = [React.ReactNode, React.ReactNode]; + +export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { + const [lastSignals, setLastSignals] = useState( + + ); + const [totalSignals, setTotalSignals] = useState( + + ); + + let query = ''; + try { + query = JSON.stringify(buildlastSignalsQuery(ruleId)); + } catch { + query = ''; + } + + const [, signals] = useQuerySignals(query); + + useEffect(() => { + if (signals != null) { + const mySignals = signals; + setLastSignals( + mySignals.aggregations?.lastSeen.value != null ? ( + + ) : null + ); + setTotalSignals(<>{mySignals.hits.total.value}); + } + }, [signals]); + + return [lastSignals, totalSignals]; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/query.dsl.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/query.dsl.ts new file mode 100644 index 0000000000000..0b14aa17a9450 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/query.dsl.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const buildlastSignalsQuery = (ruleId: string | undefined | null) => { + const queryFilter = [ + { + bool: { should: [{ match: { 'signal.status': 'open' } }], minimum_should_match: 1 }, + }, + ]; + return { + aggs: { + lastSeen: { max: { field: '@timestamp' } }, + }, + query: { + bool: { + filter: + ruleId != null + ? [ + ...queryFilter, + { + bool: { + should: [{ match: { 'signal.rule.id': ruleId } }], + minimum_should_match: 1, + }, + }, + ] + : queryFilter, + }, + }, + size: 0, + track_total_hits: true, + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/apps/kibana_app.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/types.ts similarity index 67% rename from x-pack/legacy/plugins/infra/public/apps/kibana_app.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/types.ts index 5c5687c6c1283..68bc8dc3eed59 100644 --- a/x-pack/legacy/plugins/infra/public/apps/kibana_app.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/types.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { compose } from '../lib/compose/kibana_compose'; -import { startApp } from './start_app'; -startApp(compose()); +export interface Aggs { + lastSeen: { + value: number; + value_as_string: string; + }; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx deleted file mode 100644 index aeb70061c44bf..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx +++ /dev/null @@ -1,161 +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 { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { memo, useCallback, useState } from 'react'; - -import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; -import * as CreateRuleI18n from '../../translations'; -import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; -import { AddItem } from '../add_item_form'; -import { defaultRiskScoreBySeverity, severityOptions, SeverityValue } from './data'; -import { defaultValue } from './default_value'; -import { schema } from './schema'; -import * as I18n from './translations'; -import { StepRuleDescription } from '../description_step'; -import { AddMitreThreat } from '../mitre'; - -const CommonUseField = getUseField({ component: Field }); - -export const StepAboutRule = memo(({ isEditView, isLoading, setStepData }) => { - const [myStepData, setMyStepData] = useState(defaultValue); - const { form } = useForm({ - defaultValue: myStepData, - options: { stripEmptyFields: false }, - schema, - }); - - const onSubmit = useCallback(async () => { - const { isValid, data } = await form.submit(); - if (isValid) { - setStepData(RuleStep.aboutRule, data, isValid); - setMyStepData({ ...data, isNew: false } as AboutStepRule); - } - }, [form]); - - return isEditView && myStepData != null ? ( - - ) : ( - <> -
- - - - - - - - - - {({ severity }) => { - const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; - const riskScoreField = form.getFields().riskScore; - if (newRiskScore != null && riskScoreField.value !== newRiskScore) { - riskScoreField.setValue(newRiskScore); - } - return null; - }} - - - - - - - {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} - - - - - ); -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/index.tsx deleted file mode 100644 index bd4d5aa4f8ca1..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import React, { memo, useCallback, useState } from 'react'; - -import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types'; -import { StepRuleDescription } from '../description_step'; -import { ScheduleItem } from '../schedule_item_form'; -import { Form, UseField, useForm } from '../shared_imports'; -import { schema } from './schema'; -import * as I18n from './translations'; - -export const StepScheduleRule = memo(({ isEditView, isLoading, setStepData }) => { - const [myStepData, setMyStepData] = useState({ - enabled: true, - interval: '5m', - isNew: true, - from: '0m', - }); - const { form } = useForm({ - schema, - defaultValue: myStepData, - options: { stripEmptyFields: false }, - }); - - const onSubmit = useCallback( - async (enabled: boolean) => { - const { isValid: newIsValid, data } = await form.submit(); - if (newIsValid) { - setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid); - setMyStepData({ ...data, isNew: false } as ScheduleStepRule); - } - }, - [form] - ); - - return isEditView && myStepData != null ? ( - - ) : ( - <> -
- - - - - - - - {I18n.COMPLETE_WITHOUT_ACTIVATING} - - - - - {I18n.COMPLETE_WITH_ACTIVATING} - - - - - ); -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts deleted file mode 100644 index 5ff9dffb658ab..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/translations.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule.pageTitle', { - defaultMessage: 'Create new rule', -}); - -export const DEFINE_RULE = i18n.translate('xpack.siem.detectionEngine.createRule.defineRuleTitle', { - defaultMessage: 'Define Rule', -}); - -export const ABOUT_RULE = i18n.translate('xpack.siem.detectionEngine.createRule.aboutRuleTitle', { - defaultMessage: 'About Rule', -}); - -export const SCHEDULE_RULE = i18n.translate( - 'xpack.siem.detectionEngine.createRule.scheduleRuleTitle', - { - defaultMessage: 'Schedule Rule', - } -); - -export const OPTIONAL_FIELD = i18n.translate( - 'xpack.siem.detectionEngine.createRule.optionalFieldDescription', - { - defaultMessage: 'Optional', - } -); - -export const CONTINUE = i18n.translate( - 'xpack.siem.detectionEngine.createRule.continueButtonTitle', - { - defaultMessage: 'Continue', - } -); - -export const UPDATE = i18n.translate('xpack.siem.detectionEngine.createRule.updateButtonTitle', { - defaultMessage: 'Update', -}); - -export const DELETE = i18n.translate('xpack.siem.detectionEngine.createRule.deleteDescription', { - defaultMessage: 'Delete', -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/types.ts deleted file mode 100644 index 241e4530b4b57..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/types.ts +++ /dev/null @@ -1,88 +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 { FieldValueQueryBar } from './components/query_bar'; -import { esFilters } from '../../../../../../../../src/plugins/data/common'; - -export enum RuleStep { - defineRule = 'define-rule', - aboutRule = 'about-rule', - scheduleRule = 'schedule-rule', -} -export type RuleStatusType = 'passive' | 'active' | 'valid'; - -export interface RuleStepData { - data: unknown; - isValid: boolean; -} - -export interface RuleStepProps { - setStepData: (step: RuleStep, data: unknown, isValid: boolean) => void; - isEditView: boolean; - isLoading: boolean; - resizeParentContainer?: (height: number) => void; -} - -interface StepRuleData { - isNew: boolean; -} -export interface AboutStepRule extends StepRuleData { - name: string; - description: string; - severity: string; - riskScore: number; - references: string[]; - falsePositives: string[]; - tags: string[]; - threats: IMitreEnterpriseAttack[]; -} - -export interface DefineStepRule extends StepRuleData { - useIndicesConfig: string; - index: string[]; - queryBar: FieldValueQueryBar; -} - -export interface ScheduleStepRule extends StepRuleData { - enabled: boolean; - interval: string; - from: string; - to?: string; -} - -export interface DefineStepRuleJson { - index: string[]; - filters: esFilters.Filter[]; - saved_id?: string; - query: string; - language: string; -} - -export interface AboutStepRuleJson { - name: string; - description: string; - severity: string; - risk_score: number; - references: string[]; - false_positives: string[]; - tags: string[]; - threats: IMitreEnterpriseAttack[]; -} - -export type ScheduleStepRuleJson = ScheduleStepRule; - -export type FormatRuleType = 'query' | 'saved_query'; - -export interface IMitreAttack { - id: string; - name: string; - reference: string; -} -export interface IMitreEnterpriseAttack { - framework: string; - tactic: IMitreAttack; - techniques: IMitreAttack[]; -} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index e1373c8b18bb7..6e6b71729b07e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -4,36 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiPanel, EuiSelect, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { StickyContainer } from 'react-sticky'; import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; -import { HeaderSection } from '../../components/header_section'; -import { HistogramSignals } from '../../components/page/detection_engine/histogram_signals'; import { SiemSearchBar } from '../../components/search_bar'; import { WrapperPage } from '../../components/wrapper_page'; +import { GlobalTime } from '../../containers/global_time'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { SpyRoute } from '../../utils/route/spy_routes'; + +import { SignalsTable } from './components/signals'; +import { SignalsCharts } from './components/signals_chart'; +import { useSignalInfo } from './components/signals_info'; import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; import * as i18n from './translations'; -import { SignalsTable } from './signals'; export const DetectionEngineComponent = React.memo(() => { - const sampleChartOptions = [ - { text: 'Risk scores', value: 'risk_scores' }, - { text: 'Severities', value: 'severities' }, - { text: 'Top destination IPs', value: 'destination_ips' }, - { text: 'Top event actions', value: 'event_actions' }, - { text: 'Top event categories', value: 'event_categories' }, - { text: 'Top host names', value: 'host_names' }, - { text: 'Top rule types', value: 'rule_types' }, - { text: 'Top rules', value: 'rules' }, - { text: 'Top source IPs', value: 'source_ips' }, - { text: 'Top users', value: 'users' }, - ]; - + const [lastSignals] = useSignalInfo({}); return ( <> @@ -45,34 +35,33 @@ export const DetectionEngineComponent = React.memo(() => { - + + {i18n.LAST_SIGNAL} + {': '} + {lastSignals} + + ) + } + title={i18n.PAGE_TITLE} + > {i18n.BUTTON_MANAGE_RULES} - - - {}} - prepend="Stack by" - value={sampleChartOptions[0].value} - /> - - - - + - - + {({ to, from }) => } ) : ( - ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx index cb3e690615395..a217fd6a737e7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_empty_page.tsx @@ -6,8 +6,8 @@ import React from 'react'; import chrome from 'ui/chrome'; -import { documentationLinks } from 'ui/documentation_links'; +import { useKibana } from '../../lib/kibana'; import { EmptyPage } from '../../components/empty_page'; import * as i18n from './translations'; @@ -21,7 +21,7 @@ export const DetectionEngineEmptyPage = React.memo(() => ( actionSecondaryIcon="popout" actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY} actionSecondaryTarget="_blank" - actionSecondaryUrl={documentationLinks.siem} + actionSecondaryUrl={useKibana().services.docLinks.links.siem} data-test-subj="empty-page" title={i18n.EMPTY_TITLE} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/index.tsx deleted file mode 100644 index 9b8607fdc7685..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/index.tsx +++ /dev/null @@ -1,128 +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 { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSpacer, - EuiTabbedContent, -} from '@elastic/eui'; -import React from 'react'; - -import { HeaderPage } from '../../../components/header_page'; -import { HeaderSection } from '../../../components/header_section'; -import { WrapperPage } from '../../../components/wrapper_page'; -import { SpyRoute } from '../../../utils/route/spy_routes'; -import * as i18n from './translations'; - -const Define = React.memo(() => ( - <> - - - - - - -)); -Define.displayName = 'Define'; - -const About = React.memo(() => ( - <> - - - - - - -)); -About.displayName = 'About'; - -const Schedule = React.memo(() => ( - <> - - - - - - -)); -Schedule.displayName = 'Schedule'; - -export const EditRuleComponent = React.memo(() => { - return ( - <> - - - - - - {'Cancel'} - - - - - - {'Save changes'} - - - - - - , - }, - { - id: 'tabAbout', - name: 'About', - content: , - }, - { - id: 'tabSchedule', - name: 'Schedule', - content: , - }, - ]} - /> - - - - - - - {'Cancel'} - - - - - - {'Save changes'} - - - - - - - - ); -}); -EditRuleComponent.displayName = 'EditRuleComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts new file mode 100644 index 0000000000000..1399df0fcf6d1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/helpers.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const sampleChartOptions = [ + { text: 'Risk scores', value: 'risk_scores' }, + { text: 'Severities', value: 'severities' }, + { text: 'Top destination IPs', value: 'destination_ips' }, + { text: 'Top event actions', value: 'event_actions' }, + { text: 'Top event categories', value: 'event_categories' }, + { text: 'Top host names', value: 'host_names' }, + { text: 'Top rule types', value: 'rule_types' }, + { text: 'Top rules', value: 'rules' }, + { text: 'Top source IPs', value: 'source_ips' }, + { text: 'Top users', value: 'users' }, +]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index 90524b4da0af4..21ebac2b4d337 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -7,10 +7,10 @@ import React from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; -import { CreateRuleComponent } from './create_rule'; +import { CreateRuleComponent } from './rules/create'; import { DetectionEngineComponent } from './detection_engine'; -import { EditRuleComponent } from './edit_rule'; -import { RuleDetailsComponent } from './rule_details'; +import { EditRuleComponent } from './rules/edit'; +import { RuleDetailsComponent } from './rules/details'; import { RulesComponent } from './rules'; const detectionEnginePath = `/:pageName(detection-engine)`; @@ -19,21 +19,21 @@ type Props = Partial> & { url: string }; export const DetectionEngineContainer = React.memo(() => ( - } strict /> - } /> - } - /> - } - /> - } - /> + + + + + + + + + + + + + + + ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/index.tsx deleted file mode 100644 index b1b5af701123c..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/index.tsx +++ /dev/null @@ -1,663 +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 { - EuiBasicTable as _EuiBasicTable, - EuiButton, - EuiButtonIcon, - EuiCallOut, - EuiFilterButton, - EuiFilterGroup, - EuiFlexGroup, - EuiFlexItem, - EuiIconTip, - EuiPanel, - EuiPopover, - EuiSelect, - EuiSpacer, - EuiSwitch, - EuiTabbedContent, - EuiTextColor, -} from '@elastic/eui'; -import moment from 'moment'; -import React, { useState } from 'react'; -import { StickyContainer } from 'react-sticky'; - -import { getEmptyTagValue } from '../../../components/empty_value'; -import { FiltersGlobal } from '../../../components/filters_global'; -import { HeaderPage } from '../../../components/header_page'; -import { HeaderSection } from '../../../components/header_section'; -import { HistogramSignals } from '../../../components/page/detection_engine/histogram_signals'; -import { ProgressInline } from '../../../components/progress_inline'; -import { SiemSearchBar } from '../../../components/search_bar'; -import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../../../components/detection_engine/utility_bar'; -import { WrapperPage } from '../../../components/wrapper_page'; -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; -import { SpyRoute } from '../../../utils/route/spy_routes'; -import { DetectionEngineEmptyPage } from '../detection_engine_empty_page'; -import * as i18n from './translations'; - -// there are a number of type mismatches across this file -const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any - -// Michael: Will need to change this to get the current datetime format from Kibana settings. -const dateTimeFormat = (value: string) => { - return moment(value).format('M/D/YYYY, h:mm A'); -}; - -const OpenSignals = React.memo(() => { - return ( - <> - - - - {'Showing: 439 signals'} - - - - {'Selected: 20 signals'} - -

{'Batch actions context menu here.'}

} - > - {'Batch actions'} -
- - - {'Select all signals on all pages'} - -
- - - {'Clear 7 filters'} - - {'Clear aggregation'} - -
- - - -

{'Customize columns context menu here.'}

} - > - {'Customize columns'} -
- - {'Aggregate data'} -
-
-
- - {/* Michael: Open signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} - - ); -}); - -const ClosedSignals = React.memo(() => { - return ( - <> - - - - {'Showing: 439 signals'} - - - - - -

{'Customize columns context menu here.'}

} - > - {'Customize columns'} -
- - {'Aggregate data'} -
-
-
- - {/* Michael: Closed signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} - - ); -}); - -const Signals = React.memo(() => { - const sampleChartOptions = [ - { text: 'Risk scores', value: 'risk_scores' }, - { text: 'Severities', value: 'severities' }, - { text: 'Top destination IPs', value: 'destination_ips' }, - { text: 'Top event actions', value: 'event_actions' }, - { text: 'Top event categories', value: 'event_categories' }, - { text: 'Top host names', value: 'host_names' }, - { text: 'Top source IPs', value: 'source_ips' }, - { text: 'Top users', value: 'users' }, - ]; - - const filterGroupOptions = ['open', 'closed']; - const [filterGroupState, setFilterGroupState] = useState(filterGroupOptions[0]); - - return ( - <> - - - - - {}} - prepend="Stack by" - value={sampleChartOptions[0].value} - /> - - - - - - - - - - - setFilterGroupState(filterGroupOptions[0])} - withNext - > - {'Open signals'} - - - setFilterGroupState(filterGroupOptions[1])} - > - {'Closed signals'} - - - - - {filterGroupState === filterGroupOptions[0] ? : } - - - ); -}); -Signals.displayName = 'Signals'; - -const ActivityMonitor = React.memo(() => { - interface ColumnTypes { - id: number; - ran: string; - lookedBackTo: string; - status: string; - response: string | undefined; - } - - interface PageTypes { - index: number; - size: number; - } - - interface SortTypes { - field: string; - direction: string; - } - - const actions = [ - { - available: (item: ColumnTypes) => item.status === 'Running', - description: 'Stop', - icon: 'stop', - isPrimary: true, - name: 'Stop', - onClick: () => {}, - type: 'icon', - }, - { - available: (item: ColumnTypes) => item.status === 'Stopped', - description: 'Resume', - icon: 'play', - isPrimary: true, - name: 'Resume', - onClick: () => {}, - type: 'icon', - }, - ]; - - // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? - const columns = [ - { - field: 'ran', - name: 'Ran', - render: (value: ColumnTypes['ran']) => , - sortable: true, - truncateText: true, - }, - { - field: 'lookedBackTo', - name: 'Looked back to', - render: (value: ColumnTypes['lookedBackTo']) => ( - - ), - sortable: true, - truncateText: true, - }, - { - field: 'status', - name: 'Status', - sortable: true, - truncateText: true, - }, - { - field: 'response', - name: 'Response', - render: (value: ColumnTypes['response']) => { - return value === undefined ? ( - getEmptyTagValue() - ) : ( - <> - {value === 'Fail' ? ( - - {value} - - ) : ( - {value} - )} - - ); - }, - sortable: true, - truncateText: true, - }, - { - actions, - width: '40px', - }, - ]; - - const sampleTableData = [ - { - id: 1, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Running', - }, - { - id: 2, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Stopped', - }, - { - id: 3, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Fail', - }, - { - id: 4, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 5, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 6, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 7, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 8, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 9, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 10, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 11, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 12, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 13, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 14, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 15, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 16, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 17, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 18, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 19, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 20, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - { - id: 21, - ran: '2019-12-28 00:00:00.000-05:00', - lookedBackTo: '2019-12-28 00:00:00.000-05:00', - status: 'Completed', - response: 'Success', - }, - ]; - - const [itemsTotalState] = useState(sampleTableData.length); - const [pageState, setPageState] = useState({ index: 0, size: 20 }); - // const [selectedState, setSelectedState] = useState([]); - const [sortState, setSortState] = useState({ field: 'ran', direction: 'desc' }); - - return ( - <> - - - - - - - - - {'Showing: 39 activites'} - - - - {'Selected: 2 activities'} - - {'Stop selected'} - - - - {'Clear 7 filters'} - - - - - { - setPageState(page); - setSortState(sort); - }} - pagination={{ - pageIndex: pageState.index, - pageSize: pageState.size, - totalItemCount: itemsTotalState, - pageSizeOptions: [5, 10, 20], - }} - selection={{ - selectable: (item: ColumnTypes) => item.status !== 'Completed', - selectableMessage: (selectable: boolean) => - selectable ? undefined : 'Completed runs cannot be acted upon', - onSelectionChange: (selectedItems: ColumnTypes[]) => { - // setSelectedState(selectedItems); - }, - }} - sorting={{ - sort: sortState, - }} - /> - - - ); -}); -ActivityMonitor.displayName = 'ActivityMonitor'; - -export const RuleDetailsComponent = React.memo(() => { - const [popoverState, setPopoverState] = useState(false); - - return ( - <> - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - - {'Status: Running'} - , - ]} - title="Automated exfiltration" - > - - - {}} /> - - - - - - - {'Edit rule settings'} - - - - - setPopoverState(!popoverState)} - /> - } - closePopover={() => setPopoverState(false)} - isOpen={popoverState} - > -

{'Overflow context menu here.'}

-
-
-
-
-
-
- - -

{'Full fail message here.'}

-
- - - - - - - - - - - - - - - {/*

{'Description'}

*/} - - {/* - -

{'Description'}

-
- - -

{'Severity'}

-
- - -

{'Risk score boost'}

-
- - -

{'References'}

-
- - -

{'False positives'}

-
- - -

{'Mitre ATT&CK types'}

-
- - -

{'Tags'}

-
-
*/} -
-
- - - - - - -
- - - - , - }, - { - id: 'tabActivityMonitor', - name: 'Activity monitor', - content: , - }, - ]} - /> -
-
- ) : ( - - - - - - ); - }} -
- - - - ); -}); -RuleDetailsComponent.displayName = 'RuleDetailsComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx similarity index 80% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/actions.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index a54296f65a382..4de6136e9d3de 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -4,16 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as H from 'history'; import React from 'react'; + +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { deleteRules, duplicateRules, enableRules, -} from '../../../../containers/detection_engine/rules/api'; + Rule, +} from '../../../../containers/detection_engine/rules'; import { Action } from './reducer'; -import { Rule } from '../../../../containers/detection_engine/rules/types'; -export const editRuleAction = () => {}; +export const editRuleAction = (rule: Rule, history: H.History) => { + history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/${rule.id}/edit`); +}; export const runRuleAction = () => {}; @@ -25,7 +30,7 @@ export const duplicateRuleAction = async ( dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true }); const duplicatedRule = await duplicateRules({ rules: [rule], kbnVersion }); dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false }); - dispatch({ type: 'updateRules', rules: duplicatedRule }); + dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id }); }; export const exportRulesAction = async (rules: Rule[], dispatch: React.Dispatch) => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/batch_actions.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx similarity index 82% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/columns.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index f716b0f83c804..ad5d210efa42d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -9,9 +9,11 @@ import { EuiHealth, EuiIconTip, EuiLink, - EuiTableActionsColumnType, EuiTextColor, + EuiBasicTableColumn, + EuiTableActionsColumnType, } from '@elastic/eui'; +import * as H from 'history'; import React from 'react'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { getEmptyTagValue } from '../../../../components/empty_value'; @@ -19,7 +21,6 @@ import { deleteRulesAction, duplicateRuleAction, editRuleAction, - enableRulesAction, exportRulesAction, runRuleAction, } from './actions'; @@ -28,20 +29,18 @@ import { Action } from './reducer'; import { TableData } from '../types'; import * as i18n from '../translations'; import { PreferenceFormattedDate } from '../../../../components/formatted_date'; -import { RuleSwitch, RuleStateChangeCallback } from '../components/rule_switch'; +import { RuleSwitch } from '../components/rule_switch'; -const getActions = (dispatch: React.Dispatch, kbnVersion: string) => [ +const getActions = (dispatch: React.Dispatch, kbnVersion: string, history: H.History) => [ { description: i18n.EDIT_RULE_SETTINGS, - type: 'icon', icon: 'visControls', name: i18n.EDIT_RULE_SETTINGS, - onClick: editRuleAction, - enabled: () => false, + onClick: (rowItem: TableData) => editRuleAction(rowItem.sourceRule, history), + enabled: (rowItem: TableData) => !rowItem.sourceRule.immutable, }, { description: i18n.RUN_RULE_MANUALLY, - type: 'icon', icon: 'play', name: i18n.RUN_RULE_MANUALLY, onClick: runRuleAction, @@ -49,21 +48,18 @@ const getActions = (dispatch: React.Dispatch, kbnVersion: string) => [ }, { description: i18n.DUPLICATE_RULE, - type: 'icon', icon: 'copy', name: i18n.DUPLICATE_RULE, onClick: (rowItem: TableData) => duplicateRuleAction(rowItem.sourceRule, dispatch, kbnVersion), }, { description: i18n.EXPORT_RULE, - type: 'icon', icon: 'exportAction', name: i18n.EXPORT_RULE, onClick: (rowItem: TableData) => exportRulesAction([rowItem.sourceRule], dispatch), }, { description: i18n.DELETE_RULE, - type: 'icon', icon: 'trash', name: i18n.DELETE_RULE, onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, kbnVersion), @@ -71,7 +67,11 @@ const getActions = (dispatch: React.Dispatch, kbnVersion: string) => [ ]; // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? -export const getColumns = (dispatch: React.Dispatch, kbnVersion: string) => [ +export const getColumns = ( + dispatch: React.Dispatch, + kbnVersion: string, + history: H.History +): Array | EuiTableActionsColumnType> => [ { field: 'rule', name: i18n.COLUMN_RULE, @@ -156,28 +156,22 @@ export const getColumns = (dispatch: React.Dispatch, kbnVersion: string) width: '20%', }, { - align: 'center' as const, + align: 'center', field: 'activate', name: i18n.COLUMN_ACTIVATE, - render: (value: TableData['activate'], item: TableData) => { - const handleRuleStateChange: RuleStateChangeCallback = async (enabled, id) => { - await enableRulesAction([id], enabled, dispatch, kbnVersion); - }; - - return ( - - ); - }, + render: (value: TableData['activate'], item: TableData) => ( + + ), sortable: true, width: '85px', }, { - actions: getActions(dispatch, kbnVersion), + actions: getActions(dispatch, kbnVersion, history), width: '40px', } as EuiTableActionsColumnType, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts similarity index 91% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/helpers.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts index db02d41771f68..1909b75a85835 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Rule } from '../../../../containers/detection_engine/rules/types'; +import { Rule } from '../../../../containers/detection_engine/rules'; import { TableData } from '../types'; import { getEmptyValue } from '../../../../components/empty_value'; @@ -13,7 +13,7 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] id: rule.id, rule_id: rule.rule_id, rule: { - href: `#/detection-engine/rules/rule-details/${encodeURIComponent(rule.id)}`, + href: `#/detection-engine/rules/${encodeURIComponent(rule.id)}`, name: rule.name, status: 'Status Placeholder', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx similarity index 96% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index b2adba28af20d..442360bbf1484 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all_rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -12,6 +12,7 @@ import { EuiSpacer, } from '@elastic/eui'; import React, { useCallback, useEffect, useReducer, useState } from 'react'; +import { useHistory } from 'react-router-dom'; import uuid from 'uuid'; import { HeaderSection } from '../../../../components/header_section'; @@ -23,14 +24,14 @@ import { UtilityBarText, } from '../../../../components/detection_engine/utility_bar'; import { getColumns } from './columns'; -import { useRules } from '../../../../containers/detection_engine/rules/use_rules'; +import { useRules } from '../../../../containers/detection_engine/rules'; import { Loader } from '../../../../components/loader'; import { Panel } from '../../../../components/panel'; import { getBatchItems } from './batch_actions'; import { EuiBasicTableOnChange, TableData } from '../types'; import { allRulesReducer, State } from './reducer'; import * as i18n from '../translations'; -import { useKibanaUiSetting } from '../../../../lib/settings/use_kibana_ui_setting'; +import { useUiSetting$ } from '../../../../lib/kibana'; import { DEFAULT_KBN_VERSION } from '../../../../../common/constants'; import { JSONDownloader } from '../components/json_downloader'; import { useStateToaster } from '../../../../components/toasters'; @@ -74,10 +75,10 @@ export const AllRules = React.memo<{ importCompleteToggle: boolean }>(importComp }, dispatch, ] = useReducer(allRulesReducer, initialState); - + const history = useHistory(); const [isInitialLoad, setIsInitialLoad] = useState(true); const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); const getBatchItemsPopoverContent = useCallback( @@ -184,7 +185,7 @@ export const AllRules = React.memo<{ importCompleteToggle: boolean }>(importComp { } const ruleIds = state.rules.map(r => r.rule_id); + const appendIdx = + action.appendRuleId != null ? state.rules.findIndex(r => r.id === action.appendRuleId) : -1; const updatedRules = action.rules.reduce( (rules, updatedRule) => ruleIds.includes(updatedRule.rule_id) ? rules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)) + : appendIdx !== -1 + ? [ + ...rules.slice(0, appendIdx + 1), + updatedRule, + ...rules.slice(appendIdx + 1, rules.length - 1), + ] : [...rules, updatedRule], [...state.rules] ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/accordion_title/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/accordion_title/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx similarity index 97% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx index e972cd21b6be9..f090f6d97eaf9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx @@ -8,8 +8,8 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiFormRow, EuiFieldText, EuiSpacer } fr import { isEmpty } from 'lodash/fp'; import React, { ChangeEvent, useCallback, useEffect, useState, useRef } from 'react'; +import * as RuleI18n from '../../translations'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; -import * as CreateRuleI18n from '../../translations'; interface AddItemProps { addText: string; @@ -134,7 +134,7 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad iconType="trash" isDisabled={isDisabled} onClick={() => removeItem(index)} - aria-label={CreateRuleI18n.DELETE} + aria-label={RuleI18n.DELETE} /> } onChange={e => updateItem(e, index)} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_label.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_label.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_label.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_label.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_operator.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_operator.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/filter_operator.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_operator.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx similarity index 73% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx index e6fec597ed8ea..39c660a0079a6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx @@ -7,6 +7,7 @@ import { EuiBadge, EuiDescriptionList, + EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, EuiTextArea, @@ -15,15 +16,16 @@ import { EuiListGroup, } from '@elastic/eui'; import { isEmpty, chunk, get, pick } from 'lodash/fp'; -import React, { memo, ReactNode } from 'react'; +import React, { memo, ReactNode, useState } from 'react'; import styled from 'styled-components'; import { IIndexPattern, esFilters, + FilterManager, Query, } from '../../../../../../../../../../src/plugins/data/public'; - +import { useKibana } from '../../../../../lib/kibana'; import { FilterLabel } from './filter_label'; import { FormSchema } from '../shared_imports'; import * as I18n from './translations'; @@ -32,6 +34,7 @@ import { IMitreEnterpriseAttack } from '../../types'; import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; interface StepRuleDescriptionProps { + direction?: 'row' | 'column'; data: unknown; indexPatterns?: IIndexPattern; schema: FormSchema; @@ -43,8 +46,8 @@ const EuiBadgeWrap = styled(EuiBadge)` } `; -const EuiFlexItemWidth = styled(EuiFlexItem)` - width: 50%; +const EuiFlexItemWidth = styled(EuiFlexItem)<{ direction: string }>` + ${props => (props.direction === 'row' ? 'width : 50%;' : 'width: 100%;')}; `; const MyEuiListGroup = styled(EuiListGroup)` @@ -60,21 +63,33 @@ const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` } `; +const MyEuiTextArea = styled(EuiTextArea)` + max-width: 100%; + height: 80px; +`; + export const StepRuleDescription = memo( - ({ data, indexPatterns, schema }) => { + ({ data, direction = 'row', indexPatterns, schema }) => { + const kibana = useKibana(); + const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); + const keys = Object.keys(schema); const listItems = keys.reduce( (acc: ListItems[], key: string) => [ ...acc, - ...buildListItems(data, pick(key, schema), indexPatterns), + ...buildListItems(data, pick(key, schema), filterManager, indexPatterns), ], [] ); return ( - + {chunk(Math.ceil(listItems.length / 2), listItems).map((chunckListItems, index) => ( - - + + ))} @@ -90,12 +105,19 @@ interface ListItems { const buildListItems = ( data: unknown, schema: FormSchema, + filterManager: FilterManager, indexPatterns?: IIndexPattern ): ListItems[] => Object.keys(schema).reduce( (acc, field) => [ ...acc, - ...getDescriptionItem(field, get([field, 'label'], schema), data, indexPatterns), + ...getDescriptionItem( + field, + get([field, 'label'], schema), + data, + filterManager, + indexPatterns + ), ], [] ); @@ -104,29 +126,35 @@ const getDescriptionItem = ( field: string, label: string, value: unknown, + filterManager: FilterManager, indexPatterns?: IIndexPattern ): ListItems[] => { if (field === 'useIndicesConfig') { return []; - } else if (field === 'queryBar' && indexPatterns != null) { + } else if (field === 'queryBar') { const filters = get('queryBar.filters', value) as esFilters.Filter[]; const query = get('queryBar.query', value) as Query; const savedId = get('queryBar.saved_id', value); let items: ListItems[] = []; if (!isEmpty(filters)) { + filterManager.setFilters(filters); items = [ ...items, { title: <>{I18n.FILTERS_LABEL}, description: ( - {filters.map((filter, index) => ( + {filterManager.getFilters().map((filter, index) => ( - + {indexPatterns != null ? ( + + ) : ( + + )} ))} @@ -202,7 +230,7 @@ const getDescriptionItem = ( return [ { title: label, - description: , + description: , }, ]; } else if (Array.isArray(get(field, value))) { @@ -212,7 +240,7 @@ const getDescriptionItem = ( { title: label, description: ( - + {values.map((val: string) => isEmpty(val) ? null : ( @@ -227,10 +255,14 @@ const getDescriptionItem = ( } return []; } - return [ - { - title: label, - description: get(field, value), - }, - ]; + const description: string = get(field, value); + if (!isEmpty(description)) { + return [ + { + title: label, + description, + }, + ]; + } + return []; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/translations.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/translations.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/translations.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx index 8dcce36e1a409..381a3138bf617 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx @@ -8,20 +8,11 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { ImportRuleModalComponent } from './index'; -import { useKibanaUiSetting } from '../../../../../lib/settings/use_kibana_ui_setting'; -import { getMockKibanaUiSetting, MockFrameworks } from '../../../../../mock'; -import { DEFAULT_KBN_VERSION } from '../../../../../../common/constants'; -const mockUseKibanaUiSetting: jest.Mock = useKibanaUiSetting as jest.Mock; -jest.mock('../../../../../lib/settings/use_kibana_ui_setting', () => ({ - useKibanaUiSetting: jest.fn(), -})); +jest.mock('../../../../../lib/kibana'); describe('ImportRuleModal', () => { test('renders correctly against snapshot', () => { - mockUseKibanaUiSetting.mockImplementation( - getMockKibanaUiSetting((DEFAULT_KBN_VERSION as unknown) as MockFrameworks) - ); const wrapper = shallow( { const [selectedFiles, setSelectedFiles] = useState(null); const [isImporting, setIsImporting] = useState(false); - const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); const cleanupAndCloseModal = () => { @@ -138,7 +138,7 @@ export const ImportRuleModalComponent = ({ id="rule-overwrite-saved-object" label={i18n.OVERWRITE_WITH_SAME_NAME} disabled={true} - onChange={() => {}} + onChange={() => noop} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx similarity index 98% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx index 6ab4ca4b51447..a777506ee12ae 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx @@ -20,7 +20,7 @@ import React, { ChangeEvent, useCallback } from 'react'; import styled from 'styled-components'; import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; -import * as CreateRuleI18n from '../../translations'; +import * as RuleI18n from '../../translations'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; import * as I18n from './translations'; import { IMitreEnterpriseAttack } from '../../types'; @@ -154,7 +154,7 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI iconType="trash" isDisabled={isDisabled} onClick={() => removeItem(index)} - aria-label={CreateRuleI18n.DELETE} + aria-label={RuleI18n.DELETE} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx similarity index 97% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx index 8dc402f00e621..c294ec24c4cb7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx @@ -20,7 +20,7 @@ import { } from '../../../../../../../../../../src/plugins/data/public'; import { QueryBar } from '../../../../../components/query_bar'; -import { useKibanaCore } from '../../../../../lib/compose/kibana_core'; +import { useKibana } from '../../../../../lib/kibana'; import { useSavedQueryServices } from '../../../../../utils/saved_query_services'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; @@ -68,8 +68,8 @@ export const QueryBarDefineRule = ({ const [queryDraft, setQueryDraft] = useState({ query: '', language: 'kuery' }); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - const core = useKibanaCore(); - const [filterManager] = useState(new FilterManager(core.uiSettings)); + const kibana = useKibana(); + const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); const savedQueryServices = useSavedQueryServices(); @@ -217,6 +217,7 @@ export const QueryBarDefineRule = ({ onSubmitQuery={onSubmitQuery} savedQuery={savedQuery} onSavedQuery={onSavedQuery} + hideSavedQuery={false} />
)} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap index 98f8ae6a80e07..f264dde07c594 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap @@ -14,7 +14,7 @@ exports[`RuleSwitch renders correctly against snapshot 1`] = ` disabled={false} label="rule-switch" onChange={[Function]} - showLabel={false} + showLabel={true} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx index fcea7101ba54b..7a3a99b183971 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx @@ -7,17 +7,14 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; + import { RuleSwitchComponent } from './index'; +jest.mock('../../../../../lib/kibana'); describe('RuleSwitch', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx index 6d9b0a36f8548..54f0fc453830e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx @@ -4,9 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiSwitch, + EuiSwitchEvent, +} from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; import styled from 'styled-components'; -import React, { useCallback } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; +import React, { useCallback, useState, useEffect } from 'react'; + +import { DEFAULT_KBN_VERSION } from '../../../../../../common/constants'; +import { enableRules } from '../../../../../containers/detection_engine/rules'; +import { useUiSetting$ } from '../../../../../lib/kibana'; +import { enableRulesAction } from '../../all/actions'; +import { Action } from '../../all/reducer'; const StaticSwitch = styled(EuiSwitch)` .euiSwitch__thumb, @@ -17,43 +30,75 @@ const StaticSwitch = styled(EuiSwitch)` StaticSwitch.displayName = 'StaticSwitch'; -export type RuleStateChangeCallback = (isEnabled: boolean, id: string) => void; - export interface RuleSwitchProps { + dispatch?: React.Dispatch; id: string; enabled: boolean; - isLoading: boolean; - onRuleStateChange: RuleStateChangeCallback; + isLoading?: boolean; + optionLabel?: string; } /** * Basic switch component for displaying loader when enabled/disabled */ export const RuleSwitchComponent = ({ + dispatch, id, - enabled, isLoading, - onRuleStateChange, + enabled, + optionLabel, }: RuleSwitchProps) => { - const handleChange = useCallback( - e => { - onRuleStateChange(e.target.checked!, id); + const [myIsLoading, setMyIsLoading] = useState(false); + const [myEnabled, setMyEnabled] = useState(enabled ?? false); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + + const onRuleStateChange = useCallback( + async (event: EuiSwitchEvent) => { + setMyIsLoading(true); + if (dispatch != null) { + await enableRulesAction([id], event.target.checked!, dispatch, kbnVersion); + } else { + try { + const updatedRules = await enableRules({ + ids: [id], + enabled: event.target.checked!, + kbnVersion, + }); + setMyEnabled(updatedRules[0].enabled); + } catch { + setMyIsLoading(false); + } + } + setMyIsLoading(false); }, - [onRuleStateChange, id] + [dispatch, id, kbnVersion] ); + + useEffect(() => { + if (myEnabled !== enabled) { + setMyEnabled(enabled); + } + }, [enabled]); + + useEffect(() => { + if (myIsLoading !== isLoading) { + setMyIsLoading(isLoading ?? false); + } + }, [isLoading]); + return ( - {isLoading ? ( + {myIsLoading ? ( ) : ( )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/schedule_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx similarity index 84% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/schedule_item_form/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx index ebb365f6087a9..2e57ff8ba2c4f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/schedule_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx @@ -37,23 +37,25 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu const [timeVal, setTimeVal] = useState(0); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - const onChangeTimeType = useCallback(e => { - setTimeType(e.target.value); - }, []); - - const onChangeTimeVal = useCallback(e => { - const sanitizedValue: number = parseInt(e.target.value, 10); - setTimeVal(isNaN(sanitizedValue) ? 0 : sanitizedValue); - }, []); + const onChangeTimeType = useCallback( + e => { + setTimeType(e.target.value); + field.setValue(`${timeVal}${e.target.value}`); + }, + [timeVal] + ); - useEffect(() => { - if (!isEmpty(timeVal) && Number(timeVal) >= 0 && field.value !== `${timeVal}${timeType}`) { - field.setValue(`${timeVal}${timeType}`); - } - }, [field.value, timeType, timeVal]); + const onChangeTimeVal = useCallback( + e => { + const sanitizedValue: number = parseInt(e.target.value, 10); + setTimeVal(sanitizedValue); + field.setValue(`${sanitizedValue}${timeType}`); + }, + [timeType] + ); useEffect(() => { - if (!isEmpty(field.value)) { + if (field.value !== `${timeVal}${timeType}`) { const filterTimeVal = (field.value as string).match(/\d+/g); const filterTimeType = (field.value as string).match(/[a-zA-Z]+/g); if ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/schedule_item_form/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/translations.ts similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/schedule_item_form/translations.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/shared_imports.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/shared_imports.ts similarity index 97% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/shared_imports.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/shared_imports.ts index 8eb85c9fe3fae..494da24be706a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/shared_imports.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/shared_imports.ts @@ -10,7 +10,9 @@ export { FieldHook, FIELD_TYPES, Form, + FormData, FormDataProvider, + FormHook, FormSchema, UseField, useForm, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/status_icon/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/status_icon/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/data.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.ts similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/data.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.ts diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts similarity index 91% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts index 504b5ca85a3ab..c0c5ae77a1960 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts @@ -6,7 +6,7 @@ import { AboutStepRule } from '../../types'; -export const defaultValue: AboutStepRule = { +export const stepAboutDefaultValue: AboutStepRule = { name: '', description: '', isNew: true, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx new file mode 100644 index 0000000000000..e266c0b9ab47d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -0,0 +1,214 @@ +/* + * 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 { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { isEqual, get } from 'lodash/fp'; +import React, { memo, useCallback, useEffect, useState } from 'react'; + +import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; +import * as RuleI18n from '../../translations'; +import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; +import { AddItem } from '../add_item_form'; +import { defaultRiskScoreBySeverity, severityOptions, SeverityValue } from './data'; +import { stepAboutDefaultValue } from './default_value'; +import { schema } from './schema'; +import * as I18n from './translations'; +import { StepRuleDescription } from '../description_step'; +import { AddMitreThreat } from '../mitre'; + +const CommonUseField = getUseField({ component: Field }); + +interface StepAboutRuleProps extends RuleStepProps { + defaultValues?: AboutStepRule | null; +} + +export const StepAboutRule = memo( + ({ + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isUpdateView = false, + isLoading, + setForm, + setStepData, + }) => { + const [myStepData, setMyStepData] = useState(stepAboutDefaultValue); + + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); + + const onSubmit = useCallback(async () => { + if (setStepData) { + setStepData(RuleStep.aboutRule, null, false); + const { isValid, data } = await form.submit(); + if (isValid) { + setStepData(RuleStep.aboutRule, data, isValid); + setMyStepData({ ...data, isNew: false } as AboutStepRule); + } + } + }, [form]); + + useEffect(() => { + const { isNew, ...initDefaultValue } = myStepData; + if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + const myDefaultValues = { + ...defaultValues, + isNew: false, + }; + setMyStepData(myDefaultValues); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); + } + } + }, [defaultValues]); + + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.aboutRule, form); + } + }, [form]); + + return isReadOnlyView && myStepData != null ? ( + + ) : ( + <> +
+ + + + + + + + + + {({ severity }) => { + const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; + const riskScoreField = form.getFields().riskScore; + if (newRiskScore != null && riskScoreField.value !== newRiskScore) { + riskScoreField.setValue(newRiskScore); + } + return null; + }} + + + {!isUpdateView && ( + <> + + + + + {myStepData.isNew ? RuleI18n.CONTINUE : RuleI18n.UPDATE} + + + + + )} + + ); + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx similarity index 91% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/schema.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx index 534c3142335c5..c72312bb90836 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash/fp'; import React from 'react'; -import * as CreateRuleI18n from '../../translations'; +import * as RuleI18n from '../../translations'; import { IMitreEnterpriseAttack } from '../../types'; import { FIELD_TYPES, @@ -99,7 +99,7 @@ export const schema: FormSchema = { defaultMessage: 'Reference URLs', } ), - labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + labelAppend: {RuleI18n.OPTIONAL_FIELD}, }, falsePositives: { label: i18n.translate( @@ -108,7 +108,7 @@ export const schema: FormSchema = { defaultMessage: 'False positives', } ), - labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + labelAppend: {RuleI18n.OPTIONAL_FIELD}, }, threats: { label: i18n.translate( @@ -117,7 +117,7 @@ export const schema: FormSchema = { defaultMessage: 'MITRE ATT&CK', } ), - labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + labelAppend: {RuleI18n.OPTIONAL_FIELD}, validations: [ { validator: ( @@ -146,6 +146,6 @@ export const schema: FormSchema = { label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTagsLabel', { defaultMessage: 'Tags', }), - labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + labelAppend: {RuleI18n.OPTIONAL_FIELD}, }, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx similarity index 57% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx index 6954bd6bf733f..cc4e959cc9c78 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx @@ -5,14 +5,14 @@ */ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import { isEqual } from 'lodash/fp'; -import React, { memo, useCallback, useState } from 'react'; +import { isEqual, get } from 'lodash/fp'; +import React, { memo, useCallback, useState, useEffect } from 'react'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; -import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules/fetch_index_patterns'; +import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules'; import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants'; -import { useKibanaUiSetting } from '../../../../../lib/settings/use_kibana_ui_setting'; -import * as CreateRuleI18n from '../../translations'; +import { useUiSetting$ } from '../../../../../lib/kibana'; +import * as RuleI18n from '../../translations'; import { DefineStepRule, RuleStep, RuleStepProps } from '../../types'; import { StepRuleDescription } from '../description_step'; import { QueryBarDefineRule } from '../query_bar'; @@ -22,40 +22,102 @@ import * as I18n from './translations'; const CommonUseField = getUseField({ component: Field }); -export const StepDefineRule = memo( - ({ isEditView, isLoading, resizeParentContainer, setStepData }) => { +interface StepDefineRuleProps extends RuleStepProps { + defaultValues?: DefineStepRule | null; +} + +const stepDefineDefaultValue = { + index: [], + isNew: true, + queryBar: { + query: { query: '', language: 'kuery' }, + filters: [], + saved_id: null, + }, + useIndicesConfig: 'true', +}; + +const getStepDefaultValue = ( + indicesConfig: string[], + defaultValues: DefineStepRule | null +): DefineStepRule => { + if (defaultValues != null) { + return { + ...defaultValues, + isNew: false, + useIndicesConfig: `${isEqual(defaultValues.index, indicesConfig)}`, + }; + } else { + return { + ...stepDefineDefaultValue, + index: indicesConfig != null ? indicesConfig : [], + }; + } +}; + +export const StepDefineRule = memo( + ({ + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isLoading, + isUpdateView = false, + resizeParentContainer, + setForm, + setStepData, + }) => { const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); + const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); const [ { indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, setIndices, - ] = useFetchIndexPatterns(); - const [indicesConfig] = useKibanaUiSetting(DEFAULT_INDEX_KEY); - const [myStepData, setMyStepData] = useState({ - index: indicesConfig || [], - isNew: true, - queryBar: { - query: { query: '', language: 'kuery' }, - filters: [], - saved_id: null, - }, - useIndicesConfig: 'true', - }); + ] = useFetchIndexPatterns(defaultValues != null ? defaultValues.index : indicesConfig ?? []); + const [myStepData, setMyStepData] = useState(stepDefineDefaultValue); + const { form } = useForm({ - schema, defaultValue: myStepData, options: { stripEmptyFields: false }, + schema, }); const onSubmit = useCallback(async () => { - const { isValid, data } = await form.submit(); - if (isValid) { - setStepData(RuleStep.defineRule, data, isValid); - setMyStepData({ ...data, isNew: false } as DefineStepRule); + if (setStepData) { + setStepData(RuleStep.defineRule, null, false); + const { isValid, data } = await form.submit(); + if (isValid && setStepData) { + setStepData(RuleStep.defineRule, data, isValid); + setMyStepData({ ...data, isNew: false } as DefineStepRule); + } + } + }, [form]); + + useEffect(() => { + if (indicesConfig != null && defaultValues != null) { + const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues); + if (!isEqual(myDefaultValues, myStepData)) { + setMyStepData(myDefaultValues); + setLocalUseIndicesConfig(myDefaultValues.useIndicesConfig); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); + } + } + } + }, [defaultValues, indicesConfig]); + + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.defineRule, form); } }, [form]); - return isEditView && myStepData != null ? ( + return isReadOnlyView && myStepData != null ? ( ( }} - - - - - {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} - - - + {!isUpdateView && ( + <> + + + + + {myStepData.isNew ? RuleI18n.CONTINUE : RuleI18n.UPDATE} + + + + + )} ); } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx similarity index 95% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx index 0f6c5f72e1683..9b54ada8227c6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx @@ -10,9 +10,7 @@ import { isEmpty } from 'lodash/fp'; import React from 'react'; import { esKuery } from '../../../../../../../../../../src/plugins/data/public'; - -import * as CreateRuleI18n from '../../translations'; - +import * as RuleI18n from '../../translations'; import { FieldValueQueryBar } from '../query_bar'; import { ERROR_CODE, @@ -40,7 +38,7 @@ export const schema: FormSchema = { label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fiedIndicesLabel', { defaultMessage: 'Indices', }), - labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + labelAppend: {RuleI18n.OPTIONAL_FIELD}, validations: [ { validator: emptyField( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/translations.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/types.ts similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/types.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/types.ts diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx new file mode 100644 index 0000000000000..21b38a83dad9d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiPanel, EuiProgress } from '@elastic/eui'; +import React, { memo } from 'react'; +import styled from 'styled-components'; + +import { HeaderSection } from '../../../../../components/header_section'; + +interface StepPanelProps { + children: React.ReactNode; + loading: boolean; + title: string; +} + +const MyPanel = styled(EuiPanel)` + poistion: relative; +`; + +export const StepPanel = memo(({ children, loading, title }) => { + return ( + + {loading && } + + {children} + + ); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx new file mode 100644 index 0000000000000..6f7e49bc8ab9a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { isEqual, get } from 'lodash/fp'; +import React, { memo, useCallback, useEffect, useState } from 'react'; + +import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types'; +import { StepRuleDescription } from '../description_step'; +import { ScheduleItem } from '../schedule_item_form'; +import { Form, UseField, useForm } from '../shared_imports'; +import { schema } from './schema'; +import * as I18n from './translations'; + +interface StepScheduleRuleProps extends RuleStepProps { + defaultValues?: ScheduleStepRule | null; +} + +const stepScheduleDefaultValue = { + enabled: true, + interval: '5m', + isNew: true, + from: '0m', +}; + +export const StepScheduleRule = memo( + ({ + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isLoading, + isUpdateView = false, + setStepData, + setForm, + }) => { + const [myStepData, setMyStepData] = useState(stepScheduleDefaultValue); + + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); + + const onSubmit = useCallback( + async (enabled: boolean) => { + if (setStepData) { + setStepData(RuleStep.scheduleRule, null, false); + const { isValid: newIsValid, data } = await form.submit(); + if (newIsValid) { + setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid); + setMyStepData({ ...data, isNew: false } as ScheduleStepRule); + } + } + }, + [form] + ); + + useEffect(() => { + const { isNew, ...initDefaultValue } = myStepData; + if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + const myDefaultValues = { + ...defaultValues, + isNew: false, + }; + setMyStepData(myDefaultValues); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); + } + } + }, [defaultValues]); + + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.scheduleRule, form); + } + }, [form]); + + return isReadOnlyView && myStepData != null ? ( + + ) : ( + <> +
+ + + + + {!isUpdateView && ( + <> + + + + + {I18n.COMPLETE_WITHOUT_ACTIVATING} + + + + + {I18n.COMPLETE_WITH_ACTIVATING} + + + + + )} + + ); + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx similarity index 91% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/schema.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx index 7e7bd541bdb0b..31e56265dec42 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx @@ -8,7 +8,7 @@ import { EuiText } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import * as CreateRuleI18n from '../../translations'; +import * as RuleI18n from '../../translations'; import { FormSchema } from '../shared_imports'; export const schema: FormSchema = { @@ -33,7 +33,7 @@ export const schema: FormSchema = { defaultMessage: 'Additional look-back', } ), - labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + labelAppend: {RuleI18n.OPTIONAL_FIELD}, helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx similarity index 100% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_schedule_rule/translations.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts similarity index 86% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts index f6546a680ad81..a25ccce569dd4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash/fp'; import moment from 'moment'; -import { NewRule } from '../../../containers/detection_engine/rules/types'; +import { NewRule } from '../../../../containers/detection_engine/rules'; import { AboutStepRule, @@ -17,7 +17,7 @@ import { ScheduleStepRuleJson, AboutStepRuleJson, FormatRuleType, -} from './types'; +} from '../types'; const getTimeTypeValue = (time: string): { unit: string; value: number } => { const timeObj = { @@ -40,7 +40,7 @@ const getTimeTypeValue = (time: string): { unit: string; value: number } => { }; const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJson => { - const { queryBar, useIndicesConfig, ...rest } = defineStepData; + const { queryBar, useIndicesConfig, isNew, ...rest } = defineStepData; const { filters, query, saved_id: savedId } = queryBar; return { ...rest, @@ -52,8 +52,7 @@ const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJso }; const formatScheduleStepData = (scheduleData: ScheduleStepRule): ScheduleStepRuleJson => { - const formatScheduleData = scheduleData; - + const { isNew, ...formatScheduleData } = scheduleData; if (!isEmpty(formatScheduleData.interval) && !isEmpty(formatScheduleData.from)) { const { unit: intervalUnit, value: intervalValue } = getTimeTypeValue( formatScheduleData.interval @@ -64,12 +63,16 @@ const formatScheduleStepData = (scheduleData: ScheduleStepRule): ScheduleStepRul formatScheduleData.from = `now-${duration.asSeconds()}s`; formatScheduleData.to = 'now'; } - return formatScheduleData; + return { + ...formatScheduleData, + meta: { + from: scheduleData.from, + }, + }; }; const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => { - const { falsePositives, references, riskScore, threats, ...rest } = aboutStepData; - + const { falsePositives, references, riskScore, threats, isNew, ...rest } = aboutStepData; return { false_positives: falsePositives.filter(item => !isEmpty(item)), references: references.filter(item => !isEmpty(item)), @@ -91,7 +94,8 @@ const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => export const formatRule = ( defineStepData: DefineStepRule, aboutStepData: AboutStepRule, - scheduleData: ScheduleStepRule + scheduleData: ScheduleStepRule, + ruleId?: string ): NewRule => { const type: FormatRuleType = defineStepData.queryBar.saved_id != null ? 'saved_query' : 'query'; const persistData = { @@ -99,10 +103,6 @@ export const formatRule = ( ...formatDefineStepData(defineStepData), ...formatAboutStepData(aboutStepData), ...formatScheduleStepData(scheduleData), - meta: { - from: scheduleData.from, - }, }; - - return persistData; + return ruleId != null ? { id: ruleId, ...persistData } : persistData; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx similarity index 73% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index 393b72d16b0a4..3e8dbeba89546 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -9,19 +9,19 @@ import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; import styled from 'styled-components'; -import { HeaderPage } from '../../../components/header_page'; -import { WrapperPage } from '../../../components/wrapper_page'; -import { AccordionTitle } from './components/accordion_title'; -import { StepAboutRule } from './components/step_about_rule'; -import { StepDefineRule } from './components/step_define_rule'; -import { StepScheduleRule } from './components/step_schedule_rule'; -import { usePersistRule } from '../../../containers/detection_engine/rules/persist_rule'; -import { SpyRoute } from '../../../utils/route/spy_routes'; - +import { HeaderPage } from '../../../../components/header_page'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; +import { WrapperPage } from '../../../../components/wrapper_page'; +import { AccordionTitle } from '../components/accordion_title'; +import { StepAboutRule } from '../components/step_about_rule'; +import { StepDefineRule } from '../components/step_define_rule'; +import { StepScheduleRule } from '../components/step_schedule_rule'; +import { usePersistRule } from '../../../../containers/detection_engine/rules'; +import { SpyRoute } from '../../../../utils/route/spy_routes'; +import * as RuleI18n from '../translations'; +import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types'; import { formatRule } from './helpers'; import * as i18n from './translations'; -import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from './types'; -import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect_to_detection_engine'; const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; @@ -44,41 +44,44 @@ export const CreateRuleComponent = React.memo(() => { [RuleStep.aboutRule]: { isValid: false, data: {} }, [RuleStep.scheduleRule]: { isValid: false, data: {} }, }); - const [isStepRuleInEditView, setIsStepRuleInEditView] = useState>({ + const [isStepRuleInReadOnlyView, setIsStepRuleInEditView] = useState>({ [RuleStep.defineRule]: false, [RuleStep.aboutRule]: false, [RuleStep.scheduleRule]: false, }); const [{ isLoading, isSaved }, setRule] = usePersistRule(); - const setStepData = (step: RuleStep, data: unknown, isValid: boolean) => { - stepsData.current[step] = { ...stepsData.current[step], data, isValid }; - if (isValid) { - const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item); - if ([0, 1].includes(stepRuleIdx)) { - setIsStepRuleInEditView({ - ...isStepRuleInEditView, - [step]: true, - }); - if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { - openCloseAccordion(stepsRuleOrder[stepRuleIdx + 1]); - setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); + const setStepData = useCallback( + (step: RuleStep, data: unknown, isValid: boolean) => { + stepsData.current[step] = { ...stepsData.current[step], data, isValid }; + if (isValid) { + const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item); + if ([0, 1].includes(stepRuleIdx)) { + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [step]: true, + }); + if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { + openCloseAccordion(stepsRuleOrder[stepRuleIdx + 1]); + setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); + } + } else if ( + stepRuleIdx === 2 && + stepsData.current[RuleStep.defineRule].isValid && + stepsData.current[RuleStep.aboutRule].isValid + ) { + setRule( + formatRule( + stepsData.current[RuleStep.defineRule].data as DefineStepRule, + stepsData.current[RuleStep.aboutRule].data as AboutStepRule, + stepsData.current[RuleStep.scheduleRule].data as ScheduleStepRule + ) + ); } - } else if ( - stepRuleIdx === 2 && - stepsData.current[RuleStep.defineRule].isValid && - stepsData.current[RuleStep.aboutRule].isValid - ) { - setRule( - formatRule( - stepsData.current[RuleStep.defineRule].data as DefineStepRule, - stepsData.current[RuleStep.aboutRule].data as AboutStepRule, - stepsData.current[RuleStep.scheduleRule].data as ScheduleStepRule - ) - ); } - } - }; + }, + [openAccordionId, stepsData.current, setRule] + ); const getAccordionType = useCallback( (accordionId: RuleStep) => { @@ -95,19 +98,23 @@ export const CreateRuleComponent = React.memo(() => { const defineRuleButton = ( ); const aboutRuleButton = ( - + ); const scheduleRuleButton = ( ); @@ -142,7 +149,7 @@ export const CreateRuleComponent = React.memo(() => { openAccordionId != null && openAccordionId !== id && !stepsData.current[openAccordionId].isValid && - !isStepRuleInEditView[id] && + !isStepRuleInReadOnlyView[id] && isOpen ) { openCloseAccordion(id); @@ -153,20 +160,20 @@ export const CreateRuleComponent = React.memo(() => { } } }, - [isStepRuleInEditView, openAccordionId] + [isStepRuleInReadOnlyView, openAccordionId] ); const manageIsEditable = useCallback( (id: RuleStep) => { setIsStepRuleInEditView({ - ...isStepRuleInEditView, + ...isStepRuleInReadOnlyView, [id]: false, }); }, - [isStepRuleInEditView] + [isStepRuleInReadOnlyView] ); - if (isSaved && stepsData.current[RuleStep.scheduleRule].isValid) { + if (isSaved) { return ; } @@ -201,7 +208,7 @@ export const CreateRuleComponent = React.memo(() => { > setHeightAccordion(height)} @@ -231,7 +238,7 @@ export const CreateRuleComponent = React.memo(() => { > @@ -260,7 +267,7 @@ export const CreateRuleComponent = React.memo(() => { > diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts similarity index 83% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/translations.ts rename to x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts index 3dd5945ff597c..884f3f3741228 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rule_details/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts @@ -6,6 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.ruleDetails.pageTitle', { - defaultMessage: 'Rule details', +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule.pageTitle', { + defaultMessage: 'Create new rule', }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx new file mode 100644 index 0000000000000..b0cf183949dd9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -0,0 +1,223 @@ +/* + * 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 { EuiButton, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { memo, useMemo } from 'react'; +import { useParams } from 'react-router-dom'; +import { StickyContainer } from 'react-sticky'; + +import { FiltersGlobal } from '../../../../components/filters_global'; +import { FormattedDate } from '../../../../components/formatted_date'; +import { HeaderPage } from '../../../../components/header_page'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; +import { SiemSearchBar } from '../../../../components/search_bar'; +import { WrapperPage } from '../../../../components/wrapper_page'; +import { useRule } from '../../../../containers/detection_engine/rules'; + +import { + indicesExistOrDataTemporarilyUnavailable, + WithSource, +} from '../../../../containers/source'; +import { SpyRoute } from '../../../../utils/route/spy_routes'; + +import { SignalsCharts } from '../../components/signals_chart'; +import { SignalsTable } from '../../components/signals'; +import { DetectionEngineEmptyPage } from '../../detection_engine_empty_page'; +import { useSignalInfo } from '../../components/signals_info'; +import { StepAboutRule } from '../components/step_about_rule'; +import { StepDefineRule } from '../components/step_define_rule'; +import { StepScheduleRule } from '../components/step_schedule_rule'; +import { buildSignalsRuleIdFilter } from '../../components/signals/default_config'; +import * as detectionI18n from '../../translations'; +import { RuleSwitch } from '../components/rule_switch'; +import { StepPanel } from '../components/step_panel'; +import { getStepsData } from '../helpers'; +import * as ruleI18n from '../translations'; +import * as i18n from './translations'; +import { GlobalTime } from '../../../../containers/global_time'; + +export const RuleDetailsComponent = memo(() => { + const { ruleId } = useParams(); + const [loading, rule] = useRule(ruleId); + const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ + rule, + detailsView: true, + }); + const [lastSignals] = useSignalInfo({ ruleId }); + + const title = loading === true || rule === null ? : rule.name; + const subTitle = useMemo( + () => + loading === true || rule === null ? ( + + ) : ( + [ + + ), + }} + />, + rule?.updated_by != null ? ( + + ), + }} + /> + ) : ( + '' + ), + ] + ), + [loading, rule] + ); + + const signalDefaultFilters = useMemo( + () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), + [ruleId] + ); + return ( + <> + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + {({ to, from }) => ( + + + + + + + + {detectionI18n.LAST_SIGNAL} + {': '} + {lastSignals} + + ) : null, + 'Status: Comming Soon', + ]} + title={title} + > + + + + + + + + + + {ruleI18n.EDIT_RULE_SETTINGS} + + + + + + + + + + + + + {defineRuleData != null && ( + + )} + + + + + + {aboutRuleData != null && ( + + )} + + + + + + {scheduleRuleData != null && ( + + )} + + + + + + + + + + + {ruleId != null && ( + + )} + + + )} + + ) : ( + + + + + + ); + }} + + + + + ); +}); +RuleDetailsComponent.displayName = 'RuleDetailsComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts new file mode 100644 index 0000000000000..9dbb3b0079b0b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.ruleDetails.pageTitle', { + defaultMessage: 'Rule details', +}); + +export const BACK_TO_RULES = i18n.translate( + 'xpack.siem.detectionEngine.ruleDetails.backToRulesDescription', + { + defaultMessage: 'Back to rules', + } +); + +export const EXPERIMENTAL = i18n.translate( + 'xpack.siem.detectionEngine.ruleDetails.experimentalDescription', + { + defaultMessage: 'Experimental', + } +); + +export const ACTIVATE_RULE = i18n.translate( + 'xpack.siem.detectionEngine.ruleDetails.activateRuleLabel', + { + defaultMessage: 'Activate', + } +); + +export const UNKNOWN = i18n.translate('xpack.siem.detectionEngine.ruleDetails.unknownDescription', { + defaultMessage: 'Unknown', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx new file mode 100644 index 0000000000000..8e32f82dff0b1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -0,0 +1,323 @@ +/* + * 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 { + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Redirect, useParams } from 'react-router-dom'; + +import { HeaderPage } from '../../../../components/header_page'; +import { WrapperPage } from '../../../../components/wrapper_page'; +import { SpyRoute } from '../../../../utils/route/spy_routes'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; +import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules'; +import { FormHook, FormData } from '../components/shared_imports'; +import { StepPanel } from '../components/step_panel'; +import { StepAboutRule } from '../components/step_about_rule'; +import { StepDefineRule } from '../components/step_define_rule'; +import { StepScheduleRule } from '../components/step_schedule_rule'; +import { formatRule } from '../create/helpers'; +import { getStepsData } from '../helpers'; +import * as ruleI18n from '../translations'; +import { RuleStep, DefineStepRule, AboutStepRule, ScheduleStepRule } from '../types'; +import * as i18n from './translations'; + +interface StepRuleForm { + isValid: boolean; +} +interface AboutStepRuleForm extends StepRuleForm { + data: AboutStepRule | null; +} +interface DefineStepRuleForm extends StepRuleForm { + data: DefineStepRule | null; +} +interface ScheduleStepRuleForm extends StepRuleForm { + data: ScheduleStepRule | null; +} + +export const EditRuleComponent = memo(() => { + const { ruleId } = useParams(); + const [loading, rule] = useRule(ruleId); + const [initForm, setInitForm] = useState(false); + const [myAboutRuleForm, setMyAboutRuleForm] = useState({ + data: null, + isValid: false, + }); + const [myDefineRuleForm, setMyDefineRuleForm] = useState({ + data: null, + isValid: false, + }); + const [myScheduleRuleForm, setMyScheduleRuleForm] = useState({ + data: null, + isValid: false, + }); + const [selectedTab, setSelectedTab] = useState(); + const stepsForm = useRef | null>>({ + [RuleStep.defineRule]: null, + [RuleStep.aboutRule]: null, + [RuleStep.scheduleRule]: null, + }); + const [{ isLoading, isSaved }, setRule] = usePersistRule(); + const [tabHasError, setTabHasError] = useState([]); + const setStepsForm = useCallback( + (step: RuleStep, form: FormHook) => { + stepsForm.current[step] = form; + if (initForm && step === (selectedTab?.id as RuleStep) && form.isSubmitted === false) { + setInitForm(false); + form.submit(); + } + }, + [initForm, selectedTab] + ); + const tabs = useMemo( + () => [ + { + id: RuleStep.defineRule, + name: ruleI18n.DEFINITION, + content: ( + <> + + + {myDefineRuleForm.data != null && ( + + )} + + + + ), + }, + { + id: RuleStep.aboutRule, + name: ruleI18n.ABOUT, + content: ( + <> + + + {myAboutRuleForm.data != null && ( + + )} + + + + ), + }, + { + id: RuleStep.scheduleRule, + name: ruleI18n.SCHEDULE, + content: ( + <> + + + {myScheduleRuleForm.data != null && ( + + )} + + + + ), + }, + ], + [ + loading, + isLoading, + myAboutRuleForm, + myDefineRuleForm, + myScheduleRuleForm, + setStepsForm, + stepsForm, + ] + ); + + const onSubmit = useCallback(async () => { + const activeFormId = selectedTab?.id as RuleStep; + const activeForm = await stepsForm.current[activeFormId]?.submit(); + + const invalidForms = [RuleStep.aboutRule, RuleStep.defineRule, RuleStep.scheduleRule].reduce< + RuleStep[] + >((acc, step) => { + if ( + (step === activeFormId && activeForm != null && !activeForm?.isValid) || + (step === RuleStep.aboutRule && !myAboutRuleForm.isValid) || + (step === RuleStep.defineRule && !myDefineRuleForm.isValid) || + (step === RuleStep.scheduleRule && !myScheduleRuleForm.isValid) + ) { + return [...acc, step]; + } + return acc; + }, []); + + if (invalidForms.length === 0 && activeForm != null) { + setTabHasError([]); + setRule( + formatRule( + (activeFormId === RuleStep.defineRule + ? activeForm.data + : myDefineRuleForm.data) as DefineStepRule, + (activeFormId === RuleStep.aboutRule + ? activeForm.data + : myAboutRuleForm.data) as AboutStepRule, + (activeFormId === RuleStep.scheduleRule + ? activeForm.data + : myScheduleRuleForm.data) as ScheduleStepRule, + ruleId + ) + ); + } else { + setTabHasError(invalidForms); + } + }, [stepsForm, myAboutRuleForm, myDefineRuleForm, myScheduleRuleForm, selectedTab, ruleId]); + + useEffect(() => { + if (rule != null) { + const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ rule }); + setMyAboutRuleForm({ data: aboutRuleData, isValid: true }); + setMyDefineRuleForm({ data: defineRuleData, isValid: true }); + setMyScheduleRuleForm({ data: scheduleRuleData, isValid: true }); + } + }, [rule]); + + const onTabClick = useCallback( + async (tab: EuiTabbedContentTab) => { + if (selectedTab != null) { + const ruleStep = selectedTab.id as RuleStep; + const respForm = await stepsForm.current[ruleStep]?.submit(); + if (respForm != null) { + if (ruleStep === RuleStep.aboutRule) { + setMyAboutRuleForm({ + data: respForm.data as AboutStepRule, + isValid: respForm.isValid, + }); + } else if (ruleStep === RuleStep.defineRule) { + setMyDefineRuleForm({ + data: respForm.data as DefineStepRule, + isValid: respForm.isValid, + }); + } else if (ruleStep === RuleStep.scheduleRule) { + setMyScheduleRuleForm({ + data: respForm.data as ScheduleStepRule, + isValid: respForm.isValid, + }); + } + } + } + setInitForm(true); + setSelectedTab(tab); + }, + [selectedTab, stepsForm.current] + ); + + useEffect(() => { + if (rule != null) { + const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ rule }); + setMyAboutRuleForm({ data: aboutRuleData, isValid: true }); + setMyDefineRuleForm({ data: defineRuleData, isValid: true }); + setMyScheduleRuleForm({ data: scheduleRuleData, isValid: true }); + } + }, [rule]); + + useEffect(() => { + setSelectedTab(tabs[0]); + }, []); + + if (isSaved || (rule != null && rule.immutable)) { + return ; + } + + return ( + <> + + + {tabHasError.length > 0 && ( + + { + if (t === RuleStep.aboutRule) { + return ruleI18n.ABOUT; + } else if (t === RuleStep.defineRule) { + return ruleI18n.DEFINITION; + } else if (t === RuleStep.scheduleRule) { + return ruleI18n.SCHEDULE; + } + return t; + }) + .join(', '), + }} + /> + + )} + + t.id === selectedTab?.id)} + onTabClick={onTabClick} + tabs={tabs} + /> + + + + + + + {i18n.CANCEL} + + + + + + {i18n.SAVE_CHANGES} + + + + + + + + ); +}); +EditRuleComponent.displayName = 'EditRuleComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts new file mode 100644 index 0000000000000..b81ae58e565f0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.editRule.pageTitle', { + defaultMessage: 'Edit rule settings', +}); + +export const CANCEL = i18n.translate('xpack.siem.detectionEngine.editRule.cancelTitle', { + defaultMessage: 'Cancel', +}); + +export const SAVE_CHANGES = i18n.translate('xpack.siem.detectionEngine.editRule.saveChangeTitle', { + defaultMessage: 'Save changes', +}); + +export const SORRY_ERRORS = i18n.translate( + 'xpack.siem.detectionEngine.editRule.errorMsgDescription', + { + defaultMessage: 'Sorry', + } +); + +export const BACK_TO = i18n.translate('xpack.siem.detectionEngine.editRule.backToDescription', { + defaultMessage: 'Back to', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx new file mode 100644 index 0000000000000..46301ae808919 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -0,0 +1,63 @@ +/* + * 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 { pick } from 'lodash/fp'; + +import { esFilters } from '../../../../../../../../src/plugins/data/public'; +import { Rule } from '../../../containers/detection_engine/rules'; +import { AboutStepRule, DefineStepRule, IMitreEnterpriseAttack, ScheduleStepRule } from './types'; + +interface GetStepsData { + aboutRuleData: AboutStepRule | null; + defineRuleData: DefineStepRule | null; + scheduleRuleData: ScheduleStepRule | null; +} + +export const getStepsData = ({ + rule, + detailsView = false, +}: { + rule: Rule | null; + detailsView?: boolean; +}): GetStepsData => { + const defineRuleData: DefineStepRule | null = + rule != null + ? { + isNew: false, + index: rule.index, + queryBar: { + query: { query: rule.query as string, language: rule.language }, + filters: rule.filters as esFilters.Filter[], + saved_id: rule.saved_id ?? null, + }, + useIndicesConfig: 'true', + } + : null; + const aboutRuleData: AboutStepRule | null = + rule != null + ? { + isNew: false, + ...pick(['description', 'name', 'references', 'severity', 'tags', 'threats'], rule), + ...(detailsView ? { name: '' } : {}), + threats: rule.threats as IMitreEnterpriseAttack[], + falsePositives: rule.false_positives, + riskScore: rule.risk_score, + } + : null; + const scheduleRuleData: ScheduleStepRule | null = + rule != null + ? { + isNew: false, + ...pick(['enabled', 'interval'], rule), + from: + rule?.meta?.from != null + ? rule.meta.from.replace('now-', '') + : rule.from.replace('now-', ''), + } + : null; + + return { aboutRuleData, defineRuleData, scheduleRuleData }; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index afff0f07dfac4..8b4cc2a213589 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -4,20 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiTabbedContent } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedRelativePreferenceDate } from '../../../components/formatted_date'; +import { getEmptyTagValue } from '../../../components/empty_value'; import { HeaderPage } from '../../../components/header_page'; - import { WrapperPage } from '../../../components/wrapper_page'; import { SpyRoute } from '../../../utils/route/spy_routes'; -import * as i18n from './translations'; -import { AllRules } from './all_rules'; -import { ActivityMonitor } from './activity_monitor'; -import { FormattedRelativePreferenceDate } from '../../../components/formatted_date'; -import { getEmptyTagValue } from '../../../components/empty_value'; + +import { AllRules } from './all'; import { ImportRuleModal } from './components/import_rule_modal'; +import * as i18n from './translations'; export const RulesComponent = React.memo(() => { const [showImportModal, setShowImportModal] = useState(false); @@ -62,27 +61,14 @@ export const RulesComponent = React.memo(() => { - + {i18n.ADD_NEW_RULE}
- , - }, - { - id: 'tabActivityMonitor', - name: i18n.ACTIVITY_MONITOR, - content: , - }, - ]} - /> + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index 9ae266e396f6d..ecd6bef942bfb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -205,3 +205,46 @@ export const COLUMN_ACTIVATE = i18n.translate( defaultMessage: 'Activate', } ); + +export const DEFINE_RULE = i18n.translate('xpack.siem.detectionEngine.rules.defineRuleTitle', { + defaultMessage: 'Define Rule', +}); + +export const ABOUT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.aboutRuleTitle', { + defaultMessage: 'About Rule', +}); + +export const SCHEDULE_RULE = i18n.translate('xpack.siem.detectionEngine.rules.scheduleRuleTitle', { + defaultMessage: 'Schedule Rule', +}); + +export const DEFINITION = i18n.translate('xpack.siem.detectionEngine.rules.stepDefinitionTitle', { + defaultMessage: 'Definition', +}); + +export const ABOUT = i18n.translate('xpack.siem.detectionEngine.rules.stepAboutTitle', { + defaultMessage: 'About', +}); + +export const SCHEDULE = i18n.translate('xpack.siem.detectionEngine.rules.stepScheduleTitle', { + defaultMessage: 'Schedule', +}); + +export const OPTIONAL_FIELD = i18n.translate( + 'xpack.siem.detectionEngine.rules.optionalFieldDescription', + { + defaultMessage: 'Optional', + } +); + +export const CONTINUE = i18n.translate('xpack.siem.detectionEngine.rules.continueButtonTitle', { + defaultMessage: 'Continue', +}); + +export const UPDATE = i18n.translate('xpack.siem.detectionEngine.rules.updateButtonTitle', { + defaultMessage: 'Update', +}); + +export const DELETE = i18n.translate('xpack.siem.detectionEngine.rules.deleteDescription', { + defaultMessage: 'Delete', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts index de3fa839ea91e..9b535034810bd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Rule } from '../../../containers/detection_engine/rules/types'; +import { esFilters } from '../../../../../../../../src/plugins/data/common'; +import { Rule } from '../../../containers/detection_engine/rules'; +import { FieldValueQueryBar } from './components/query_bar'; +import { FormData, FormHook } from './components/shared_imports'; export interface EuiBasicTableSortTypes { field: string; @@ -39,3 +42,96 @@ export interface TableData { isLoading: boolean; sourceRule: Rule; } + +export enum RuleStep { + defineRule = 'define-rule', + aboutRule = 'about-rule', + scheduleRule = 'schedule-rule', +} +export type RuleStatusType = 'passive' | 'active' | 'valid'; + +export interface RuleStepData { + data: unknown; + isValid: boolean; +} + +export interface RuleStepProps { + descriptionDirection?: 'row' | 'column'; + setStepData?: (step: RuleStep, data: unknown, isValid: boolean) => void; + isReadOnlyView: boolean; + isUpdateView?: boolean; + isLoading: boolean; + resizeParentContainer?: (height: number) => void; + setForm?: (step: RuleStep, form: FormHook) => void; +} + +interface StepRuleData { + isNew: boolean; +} +export interface AboutStepRule extends StepRuleData { + name: string; + description: string; + severity: string; + riskScore: number; + references: string[]; + falsePositives: string[]; + tags: string[]; + threats: IMitreEnterpriseAttack[]; +} + +export interface DefineStepRule extends StepRuleData { + useIndicesConfig: string; + index: string[]; + queryBar: FieldValueQueryBar; +} + +export interface ScheduleStepRule extends StepRuleData { + enabled: boolean; + interval: string; + from: string; + to?: string; +} + +export interface DefineStepRuleJson { + index: string[]; + filters: esFilters.Filter[]; + saved_id?: string; + query: string; + language: string; +} + +export interface AboutStepRuleJson { + name: string; + description: string; + severity: string; + risk_score: number; + references: string[]; + false_positives: string[]; + tags: string[]; + threats: IMitreEnterpriseAttack[]; +} + +export interface ScheduleStepRuleJson { + enabled: boolean; + interval: string; + from: string; + to?: string; + meta?: unknown; +} + +export type MyRule = Omit & { + immutable: boolean; +}; + +export type FormatRuleType = 'query' | 'saved_query'; + +export interface IMitreAttack { + id: string; + name: string; + reference: string; +} +export interface IMitreEnterpriseAttack { + framework: string; + tactic: IMitreAttack; + techniques: IMitreAttack[]; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/components/closed_signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/components/closed_signals/index.tsx deleted file mode 100644 index 7ccc95a1d7083..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/components/closed_signals/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../../../../../components/detection_engine/utility_bar'; -import * as i18n from '../../translations'; - -export const ClosedSignals = React.memo<{ totalCount: number }>(({ totalCount }) => { - return ( - <> - - - - {`${i18n.PANEL_SUBTITLE_SHOWING}: ${totalCount} signals`} - - - - - -

{'Customize columns context menu here.'}

} - > - {'Customize columns'} -
- - {'Aggregate data'} -
-
-
- - {/* Michael: Closed signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} - - ); -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/components/open_signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/components/open_signals/index.tsx deleted file mode 100644 index f65f511ab33a9..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/components/open_signals/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../../../../../components/detection_engine/utility_bar'; -import * as i18n from '../../translations'; - -export const OpenSignals = React.memo<{ totalCount: number }>(({ totalCount }) => { - return ( - <> - - - - {`${i18n.PANEL_SUBTITLE_SHOWING}: ${totalCount} signals`} - - - - {'Selected: 20 signals'} - -

{'Batch actions context menu here.'}

} - > - {'Batch actions'} -
- - - {'Select all signals on all pages'} - -
-
-
- - {/* Michael: Open signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */} - - ); -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_config.tsx deleted file mode 100644 index e90487a3b023c..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/default_config.tsx +++ /dev/null @@ -1,126 +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 { ColumnHeader } from '../../../components/timeline/body/column_headers/column_header'; -import { defaultColumnHeaderType } from '../../../components/timeline/body/column_headers/default_headers'; -import { - DEFAULT_COLUMN_MIN_WIDTH, - DEFAULT_DATE_COLUMN_MIN_WIDTH, -} from '../../../components/timeline/body/helpers'; - -import * as i18n from './translations'; -import { SubsetTimelineModel, timelineDefaults } from '../../../store/timeline/model'; -import { esFilters } from '../../../../../../../../src/plugins/data/common/es_query'; - -export const signalsOpenFilters: esFilters.Filter[] = [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'signal.status', - params: { - query: 'open', - }, - }, - query: { - match_phrase: { - 'signal.status': 'open', - }, - }, - }, -]; - -export const signalsClosedFilters: esFilters.Filter[] = [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'signal.status', - params: { - query: 'closed', - }, - }, - query: { - match_phrase: { - 'signal.status': 'closed', - }, - }, - }, -]; - -export const signalsHeaders: ColumnHeader[] = [ - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.name', - label: i18n.SIGNALS_HEADERS_RULE, - width: DEFAULT_COLUMN_MIN_WIDTH, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.type', - label: i18n.SIGNALS_HEADERS_METHOD, - width: 80, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.severity', - label: i18n.SIGNALS_HEADERS_SEVERITY, - width: 80, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.risk_score', - label: i18n.SIGNALS_HEADERS_RISK_SCORE, - width: 120, - }, - { - category: 'event', - columnHeaderType: defaultColumnHeaderType, - id: 'event.action', - type: 'string', - aggregatable: true, - width: 140, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'event.category', - width: 150, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'host.name', - width: 120, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'user.name', - width: 120, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'source.ip', - width: 120, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'destination.ip', - width: 120, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: '@timestamp', - width: DEFAULT_DATE_COLUMN_MIN_WIDTH, - }, -]; - -export const signalsDefaultModel: SubsetTimelineModel = { - ...timelineDefaults, - columns: signalsHeaders, -}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx deleted file mode 100644 index 74b7b9349c2cb..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx +++ /dev/null @@ -1,97 +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 { EuiFilterButton, EuiFilterGroup } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; -import { OpenSignals } from './components/open_signals'; -import { ClosedSignals } from './components/closed_signals'; -import { GlobalTime } from '../../../containers/global_time'; -import { StatefulEventsViewer } from '../../../components/events_viewer'; -import * as i18n from './translations'; -import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { signalsClosedFilters, signalsDefaultModel, signalsOpenFilters } from './default_config'; - -const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; -const FILTER_OPEN = 'open'; -const FILTER_CLOSED = 'closed'; - -export const SignalsTableFilterGroup = React.memo( - ({ onFilterGroupChanged }: { onFilterGroupChanged: (filterGroup: string) => void }) => { - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - - return ( - - { - setFilterGroup(FILTER_OPEN); - onFilterGroupChanged(FILTER_OPEN); - }} - withNext - > - {'Open signals'} - - - { - setFilterGroup(FILTER_CLOSED); - onFilterGroupChanged(FILTER_CLOSED); - }} - > - {'Closed signals'} - - - ); - } -); - -export const SignalsTable = React.memo(() => { - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - - const onFilterGroupChangedCallback = useCallback( - (newFilterGroup: string) => { - setFilterGroup(newFilterGroup); - }, - [setFilterGroup] - ); - - return ( - <> - - {({ to, from, setQuery, deleteQuery, isInitializing }) => ( - - } - id={SIGNALS_PAGE_TIMELINE_ID} - start={from} - timelineTypeContext={{ - documentType: i18n.SIGNALS_DOCUMENT_TYPE, - footerText: i18n.TOTAL_COUNT_OF_SIGNALS, - showCheckboxes: true, - showRowRenderers: false, - title: i18n.SIGNALS_TABLE_TITLE, - }} - utilityBar={(totalCount: number) => - filterGroup === FILTER_OPEN ? ( - - ) : ( - - ) - } - /> - )} - - - ); -}); - -SignalsTable.displayName = 'SignalsTable'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index a7e7fa5133a64..94adbfcde1e53 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -10,8 +10,16 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', defaultMessage: 'Detection engine', }); -export const PAGE_SUBTITLE = i18n.translate('xpack.siem.detectionEngine.pageSubtitle', { - defaultMessage: 'Last signal: X minutes ago', +export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', { + defaultMessage: 'Last signal:', +}); + +export const TOTAL_SIGNAL = i18n.translate('xpack.siem.detectionEngine.totalSignalTitle', { + defaultMessage: 'Total', +}); + +export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', { + defaultMessage: 'Signals', }); export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx index 8d45bbbe34d33..092c2463419d1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx @@ -10,21 +10,13 @@ import { MemoryRouter } from 'react-router-dom'; import { mockIndexPattern } from '../../../mock/index_pattern'; import { TestProviders } from '../../../mock/test_providers'; -import { mockUiSettings } from '../../../mock/ui_settings'; import { HostDetailsTabs } from './details_tabs'; import { SetAbsoluteRangeDatePicker } from './types'; import { hostDetailsPagePath } from '../types'; import { type } from './utils'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { useMountAppended } from '../../../utils/use_mount_appended'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); +jest.mock('../../../lib/kibana'); jest.mock('../../../containers/source', () => ({ indicesExistOrDataTemporarilyUnavailable: () => true, diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx index e062e65bde496..b548d91615d19 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx @@ -28,7 +28,7 @@ import { HostOverviewByNameQuery } from '../../../containers/hosts/overview'; import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; import { LastEventIndexKey } from '../../../graphql/types'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; +import { useKibana } from '../../../lib/kibana'; import { convertToBuildEsQuery } from '../../../lib/keury'; import { inputsSelectors, State } from '../../../store'; import { setHostDetailsTablesActivePageToZero as dispatchHostDetailsTablesActivePageToZero } from '../../../store/hosts/actions'; @@ -63,7 +63,7 @@ const HostDetailsComponent = React.memo( setHostDetailsTablesActivePageToZero(null); }, [setHostDetailsTablesActivePageToZero, detailName]); const capabilities = useContext(MlCapabilitiesContext); - const core = useKibanaCore(); + const kibana = useKibana(); const hostDetailsPageFilters: esFilters.Filter[] = [ { meta: { @@ -100,7 +100,7 @@ const HostDetailsComponent = React.memo( {({ indicesExist, indexPattern }) => { const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(core.uiSettings), + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, queries: [query], filters: getFilters(), diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx index f08cee824afa7..00dcb5908a98b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx @@ -12,30 +12,15 @@ import { MockedProvider } from 'react-apollo/test-utils'; import { ActionCreator } from 'typescript-fsa'; import '../../mock/match_media'; - -import { SiemNavigation } from '../../components/navigation'; import { mocksSource } from '../../containers/source/mock'; import { wait } from '../../lib/helpers'; import { TestProviders } from '../../mock'; -import { mockUiSettings } from '../../mock/ui_settings'; import { InputsModelId } from '../../store/inputs/constants'; +import { SiemNavigation } from '../../components/navigation'; import { HostsComponentProps } from './types'; import { Hosts } from './hosts'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; - -jest.mock('../../lib/settings/use_kibana_ui_setting'); -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); - -jest.mock('ui/documentation_links', () => ({ - documentationLinks: { - kibana: 'http://www.example.com', - }, -})); +jest.mock('../../lib/kibana'); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx index 6d217a9301884..6b69f06b97b83 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -25,7 +25,7 @@ import { GlobalTimeArgs } from '../../containers/global_time'; import { KpiHostsQuery } from '../../containers/kpi_hosts'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { LastEventIndexKey } from '../../graphql/types'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; +import { useKibana } from '../../lib/kibana'; import { convertToBuildEsQuery } from '../../lib/keury'; import { inputsSelectors, State, hostsModel } from '../../store'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; @@ -54,7 +54,7 @@ const HostsComponent = React.memo( hostsPagePath, }) => { const capabilities = React.useContext(MlCapabilitiesContext); - const core = useKibanaCore(); + const kibana = useKibana(); const { tabName } = useParams(); const hostsFilters = React.useMemo(() => { @@ -75,7 +75,7 @@ const HostsComponent = React.memo( {({ indicesExist, indexPattern }) => { const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(core.uiSettings), + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, queries: [query], filters: hostsFilters, diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx index ecd1e4f378cc5..1d2a3f83e8808 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_empty_page.tsx @@ -6,26 +6,30 @@ import React from 'react'; import chrome from 'ui/chrome'; -import { documentationLinks } from 'ui/documentation_links'; import { EmptyPage } from '../../components/empty_page'; +import { useKibana } from '../../lib/kibana'; import * as i18n from './translations'; const basePath = chrome.getBasePath(); -export const HostsEmptyPage = React.memo(() => ( - -)); +export const HostsEmptyPage = React.memo(() => { + const docLinks = useKibana().services.docLinks; + + return ( + + ); +}); HostsEmptyPage.displayName = 'HostsEmptyPage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx index 72f7858847649..d624631c1feae 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx @@ -16,27 +16,19 @@ import '../../../mock/match_media'; import { mocksSource } from '../../../containers/source/mock'; import { FlowTarget } from '../../../graphql/types'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../mock'; import { useMountAppended } from '../../../utils/use_mount_appended'; -import { mockUiSettings } from '../../../mock/ui_settings'; import { createStore, State } from '../../../store'; import { InputsModelId } from '../../../store/inputs/constants'; import { IPDetailsComponent, IPDetails } from './index'; -jest.mock('../../../lib/settings/use_kibana_ui_setting'); - type Action = 'PUSH' | 'POP' | 'REPLACE'; const pop: Action = 'POP'; type GlobalWithFetch = NodeJS.Global & { fetch: jest.Mock }; -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); +jest.mock('../../../lib/kibana'); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -106,12 +98,6 @@ const getMockProps = (ip: string) => ({ setIpDetailsTablesActivePageToZero: (jest.fn() as unknown) as ActionCreator, }); -jest.mock('ui/documentation_links', () => ({ - documentationLinks: { - siem: 'http://www.example.com', - }, -})); - describe('Ip Details', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx index 97db422b539e8..99ca12292a52c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.tsx @@ -24,7 +24,7 @@ import { WrapperPage } from '../../../components/wrapper_page'; import { IpOverviewQuery } from '../../../containers/ip_overview'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; import { FlowTargetSourceDest, LastEventIndexKey } from '../../../graphql/types'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; +import { useKibana } from '../../../lib/kibana'; import { decodeIpv6 } from '../../../lib/helpers'; import { convertToBuildEsQuery } from '../../../lib/keury'; import { ConditionalFlexGroup } from '../../../pages/network/navigation/conditional_flex_group'; @@ -70,7 +70,7 @@ export const IPDetailsComponent = ({ }, [setAbsoluteRangeDatePicker] ); - const core = useKibanaCore(); + const kibana = useKibana(); useEffect(() => { setIpDetailsTablesActivePageToZero(null); @@ -82,7 +82,7 @@ export const IPDetailsComponent = ({ {({ indicesExist, indexPattern }) => { const ip = decodeIpv6(detailName); const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(core.uiSettings), + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, queries: [query], filters, diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx index 327b0fb4c1e5b..335bb62c5c852 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx @@ -13,24 +13,10 @@ import { MockedProvider } from 'react-apollo/test-utils'; import '../../mock/match_media'; import { mocksSource } from '../../containers/source/mock'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; import { TestProviders } from '../../mock'; -import { mockUiSettings } from '../../mock/ui_settings'; import { Network } from './network'; -jest.mock('../../lib/settings/use_kibana_ui_setting'); - -jest.mock('ui/documentation_links', () => ({ - documentationLinks: { - kibana: 'http://www.example.com', - }, -})); - -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); +jest.mock('../../lib/kibana'); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx index 0d8d3a6753c59..c39935742a2e0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -23,7 +23,7 @@ import { WrapperPage } from '../../components/wrapper_page'; import { KpiNetworkQuery } from '../../containers/kpi_network'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { LastEventIndexKey } from '../../graphql/types'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; +import { useKibana } from '../../lib/kibana'; import { convertToBuildEsQuery } from '../../lib/keury'; import { networkModel, State, inputsSelectors } from '../../store'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; @@ -51,7 +51,7 @@ const NetworkComponent = React.memo( hasMlUserPermissions, capabilitiesFetched, }) => { - const core = useKibanaCore(); + const kibana = useKibana(); const { tabName } = useParams(); const networkFilters = useMemo(() => { @@ -72,7 +72,7 @@ const NetworkComponent = React.memo( {({ indicesExist, indexPattern }) => { const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(core.uiSettings), + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, queries: [query], filters: networkFilters, diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx index 34e7f49bd9bd5..e22802fd29d49 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network_empty_page.tsx @@ -6,26 +6,29 @@ import React from 'react'; import chrome from 'ui/chrome'; -import { documentationLinks } from 'ui/documentation_links'; +import { useKibana } from '../../lib/kibana'; import { EmptyPage } from '../../components/empty_page'; - import * as i18n from './translations'; const basePath = chrome.getBasePath(); -export const NetworkEmptyPage = React.memo(() => ( - -)); +export const NetworkEmptyPage = React.memo(() => { + const docLinks = useKibana().services.docLinks; + + return ( + + ); +}); NetworkEmptyPage.displayName = 'NetworkEmptyPage'; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx index 833030e0dc8a1..300df4a742adf 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx @@ -10,17 +10,11 @@ import * as React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { MemoryRouter } from 'react-router-dom'; -import { Overview } from './index'; - -import '../../mock/ui_settings'; -import { mocksSource } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; +import { mocksSource } from '../../containers/source/mock'; +import { Overview } from './index'; -jest.mock('ui/documentation_links', () => ({ - documentationLinks: { - kibana: 'http://www.example.com', - }, -})); +jest.mock('../../lib/kibana'); let localSource: Array<{ request: {}; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx index de976b1a5c5a3..a0e94431054cc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup } from '@elastic/eui'; import moment from 'moment'; import React from 'react'; import chrome from 'ui/chrome'; -import { documentationLinks } from 'ui/documentation_links'; +import { useKibana } from '../../lib/kibana'; import { EmptyPage } from '../../components/empty_page'; import { HeaderPage } from '../../components/header_page'; import { OverviewHost } from '../../components/page/overview/overview_host'; @@ -24,6 +24,7 @@ import * as i18n from './translations'; const basePath = chrome.getBasePath(); export const OverviewComponent = React.memo(() => { + const docLinks = useKibana().services.docLinks; const dateEnd = Date.now(); const dateRange = moment.duration(24, 'hours').asMilliseconds(); const dateStart = dateEnd - dateRange; @@ -62,7 +63,7 @@ export const OverviewComponent = React.memo(() => { actionSecondaryIcon="popout" actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY} actionSecondaryTarget="_blank" - actionSecondaryUrl={documentationLinks.siem} + actionSecondaryUrl={docLinks.links.siem} data-test-subj="empty-page" title={i18n.EMPTY_TITLE} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx index 7fd8f84129d89..51cfcbe9374ab 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/summary.tsx @@ -4,73 +4,81 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import { EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import { documentationLinks } from 'ui/documentation_links'; -export const Summary = React.memo(() => ( - - -

- -

+import { useKibana } from '../../lib/kibana'; + +export const Summary = React.memo(() => { + const docLinks = useKibana().services.docLinks; + + return ( + + +

+ +

-

- - - - ), - data: ( - - - - ), - siemSolution: ( - - - - ), - }} - /> -

+

+ + + + ), + data: ( + + + + ), + siemSolution: ( + + + + ), + }} + /> +

-

- -

+

+ +

-

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

-
-
-)); +

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

+
+
+ ); +}); Summary.displayName = 'Summary'; diff --git a/x-pack/legacy/plugins/siem/public/store/timeline/actions.ts b/x-pack/legacy/plugins/siem/public/store/timeline/actions.ts index 931d3e26172cd..f8d23ac72e202 100644 --- a/x-pack/legacy/plugins/siem/public/store/timeline/actions.ts +++ b/x-pack/legacy/plugins/siem/public/store/timeline/actions.ts @@ -16,6 +16,7 @@ import { import { KueryFilterQuery, SerializedFilterQuery } from '../model'; import { KqlMode, TimelineModel } from './model'; +import { TimelineNonEcsData } from '../../graphql/types'; const actionCreator = actionCreatorFactory('x-pack/siem/local/timeline'); @@ -49,10 +50,21 @@ export const applyDeltaToColumnWidth = actionCreator<{ export const createTimeline = actionCreator<{ id: string; + dateRange?: { + start: number; + end: number; + }; + filters?: esFilters.Filter[]; columns: ColumnHeader[]; itemsPerPage?: number; + kqlQuery?: { + filterQuery: SerializedFilterQuery | null; + filterQueryDraft: KueryFilterQuery | null; + }; show?: boolean; sort?: Sort; + showCheckboxes?: boolean; + showRowRenderers?: boolean; }>('CREATE_TIMELINE'); export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT'); @@ -198,3 +210,34 @@ export const setFilters = actionCreator<{ id: string; filters: esFilters.Filter[]; }>('SET_TIMELINE_FILTERS'); + +export const setSelected = actionCreator<{ + id: string; + eventIds: Readonly>; + isSelected: boolean; + isSelectAllChecked: boolean; +}>('SET_TIMELINE_SELECTED'); + +export const clearSelected = actionCreator<{ + id: string; +}>('CLEAR_TIMELINE_SELECTED'); + +export const setEventsLoading = actionCreator<{ + id: string; + eventIds: string[]; + isLoading: boolean; +}>('SET_TIMELINE_EVENTS_LOADING'); + +export const clearEventsLoading = actionCreator<{ + id: string; +}>('CLEAR_TIMELINE_EVENTS_LOADING'); + +export const setEventsDeleted = actionCreator<{ + id: string; + eventIds: string[]; + isDeleted: boolean; +}>('SET_TIMELINE_EVENTS_DELETED'); + +export const clearEventsDeleted = actionCreator<{ + id: string; +}>('CLEAR_TIMELINE_EVENTS_DELETED'); diff --git a/x-pack/legacy/plugins/siem/public/store/timeline/epic.test.ts b/x-pack/legacy/plugins/siem/public/store/timeline/epic.test.ts index c04da21e6bb23..6e62ce8cb8b06 100644 --- a/x-pack/legacy/plugins/siem/public/store/timeline/epic.test.ts +++ b/x-pack/legacy/plugins/siem/public/store/timeline/epic.test.ts @@ -85,6 +85,7 @@ describe('Epic Timeline', () => { ], }, ], + deletedEventIds: [], description: '', eventIdToNoteIds: {}, highlightedDropAndProviderId: '', @@ -117,6 +118,7 @@ describe('Epic Timeline', () => { ], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, isSaving: false, itemsPerPage: 25, @@ -130,13 +132,17 @@ describe('Epic Timeline', () => { }, filterQueryDraft: { kind: 'kuery', expression: 'endgame.user_name : "zeus" ' }, }, + loadingEventIds: [], title: 'saved', noteIds: [], pinnedEventIds: {}, pinnedEventsSaveObject: {}, dateRange: { start: 1572469587644, end: 1572555987644 }, savedObjectId: '11169110-fc22-11e9-8ca9-072f15ce2685', + selectedEventIds: {}, show: true, + showCheckboxes: false, + showRowRenderers: true, sort: { columnId: '@timestamp', sortDirection: Direction.desc }, width: 1100, version: 'WzM4LDFd', diff --git a/x-pack/legacy/plugins/siem/public/store/timeline/helpers.ts b/x-pack/legacy/plugins/siem/public/store/timeline/helpers.ts index 16ae53ade7969..1f79a38b9f5b7 100644 --- a/x-pack/legacy/plugins/siem/public/store/timeline/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/store/timeline/helpers.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr, omit, uniq, isEmpty, isEqualWith } from 'lodash/fp'; +import { getOr, omit, uniq, isEmpty, isEqualWith, union } from 'lodash/fp'; import { esFilters } from '../../../../../../../src/plugins/data/public'; import { ColumnHeader } from '../../components/timeline/body/column_headers/column_header'; @@ -19,6 +19,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../model'; import { KqlMode, timelineDefaults, TimelineModel } from './model'; import { TimelineById, TimelineState } from './types'; +import { TimelineNonEcsData } from '../../graphql/types'; const EMPTY_TIMELINE_BY_ID: TimelineById = {}; // stable reference @@ -129,20 +130,36 @@ export const addTimelineToStore = ({ interface AddNewTimelineParams { columns: ColumnHeader[]; + dateRange?: { + start: number; + end: number; + }; + filters?: esFilters.Filter[]; id: string; itemsPerPage?: number; + kqlQuery?: { + filterQuery: SerializedFilterQuery | null; + filterQueryDraft: KueryFilterQuery | null; + }; show?: boolean; sort?: Sort; + showCheckboxes?: boolean; + showRowRenderers?: boolean; timelineById: TimelineById; } /** Adds a new `Timeline` to the provided collection of `TimelineById` */ export const addNewTimeline = ({ columns, + dateRange = { start: 0, end: 0 }, + filters = timelineDefaults.filters, id, itemsPerPage = timelineDefaults.itemsPerPage, + kqlQuery = { filterQuery: null, filterQueryDraft: null }, sort = timelineDefaults.sort, show = false, + showCheckboxes = false, + showRowRenderers = true, timelineById, }: AddNewTimelineParams): TimelineById => ({ ...timelineById, @@ -150,13 +167,18 @@ export const addNewTimeline = ({ id, ...timelineDefaults, columns, + dateRange, + filters, itemsPerPage, + kqlQuery, sort, show, savedObjectId: null, version: null, isSaving: false, isLoading: false, + showCheckboxes, + showRowRenderers, }, }); @@ -1095,6 +1117,93 @@ export const removeTimelineProvider = ({ }; }; +interface SetDeletedTimelineEventsParams { + id: string; + eventIds: string[]; + isDeleted: boolean; + timelineById: TimelineById; +} + +export const setDeletedTimelineEvents = ({ + id, + eventIds, + isDeleted, + timelineById, +}: SetDeletedTimelineEventsParams): TimelineById => { + const timeline = timelineById[id]; + + const deletedEventIds = isDeleted + ? union(timeline.deletedEventIds, eventIds) + : timeline.deletedEventIds.filter(currentEventId => !eventIds.includes(currentEventId)); + + return { + ...timelineById, + [id]: { + ...timeline, + deletedEventIds, + }, + }; +}; + +interface SetLoadingTimelineEventsParams { + id: string; + eventIds: string[]; + isLoading: boolean; + timelineById: TimelineById; +} + +export const setLoadingTimelineEvents = ({ + id, + eventIds, + isLoading, + timelineById, +}: SetLoadingTimelineEventsParams): TimelineById => { + const timeline = timelineById[id]; + + const loadingEventIds = isLoading + ? union(timeline.loadingEventIds, eventIds) + : timeline.loadingEventIds.filter(currentEventId => !eventIds.includes(currentEventId)); + + return { + ...timelineById, + [id]: { + ...timeline, + loadingEventIds, + }, + }; +}; + +interface SetSelectedTimelineEventsParams { + id: string; + eventIds: Record; + isSelectAllChecked: boolean; + isSelected: boolean; + timelineById: TimelineById; +} + +export const setSelectedTimelineEvents = ({ + id, + eventIds, + isSelectAllChecked = false, + isSelected, + timelineById, +}: SetSelectedTimelineEventsParams): TimelineById => { + const timeline = timelineById[id]; + + const selectedEventIds = isSelected + ? { ...timeline.selectedEventIds, ...eventIds } + : omit(Object.keys(eventIds), timeline.selectedEventIds); + + return { + ...timelineById, + [id]: { + ...timeline, + selectedEventIds, + isSelectAllChecked, + }, + }; +}; + interface UnPinTimelineEventParams { id: string; eventId: string; diff --git a/x-pack/legacy/plugins/siem/public/store/timeline/model.ts b/x-pack/legacy/plugins/siem/public/store/timeline/model.ts index 401edb73a0de8..517392857e4a6 100644 --- a/x-pack/legacy/plugins/siem/public/store/timeline/model.ts +++ b/x-pack/legacy/plugins/siem/public/store/timeline/model.ts @@ -10,7 +10,7 @@ import { DataProvider } from '../../components/timeline/data_providers/data_prov import { DEFAULT_TIMELINE_WIDTH } from '../../components/timeline/body/helpers'; import { defaultHeaders } from '../../components/timeline/body/column_headers/default_headers'; import { Sort } from '../../components/timeline/body/sort'; -import { Direction, PinnedEvent } from '../../graphql/types'; +import { Direction, PinnedEvent, TimelineNonEcsData } from '../../graphql/types'; import { KueryFilterQuery, SerializedFilterQuery } from '../model'; export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages @@ -21,6 +21,8 @@ export interface TimelineModel { columns: ColumnHeader[]; /** The sources of the event data shown in the timeline */ dataProviders: DataProvider[]; + /** Events to not be rendered **/ + deletedEventIds: string[]; /** A summary of the events and notes in this timeline */ description: string; /** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */ @@ -32,6 +34,10 @@ export interface TimelineModel { highlightedDropAndProviderId: string; /** Uniquely identifies the timeline */ id: string; + /** If selectAll checkbox in header is checked **/ + isSelectAllChecked: boolean; + /** Events to be rendered as loading **/ + loadingEventIds: string[]; savedObjectId: string | null; /** When true, this timeline was marked as "favorite" by the user */ isFavorite: boolean; @@ -61,8 +67,14 @@ export interface TimelineModel { end: number; }; savedQueryId?: string | null; + /** Events selected on this timeline -- eventId to TimelineNonEcsData[] mapping of data required for batch actions **/ + selectedEventIds: Record; /** When true, show the timeline flyover */ show: boolean; + /** When true, shows checkboxes enabling selection. Selected events store in selectedEventIds **/ + showCheckboxes: boolean; + /** When true, shows additional rowRenderers below the PlainRowRenderer **/ + showRowRenderers: boolean; /** Specifies which column the timeline is sorted on, and the direction (ascending / descending) */ sort: Sort; /** Persists the UI state (width) of the timeline flyover */ @@ -78,22 +90,28 @@ export type SubsetTimelineModel = Readonly< TimelineModel, | 'columns' | 'dataProviders' + | 'deletedEventIds' | 'description' | 'eventIdToNoteIds' | 'highlightedDropAndProviderId' | 'historyIds' | 'isFavorite' | 'isLive' + | 'isSelectAllChecked' | 'itemsPerPage' | 'itemsPerPageOptions' | 'kqlMode' | 'kqlQuery' | 'title' + | 'loadingEventIds' | 'noteIds' | 'pinnedEventIds' | 'pinnedEventsSaveObject' | 'dateRange' + | 'selectedEventIds' | 'show' + | 'showCheckboxes' + | 'showRowRenderers' | 'sort' | 'width' | 'isSaving' @@ -106,6 +124,7 @@ export type SubsetTimelineModel = Readonly< export const timelineDefaults: SubsetTimelineModel & Pick = { columns: defaultHeaders, dataProviders: [], + deletedEventIds: [], description: '', eventIdToNoteIds: {}, highlightedDropAndProviderId: '', @@ -113,6 +132,7 @@ export const timelineDefaults: SubsetTimelineModel & Pick { }, }, ], + deletedEventIds: [], description: '', eventIdToNoteIds: {}, highlightedDropAndProviderId: '', historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: true, + showRowRenderers: true, + showCheckboxes: false, sort: { columnId: '@timestamp', sortDirection: Direction.desc, @@ -1174,21 +1186,27 @@ describe('Timeline', () => { }, ], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, highlightedDropAndProviderId: '', historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: true, + showRowRenderers: true, + showCheckboxes: false, sort: { columnId: '@timestamp', sortDirection: Direction.desc, @@ -1366,21 +1384,27 @@ describe('Timeline', () => { }, ], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, highlightedDropAndProviderId: '', historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: true, + showRowRenderers: true, + showCheckboxes: false, sort: { columnId: '@timestamp', sortDirection: Direction.desc, @@ -1452,21 +1476,27 @@ describe('Timeline', () => { }, ], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, highlightedDropAndProviderId: '', historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: true, + showRowRenderers: true, + showCheckboxes: false, sort: { columnId: '@timestamp', sortDirection: Direction.desc, @@ -1633,21 +1663,27 @@ describe('Timeline', () => { }, ], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, highlightedDropAndProviderId: '', historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: true, + showRowRenderers: true, + showCheckboxes: false, sort: { columnId: '@timestamp', sortDirection: Direction.desc, @@ -1701,23 +1737,29 @@ describe('Timeline', () => { }, ], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, highlightedDropAndProviderId: '', historyIds: [], isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, id: 'foo', savedObjectId: null, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: true, + showRowRenderers: true, + showCheckboxes: false, sort: { columnId: '@timestamp', sortDirection: Direction.desc, @@ -1795,6 +1837,7 @@ describe('Timeline', () => { }, ], description: '', + deletedEventIds: [], eventIdToNoteIds: {}, highlightedDropAndProviderId: '', historyIds: [], @@ -1802,16 +1845,21 @@ describe('Timeline', () => { savedObjectId: null, isFavorite: false, isLive: false, + isSelectAllChecked: false, isLoading: false, kqlMode: 'filter', kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], title: '', noteIds: [], dateRange: { start: 0, end: 0, }, + selectedEventIds: {}, show: true, + showRowRenderers: true, + showCheckboxes: false, sort: { columnId: '@timestamp', sortDirection: Direction.desc, diff --git a/x-pack/legacy/plugins/siem/public/store/timeline/reducer.ts b/x-pack/legacy/plugins/siem/public/store/timeline/reducer.ts index 3b88785fa9061..f66638d644608 100644 --- a/x-pack/legacy/plugins/siem/public/store/timeline/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/store/timeline/reducer.ts @@ -20,7 +20,13 @@ import { pinEvent, removeColumn, removeProvider, + setEventsDeleted, + clearEventsDeleted, + setEventsLoading, + clearEventsLoading, setKqlFilterQueryDraft, + setSelected, + clearSelected, showCallOutUnauthorizedMsg, showTimeline, startTimelineSaving, @@ -61,6 +67,9 @@ import { pinTimelineEvent, removeTimelineColumn, removeTimelineProvider, + setDeletedTimelineEvents, + setLoadingTimelineEvents, + setSelectedTimelineEvents, unPinTimelineEvent, updateHighlightedDropAndProvider, updateKqlFilterQueryDraft, @@ -103,17 +112,39 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: addTimelineToStore({ id, timeline, timelineById: state.timelineById }), })) - .case(createTimeline, (state, { id, show, columns, itemsPerPage, sort }) => ({ - ...state, - timelineById: addNewTimeline({ - columns, - id, - itemsPerPage, - sort, - show, - timelineById: state.timelineById, - }), - })) + .case( + createTimeline, + ( + state, + { + id, + dateRange, + show, + columns, + itemsPerPage, + kqlQuery, + sort, + showCheckboxes, + showRowRenderers, + filters, + } + ) => ({ + ...state, + timelineById: addNewTimeline({ + columns, + dateRange, + filters, + id, + itemsPerPage, + kqlQuery, + sort, + show, + showCheckboxes, + showRowRenderers, + timelineById: state.timelineById, + }), + }) + ) .case(upsertColumn, (state, { column, id, index }) => ({ ...state, timelineById: upsertTimelineColumn({ column, id, index, timelineById: state.timelineById }), @@ -218,6 +249,65 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) }, }, })) + .case(setEventsDeleted, (state, { id, eventIds, isDeleted }) => ({ + ...state, + timelineById: setDeletedTimelineEvents({ + id, + eventIds, + timelineById: state.timelineById, + isDeleted, + }), + })) + .case(clearEventsDeleted, (state, { id }) => ({ + ...state, + timelineById: { + ...state.timelineById, + [id]: { + ...state.timelineById[id], + deletedEventIds: [], + }, + }, + })) + .case(setEventsLoading, (state, { id, eventIds, isLoading }) => ({ + ...state, + timelineById: setLoadingTimelineEvents({ + id, + eventIds, + timelineById: state.timelineById, + isLoading, + }), + })) + .case(clearEventsLoading, (state, { id }) => ({ + ...state, + timelineById: { + ...state.timelineById, + [id]: { + ...state.timelineById[id], + loadingEventIds: [], + }, + }, + })) + .case(setSelected, (state, { id, eventIds, isSelected, isSelectAllChecked }) => ({ + ...state, + timelineById: setSelectedTimelineEvents({ + id, + eventIds, + timelineById: state.timelineById, + isSelected, + isSelectAllChecked, + }), + })) + .case(clearSelected, (state, { id }) => ({ + ...state, + timelineById: { + ...state.timelineById, + [id]: { + ...state.timelineById[id], + selectedEventIds: {}, + isSelectAllChecked: false, + }, + }, + })) .case(updateIsLoading, (state, { id, isLoading }) => ({ ...state, timelineById: { diff --git a/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx b/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx index cda6882fe1714..a8ee10ba2b801 100644 --- a/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/saved_query_services/index.tsx @@ -10,16 +10,18 @@ import { createSavedQueryService, } from '../../../../../../../src/plugins/data/public'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; +import { useKibana } from '../../lib/kibana'; export const useSavedQueryServices = () => { - const core = useKibanaCore(); + const kibana = useKibana(); + const client = kibana.services.savedObjects.client; + const [savedQueryService, setSavedQueryService] = useState( - createSavedQueryService(core.savedObjects.client) + createSavedQueryService(client) ); useEffect(() => { - setSavedQueryService(createSavedQueryService(core.savedObjects.client)); - }, [core.savedObjects.client]); + setSavedQueryService(createSavedQueryService(client)); + }, [client]); return savedQueryService; }; diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index 647894e9e7187..59df643246835 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -22,6 +22,9 @@ import { isAlertExecutor } from './lib/detection_engine/signals/types'; import { readTagsRoute } from './lib/detection_engine/routes/tags/read_tags_route'; import { readPrivilegesRoute } from './lib/detection_engine/routes/privileges/read_privileges_route'; import { addPrepackedRulesRoute } from './lib/detection_engine/routes/rules/add_prepackaged_rules_route'; +import { createRulesBulkRoute } from './lib/detection_engine/routes/rules/create_rules_bulk_route'; +import { updateRulesBulkRoute } from './lib/detection_engine/routes/rules/update_rules_bulk_route'; +import { deleteRulesBulkRoute } from './lib/detection_engine/routes/rules/delete_rules_bulk_route'; const APP_ID = 'siem'; @@ -44,6 +47,9 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy deleteRulesRoute(__legacy); findRulesRoute(__legacy); addPrepackedRulesRoute(__legacy); + createRulesBulkRoute(__legacy); + updateRulesBulkRoute(__legacy); + deleteRulesBulkRoute(__legacy); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals // POST /api/detection_engine/signals/status diff --git a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts index 2e4dfbc31b65b..30fdf7520a3ed 100644 --- a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { CoreSetup, PluginInitializerContext } from '../../../../../../../src/core/server'; +import { PluginsSetup } from '../../plugin'; + import { Anomalies } from '../anomalies'; import { ElasticsearchAnomaliesAdapter } from '../anomalies/elasticsearch_adapter'; import { Authentications } from '../authentications'; @@ -33,8 +35,12 @@ import { PinnedEvent } from '../pinned_event/saved_object'; import { Timeline } from '../timeline/saved_object'; import { Alerts, ElasticsearchAlertsAdapter } from '../alerts'; -export function compose(core: CoreSetup, env: PluginInitializerContext['env']): AppBackendLibs { - const framework = new KibanaBackendFrameworkAdapter(core, env); +export function compose( + core: CoreSetup, + plugins: PluginsSetup, + env: PluginInitializerContext['env'] +): AppBackendLibs { + const framework = new KibanaBackendFrameworkAdapter(core, plugins, env); const sources = new Sources(new ConfigurationSourcesAdapter()); const sourceStatus = new SourceStatus(new ElasticsearchSourceStatusAdapter(framework)); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 3c5182b5178b3..a78879924acd0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -117,6 +117,42 @@ export const getFindRequest = (): ServerInjectOptions => ({ url: `${DETECTION_ENGINE_RULES_URL}/_find`, }); +export const getReadBulkRequest = (): ServerInjectOptions => ({ + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + payload: [typicalPayload()], +}); + +export const getUpdateBulkRequest = (): ServerInjectOptions => ({ + method: 'PUT', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + payload: [typicalPayload()], +}); + +export const getDeleteBulkRequest = (): ServerInjectOptions => ({ + method: 'DELETE', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + payload: [{ rule_id: 'rule-1' }], +}); + +export const getDeleteBulkRequestById = (): ServerInjectOptions => ({ + method: 'DELETE', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + payload: [{ id: 'rule-04128c15-0d1b-4716-a4c5-46997ac7f3bd' }], +}); + +export const getDeleteAsPostBulkRequestById = (): ServerInjectOptions => ({ + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + payload: [{ id: 'rule-04128c15-0d1b-4716-a4c5-46997ac7f3bd' }], +}); + +export const getDeleteAsPostBulkRequest = (): ServerInjectOptions => ({ + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + payload: [{ rule_id: 'rule-1' }], +}); + export const getPrivilegeRequest = (): ServerInjectOptions => ({ method: 'GET', url: `${DETECTION_ENGINE_PRIVILEGES_URL}`, @@ -220,6 +256,7 @@ export const getResult = (): RuleAlertType => ({ name: 'Detect Root/Admin Users', tags: [`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`], alertTypeId: 'siem.signals', + consumer: 'siem', params: { createdAt: '2019-12-13T16:40:33.400Z', updatedAt: '2019-12-13T16:40:33.400Z', @@ -277,6 +314,7 @@ export const getResult = (): RuleAlertType => ({ throttle: null, createdBy: 'elastic', updatedBy: 'elastic', + apiKey: null, apiKeyOwner: 'elastic', muteAll: false, mutedInstanceIds: [], diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts new file mode 100644 index 0000000000000..d3890f82c8abf --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockServer, + createMockServerWithoutActionClientDecoration, + createMockServerWithoutAlertClientDecoration, + createMockServerWithoutActionOrAlertClientDecoration, +} from '../__mocks__/_mock_server'; +import { createRulesRoute } from './create_rules_route'; +import { ServerInjectOptions } from 'hapi'; +import { + getFindResult, + getResult, + createActionResult, + typicalPayload, + getReadBulkRequest, +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { createRulesBulkRoute } from './create_rules_bulk_route'; + +describe('create_rules_bulk', () => { + let { server, alertsClient, actionsClient, elasticsearch } = createMockServer(); + + beforeEach(() => { + jest.resetAllMocks(); + ({ server, alertsClient, actionsClient, elasticsearch } = createMockServer()); + elasticsearch.getCluster = jest.fn().mockImplementation(() => ({ + callWithRequest: jest.fn().mockImplementation(() => true), + })); + + createRulesBulkRoute(server); + }); + + describe('status codes with actionClient and alertClient', () => { + test('returns 200 when creating a single rule with a valid actionClient and alertClient', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { statusCode } = await server.inject(getReadBulkRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 404 if actionClient is not available on the route', async () => { + const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); + createRulesRoute(serverWithoutActionClient); + const { statusCode } = await serverWithoutActionClient.inject(getReadBulkRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient is not available on the route', async () => { + const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); + createRulesRoute(serverWithoutAlertClient); + const { statusCode } = await serverWithoutAlertClient.inject(getReadBulkRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient and actionClient are both not available on the route', async () => { + const { + serverWithoutActionOrAlertClient, + } = createMockServerWithoutActionOrAlertClientDecoration(); + createRulesRoute(serverWithoutActionOrAlertClient); + const { statusCode } = await serverWithoutActionOrAlertClient.inject(getReadBulkRequest()); + expect(statusCode).toBe(404); + }); + }); + + describe('validation', () => { + test('returns 200 if rule_id is not given as the id is auto generated from the alert framework', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + // missing rule_id should return 200 as it will be auto generated if not given + const { rule_id, ...noRuleId } = typicalPayload(); + const request: ServerInjectOptions = { + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + payload: [noRuleId], + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 200 if type is query', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { type, ...noType } = typicalPayload(); + const request: ServerInjectOptions = { + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + payload: [ + { + ...noType, + type: 'query', + }, + ], + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 400 if type is not filter or kql', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.create.mockResolvedValue(createActionResult()); + alertsClient.create.mockResolvedValue(getResult()); + const { type, ...noType } = typicalPayload(); + const request: ServerInjectOptions = { + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + payload: [ + { + ...noType, + type: 'something-made-up', + }, + ], + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts new file mode 100644 index 0000000000000..256b341fca656 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Hapi from 'hapi'; +import { isFunction } from 'lodash/fp'; +import uuid from 'uuid'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { createRules } from '../../rules/create_rules'; +import { BulkRulesRequest } from '../../rules/types'; +import { ServerFacade } from '../../../../types'; +import { readRules } from '../../rules/read_rules'; +import { transformOrBulkError } from './utils'; +import { getIndexExists } from '../../index/get_index_exists'; +import { + callWithRequestFactory, + getIndex, + transformBulkError, + createBulkErrorObject, +} from '../utils'; +import { createRulesBulkSchema } from '../schemas/create_rules_bulk_schema'; + +export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'POST', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + payload: createRulesBulkSchema, + }, + }, + async handler(request: BulkRulesRequest, headers) { + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = isFunction(request.getActionsClient) + ? request.getActionsClient() + : null; + + if (!alertsClient || !actionsClient) { + return headers.response().code(404); + } + + const rules = Promise.all( + request.payload.map(async payloadRule => { + const { + created_at: createdAt, + description, + enabled, + false_positives: falsePositives, + from, + immutable, + query, + language, + output_index: outputIndex, + saved_id: savedId, + meta, + filters, + rule_id: ruleId, + index, + interval, + max_signals: maxSignals, + risk_score: riskScore, + name, + severity, + tags, + threats, + to, + type, + updated_at: updatedAt, + references, + timeline_id: timelineId, + version, + } = payloadRule; + const ruleIdOrUuid = ruleId ?? uuid.v4(); + try { + const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server); + const callWithRequest = callWithRequestFactory(request, server); + const indexExists = await getIndexExists(callWithRequest, finalIndex); + if (!indexExists) { + return createBulkErrorObject({ + ruleId: ruleIdOrUuid, + statusCode: 409, + message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, + }); + } + if (ruleId != null) { + const rule = await readRules({ alertsClient, ruleId }); + if (rule != null) { + return createBulkErrorObject({ + ruleId, + statusCode: 409, + message: `rule_id: "${ruleId}" already exists`, + }); + } + } + const createdRule = await createRules({ + alertsClient, + actionsClient, + createdAt, + description, + enabled, + falsePositives, + from, + immutable, + query, + language, + outputIndex: finalIndex, + savedId, + timelineId, + meta, + filters, + ruleId: ruleIdOrUuid, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + updatedAt, + references, + version, + }); + return transformOrBulkError(ruleIdOrUuid, createdRule); + } catch (err) { + return transformBulkError(ruleIdOrUuid, err); + } + }) + ); + return rules; + }, + }; +}; + +export const createRulesBulkRoute = (server: ServerFacade): void => { + server.route(createCreateRulesBulkRoute(server)); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts new file mode 100644 index 0000000000000..11a076951fd8c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockServer, + createMockServerWithoutActionClientDecoration, + createMockServerWithoutAlertClientDecoration, + createMockServerWithoutActionOrAlertClientDecoration, +} from '../__mocks__/_mock_server'; + +import { ServerInjectOptions } from 'hapi'; +import { + getFindResult, + getResult, + getFindResultWithSingleHit, + getDeleteBulkRequest, + getDeleteBulkRequestById, + getDeleteAsPostBulkRequest, + getDeleteAsPostBulkRequestById, +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; + +import { deleteRulesBulkRoute } from './delete_rules_bulk_route'; + +describe('delete_rules', () => { + let { server, alertsClient } = createMockServer(); + + beforeEach(() => { + ({ server, alertsClient } = createMockServer()); + deleteRulesBulkRoute(server); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('status codes with actionClient and alertClient', () => { + test('returns 200 when deleting a single rule with a valid actionClient and alertClient by alertId', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.delete.mockResolvedValue({}); + const { statusCode } = await server.inject(getDeleteBulkRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 200 when deleting a single rule with a valid actionClient and alertClient by alertId using POST', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.delete.mockResolvedValue({}); + const { statusCode } = await server.inject(getDeleteAsPostBulkRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 200 when deleting a single rule with a valid actionClient and alertClient by id', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.delete.mockResolvedValue({}); + const { statusCode } = await server.inject(getDeleteBulkRequestById()); + expect(statusCode).toBe(200); + }); + + test('returns 200 when deleting a single rule with a valid actionClient and alertClient by id using POST', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.delete.mockResolvedValue({}); + const { statusCode } = await server.inject(getDeleteAsPostBulkRequestById()); + expect(statusCode).toBe(200); + }); + + test('returns 200 because the error is in the payload when deleting a single rule that does not exist with a valid actionClient and alertClient', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.delete.mockResolvedValue({}); + const { statusCode } = await server.inject(getDeleteBulkRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 404 in the payload when deleting a single rule that does not exist with a valid actionClient and alertClient', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.delete.mockResolvedValue({}); + const { payload } = await server.inject(getDeleteBulkRequest()); + const parsed = JSON.parse(payload); + expect(parsed).toEqual([ + { error: { message: 'rule_id: "rule-1" not found', statusCode: 404 }, id: 'rule-1' }, + ]); + }); + + test('returns 404 if actionClient is not available on the route', async () => { + const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); + deleteRulesBulkRoute(serverWithoutActionClient); + const { statusCode } = await serverWithoutActionClient.inject(getDeleteBulkRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient is not available on the route', async () => { + const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); + deleteRulesBulkRoute(serverWithoutAlertClient); + const { statusCode } = await serverWithoutAlertClient.inject(getDeleteBulkRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient and actionClient are both not available on the route', async () => { + const { + serverWithoutActionOrAlertClient, + } = createMockServerWithoutActionOrAlertClientDecoration(); + deleteRulesBulkRoute(serverWithoutActionOrAlertClient); + const { statusCode } = await serverWithoutActionOrAlertClient.inject(getDeleteBulkRequest()); + expect(statusCode).toBe(404); + }); + }); + + describe('validation', () => { + test('returns 400 if given a non-existent id in the payload', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.delete.mockResolvedValue({}); + const request: ServerInjectOptions = { + method: 'DELETE', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts new file mode 100644 index 0000000000000..a0801930f879a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Hapi from 'hapi'; +import { isFunction } from 'lodash/fp'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { deleteRules } from '../../rules/delete_rules'; +import { ServerFacade } from '../../../../types'; +import { queryRulesBulkSchema } from '../schemas/query_rules_bulk_schema'; +import { transformOrBulkError, getIdBulkError } from './utils'; +import { transformBulkError } from '../utils'; +import { QueryBulkRequest } from '../../rules/types'; + +export const createDeleteRulesBulkRoute: Hapi.ServerRoute = { + method: ['POST', 'DELETE'], // allow both POST and DELETE in case their client does not support bodies in DELETE + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + payload: queryRulesBulkSchema, + }, + }, + async handler(request: QueryBulkRequest, headers) { + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; + + if (alertsClient == null || actionsClient == null) { + return headers.response().code(404); + } + const rules = Promise.all( + request.payload.map(async payloadRule => { + const { id, rule_id: ruleId } = payloadRule; + const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)'; + try { + const rule = await deleteRules({ + actionsClient, + alertsClient, + id, + ruleId, + }); + + if (rule != null) { + return transformOrBulkError(idOrRuleIdOrUnknown, rule); + } else { + return getIdBulkError({ id, ruleId }); + } + } catch (err) { + return transformBulkError(idOrRuleIdOrUnknown, err); + } + }) + ); + return rules; + }, +}; + +export const deleteRulesBulkRoute = (server: ServerFacade): void => { + server.route(createDeleteRulesBulkRoute); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts index c2b2e2fdbbaef..8f771236269d9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -13,7 +13,7 @@ import { ServerFacade } from '../../../../types'; import { queryRulesSchema } from '../schemas/query_rules_schema'; import { getIdError, transformOrError } from './utils'; import { transformError } from '../utils'; -import { QueryRequest } from './types'; +import { QueryRequest } from '../../rules/types'; export const createDeleteRulesRoute: Hapi.ServerRoute = { method: 'DELETE', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts index a842e68b6b7fe..4ff26ae99bd13 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -13,7 +13,7 @@ import { transformError } from '../utils'; import { readRules } from '../../rules/read_rules'; import { ServerFacade } from '../../../../types'; import { queryRulesSchema } from '../schemas/query_rules_schema'; -import { QueryRequest } from './types'; +import { QueryRequest } from '../../rules/types'; export const createReadRulesRoute: Hapi.ServerRoute = { method: 'GET', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts deleted file mode 100644 index f6878c9edc9b8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { RequestFacade } from '../../../../types'; - -export type QueryRequest = Omit & { - query: { id: string | undefined; rule_id: string | undefined }; -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts new file mode 100644 index 0000000000000..9ae2941e6e5f2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockServer, + createMockServerWithoutActionClientDecoration, + createMockServerWithoutAlertClientDecoration, + createMockServerWithoutActionOrAlertClientDecoration, +} from '../__mocks__/_mock_server'; + +import { updateRulesRoute } from './update_rules_route'; +import { ServerInjectOptions } from 'hapi'; +import { + getFindResult, + getResult, + updateActionResult, + typicalPayload, + getFindResultWithSingleHit, + getUpdateBulkRequest, +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { updateRulesBulkRoute } from './update_rules_bulk_route'; + +describe('update_rules_bulk', () => { + let { server, alertsClient, actionsClient } = createMockServer(); + + beforeEach(() => { + jest.resetAllMocks(); + ({ server, alertsClient, actionsClient } = createMockServer()); + updateRulesBulkRoute(server); + }); + + describe('status codes with actionClient and alertClient', () => { + test('returns 200 when updating a single rule with a valid actionClient and alertClient', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.update.mockResolvedValue(updateActionResult()); + alertsClient.update.mockResolvedValue(getResult()); + const { statusCode } = await server.inject(getUpdateBulkRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 200 as a response when updating a single rule that does not exist', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.update.mockResolvedValue(updateActionResult()); + alertsClient.update.mockResolvedValue(getResult()); + const { statusCode } = await server.inject(getUpdateBulkRequest()); + expect(statusCode).toBe(200); + }); + + test('returns 404 within the payload when updating a single rule that does not exist', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.update.mockResolvedValue(updateActionResult()); + alertsClient.update.mockResolvedValue(getResult()); + const { payload } = await server.inject(getUpdateBulkRequest()); + const parsed = JSON.parse(payload); + expect(parsed).toEqual([ + { error: { message: 'rule_id: "rule-1" not found', statusCode: 404 }, id: 'rule-1' }, + ]); + }); + + test('returns 404 if actionClient is not available on the route', async () => { + const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); + updateRulesRoute(serverWithoutActionClient); + const { statusCode } = await serverWithoutActionClient.inject(getUpdateBulkRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient is not available on the route', async () => { + const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); + updateRulesRoute(serverWithoutAlertClient); + const { statusCode } = await serverWithoutAlertClient.inject(getUpdateBulkRequest()); + expect(statusCode).toBe(404); + }); + + test('returns 404 if alertClient and actionClient are both not available on the route', async () => { + const { + serverWithoutActionOrAlertClient, + } = createMockServerWithoutActionOrAlertClientDecoration(); + updateRulesRoute(serverWithoutActionOrAlertClient); + const { statusCode } = await serverWithoutActionOrAlertClient.inject(getUpdateBulkRequest()); + expect(statusCode).toBe(404); + }); + }); + + describe('validation', () => { + test('returns 400 if id is not given in either the body or the url', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + const { rule_id, ...noId } = typicalPayload(); + const request: ServerInjectOptions = { + method: 'PUT', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + payload: [noId], + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + + test('returns errors as 200 to just indicate ok something happened', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + actionsClient.update.mockResolvedValue(updateActionResult()); + alertsClient.update.mockResolvedValue(getResult()); + const request: ServerInjectOptions = { + method: 'PUT', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + payload: [typicalPayload()], + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toEqual(200); + }); + + test('returns 404 in the payload if the record does not exist yet', async () => { + alertsClient.find.mockResolvedValue(getFindResult()); + actionsClient.update.mockResolvedValue(updateActionResult()); + alertsClient.update.mockResolvedValue(getResult()); + const request: ServerInjectOptions = { + method: 'PUT', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + payload: [typicalPayload()], + }; + const { payload } = await server.inject(request); + const parsed = JSON.parse(payload); + expect(parsed).toEqual([ + { error: { message: 'rule_id: "rule-1" not found', statusCode: 404 }, id: 'rule-1' }, + ]); + }); + + test('returns 200 if type is query', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.update.mockResolvedValue(updateActionResult()); + alertsClient.update.mockResolvedValue(getResult()); + const request: ServerInjectOptions = { + method: 'PUT', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + payload: [typicalPayload()], + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 400 if type is not filter or kql', async () => { + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + alertsClient.get.mockResolvedValue(getResult()); + actionsClient.update.mockResolvedValue(updateActionResult()); + alertsClient.update.mockResolvedValue(getResult()); + const { type, ...noType } = typicalPayload(); + const request: ServerInjectOptions = { + method: 'PUT', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + payload: [ + { + ...noType, + type: 'something-made-up', + }, + ], + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts new file mode 100644 index 0000000000000..b30b6c791522b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Hapi from 'hapi'; +import { isFunction } from 'lodash/fp'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { BulkUpdateRulesRequest } from '../../rules/types'; +import { ServerFacade } from '../../../../types'; +import { transformOrBulkError, getIdBulkError } from './utils'; +import { transformBulkError } from '../utils'; +import { updateRulesBulkSchema } from '../schemas/update_rules_bulk_schema'; +import { updateRules } from '../../rules/update_rules'; + +export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'PUT', + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + payload: updateRulesBulkSchema, + }, + }, + async handler(request: BulkUpdateRulesRequest, headers) { + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = isFunction(request.getActionsClient) + ? request.getActionsClient() + : null; + + if (!alertsClient || !actionsClient) { + return headers.response().code(404); + } + + const rules = Promise.all( + request.payload.map(async payloadRule => { + const { + description, + enabled, + false_positives: falsePositives, + from, + immutable, + query, + language, + output_index: outputIndex, + saved_id: savedId, + timeline_id: timelineId, + meta, + filters, + rule_id: ruleId, + id, + index, + interval, + max_signals: maxSignals, + risk_score: riskScore, + name, + severity, + tags, + to, + type, + threats, + references, + version, + } = payloadRule; + const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)'; + try { + const rule = await updateRules({ + alertsClient, + actionsClient, + description, + enabled, + falsePositives, + from, + immutable, + query, + language, + outputIndex, + savedId, + timelineId, + meta, + filters, + id, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + references, + version, + }); + if (rule != null) { + return transformOrBulkError(rule.id, rule); + } else { + return getIdBulkError({ id, ruleId }); + } + } catch (err) { + return transformBulkError(idOrRuleIdOrUnknown, err); + } + }) + ); + return rules; + }, + }; +}; + +export const updateRulesBulkRoute = (server: ServerFacade): void => { + server.route(createUpdateRulesBulkRoute(server)); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts index e22c873741392..b1f61d11458fe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -12,10 +12,13 @@ import { transformFindAlertsOrError, transformOrError, transformTags, + getIdBulkError, + transformOrBulkError, } from './utils'; import { getResult } from '../__mocks__/request_responses'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { OutputRuleAlertRest } from '../../types'; +import { BulkError } from '../utils'; describe('utils', () => { describe('transformAlertToRule', () => { @@ -738,4 +741,151 @@ describe('utils', () => { ]); }); }); + + describe('getIdBulkError', () => { + test('outputs message about id not being found if only id is defined and ruleId is undefined', () => { + const error = getIdBulkError({ id: '123', ruleId: undefined }); + const expected: BulkError = { + id: '123', + error: { message: 'id: "123" not found', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + + test('outputs message about id not being found if only id is defined and ruleId is null', () => { + const error = getIdBulkError({ id: '123', ruleId: null }); + const expected: BulkError = { + id: '123', + error: { message: 'id: "123" not found', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + + test('outputs message about ruleId not being found if only ruleId is defined and id is undefined', () => { + const error = getIdBulkError({ id: undefined, ruleId: 'rule-id-123' }); + const expected: BulkError = { + id: 'rule-id-123', + error: { message: 'rule_id: "rule-id-123" not found', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + + test('outputs message about ruleId not being found if only ruleId is defined and id is null', () => { + const error = getIdBulkError({ id: null, ruleId: 'rule-id-123' }); + const expected: BulkError = { + id: 'rule-id-123', + error: { message: 'rule_id: "rule-id-123" not found', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + + test('outputs message about both being not defined when both are undefined', () => { + const error = getIdBulkError({ id: undefined, ruleId: undefined }); + const expected: BulkError = { + id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + + test('outputs message about both being not defined when both are null', () => { + const error = getIdBulkError({ id: null, ruleId: null }); + const expected: BulkError = { + id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + + test('outputs message about both being not defined when id is null and ruleId is undefined', () => { + const error = getIdBulkError({ id: null, ruleId: undefined }); + const expected: BulkError = { + id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + + test('outputs message about both being not defined when id is undefined and ruleId is null', () => { + const error = getIdBulkError({ id: undefined, ruleId: null }); + const expected: BulkError = { + id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + }; + expect(error).toEqual(expected); + }); + }); + + describe('transformOrBulkError', () => { + test('outputs 200 if the data is of type siem alert', () => { + const output = transformOrBulkError('rule-1', getResult()); + const expected: OutputRuleAlertRest = { + created_by: 'elastic', + created_at: '2019-12-13T16:40:33.400Z', + updated_at: '2019-12-13T16:40:33.400Z', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + rule_id: 'rule-1', + risk_score: 50, + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + filters: [ + { + query: { + match_phrase: { + 'host.name': 'some-host', + }, + }, + }, + ], + meta: { + someMeta: 'someField', + }, + saved_id: 'some-id', + timeline_id: 'some-timeline-id', + version: 1, + }; + expect(output).toEqual(expected); + }); + + test('returns 500 if the data is not of type siem alert', () => { + const output = transformOrBulkError('rule-1', { data: [{ random: 1 }] }); + expect(output).toEqual({ + id: 'rule-1', + error: { message: 'Internal error transforming', statusCode: 500 }, + }); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index dad22c74398d2..b9bf3f8a942fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -9,6 +9,7 @@ import { pickBy } from 'lodash/fp'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, isAlertType, isAlertTypes } from '../../rules/types'; import { OutputRuleAlertRest } from '../../types'; +import { createBulkErrorObject, BulkError } from '../utils'; export const getIdError = ({ id, @@ -26,6 +27,34 @@ export const getIdError = ({ } }; +export const getIdBulkError = ({ + id, + ruleId, +}: { + id: string | undefined | null; + ruleId: string | undefined | null; +}): BulkError => { + if (id != null) { + return createBulkErrorObject({ + ruleId: id, + statusCode: 404, + message: `id: "${id}" not found`, + }); + } else if (ruleId != null) { + return createBulkErrorObject({ + ruleId, + statusCode: 404, + message: `rule_id: "${ruleId}" not found`, + }); + } else { + return createBulkErrorObject({ + ruleId: '(unknown id)', + statusCode: 404, + message: `id or rule_id should have been defined`, + }); + } +}; + export const transformTags = (tags: string[]): string[] => { return tags.filter(tag => !tag.startsWith(INTERNAL_IDENTIFIER)); }; @@ -83,3 +112,18 @@ export const transformOrError = (alert: unknown): Partial | return new Boom('Internal error transforming', { statusCode: 500 }); } }; + +export const transformOrBulkError = ( + ruleId: string, + alert: unknown +): Partial | BulkError => { + if (isAlertType(alert)) { + return transformAlertToRule(alert); + } else { + return createBulkErrorObject({ + ruleId, + statusCode: 500, + message: 'Internal error transforming', + }); + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts new file mode 100644 index 0000000000000..17fb5320daa01 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createRulesBulkSchema } from './create_rules_bulk_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; + +// only the basics of testing are here. +// see: create_rules_schema.test.ts for the bulk of the validation tests +// this just wraps createRulesSchema in an array +describe('create_rules_bulk_schema', () => { + test('can take an empty array and validate it', () => { + expect( + createRulesBulkSchema.validate>>([]).error + ).toBeFalsy(); + }); + + test('made up values do not validate', () => { + expect( + createRulesBulkSchema.validate<[{ madeUp: string }]>([ + { + madeUp: 'hi', + }, + ]).error + ).toBeTruthy(); + }); + + test('single array of [id] does validate', () => { + expect( + createRulesBulkSchema.validate>>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }, + ]).error + ).toBeFalsy(); + }); + + test('two values of [id] does validate', () => { + expect( + createRulesBulkSchema.validate>>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }, + { + rule_id: 'rule-2', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }, + ]).error + ).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.ts new file mode 100644 index 0000000000000..bcc4475f2d9f0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; + +import { createRulesSchema } from './create_rules_schema'; + +export const createRulesBulkSchema = Joi.array().items(createRulesSchema); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.test.ts new file mode 100644 index 0000000000000..6450da37699d8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { queryRulesBulkSchema } from './query_rules_bulk_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; + +// only the basics of testing are here. +// see: query_rules_bulk_schema.test.ts for the bulk of the validation tests +// this just wraps queryRulesSchema in an array +describe('query_rules_bulk_schema', () => { + test('can take an empty array and validate it', () => { + expect( + queryRulesBulkSchema.validate>>([]).error + ).toBeFalsy(); + }); + + test('both rule_id and id being supplied do not validate', () => { + expect( + queryRulesBulkSchema.validate>>([ + { + rule_id: '1', + id: '1', + }, + ]).error + ).toBeTruthy(); + }); + + test('both rule_id and id being supplied do not validate if one array element works but the second does not', () => { + expect( + queryRulesBulkSchema.validate>>([ + { + id: '1', + }, + { + rule_id: '1', + id: '1', + }, + ]).error + ).toBeTruthy(); + }); + + test('only id validates', () => { + expect( + queryRulesBulkSchema.validate>>([{ id: '1' }]).error + ).toBeFalsy(); + }); + + test('only id validates with two elements', () => { + expect( + queryRulesBulkSchema.validate>>([ + { id: '1' }, + { id: '2' }, + ]).error + ).toBeFalsy(); + }); + + test('only rule_id validates', () => { + expect( + queryRulesBulkSchema.validate>>([{ rule_id: '1' }]) + .error + ).toBeFalsy(); + }); + + test('only rule_id validates with two elements', () => { + expect( + queryRulesBulkSchema.validate>>([ + { rule_id: '1' }, + { rule_id: '2' }, + ]).error + ).toBeFalsy(); + }); + + test('both id and rule_id validates with two separate elements', () => { + expect( + queryRulesBulkSchema.validate>>([ + { id: '1' }, + { rule_id: '2' }, + ]).error + ).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.ts new file mode 100644 index 0000000000000..13ccac282281d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_bulk_schema.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import Joi from 'joi'; + +import { queryRulesSchema } from './query_rules_schema'; + +export const queryRulesBulkSchema = Joi.array().items(queryRulesSchema); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts index 4f0dbf10f4559..5c293f4825b95 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts @@ -7,12 +7,15 @@ import { querySignalsSchema } from './query_signals_index_schema'; import { SignalsQueryRestParams } from '../../signals/types'; -describe('query and aggs on signals index', () => { - test('query and aggs simultaneously', () => { +describe('query, aggs, size, _source and track_total_hits on signals index', () => { + test('query, aggs, size, _source and track_total_hits simultaneously', () => { expect( querySignalsSchema.validate>({ query: {}, aggs: {}, + size: 1, + track_total_hits: true, + _source: ['field'], }).error ).toBeFalsy(); }); @@ -33,7 +36,31 @@ describe('query and aggs on signals index', () => { ).toBeFalsy(); }); - test('missing query and aggs is invalid', () => { + test('size only', () => { + expect( + querySignalsSchema.validate>({ + size: 1, + }).error + ).toBeFalsy(); + }); + + test('track_total_hits only', () => { + expect( + querySignalsSchema.validate>({ + track_total_hits: true, + }).error + ).toBeFalsy(); + }); + + test('_source only', () => { + expect( + querySignalsSchema.validate>({ + _source: ['field'], + }).error + ).toBeFalsy(); + }); + + test('missing query, aggs, size, _source and track_total_hits is invalid', () => { expect(querySignalsSchema.validate>({}).error).toBeTruthy(); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts index 53ce50692e84a..0a6fceb44f845 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts @@ -9,4 +9,7 @@ import Joi from 'joi'; export const querySignalsSchema = Joi.object({ query: Joi.object(), aggs: Joi.object(), + size: Joi.number(), + track_total_hits: Joi.boolean(), + _source: Joi.array().items(Joi.string()), }).min(1); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.test.ts new file mode 100644 index 0000000000000..2b1bad39eb686 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.test.ts @@ -0,0 +1,52 @@ +/* + * 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 { updateRulesBulkSchema } from './update_rules_bulk_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; + +// only the basics of testing are here. +// see: update_rules_schema.test.ts for the bulk of the validation tests +// this just wraps updateRulesSchema in an array +describe('update_rules_bulk_schema', () => { + test('can take an empty array and validate it', () => { + expect( + updateRulesBulkSchema.validate>>([]).error + ).toBeFalsy(); + }); + + test('made up values do not validate', () => { + expect( + updateRulesBulkSchema.validate<[{ madeUp: string }]>([ + { + madeUp: 'hi', + }, + ]).error + ).toBeTruthy(); + }); + + test('single array of [id] does validate', () => { + expect( + updateRulesBulkSchema.validate>>([ + { + id: 'rule-1', + }, + ]).error + ).toBeFalsy(); + }); + + test('two values of [id] does validate', () => { + expect( + updateRulesBulkSchema.validate>>([ + { + id: 'rule-1', + }, + { + id: 'rule-2', + }, + ]).error + ).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.ts new file mode 100644 index 0000000000000..123ec2d5b7e15 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; + +import { updateRulesSchema } from './update_rules_schema'; + +export const updateRulesBulkSchema = Joi.array().items(updateRulesSchema); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts index 89ffed259cf77..6d1896b1a8171 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -25,14 +25,14 @@ export const querySignalsRouteDef = (server: ServerFacade): Hapi.ServerRoute => }, }, async handler(request: SignalsQueryRequest) { - const { query, aggs } = request.payload; - const body = { query, aggs }; + const { query, aggs, _source, track_total_hits, size } = request.payload; const index = getIndex(request, server); const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); + try { return callWithRequest(request, 'search', { index, - body, + body: { query, aggs, _source, track_total_hits, size }, }); } catch (exc) { // error while getting or updating signal with id: id in signal index .siem-signals diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index 437c0a4362430..fa95c77f646d6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; -import { transformError } from './utils'; +import { transformError, transformBulkError, BulkError } from './utils'; describe('utils', () => { describe('transformError', () => { @@ -57,4 +57,53 @@ describe('utils', () => { expect(transformed.output.statusCode).toBe(400); }); }); + + describe('transformBulkError', () => { + test('returns transformed object if it is a boom object', () => { + const boom = new Boom('some boom message', { statusCode: 400 }); + const transformed = transformBulkError('rule-1', boom); + const expected: BulkError = { + id: 'rule-1', + error: { message: 'some boom message', statusCode: 400 }, + }; + expect(transformed).toEqual(expected); + }); + + test('returns a normal error if it is some non boom object that has a statusCode', () => { + const error: Error & { statusCode?: number } = { + statusCode: 403, + name: 'some name', + message: 'some message', + }; + const transformed = transformBulkError('rule-1', error); + const expected: BulkError = { + id: 'rule-1', + error: { message: 'some message', statusCode: 403 }, + }; + expect(transformed).toEqual(expected); + }); + + test('returns a 500 if the status code is not set', () => { + const error: Error & { statusCode?: number } = { + name: 'some name', + message: 'some message', + }; + const transformed = transformBulkError('rule-1', error); + const expected: BulkError = { + id: 'rule-1', + error: { message: 'some message', statusCode: 500 }, + }; + expect(transformed).toEqual(expected); + }); + + test('it detects a TypeError and returns a Boom status of 400', () => { + const error: TypeError = new TypeError('I have a type error'); + const transformed = transformBulkError('rule-1', error); + const expected: BulkError = { + id: 'rule-1', + error: { message: 'I have a type error', statusCode: 400 }, + }; + expect(transformed).toEqual(expected); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 62281c7ebaacb..d9a8efd673883 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -26,6 +26,56 @@ export const transformError = (err: Error & { statusCode?: number }) => { } }; +export interface BulkError { + id: string; + error: { + statusCode: number; + message: string; + }; +} +export const createBulkErrorObject = ({ + ruleId, + statusCode, + message, +}: { + ruleId: string; + statusCode: number; + message: string; +}): BulkError => { + return { + id: ruleId, + error: { + statusCode, + message, + }, + }; +}; + +export const transformBulkError = ( + ruleId: string, + err: Error & { statusCode?: number } +): BulkError => { + if (Boom.isBoom(err)) { + return createBulkErrorObject({ + ruleId, + statusCode: err.output.statusCode, + message: err.message, + }); + } else if (err instanceof TypeError) { + return createBulkErrorObject({ + ruleId, + statusCode: 400, + message: err.message, + }); + } else { + return createBulkErrorObject({ + ruleId, + statusCode: err.statusCode ?? 500, + message: err.message, + }); + } +}; + export const getIndex = (request: RequestFacade, server: ServerFacade): string => { const spaceId = server.plugins.spaces.getSpaceId(request); const signalsIndex = server.config().get(`xpack.${APP_ID}.${SIGNALS_INDEX_KEY}`); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index 84a0566bfa092..07cf0b0c716cc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SIGNALS_ID } from '../../../../common/constants'; +import { APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { RuleParams } from './types'; import { addTags } from './add_tags'; @@ -42,6 +42,7 @@ export const createRules = async ({ name, tags: addTags(tags, ruleId, immutable), alertTypeId: SIGNALS_ID, + consumer: APP_ID, params: { createdAt: new Date().toISOString(), description, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json new file mode 100644 index 0000000000000..7bddffb4734ef --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -0,0 +1,17 @@ +{ + "rule_id": "4630d948-40d4-4cef-ac69-4002e29bc3db", + "risk_score": 50, + "description": "EQL - Adding the Hidden File Attribute with via attrib.exe", + "immutable": true, + "interval": "5m", + "name": "EQL - Adding the Hidden File Attribute with via attrib.exe", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"attrib.exe\" and process.args:\"+h\"", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json new file mode 100644 index 0000000000000..d57e5c7709b24 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json @@ -0,0 +1,17 @@ +{ + "rule_id": "2bf78aa2-9c56-48de-b139-f169bf99cf86", + "risk_score": 50, + "description": "EQL - Adobe Hijack Persistence", + "immutable": true, + "interval": "5m", + "name": "EQL - Adobe Hijack Persistence", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "file.path:(\"C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\" or \"C:\\Program Files\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\") and event.action:\"File created (rule: FileCreate)\" and not process.name:msiexeec.exe", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json new file mode 100644 index 0000000000000..da3cf0fb46025 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json @@ -0,0 +1,17 @@ +{ + "rule_id": "b27b9f47-0a20-4807-8377-7f899b4fbada", + "risk_score": 50, + "description": "EQL - Audio Capture via PowerShell", + "immutable": true, + "interval": "5m", + "name": "EQL - Audio Capture via PowerShell", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"SoundRecorder.exe\" and process.args:\"/FILE\"", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json new file mode 100644 index 0000000000000..cc0091feb290d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json @@ -0,0 +1,17 @@ +{ + "rule_id": "f8e06892-ed10-4452-892e-2c5a38d552f1", + "risk_score": 50, + "description": "EQL - Audio Capture via SoundRecorder", + "immutable": true, + "interval": "5m", + "name": "EQL - Audio Capture via SoundRecorder", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"SoundRecorder.exe\" and process.args:\"/FILE\"", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json new file mode 100644 index 0000000000000..bdc85045009cb --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json @@ -0,0 +1,17 @@ +{ + "rule_id": "59547add-a400-4baa-aa0c-66c72efdb77f", + "risk_score": 50, + "description": "EQL -Bypass UAC Event Viewer", + "immutable": true, + "interval": "5m", + "name": "EQL -Bypass UAC Event Viewer", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "process.parent.name:eventvwr.exe and event.action:\"Process Create (rule: ProcessCreate)\" and not process.executable:(\"C:\\Windows\\System32\\mmc.exe\" or \"C:\\Windows\\SysWOW64\\mmc.exe\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json new file mode 100644 index 0000000000000..c3b28e6dce849 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json @@ -0,0 +1,17 @@ +{ + "rule_id": "2f7403da-1a4c-46bb-8ecc-c1a596e10cd0", + "risk_score": 50, + "description": "EQL - Bypass UAC via CMSTP", + "immutable": true, + "interval": "5m", + "name": "EQL - Bypass UAC via CMSTP", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"cmstp.exe\" and process.parent.args:(\"/s\" and \"/au\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json new file mode 100644 index 0000000000000..d79c551ffb9cb --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json @@ -0,0 +1,17 @@ +{ + "rule_id": "f68d83a1-24cb-4b8d-825b-e8af400b9670", + "risk_score": 50, + "description": "EQL -Bypass UAC Via sdclt", + "immutable": true, + "interval": "5m", + "name": "EQL -Bypass UAC Via sdclt", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"sdclt.exe\" and process.args:\"/kickoffelev\" and not process.executable:(\"C:\\Windows\\System32\\sdclt.exe\" or \"C:\\Windows\\System32\\control.exe\" or \"C:\\Windows\\SysWOW64\\sdclt.exe\" or \"C:\\Windows\\SysWOW64\\control.exe\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json new file mode 100644 index 0000000000000..d7eb663297a63 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json @@ -0,0 +1,17 @@ +{ + "rule_id": "d331bbe2-6db4-4941-80a5-8270db72eb61", + "risk_score": 50, + "description": "EQL - Clearing Windows Event Logs", + "immutable": true, + "interval": "5m", + "name": "EQL - Clearing Windows Event Logs", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and (process.name:\"wevtutil.exe\" and process.args:\"cl\") or (process.name:\"powershell.exe\" and process.args:\"Clear-EventLog\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json new file mode 100644 index 0000000000000..2155c2fa12913 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json @@ -0,0 +1,17 @@ +{ + "rule_id": "f675872f-6d85-40a3-b502-c0d2ef101e92", + "risk_score": 50, + "description": "EQL - Delete Volume USN Journal with fsutil", + "immutable": true, + "interval": "5m", + "name": "EQL - Delete Volume USN Journal with fsutil", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"fsutil.exe\" and process.args:(\"usn\" and \"deletejournal\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json new file mode 100644 index 0000000000000..4bf7ae5ee1a5a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json @@ -0,0 +1,17 @@ +{ + "rule_id": "581add16-df76-42bb-af8e-c979bfb39a59", + "risk_score": 50, + "description": "EQL - Deleting Backup Catalogs with wbadmin", + "immutable": true, + "interval": "5m", + "name": "EQL - Deleting Backup Catalogs with wbadmin", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wbadmin.exe\" and process.args:(\"delete\" and \"catalog\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json new file mode 100644 index 0000000000000..8a7733d069154 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json @@ -0,0 +1,17 @@ +{ + "rule_id": "c82c7d8f-fb9e-4874-a4bd-fd9e3f9becf1", + "risk_score": 50, + "description": "EQL - Direct Outbound SMB Connection", + "immutable": true, + "interval": "5m", + "name": "EQL - Direct Outbound SMB Connection", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and destination.port:445 and not process.pid:4 and not destination.ip:(\"127.0.0.1\" or \"::1\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json new file mode 100644 index 0000000000000..2ed22ed4e59a0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json @@ -0,0 +1,17 @@ +{ + "rule_id": "4b438734-3793-4fda-bd42-ceeada0be8f9", + "risk_score": 50, + "description": "EQL - Disable Windows Firewall Rules with Netsh", + "immutable": true, + "interval": "5m", + "name": "EQL - Disable Windows Firewall Rules with Netsh", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"netsh.exe\" and process.args:(\"firewall\" and \"set\" and \"disable\") or process.args:(\"advfirewall\" and \"state\" and \"off\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json new file mode 100644 index 0000000000000..e59286339290a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json @@ -0,0 +1,17 @@ +{ + "rule_id": "73fbc44c-c3cd-48a8-a473-f4eb2065c716", + "risk_score": 50, + "description": "EQL - DLL Search Order Hijack", + "immutable": true, + "interval": "5m", + "name": "EQL - DLL Search Order Hijack", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"File created (rule: FileCreate)\" and not winlog.user.identifier:(\"S-1-5-18\" or \"S-1-5-19\" or \"S-1-5-20\") and file.path:(\"C\\Windows\\ehome\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptsp.dll\" or \"C\\Windows\\System32\\Sysprep\\rpcrtremote.dll\" or \"C\\Windows\\System32\\Sysprep\\uxtheme.dll\" or \"C\\Windows\\System32\\Sysprep\\dwmapi.dll\" or \"C\\Windows\\System32\\Sysprep\\shcore.dll\" or \"C\\Windows\\System32\\Sysprep\\oleacc.dll\" or \"C\\Windows\\System32\\ntwdblib.dll\") ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json new file mode 100644 index 0000000000000..2ad0a53b6c9b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json @@ -0,0 +1,17 @@ +{ + "rule_id": "fd70c98a-c410-42dc-a2e3-761c71848acf", + "risk_score": 50, + "description": "EQL - Encoding or Decoding Files via CertUtil", + "immutable": true, + "interval": "5m", + "name": "EQL - Encoding or Decoding Files via CertUtil", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"certutil.exe\" and process.args:(\"-encode\" or \"/encode\" or \"-decode\" or \"/decode\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json new file mode 100644 index 0000000000000..bb005643031bd --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json @@ -0,0 +1,17 @@ +{ + "rule_id": "afcce5ad-65de-4ed2-8516-5e093d3ac99a", + "risk_score": 50, + "description": "EQL - Local Scheduled Task Commands", + "immutable": true, + "interval": "5m", + "name": "EQL - Local Scheduled Task Commands", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:schtasks.exe and process.args:(\"/create\" or \"-create\" or \"/S\" or \"-s\" or \"/run\" or \"-run\" or \"/change\" or \"-change\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json new file mode 100644 index 0000000000000..1254d0971f108 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json @@ -0,0 +1,17 @@ +{ + "rule_id": "e8571d5f-bea1-46c2-9f56-998de2d3ed95", + "risk_score": 50, + "description": "EQL - Local Service Commands", + "immutable": true, + "interval": "5m", + "name": "EQL - Local Service Commands", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:sc.exe and process.args:(\"create\" or \"config\" or \"failure\" or \"start\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json new file mode 100644 index 0000000000000..62b07f1f4ed37 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json @@ -0,0 +1,17 @@ +{ + "rule_id": "b9ab2f7f-f719-4417-9599-e0252fffe2d8", + "risk_score": 50, + "description": "EQL - Modification of Boot Configuration", + "immutable": true, + "interval": "5m", + "name": "EQL - Modification of Boot Configuration", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"bcdedit.exe\" and process.args:\"set\" and process.args:( (\"bootstatuspolicy\" and \"ignoreallfailures\") or (\"recoveryenabled\" and \"no\") ) ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json new file mode 100644 index 0000000000000..a3c0a8c0960ef --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json @@ -0,0 +1,17 @@ +{ + "rule_id": "0e79980b-4250-4a50-a509-69294c14e84b", + "risk_score": 50, + "description": "EQL - MsBuild Making Network Connections", + "immutable": true, + "interval": "5m", + "name": "EQL - MsBuild Making Network Connections", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:msbuild.exe and not destination.ip:(\"127.0.0.1\" or \"::1\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json new file mode 100644 index 0000000000000..2d5e73c50a73c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json @@ -0,0 +1,17 @@ +{ + "rule_id": "a4ec1382-4557-452b-89ba-e413b22ed4b8", + "risk_score": 50, + "description": "EQL - Mshta Making Network Connections", + "immutable": true, + "interval": "5m", + "name": "EQL - Mshta Making Network Connections", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:\"mshta.exe\" and not process.name:\"mshta.exe\" and not parent.process.name:\"Microsoft.ConfigurationManagement.exe\"", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json new file mode 100644 index 0000000000000..04c88def26d61 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json @@ -0,0 +1,17 @@ +{ + "rule_id": "d7351b03-135d-43ba-8b36-cc9b07854525", + "risk_score": 50, + "description": "EQL - MsXsl Making Network Connections", + "immutable": true, + "interval": "5m", + "name": "EQL - MsXsl Making Network Connections", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "process.name:msxml.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json new file mode 100644 index 0000000000000..fe87c83c0403c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json @@ -0,0 +1,17 @@ +{ + "rule_id": "55d551c6-333b-4665-ab7e-5d14a59715ce", + "risk_score": 50, + "description": "EQL - PsExec Lateral Movement Command", + "immutable": true, + "interval": "5m", + "name": "EQL - PsExec Lateral Movement Command", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "process.name:psexec.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json new file mode 100644 index 0000000000000..41deb57145abc --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json @@ -0,0 +1,17 @@ +{ + "rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", + "risk_score": 50, + "description": "EQL - Suspicious MS Office Child Process", + "immutable": true, + "interval": "5m", + "name": "EQL - Suspicious MS Office Child Process", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"winword.exe\" or \"excel.exe\" or \"powerpnt.exe\" or \"eqnedt32.exe\" or \"fltldr.exe\" or \"mspub.exe\" or \"msaccess.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json new file mode 100644 index 0000000000000..bbcc987c3b6ae --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json @@ -0,0 +1,17 @@ +{ + "rule_id": "32f4675e-6c49-4ace-80f9-97c9259dca2e", + "risk_score": 50, + "description": "EQL - Suspicious MS Outlook Child Process", + "immutable": true, + "interval": "5m", + "name": "EQL - Suspicious MS Outlook Child Process", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"outlook.exe\" and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json new file mode 100644 index 0000000000000..488dc04a3b02e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json @@ -0,0 +1,17 @@ +{ + "rule_id": "afcac7b1-d092-43ff-a136-aa7accbda38f", + "risk_score": 50, + "description": "EQL - Suspicious PDF Reader Child Process", + "immutable": true, + "interval": "5m", + "name": "EQL - Suspicious PDF Reader Child Process", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"acrord32.exe\" or \"rdrcef.exe\" or \"foxitphantomPDF.exe\" or \"foxitreader.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json new file mode 100644 index 0000000000000..810aa79ce25af --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json @@ -0,0 +1,17 @@ +{ + "rule_id": "0022d47d-39c7-4f69-a232-4fe9dc7a3acd", + "risk_score": 50, + "description": "EQL - System Shells via Services", + "immutable": true, + "interval": "5m", + "name": "EQL - System Shells via Services", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"services.exe\" and process.name:(\"cmd.exe\" or \"powershell.exe\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json new file mode 100644 index 0000000000000..6918d996256c0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json @@ -0,0 +1,17 @@ +{ + "rule_id": "52aaab7b-b51c-441a-89ce-4387b3aea886", + "risk_score": 50, + "description": "EQL - Unusual Network Connection via RunDLL32", + "immutable": true, + "interval": "5m", + "name": "EQL - Unusual Network Connection via RunDLL32", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "process.name:rundll32.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json new file mode 100644 index 0000000000000..007487ec91eed --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json @@ -0,0 +1,17 @@ +{ + "rule_id": "35df0dd8-092d-4a83-88c1-5151a804f31b", + "risk_score": 50, + "description": "EQL - Unusual Parent-Child Relationship ", + "immutable": true, + "interval": "5m", + "name": "EQL - Unusual Parent-Child Relationship ", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.executable:* and ( (process.name:\"smss.exe\" and not process.parent.name:(\"System\" or \"smss.exe\")) or (process.name:\"csrss.exe\" and not process.parent.name:(\"smss.exe\" or \"svchost.exe\")) or (process.name:\"wininit.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"winlogon.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"lsass.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"LogonUI.exe\" and not process.parent.name:(\"winlogon.exe\" or \"wininit.exe\")) or (process.name:\"services.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"svchost.exe\" and not process.parent.name:(\"services.exe\" or \"MsMpEng.exe\")) or (process.name:\"spoolsv.exe\" and not process.parent.name:\"services.exe\") or (process.name:\"taskhost.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"taskhostw.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"userinit.exe\" and not process.parent.name:(\"dwm.exe\" or \"winlogon.exe\")) )", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json new file mode 100644 index 0000000000000..7aabc9ed60416 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json @@ -0,0 +1,17 @@ +{ + "rule_id": "610949a1-312f-4e04-bb55-3a79b8c95267", + "risk_score": 50, + "description": "EQL - Unusual Process Network Connection", + "immutable": true, + "interval": "5m", + "name": "EQL - Unusual Process Network Connection", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:(bginfo.exe or cdb.exe or cmstp.exe or csi.exe or dnx.exe or fsi.exe or ieexec.exe or iexpress.exe or Microsoft.Workflow.Compiler.exe or odbcconf.exe or rcsi.exe or xwizard.exe)", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json new file mode 100644 index 0000000000000..cbe1b7fb7af4f --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json @@ -0,0 +1,17 @@ +{ + "rule_id": "1aa9181a-492b-4c01-8b16-fa0735786b2b", + "risk_score": 50, + "description": "EQL - User Account Creation", + "immutable": true, + "interval": "5m", + "name": "EQL - User Account Creation", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"user\" and (\"/add\" or \"/ad\")) ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json new file mode 100644 index 0000000000000..ed8fa5276ef34 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json @@ -0,0 +1,17 @@ +{ + "rule_id": "4426de6f-6103-44aa-a77e-49d672836c27", + "risk_score": 50, + "description": "EQL - User Added to Administrator Group", + "immutable": true, + "interval": "5m", + "name": "EQL - User Added to Administrator Group", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"group\" and \"admin\" and \"/add\") ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json new file mode 100644 index 0000000000000..186c688d21d8f --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json @@ -0,0 +1,17 @@ +{ + "rule_id": "b5ea4bfe-a1b2-421f-9d47-22a75a6f2921", + "risk_score": 50, + "description": "EQL - Volume Shadow Copy Deletion via VssAdmin", + "immutable": true, + "interval": "5m", + "name": "EQL - Volume Shadow Copy Deletion via VssAdmin", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"vssadmin.exe\" and process.args:(\"delete\" and \"shadows\") ", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json new file mode 100644 index 0000000000000..9f75cb3ab26a8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json @@ -0,0 +1,17 @@ +{ + "rule_id": "dc9c1f74-dac3-48e3-b47f-eb79db358f57", + "risk_score": 50, + "description": "EQL - Volume Shadow Copy Deletion via WMIC", + "immutable": true, + "interval": "5m", + "name": "EQL - Volume Shadow Copy Deletion via WMIC", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"shadowcopy\" and \"delete\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json new file mode 100644 index 0000000000000..034651d94d0ea --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json @@ -0,0 +1,17 @@ +{ + "rule_id": "f545ff26-3c94-4fd0-bd33-3c7f95a3a0fc", + "risk_score": 50, + "description": "EQL - Windows Script Executing PowerShell", + "immutable": true, + "interval": "5m", + "name": "EQL - Windows Script Executing PowerShell", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"wscript.exe\" or \"cscript.exe\") and process.name:\"powershell.exe\"", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json new file mode 100644 index 0000000000000..eb1f3f4dca08e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json @@ -0,0 +1,17 @@ +{ + "rule_id": "9616587f-6396-42d0-bd31-ef8dbd806210", + "risk_score": 50, + "description": "EQL - WMIC Command Lateral Movement", + "immutable": true, + "interval": "5m", + "name": "EQL - WMIC Command Lateral Movement", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"/node\" or \"-node\") and process.args:(\"call\" or \"set\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts index 406432bcbda00..69fc9a26e921a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -7,147 +7,223 @@ // Auto generated file from scripts/convert_saved_search_rules.js // Do not hand edit. Run the script against a set of saved searches instead -import rule1 from './windows_powershell_connecting_to_the_internet.json'; -import rule2 from './windows_net_user_command_activity.json'; -import rule3 from './windows_image_load_from_a_temp_directory.json'; -import rule4 from './network_ssh_secure_shell_to_the_internet.json'; -import rule5 from './suricata_nonhttp_traffic_on_tcp_port_80.json'; -import rule6 from './windows_misc_lolbin_connecting_to_the_internet.json'; -import rule7 from './linux_strace_activity.json'; -import rule8 from './suricata_directory_reversal_characters_in_an_http_request.json'; -import rule9 from './suricata_dns_traffic_on_unusual_udp_port.json'; -import rule10 from './network_telnet_port_activity.json'; -import rule11 from './suricata_directory_traversal_in_downloaded_zip_file.json'; -import rule12 from './windows_execution_via_microsoft_html_application_hta.json'; -import rule13 from './windows_credential_dumping_commands.json'; -import rule14 from './windows_net_command_activity_by_the_system_account.json'; -import rule15 from './windows_register_server_program_connecting_to_the_internet.json'; -import rule16 from './linux_java_process_connecting_to_the_internet.json'; -import rule17 from './suricata_imap_traffic_on_unusual_port_internet_destination.json'; -import rule18 from './suricata_double_encoded_characters_in_a_uri.json'; -import rule19 from './network_tor_activity_to_the_internet.json'; -import rule20 from './windows_registry_query_local.json'; -import rule21 from './linux_netcat_network_connection.json'; -import rule22 from './windows_defense_evasion_via_filter_manager.json'; -import rule23 from './suricata_nondns_traffic_on_udp_port_53.json'; -import rule24 from './suricata_double_encoded_characters_in_an_http_post.json'; -import rule25 from './command_shell_started_by_internet_explorer.json'; -import rule26 from './network_vnc_virtual_network_computing_from_the_internet.json'; -import rule27 from './windows_nmap_activity.json'; -import rule28 from './suspicious_process_started_by_a_script.json'; -import rule29 from './windows_network_anomalous_windows_process_using_https_ports.json'; -import rule30 from './powershell_network_connection.json'; -import rule31 from './windows_signed_binary_proxy_execution.json'; -import rule32 from './linux_kernel_module_activity.json'; -import rule33 from './network_vnc_virtual_network_computing_to_the_internet.json'; -import rule34 from './suricata_mimikatz_string_detected_in_http_response.json'; -import rule35 from './command_shell_started_by_svchost.json'; -import rule36 from './linux_tcpdump_activity.json'; -import rule37 from './process_started_by_ms_office_program_possible_payload.json'; -import rule38 from './windows_signed_binary_proxy_execution_download.json'; -import rule39 from './suricata_base64_encoded_startprocess_powershell_execution.json'; -import rule40 from './suricata_base64_encoded_invokecommand_powershell_execution.json'; -import rule41 from './suricata_directory_traversal_characters_in_http_response.json'; -import rule42 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; -import rule43 from './suricata_tls_traffic_on_unusual_port_internet_destination.json'; -import rule44 from './process_started_by_acrobat_reader_possible_payload.json'; -import rule45 from './suricata_http_traffic_on_unusual_port_internet_destination.json'; -import rule46 from './windows_persistence_via_modification_of_existing_service.json'; -import rule47 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; -import rule48 from './windows_execution_via_compiled_html_file.json'; -import rule49 from './linux_ptrace_activity.json'; -import rule50 from './suricata_nonimap_traffic_on_port_1443_imap.json'; -import rule51 from './windows_scheduled_task_activity.json'; -import rule52 from './suricata_ftp_traffic_on_unusual_port_internet_destination.json'; -import rule53 from './windows_wireshark_activity.json'; -import rule54 from './windows_execution_via_trusted_developer_utilities.json'; -import rule55 from './suricata_rpc_traffic_on_http_ports.json'; -import rule56 from './windows_process_discovery_via_tasklist_command.json'; -import rule57 from './suricata_cobaltstrike_artifact_in_an_dns_request.json'; -import rule58 from './suricata_serialized_php_detected.json'; -import rule59 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; -import rule60 from './windows_registry_query_network.json'; -import rule61 from './windows_persistence_via_application_shimming.json'; -import rule62 from './network_proxy_port_activity_to_the_internet.json'; -import rule63 from './windows_whoami_command_activity.json'; -import rule64 from './suricata_shell_exec_php_function_in_an_http_post.json'; -import rule65 from './windump_activity.json'; -import rule66 from './windows_management_instrumentation_wmi_execution.json'; -import rule67 from './network_rdp_remote_desktop_protocol_from_the_internet.json'; -import rule68 from './windows_priv_escalation_via_accessibility_features.json'; -import rule69 from './psexec_activity.json'; -import rule70 from './linux_rawshark_activity.json'; -import rule71 from './suricata_nonftp_traffic_on_port_21.json'; -import rule72 from './network_ftp_file_transfer_protocol_activity_to_the_internet.json'; -import rule73 from './windows_certutil_connecting_to_the_internet.json'; -import rule74 from './suricata_nonsmb_traffic_on_tcp_port_139_smb.json'; -import rule75 from './network_rdp_remote_desktop_protocol_to_the_internet.json'; -import rule76 from './linux_whoami_commmand.json'; -import rule77 from './windows_persistence_or_priv_escalation_via_hooking.json'; -import rule78 from './linux_lzop_activity_possible_julianrunnels.json'; -import rule79 from './suricata_nontls_on_tls_port.json'; -import rule80 from './network_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; -import rule81 from './linux_network_anomalous_process_using_https_ports.json'; -import rule82 from './windows_credential_dumping_via_registry_save.json'; -import rule83 from './network_rpc_remote_procedure_call_from_the_internet.json'; -import rule84 from './windows_credential_dumping_via_imageload.json'; -import rule85 from './windows_burp_ce_activity.json'; -import rule86 from './linux_hping_activity.json'; -import rule87 from './windows_command_prompt_connecting_to_the_internet.json'; -import rule88 from './network_nat_traversal_port_activity.json'; -import rule89 from './network_rpc_remote_procedure_call_to_the_internet.json'; -import rule90 from './suricata_possible_cobalt_strike_malleable_c2_null_response.json'; -import rule91 from './windows_remote_management_execution.json'; -import rule92 from './suricata_lazagne_artifact_in_an_http_post.json'; -import rule93 from './windows_netcat_network_activity.json'; -import rule94 from './windows_iodine_activity.json'; -import rule95 from './network_port_26_activity.json'; -import rule96 from './windows_execution_via_connection_manager.json'; -import rule97 from './linux_process_started_in_temp_directory.json'; -import rule98 from './suricata_eval_php_function_in_an_http_request.json'; -import rule99 from './linux_web_download.json'; -import rule100 from './suricata_ssh_traffic_not_on_port_22_internet_destination.json'; -import rule101 from './network_port_8000_activity.json'; -import rule102 from './windows_process_started_by_the_java_runtime.json'; -import rule103 from './suricata_possible_sql_injection_sql_commands_in_http_transactions.json'; -import rule104 from './network_smb_windows_file_sharing_activity_to_the_internet.json'; -import rule105 from './network_port_8000_activity_to_the_internet.json'; -import rule106 from './command_shell_started_by_powershell.json'; -import rule107 from './linux_nmap_activity.json'; -import rule108 from './search_windows_10.json'; -import rule109 from './network_smtp_to_the_internet.json'; -import rule110 from './windows_payload_obfuscation_via_certutil.json'; -import rule111 from './network_pptp_point_to_point_tunneling_protocol_activity.json'; -import rule112 from './linux_unusual_shell_activity.json'; -import rule113 from './linux_mknod_activity.json'; -import rule114 from './network_sql_server_port_activity_to_the_internet.json'; -import rule115 from './suricata_commonly_abused_dns_domain_detected.json'; -import rule116 from './linux_iodine_activity.json'; -import rule117 from './suricata_mimikatz_artifacts_in_an_http_post.json'; -import rule118 from './windows_execution_via_net_com_assemblies.json'; -import rule119 from './suricata_dns_traffic_on_unusual_tcp_port.json'; -import rule120 from './suricata_base64_encoded_newobject_powershell_execution.json'; -import rule121 from './windows_netcat_activity.json'; -import rule122 from './windows_persistence_via_bits_jobs.json'; -import rule123 from './linux_nping_activity.json'; -import rule124 from './windows_execution_via_regsvr32.json'; -import rule125 from './process_started_by_windows_defender.json'; -import rule126 from './windows_indirect_command_execution.json'; -import rule127 from './network_ssh_secure_shell_from_the_internet.json'; -import rule128 from './windows_html_help_executable_program_connecting_to_the_internet.json'; -import rule129 from './suricata_windows_executable_served_by_jpeg_web_content.json'; -import rule130 from './network_dns_directly_to_the_internet.json'; -import rule131 from './windows_defense_evasion_via_windows_event_log_tools.json'; -import rule132 from './suricata_nondns_traffic_on_tcp_port_53.json'; -import rule133 from './windows_persistence_via_netshell_helper_dll.json'; -import rule134 from './windows_script_interpreter_connecting_to_the_internet.json'; -import rule135 from './windows_defense_evasion_decoding_using_certutil.json'; -import rule136 from './linux_shell_activity_by_web_server.json'; -import rule137 from './linux_ldso_process_activity.json'; -import rule138 from './windows_mimikatz_activity.json'; -import rule139 from './suricata_nonssh_traffic_on_port_22.json'; -import rule140 from './windows_data_compression_using_powershell.json'; -import rule141 from './windows_nmap_scan_activity.json'; +import rule1 from './eql_bypass_uac_via_sdclt.json'; +import rule2 from './eql_clearing_windows_event_logs.json'; +import rule3 from './eql_suspicious_ms_office_child_process.json'; +import rule4 from './eql_bypass_uac_event_viewer.json'; +import rule5 from './eql_volume_shadow_copy_deletion_via_wmic.json'; +import rule6 from './eql_adobe_hijack_persistence.json'; +import rule7 from './eql_unusual_network_connection_via_rundll32.json'; +import rule8 from './eql_delete_volume_usn_journal_with_fsutil.json'; +import rule9 from './eql_mshta_making_network_connections.json'; +import rule10 from './eql_unusual_process_network_connection.json'; +import rule11 from './eql_suspicious_ms_outlook_child_process.json'; +import rule12 from './eql_audio_capture_via_soundrecorder.json'; +import rule13 from './eql_direct_outbound_smb_connection.json'; +import rule14 from './eql_windows_script_executing_powershell.json'; +import rule15 from './eql_deleting_backup_catalogs_with_wbadmin.json'; +import rule16 from './eql_suspicious_pdf_reader_child_process.json'; +import rule17 from './eql_local_service_commands.json'; +import rule18 from './eql_dll_search_order_hijack.json'; +import rule19 from './eql_bypass_uac_via_cmstp.json'; +import rule20 from './eql_user_account_creation.json'; +import rule21 from './eql_wmic_command_lateral_movement.json'; +import rule22 from './eql_system_shells_via_services.json'; +import rule23 from './eql_msxsl_making_network_connections.json'; +import rule24 from './eql_local_scheduled_task_commands.json'; +import rule25 from './eql_msbuild_making_network_connections.json'; +import rule26 from './eql_encoding_or_decoding_files_via_certutil.json'; +import rule27 from './eql_disable_windows_firewall_rules_with_netsh.json'; +import rule28 from './eql_adding_the_hidden_file_attribute_with_via_attribexe.json'; +import rule29 from './eql_psexec_lateral_movement_command.json'; +import rule30 from './eql_user_added_to_administrator_group.json'; +import rule31 from './eql_audio_capture_via_powershell.json'; +import rule32 from './eql_unusual_parentchild_relationship.json'; +import rule33 from './eql_modification_of_boot_configuration.json'; +import rule34 from './eql_volume_shadow_copy_deletion_via_vssadmin.json'; +import rule35 from './suricata_category_large_scale_information_leak.json'; +import rule36 from './suricata_category_attempted_information_leak.json'; +import rule37 from './suricata_category_not_suspicious_traffic.json'; +import rule38 from './suricata_category_potentially_bad_traffic.json'; +import rule39 from './suricata_category_information_leak.json'; +import rule40 from './suricata_category_unknown_traffic.json'; +import rule41 from './suricata_category_successful_administrator_privilege_gain.json'; +import rule42 from './suricata_category_attempted_administrator_privilege_gain.json'; +import rule43 from './suricata_category_unsuccessful_user_privilege_gain.json'; +import rule44 from './suricata_category_successful_user_privilege_gain.json'; +import rule45 from './suricata_category_attempted_user_privilege_gain.json'; +import rule46 from './suricata_category_attempted_denial_of_service.json'; +import rule47 from './suricata_category_decode_of_an_rpc_query.json'; +import rule48 from './suricata_category_denial_of_service.json'; +import rule49 from './suricata_category_attempted_login_with_suspicious_username.json'; +import rule50 from './suricata_category_client_using_unusual_port.json'; +import rule51 from './suricata_category_suspicious_filename_detected.json'; +import rule52 from './suricata_category_a_suspicious_string_was_detected.json'; +import rule53 from './suricata_category_tcp_connection_detected.json'; +import rule54 from './suricata_category_executable_code_was_detected.json'; +import rule55 from './suricata_category_network_trojan_detected.json'; +import rule56 from './suricata_category_system_call_detected.json'; +import rule57 from './suricata_category_potentially_vulnerable_web_application_access.json'; +import rule58 from './suricata_category_nonstandard_protocol_or_event.json'; +import rule59 from './suricata_category_denial_of_service_attack.json'; +import rule60 from './suricata_category_generic_protocol_command_decode.json'; +import rule61 from './suricata_category_network_scan_detected.json'; +import rule62 from './suricata_category_web_application_attack.json'; +import rule63 from './suricata_category_generic_icmp_event.json'; +import rule64 from './suricata_category_misc_attack.json'; +import rule65 from './suricata_category_default_username_and_password_login_attempt.json'; +import rule66 from './suricata_category_external_ip_address_retrieval.json'; +import rule67 from './suricata_category_potential_corporate_privacy_violation.json'; +import rule68 from './suricata_category_targeted_malicious_activity.json'; +import rule69 from './suricata_category_observed_c2_domain.json'; +import rule70 from './suricata_category_exploit_kit_activity.json'; +import rule71 from './suricata_category_possibly_unwanted_program.json'; +import rule72 from './suricata_category_successful_credential_theft.json'; +import rule73 from './suricata_category_possible_social_engineering_attempted.json'; +import rule74 from './suricata_category_crypto_currency_mining_activity.json'; +import rule75 from './suricata_category_malware_command_and_control_activity.json'; +import rule76 from './suricata_category_misc_activity.json'; +import rule77 from './windows_powershell_connecting_to_the_internet.json'; +import rule78 from './windows_net_user_command_activity.json'; +import rule79 from './windows_image_load_from_a_temp_directory.json'; +import rule80 from './network_ssh_secure_shell_to_the_internet.json'; +import rule81 from './suricata_nonhttp_traffic_on_tcp_port_80.json'; +import rule82 from './windows_misc_lolbin_connecting_to_the_internet.json'; +import rule83 from './linux_strace_activity.json'; +import rule84 from './suricata_directory_reversal_characters_in_an_http_request.json'; +import rule85 from './suricata_dns_traffic_on_unusual_udp_port.json'; +import rule86 from './network_telnet_port_activity.json'; +import rule87 from './suricata_directory_traversal_in_downloaded_zip_file.json'; +import rule88 from './windows_execution_via_microsoft_html_application_hta.json'; +import rule89 from './windows_credential_dumping_commands.json'; +import rule90 from './windows_net_command_activity_by_the_system_account.json'; +import rule91 from './windows_register_server_program_connecting_to_the_internet.json'; +import rule92 from './linux_java_process_connecting_to_the_internet.json'; +import rule93 from './suricata_imap_traffic_on_unusual_port_internet_destination.json'; +import rule94 from './suricata_double_encoded_characters_in_a_uri.json'; +import rule95 from './network_tor_activity_to_the_internet.json'; +import rule96 from './windows_registry_query_local.json'; +import rule97 from './linux_netcat_network_connection.json'; +import rule98 from './windows_defense_evasion_via_filter_manager.json'; +import rule99 from './suricata_nondns_traffic_on_udp_port_53.json'; +import rule100 from './suricata_double_encoded_characters_in_an_http_post.json'; +import rule101 from './command_shell_started_by_internet_explorer.json'; +import rule102 from './network_vnc_virtual_network_computing_from_the_internet.json'; +import rule103 from './windows_nmap_activity.json'; +import rule104 from './suspicious_process_started_by_a_script.json'; +import rule105 from './windows_network_anomalous_windows_process_using_https_ports.json'; +import rule106 from './powershell_network_connection.json'; +import rule107 from './windows_signed_binary_proxy_execution.json'; +import rule108 from './linux_kernel_module_activity.json'; +import rule109 from './network_vnc_virtual_network_computing_to_the_internet.json'; +import rule110 from './suricata_mimikatz_string_detected_in_http_response.json'; +import rule111 from './command_shell_started_by_svchost.json'; +import rule112 from './linux_tcpdump_activity.json'; +import rule113 from './process_started_by_ms_office_program_possible_payload.json'; +import rule114 from './windows_signed_binary_proxy_execution_download.json'; +import rule115 from './suricata_base64_encoded_startprocess_powershell_execution.json'; +import rule116 from './suricata_base64_encoded_invokecommand_powershell_execution.json'; +import rule117 from './suricata_directory_traversal_characters_in_http_response.json'; +import rule118 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; +import rule119 from './suricata_tls_traffic_on_unusual_port_internet_destination.json'; +import rule120 from './process_started_by_acrobat_reader_possible_payload.json'; +import rule121 from './suricata_http_traffic_on_unusual_port_internet_destination.json'; +import rule122 from './windows_persistence_via_modification_of_existing_service.json'; +import rule123 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; +import rule124 from './windows_execution_via_compiled_html_file.json'; +import rule125 from './linux_ptrace_activity.json'; +import rule126 from './suricata_nonimap_traffic_on_port_1443_imap.json'; +import rule127 from './windows_scheduled_task_activity.json'; +import rule128 from './suricata_ftp_traffic_on_unusual_port_internet_destination.json'; +import rule129 from './windows_wireshark_activity.json'; +import rule130 from './windows_execution_via_trusted_developer_utilities.json'; +import rule131 from './suricata_rpc_traffic_on_http_ports.json'; +import rule132 from './windows_process_discovery_via_tasklist_command.json'; +import rule133 from './suricata_cobaltstrike_artifact_in_an_dns_request.json'; +import rule134 from './suricata_serialized_php_detected.json'; +import rule135 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; +import rule136 from './windows_registry_query_network.json'; +import rule137 from './windows_persistence_via_application_shimming.json'; +import rule138 from './network_proxy_port_activity_to_the_internet.json'; +import rule139 from './windows_whoami_command_activity.json'; +import rule140 from './suricata_shell_exec_php_function_in_an_http_post.json'; +import rule141 from './windump_activity.json'; +import rule142 from './windows_management_instrumentation_wmi_execution.json'; +import rule143 from './network_rdp_remote_desktop_protocol_from_the_internet.json'; +import rule144 from './windows_priv_escalation_via_accessibility_features.json'; +import rule145 from './psexec_activity.json'; +import rule146 from './linux_rawshark_activity.json'; +import rule147 from './suricata_nonftp_traffic_on_port_21.json'; +import rule148 from './network_ftp_file_transfer_protocol_activity_to_the_internet.json'; +import rule149 from './windows_certutil_connecting_to_the_internet.json'; +import rule150 from './suricata_nonsmb_traffic_on_tcp_port_139_smb.json'; +import rule151 from './network_rdp_remote_desktop_protocol_to_the_internet.json'; +import rule152 from './linux_whoami_commmand.json'; +import rule153 from './windows_persistence_or_priv_escalation_via_hooking.json'; +import rule154 from './linux_lzop_activity_possible_julianrunnels.json'; +import rule155 from './suricata_nontls_on_tls_port.json'; +import rule156 from './network_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; +import rule157 from './linux_network_anomalous_process_using_https_ports.json'; +import rule158 from './windows_credential_dumping_via_registry_save.json'; +import rule159 from './network_rpc_remote_procedure_call_from_the_internet.json'; +import rule160 from './windows_credential_dumping_via_imageload.json'; +import rule161 from './windows_burp_ce_activity.json'; +import rule162 from './linux_hping_activity.json'; +import rule163 from './windows_command_prompt_connecting_to_the_internet.json'; +import rule164 from './network_nat_traversal_port_activity.json'; +import rule165 from './network_rpc_remote_procedure_call_to_the_internet.json'; +import rule166 from './suricata_possible_cobalt_strike_malleable_c2_null_response.json'; +import rule167 from './windows_remote_management_execution.json'; +import rule168 from './suricata_lazagne_artifact_in_an_http_post.json'; +import rule169 from './windows_netcat_network_activity.json'; +import rule170 from './windows_iodine_activity.json'; +import rule171 from './network_port_26_activity.json'; +import rule172 from './windows_execution_via_connection_manager.json'; +import rule173 from './linux_process_started_in_temp_directory.json'; +import rule174 from './suricata_eval_php_function_in_an_http_request.json'; +import rule175 from './linux_web_download.json'; +import rule176 from './suricata_ssh_traffic_not_on_port_22_internet_destination.json'; +import rule177 from './network_port_8000_activity.json'; +import rule178 from './windows_process_started_by_the_java_runtime.json'; +import rule179 from './suricata_possible_sql_injection_sql_commands_in_http_transactions.json'; +import rule180 from './network_smb_windows_file_sharing_activity_to_the_internet.json'; +import rule181 from './network_port_8000_activity_to_the_internet.json'; +import rule182 from './command_shell_started_by_powershell.json'; +import rule183 from './linux_nmap_activity.json'; +import rule184 from './search_windows_10.json'; +import rule185 from './network_smtp_to_the_internet.json'; +import rule186 from './windows_payload_obfuscation_via_certutil.json'; +import rule187 from './network_pptp_point_to_point_tunneling_protocol_activity.json'; +import rule188 from './linux_unusual_shell_activity.json'; +import rule189 from './linux_mknod_activity.json'; +import rule190 from './network_sql_server_port_activity_to_the_internet.json'; +import rule191 from './suricata_commonly_abused_dns_domain_detected.json'; +import rule192 from './linux_iodine_activity.json'; +import rule193 from './suricata_mimikatz_artifacts_in_an_http_post.json'; +import rule194 from './windows_execution_via_net_com_assemblies.json'; +import rule195 from './suricata_dns_traffic_on_unusual_tcp_port.json'; +import rule196 from './suricata_base64_encoded_newobject_powershell_execution.json'; +import rule197 from './windows_netcat_activity.json'; +import rule198 from './windows_persistence_via_bits_jobs.json'; +import rule199 from './linux_nping_activity.json'; +import rule200 from './windows_execution_via_regsvr32.json'; +import rule201 from './process_started_by_windows_defender.json'; +import rule202 from './windows_indirect_command_execution.json'; +import rule203 from './network_ssh_secure_shell_from_the_internet.json'; +import rule204 from './windows_html_help_executable_program_connecting_to_the_internet.json'; +import rule205 from './suricata_windows_executable_served_by_jpeg_web_content.json'; +import rule206 from './network_dns_directly_to_the_internet.json'; +import rule207 from './windows_defense_evasion_via_windows_event_log_tools.json'; +import rule208 from './suricata_nondns_traffic_on_tcp_port_53.json'; +import rule209 from './windows_persistence_via_netshell_helper_dll.json'; +import rule210 from './windows_script_interpreter_connecting_to_the_internet.json'; +import rule211 from './windows_defense_evasion_decoding_using_certutil.json'; +import rule212 from './linux_shell_activity_by_web_server.json'; +import rule213 from './linux_ldso_process_activity.json'; +import rule214 from './windows_mimikatz_activity.json'; +import rule215 from './suricata_nonssh_traffic_on_port_22.json'; +import rule216 from './windows_data_compression_using_powershell.json'; +import rule217 from './windows_nmap_scan_activity.json'; export const rawRules = [ rule1, @@ -291,4 +367,80 @@ export const rawRules = [ rule139, rule140, rule141, + rule142, + rule143, + rule144, + rule145, + rule146, + rule147, + rule148, + rule149, + rule150, + rule151, + rule152, + rule153, + rule154, + rule155, + rule156, + rule157, + rule158, + rule159, + rule160, + rule161, + rule162, + rule163, + rule164, + rule165, + rule166, + rule167, + rule168, + rule169, + rule170, + rule171, + rule172, + rule173, + rule174, + rule175, + rule176, + rule177, + rule178, + rule179, + rule180, + rule181, + rule182, + rule183, + rule184, + rule185, + rule186, + rule187, + rule188, + rule189, + rule190, + rule191, + rule192, + rule193, + rule194, + rule195, + rule196, + rule197, + rule198, + rule199, + rule200, + rule201, + rule202, + rule203, + rule204, + rule205, + rule206, + rule207, + rule208, + rule209, + rule210, + rule211, + rule212, + rule213, + rule214, + rule215, + rule216, + rule217, ]; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json new file mode 100644 index 0000000000000..eb4fa0fe411a9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json @@ -0,0 +1,17 @@ +{ + "rule_id": "2a3d91c1-5065-46ab-bed0-93f80835b1d5", + "risk_score": 50, + "description": "Suricata Category - A suspicious string was detected", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - A suspicious string was detected", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A suspicious string was detected\" or rule.category: \"A suspicious string was detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json new file mode 100644 index 0000000000000..3fc61c50927c7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json @@ -0,0 +1,17 @@ +{ + "rule_id": "f840129e-9089-4f46-8af1-0745e8f54713", + "risk_score": 50, + "description": "Suricata Category - Attempted Administrator Privilege Gain", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Attempted Administrator Privilege Gain", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Administrator Privilege Gain\" or rule.category: \"Attempted Administrator Privilege Gain\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json new file mode 100644 index 0000000000000..e888b2076f137 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json @@ -0,0 +1,17 @@ +{ + "rule_id": "a62927f4-2488-4679-b56f-cda1a7f4c9e1", + "risk_score": 50, + "description": "Suricata Category - Attempted Denial of Service", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Attempted Denial of Service", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Denial of Service\" or rule.category: \"Attempted Denial of Service\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json new file mode 100644 index 0000000000000..ae93e8bce7801 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json @@ -0,0 +1,17 @@ +{ + "rule_id": "88d69362-f496-41d6-8e6b-a2dbaed3513f", + "risk_score": 50, + "description": "Suricata Category - Attempted Information Leak", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Attempted Information Leak", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Information Leak\" or rule.category: \"Attempted Information Leak\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json new file mode 100644 index 0000000000000..c00e7a42aee06 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json @@ -0,0 +1,17 @@ +{ + "rule_id": "a84cd36c-dd5a-4e86-a2ce-44556c21cef0", + "risk_score": 50, + "description": "Suricata Category - Attempted Login with Suspicious Username", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Attempted Login with Suspicious Username", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"An attempted login using a suspicious username was detected\" or rule.category: \"An attempted login using a suspicious username was detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json new file mode 100644 index 0000000000000..1b2fcbee310da --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json @@ -0,0 +1,17 @@ +{ + "rule_id": "eabce895-4602-4d20-8bf9-11c903bb3e08", + "risk_score": 50, + "description": "Suricata Category - Attempted User Privilege Gain", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Attempted User Privilege Gain", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted User Privilege Gain\" or rule.category: \"Attempted User Privilege Gain\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json new file mode 100644 index 0000000000000..feedffeaacc9c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json @@ -0,0 +1,17 @@ +{ + "rule_id": "00503a3c-304c-421c-bfea-e5d8fdfd9726", + "risk_score": 50, + "description": "Suricata Category - Client Using Unusual Port", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Client Using Unusual Port", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A client was using an unusual port\" or rule.category: \"A client was using an unusual port\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json new file mode 100644 index 0000000000000..e05461baf36de --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json @@ -0,0 +1,17 @@ +{ + "rule_id": "74cd4920-a441-41d2-8a23-5bee70626e60", + "risk_score": 50, + "description": "Suricata Category - Crypto Currency Mining Activity", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Crypto Currency Mining Activity", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Crypto Currency Mining Activity Detected\" or rule.category: \"Crypto Currency Mining Activity Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json new file mode 100644 index 0000000000000..0e22aa66ca04d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json @@ -0,0 +1,17 @@ +{ + "rule_id": "e9fc5bd3-c8a1-442c-be6d-032da07c508b", + "risk_score": 50, + "description": "Suricata Category - Decode of an RPC Query", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Decode of an RPC Query", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Decode of an RPC Query\" or rule.category: \"Decode of an RPC Query\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json new file mode 100644 index 0000000000000..0810168bbaf15 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json @@ -0,0 +1,17 @@ +{ + "rule_id": "190bd112-f831-4813-98b2-e45a934277c2", + "risk_score": 50, + "description": "Suricata Category - Default Username and Password Login Attempt", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Default Username and Password Login Attempt", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempt to login by a default username and password\" or rule.category: \"Attempt to login by a default username and password\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json new file mode 100644 index 0000000000000..d6ef10a86c184 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json @@ -0,0 +1,17 @@ +{ + "rule_id": "0e97e390-84db-4725-965a-a8b0b600f7be", + "risk_score": 50, + "description": "Suricata Category - Denial of Service", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Denial of Service", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Denial of Service\" or rule.category: \"Denial of Service\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json new file mode 100644 index 0000000000000..3f4975bcdfb14 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json @@ -0,0 +1,17 @@ +{ + "rule_id": "42a60eaa-fd20-479b-b6ca-bdb88d47b34b", + "risk_score": 50, + "description": "Suricata Category - Denial of Service Attack", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Denial of Service Attack", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a Denial of Service Attack\" or rule.category: \"Detection of a Denial of Service Attack\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json new file mode 100644 index 0000000000000..f1f6177e01503 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json @@ -0,0 +1,17 @@ +{ + "rule_id": "4699296b-5127-475a-9d83-8434fcd18136", + "risk_score": 50, + "description": "Suricata Category - Executable code was detected", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Executable code was detected", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Executable code was detected\" or rule.category: \"Executable code was detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json new file mode 100644 index 0000000000000..025f0f4d266f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json @@ -0,0 +1,17 @@ +{ + "rule_id": "b3111af8-79bf-4ec3-97ae-28d9ed9fbd38", + "risk_score": 50, + "description": "Suricata Category - Exploit Kit Activity", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Exploit Kit Activity", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Exploit Kit Activity Detected\" or rule.category: \"Exploit Kit Activity Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json new file mode 100644 index 0000000000000..eab3cb5910861 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json @@ -0,0 +1,17 @@ +{ + "rule_id": "c7df9ecf-d6be-4ef8-9871-cb317dfff0b4", + "risk_score": 50, + "description": "Suricata Category - External IP Address Retrieval", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - External IP Address Retrieval", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Device Retrieving External IP Address Detected\" or rule.category: \"Device Retrieving External IP Address Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json new file mode 100644 index 0000000000000..37b93ce6886d8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json @@ -0,0 +1,17 @@ +{ + "rule_id": "3309bffa-7c43-409a-acea-6631c1b077e5", + "risk_score": 50, + "description": "Suricata Category - Generic ICMP event", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Generic ICMP event", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Generic ICMP event\" or rule.category: \"Generic ICMP event\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json new file mode 100644 index 0000000000000..ed5a6dbe47f5a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json @@ -0,0 +1,17 @@ +{ + "rule_id": "6fd2deb4-a7a9-4221-8b7b-8d26836a8c30", + "risk_score": 50, + "description": "Suricata Category - Generic Protocol Command Decode", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Generic Protocol Command Decode", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Generic Protocol Command Decode\" or rule.category: \"Generic Protocol Command Decode\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json new file mode 100644 index 0000000000000..7cec0f24570ec --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json @@ -0,0 +1,17 @@ +{ + "rule_id": "95df8ff4-7169-4c84-ae50-3561b1d1bc91", + "risk_score": 50, + "description": "Suricata Category - Information Leak", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Information Leak", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Information Leak\" or rule.category: \"Information Leak\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json new file mode 100644 index 0000000000000..c871624f86d9f --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json @@ -0,0 +1,17 @@ +{ + "rule_id": "ca98de30-c703-4170-97ae-ab2b340f6080", + "risk_score": 50, + "description": "Suricata Category - Large Scale Information Leak", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Large Scale Information Leak", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Large Scale Information Leak\" or rule.category: \"Large Scale Information Leak\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json new file mode 100644 index 0000000000000..e0b7e41b67b92 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json @@ -0,0 +1,17 @@ +{ + "rule_id": "56656341-2940-4a69-b8fe-acf3c734f540", + "risk_score": 50, + "description": "Suricata Category - Malware Command and Control Activity", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Malware Command and Control Activity", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Malware Command and Control Activity Detected\" or rule.category: \"Malware Command and Control Activity Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json new file mode 100644 index 0000000000000..aad3b2c5057ce --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json @@ -0,0 +1,17 @@ +{ + "rule_id": "403ddbde-a486-4dd7-b932-cee4ebef88b6", + "risk_score": 50, + "description": "Suricata Category - Misc Activity", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Misc Activity", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Misc activity\" or rule.category: \"Misc activity\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json new file mode 100644 index 0000000000000..eea27b6fa8ae2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json @@ -0,0 +1,17 @@ +{ + "rule_id": "83277123-749f-49da-ad3d-d59f35490db1", + "risk_score": 50, + "description": "Suricata Category - Misc Attack", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Misc Attack", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Misc Attack\" or rule.category: \"Misc Attack\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json new file mode 100644 index 0000000000000..0eb2b136bbef9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json @@ -0,0 +1,17 @@ +{ + "rule_id": "7e969b45-d005-4173-aee7-a7aaa79bc372", + "risk_score": 50, + "description": "Suricata Category - Network Scan Detected", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Network Scan Detected", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a Network Scan\" or rule.category: \"Detection of a Network Scan\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json new file mode 100644 index 0000000000000..f3aeb8393c13f --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json @@ -0,0 +1,17 @@ +{ + "rule_id": "76ffa464-ec03-42e1-87ee-87760c331061", + "risk_score": 50, + "description": "Suricata Category - Network Trojan Detected", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Network Trojan Detected", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A Network Trojan was detected\" or rule.category: \"A Network Trojan was detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json new file mode 100644 index 0000000000000..c3b696afa8e43 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json @@ -0,0 +1,17 @@ +{ + "rule_id": "82f9f485-873b-4eeb-b231-052ab81e05b8", + "risk_score": 50, + "description": "Suricata Category - Non-Standard Protocol or Event", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Non-Standard Protocol or Event", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a non-standard protocol or event\" or rule.category: \"Detection of a non-standard protocol or event\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json new file mode 100644 index 0000000000000..e26180a429a81 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json @@ -0,0 +1,17 @@ +{ + "rule_id": "c0f684ff-4f15-44e7-912d-aa8b8f08a910", + "risk_score": 50, + "description": "Suricata Category - Not Suspicious Traffic", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Not Suspicious Traffic", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Not Suspicious Traffic\" or rule.category: \"Not Suspicious Traffic\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json new file mode 100644 index 0000000000000..7a11a3738b7a4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json @@ -0,0 +1,17 @@ +{ + "rule_id": "8adfa89f-aa90-4d26-9d7a-7da652cae902", + "risk_score": 50, + "description": "Suricata Category - Observed C2 Domain", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Observed C2 Domain", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Domain Observed Used for C2 Detected\" or rule.category: \"Domain Observed Used for C2 Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json new file mode 100644 index 0000000000000..f21da57a4d7b7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json @@ -0,0 +1,17 @@ +{ + "rule_id": "7d2d5a5f-f590-407d-933a-42adb1a7bcef", + "risk_score": 50, + "description": "Suricata Category - Possible Social Engineering Attempted", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Possible Social Engineering Attempted", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Possible Social Engineering Attempted\" or rule.category: \"Possible Social Engineering Attempted\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json new file mode 100644 index 0000000000000..7303185c6e9a4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json @@ -0,0 +1,17 @@ +{ + "rule_id": "1b9a31e8-fdfa-400e-aa4e-79a7f1a1da18", + "risk_score": 50, + "description": "Suricata Category - Possibly Unwanted Program", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Possibly Unwanted Program", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Possibly Unwanted Program Detected\" or rule.category: \"Possibly Unwanted Program Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json new file mode 100644 index 0000000000000..d3f867778bb43 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json @@ -0,0 +1,17 @@ +{ + "rule_id": "1c70f5d5-eae0-4d00-b35a-d34ca607094e", + "risk_score": 50, + "description": "Suricata Category - Potential Corporate Privacy Violation", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Potential Corporate Privacy Violation", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Potential Corporate Privacy Violation\" or rule.category: \"Potential Corporate Privacy Violation\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json new file mode 100644 index 0000000000000..f77fe14014db3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json @@ -0,0 +1,17 @@ +{ + "rule_id": "197cdd5a-9880-4780-a87c-594d0ed2b7b4", + "risk_score": 50, + "description": "Suricata Category - Potentially Bad Traffic", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Potentially Bad Traffic", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Potentially Bad Traffic\" or rule.category: \"Potentially Bad Traffic\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json new file mode 100644 index 0000000000000..1665f8ca82424 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json @@ -0,0 +1,17 @@ +{ + "rule_id": "0993e926-1a01-4c28-918a-cdd5741a19a8", + "risk_score": 50, + "description": "Suricata Category - Potentially Vulnerable Web Application Access", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Potentially Vulnerable Web Application Access", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"access to a potentially vulnerable web application\" or rule.category: \"access to a potentially vulnerable web application\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json new file mode 100644 index 0000000000000..e7b636c421c16 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json @@ -0,0 +1,17 @@ +{ + "rule_id": "f068e655-1f52-4d81-839a-9c08c6543ceb", + "risk_score": 50, + "description": "Suricata Category - Successful Administrator Privilege Gain", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Successful Administrator Privilege Gain", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful Administrator Privilege Gain\" or rule.category: \"Successful Administrator Privilege Gain\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json new file mode 100644 index 0000000000000..bb87b86a75860 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json @@ -0,0 +1,17 @@ +{ + "rule_id": "90f3e735-2187-4e8e-8d28-6e3249964851", + "risk_score": 50, + "description": "Suricata Category - Successful Credential Theft", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Successful Credential Theft", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful Credential Theft Detected\" or rule.category: \"Successful Credential Theft Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json new file mode 100644 index 0000000000000..d6af6e2baabea --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json @@ -0,0 +1,17 @@ +{ + "rule_id": "f8ebd022-6e92-4b80-ac49-7ee011ba2ce0", + "risk_score": 50, + "description": "Suricata Category - Successful User Privilege Gain", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Successful User Privilege Gain", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful User Privilege Gain\" or rule.category: \"Successful User Privilege Gain\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json new file mode 100644 index 0000000000000..205940bb7d0bc --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json @@ -0,0 +1,17 @@ +{ + "rule_id": "d0489b07-8140-4e3d-a2b7-52f2c06fdc7c", + "risk_score": 50, + "description": "Suricata Category - Suspicious Filename Detected", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Suspicious Filename Detected", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A suspicious filename was detected\" or rule.category: \"A suspicious filename was detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json new file mode 100644 index 0000000000000..a86ea16ddf207 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json @@ -0,0 +1,17 @@ +{ + "rule_id": "44a5c55a-a34f-43c3-8f21-df502862aa9b", + "risk_score": 50, + "description": "Suricata Category - System Call Detected", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - System Call Detected", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A system call was detected\" or rule.category: \"A system call was detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json new file mode 100644 index 0000000000000..8923c07341b93 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json @@ -0,0 +1,17 @@ +{ + "rule_id": "d299379d-41de-4640-96b6-77aaa9adfa6f", + "risk_score": 50, + "description": "Suricata Category - Targeted Malicious Activity", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Targeted Malicious Activity", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Targeted Malicious Activity was Detected\" or rule.category: \"Targeted Malicious Activity was Detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json new file mode 100644 index 0000000000000..a1e400c71b8be --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json @@ -0,0 +1,17 @@ +{ + "rule_id": "ddf402cf-307d-4f46-a25d-dce3aee1ad13", + "risk_score": 50, + "description": "Suricata Category - TCP Connection Detected", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - TCP Connection Detected", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A TCP connection was detected\" or rule.category: \"A TCP connection was detected\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json new file mode 100644 index 0000000000000..28ae09a6cbe5c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json @@ -0,0 +1,17 @@ +{ + "rule_id": "827ea90c-00c2-45f7-b873-dd060297b2d2", + "risk_score": 50, + "description": "Suricata Category - Unknown Traffic", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Unknown Traffic", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Unknown Traffic\" or rule.category: \"Unknown Traffic\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json new file mode 100644 index 0000000000000..5eba26752f717 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json @@ -0,0 +1,17 @@ +{ + "rule_id": "85471d30-78c9-48f6-b2db-ab5b2547e450", + "risk_score": 50, + "description": "Suricata Category - Unsuccessful User Privilege Gain", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Unsuccessful User Privilege Gain", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Unsuccessful User Privilege Gain\" or rule.category: \"Unsuccessful User Privilege Gain\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json new file mode 100644 index 0000000000000..6cd7b2d87ac1a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json @@ -0,0 +1,17 @@ +{ + "rule_id": "e856918b-f26e-4893-84b9-3deb65046fb7", + "risk_score": 50, + "description": "Suricata Category - Web Application Attack", + "immutable": true, + "interval": "5m", + "name": "Suricata Category - Web Application Attack", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Web Application Attack\" or rule.category: \"Web Application Attack\")", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index caeec68c504e6..b0578174e1f65 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -31,6 +31,10 @@ export interface UpdateRulesRequest extends RequestFacade { payload: UpdateRuleAlertParamsRest; } +export interface BulkUpdateRulesRequest extends RequestFacade { + payload: UpdateRuleAlertParamsRest[]; +} + export type RuleAlertType = Alert & { id: string; params: RuleTypeParams; @@ -40,6 +44,18 @@ export interface RulesRequest extends RequestFacade { payload: RuleAlertParamsRest; } +export interface BulkRulesRequest extends RequestFacade { + payload: RuleAlertParamsRest[]; +} + +export type QueryRequest = Omit & { + query: { id: string | undefined; rule_id: string | undefined }; +}; + +export interface QueryBulkRequest extends RequestFacade { + payload: Array; +} + export interface FindRuleParams { alertsClient: AlertsClient; perPage?: number; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/delete_bulk.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/delete_bulk.sh new file mode 100755 index 0000000000000..8f540e14ecdf1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/delete_bulk.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +RULES=${1:-./rules/bulk/delete_by_rule_id.json} + +# Example: ./delete_rule_bulk.sh +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_delete \ + -d @${RULES} \ + | jq .; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule_bulk.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule_bulk.sh new file mode 100755 index 0000000000000..ad10ffc31aa94 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule_bulk.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +RULES=${1:-./rules/bulk/multiple_ruleid_queries.json} + +# Example: ./post_rule_bulk.sh +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_create \ + -d @${RULES} \ + | jq .; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/delete_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/delete_by_rule_id.json new file mode 100644 index 0000000000000..1bd9bdc703331 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/delete_by_rule_id.json @@ -0,0 +1,8 @@ +[ + { + "rule_id": "query-rule-id-1" + }, + { + "rule_id": "query-rule-id-2" + } +] diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/multiple_ruleid_queries.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/multiple_ruleid_queries.json new file mode 100644 index 0000000000000..bee2363aa5e0c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/multiple_ruleid_queries.json @@ -0,0 +1,24 @@ +[ + { + "name": "Query with a rule id Number 1", + "description": "Query with a rule_id that acts like an external id", + "rule_id": "query-rule-id-1", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" + }, + { + "name": "Query with a rule id Number 2", + "description": "Query with a rule_id that acts like an external id", + "rule_id": "query-rule-id-2", + "risk_score": 2, + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" + } +] diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/multiple_simplest_queries.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/multiple_simplest_queries.json new file mode 100644 index 0000000000000..9e5328ffabe2e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/multiple_simplest_queries.json @@ -0,0 +1,22 @@ +[ + { + "name": "Simplest Query Number 1", + "description": "Simplest query with the least amount of fields required", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" + }, + { + "name": "Simplest Query Number 2", + "description": "Simplest query with the least amount of fields required", + "risk_score": 2, + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" + } +] diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/update_names.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/update_names.json new file mode 100644 index 0000000000000..4dca8f88a7e67 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/bulk/update_names.json @@ -0,0 +1,10 @@ +[ + { + "name": "Rule id Number 1 with an updated name", + "rule_id": "query-rule-id-1" + }, + { + "name": "Rule id Number 2 with an updated name", + "rule_id": "query-rule-id-2" + } +] diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule_bulk.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule_bulk.sh new file mode 100755 index 0000000000000..c9cb0676821c5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule_bulk.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +RULES=${1:-./rules/bulk/update_names.json} + +# Example: ./update_rule_bulk.sh +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X PUT ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_bulk_update \ + -d @${RULES} \ + | jq .; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts index 43b5ce4b590a3..534215f5a1228 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts @@ -509,5 +509,140 @@ describe('get_filter', () => { }) ).rejects.toThrow('savedId parameter should be defined'); }); + + test('it works with references and does not add indexes', () => { + const esQuery = getQueryFilter( + '(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610182 or 2610183 or 2610184 or 2610185 or 2610186 or 2610187)', + 'kuery', + [], + ['my custom index'] + ); + expect(esQuery).toEqual({ + bool: { + must: [], + filter: [ + { + bool: { + filter: [ + { + bool: { + filter: [ + { + bool: { + should: [{ match: { 'event.module': 'suricata' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'event.kind': 'alert' } }], + minimum_should_match: 1, + }, + }, + ], + }, + }, + { + bool: { + should: [ + { + bool: { + should: [{ match: { 'suricata.eve.alert.signature_id': 2610182 } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [ + { match: { 'suricata.eve.alert.signature_id': 2610183 } }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [ + { match: { 'suricata.eve.alert.signature_id': 2610184 } }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [ + { + match: { + 'suricata.eve.alert.signature_id': 2610185, + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [ + { + match: { + 'suricata.eve.alert.signature_id': 2610186, + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + match: { + 'suricata.eve.alert.signature_id': 2610187, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + ], + should: [], + must_not: [], + }, + }); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts index a30182c537884..544250858a083 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -24,6 +24,9 @@ export interface SignalsStatusParams { export interface SignalQueryParams { query: object | undefined | null; aggs: object | undefined | null; + _source: string[] | undefined | null; + size: number | undefined | null; + track_total_hits: boolean | undefined | null; } export type SignalsStatusRestParams = Omit & { diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts index 38fec7fc05a3c..39f75e6ea36c3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts @@ -14,8 +14,10 @@ import { KibanaResponseFactory, RequestHandlerContext, PluginInitializerContext, -} from 'src/core/server'; + KibanaRequest, +} from '../../../../../../../src/core/server'; import { IndexPatternsFetcher } from '../../../../../../../src/plugins/data/server'; +import { AuthenticatedUser } from '../../../../../../plugins/security/common/model'; import { RequestFacade } from '../../types'; import { @@ -25,16 +27,19 @@ import { internalFrameworkRequest, WrappableRequest, } from './types'; +import { SiemPluginSecurity, PluginsSetup } from '../../plugin'; export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { public version: string; private isProductionMode: boolean; private router: IRouter; + private security: SiemPluginSecurity; - constructor(core: CoreSetup, env: PluginInitializerContext['env']) { + constructor(core: CoreSetup, plugins: PluginsSetup, env: PluginInitializerContext['env']) { this.version = env.packageInfo.version; this.isProductionMode = env.mode.prod; this.router = core.http.createRouter(); + this.security = plugins.security; } public async callWithRequest( @@ -76,10 +81,11 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { }, async (context, request, response) => { try { + const user = await this.getCurrentUserInfo(request); const gqlResponse = await runHttpQuery([request], { method: 'POST', options: (req: RequestFacade) => ({ - context: { req: wrapRequest(req, context) }, + context: { req: wrapRequest(req, context, user) }, schema, }), query: request.body, @@ -108,11 +114,12 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { }, async (context, request, response) => { try { + const user = await this.getCurrentUserInfo(request); const { query } = request; const gqlResponse = await runHttpQuery([request], { method: 'GET', options: (req: RequestFacade) => ({ - context: { req: wrapRequest(req, context) }, + context: { req: wrapRequest(req, context, user) }, schema, }), query, @@ -159,6 +166,15 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { } } + private async getCurrentUserInfo(request: KibanaRequest): Promise { + try { + const user = await this.security.authc.getCurrentUser(request); + return user; + } catch { + return null; + } + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private handleError(error: any, response: KibanaResponseFactory) { if (error.name !== 'HttpQueryError') { @@ -194,7 +210,8 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { export function wrapRequest( req: InternalRequest, - context: RequestHandlerContext + context: RequestHandlerContext, + user: AuthenticatedUser | null ): FrameworkRequest { const { auth, params, payload, query } = req; @@ -205,5 +222,6 @@ export function wrapRequest( params, payload, query, + user, }; } diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts index dd31ed9fcaf5f..27254284b577d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts @@ -8,7 +8,8 @@ import { IndicesGetMappingParams } from 'elasticsearch'; import { GraphQLSchema } from 'graphql'; import { RequestAuth } from 'hapi'; -import { RequestHandlerContext } from 'src/core/server'; +import { RequestHandlerContext } from '../../../../../../../src/core/server'; +import { AuthenticatedUser } from '../../../../../../plugins/security/common/model'; import { ESQuery } from '../../../common/typed_json'; import { PaginationInput, @@ -51,6 +52,7 @@ export interface FrameworkRequest { if (noteId == null) { savedNote.created = new Date().valueOf(); - savedNote.createdBy = getOr(null, 'credentials.username', userInfo); + savedNote.createdBy = userInfo?.username ?? UNAUTHENTICATED_USER; savedNote.updated = new Date().valueOf(); - savedNote.updatedBy = getOr(null, 'credentials.username', userInfo); + savedNote.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } else if (noteId != null) { savedNote.updated = new Date().valueOf(); - savedNote.updatedBy = getOr(null, 'credentials.username', userInfo); + savedNote.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } return savedNote; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts index 177eb05d8539d..afa3595a09e1c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts @@ -5,15 +5,15 @@ */ import { failure } from 'io-ts/lib/PathReporter'; -import { RequestAuth } from 'hapi'; import { getOr } from 'lodash/fp'; - -import { SavedObjectsFindOptions } from 'src/core/server'; - import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { FrameworkRequest, internalFrameworkRequest } from '../framework'; + +import { SavedObjectsFindOptions } from '../../../../../../../src/core/server'; +import { AuthenticatedUser } from '../../../../../../plugins/security/common/model'; +import { UNAUTHENTICATED_USER } from '../../../common/constants'; +import { FrameworkRequest } from '../framework'; import { PinnedEventSavedObject, PinnedEventSavedObjectRuntimeType, @@ -103,7 +103,7 @@ export class PinnedEvent { const timelineResult = convertSavedObjectToSavedTimeline( await savedObjectsClient.create( timelineSavedObjectType, - pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) + pickSavedTimeline(null, {}, request.user || null) ) ); timelineId = timelineResult.savedObjectId; // eslint-disable-line no-param-reassign @@ -125,11 +125,7 @@ export class PinnedEvent { return convertSavedObjectToSavedPinnedEvent( await savedObjectsClient.create( pinnedEventSavedObjectType, - pickSavedPinnedEvent( - pinnedEventId, - savedPinnedEvent, - request[internalFrameworkRequest].auth || null - ) + pickSavedPinnedEvent(pinnedEventId, savedPinnedEvent, request.user || null) ), timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined ); @@ -211,17 +207,18 @@ const convertSavedObjectToSavedPinnedEvent = ( const pickSavedPinnedEvent = ( pinnedEventId: string | null, savedPinnedEvent: SavedPinnedEvent, - userInfo: RequestAuth + userInfo: AuthenticatedUser | null // eslint-disable-next-line @typescript-eslint/no-explicit-any ): any => { + const dateNow = new Date().valueOf(); if (pinnedEventId == null) { - savedPinnedEvent.created = new Date().valueOf(); - savedPinnedEvent.createdBy = getOr(null, 'credentials.username', userInfo); - savedPinnedEvent.updated = new Date().valueOf(); - savedPinnedEvent.updatedBy = getOr(null, 'credentials.username', userInfo); + savedPinnedEvent.created = dateNow; + savedPinnedEvent.createdBy = userInfo?.username ?? UNAUTHENTICATED_USER; + savedPinnedEvent.updated = dateNow; + savedPinnedEvent.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } else if (pinnedEventId != null) { - savedPinnedEvent.updated = new Date().valueOf(); - savedPinnedEvent.updatedBy = getOr(null, 'credentials.username', userInfo); + savedPinnedEvent.updated = dateNow; + savedPinnedEvent.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } return savedPinnedEvent; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/pick_saved_timeline.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/pick_saved_timeline.ts index 9e4916e201d44..5b60086ae81b6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/pick_saved_timeline.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/pick_saved_timeline.ts @@ -4,24 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestAuth } from 'hapi'; -import { getOr } from 'lodash/fp'; +import { AuthenticatedUser } from '../../../../../../plugins/security/common/model'; +import { UNAUTHENTICATED_USER } from '../../../common/constants'; import { SavedTimeline } from './types'; export const pickSavedTimeline = ( timelineId: string | null, savedTimeline: SavedTimeline, - userInfo: RequestAuth + userInfo: AuthenticatedUser | null // eslint-disable-next-line @typescript-eslint/no-explicit-any ): any => { + const dateNow = new Date().valueOf(); if (timelineId == null) { - savedTimeline.created = new Date().valueOf(); - savedTimeline.createdBy = getOr(null, 'credentials.username', userInfo); - savedTimeline.updated = new Date().valueOf(); - savedTimeline.updatedBy = getOr(null, 'credentials.username', userInfo); + savedTimeline.created = dateNow; + savedTimeline.createdBy = userInfo?.username ?? UNAUTHENTICATED_USER; + savedTimeline.updated = dateNow; + savedTimeline.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } else if (timelineId != null) { - savedTimeline.updated = new Date().valueOf(); - savedTimeline.updatedBy = getOr(null, 'credentials.username', userInfo); + savedTimeline.updated = dateNow; + savedTimeline.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } return savedTimeline; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts index 04c1584f1204d..4b78a7bd3d06d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts @@ -6,8 +6,8 @@ import { getOr } from 'lodash/fp'; -import { SavedObjectsFindOptions } from 'src/core/server'; - +import { SavedObjectsFindOptions } from '../../../../../../../src/core/server'; +import { UNAUTHENTICATED_USER } from '../../../common/constants'; import { ResponseTimeline, PageInfoTimeline, @@ -15,16 +15,15 @@ import { ResponseFavoriteTimeline, TimelineResult, } from '../../graphql/types'; -import { FrameworkRequest, internalFrameworkRequest } from '../framework'; +import { FrameworkRequest } from '../framework'; +import { Note } from '../note/saved_object'; import { NoteSavedObject } from '../note/types'; import { PinnedEventSavedObject } from '../pinned_event/types'; - -import { SavedTimeline, TimelineSavedObject } from './types'; -import { Note } from '../note/saved_object'; import { PinnedEvent } from '../pinned_event/saved_object'; -import { timelineSavedObjectType } from './saved_object_mappings'; -import { pickSavedTimeline } from './pick_saved_timeline'; import { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; +import { pickSavedTimeline } from './pick_saved_timeline'; +import { timelineSavedObjectType } from './saved_object_mappings'; +import { SavedTimeline, TimelineSavedObject } from './types'; interface ResponseTimelines { timeline: TimelineSavedObject[]; @@ -68,8 +67,8 @@ export class Timeline { request: FrameworkRequest, timelineId: string | null ): Promise { - const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); - const fullName = getOr(null, 'credentials.fullname', request[internalFrameworkRequest].auth); + const userName = request.user?.username ?? UNAUTHENTICATED_USER; + const fullName = request.user?.full_name ?? ''; try { let timeline: SavedTimeline = {}; if (timelineId != null) { @@ -149,11 +148,7 @@ export class Timeline { timeline: convertSavedObjectToSavedTimeline( await savedObjectsClient.create( timelineSavedObjectType, - pickSavedTimeline( - timelineId, - timeline, - request[internalFrameworkRequest].auth || null - ) + pickSavedTimeline(timelineId, timeline, request.user) ) ), }; @@ -162,7 +157,7 @@ export class Timeline { await savedObjectsClient.update( timelineSavedObjectType, timelineId, - pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null), + pickSavedTimeline(timelineId, timeline, request.user), { version: version || undefined, } @@ -217,7 +212,7 @@ export class Timeline { } private async getSavedTimeline(request: FrameworkRequest, timelineId: string) { - const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); + const userName = request.user?.username ?? UNAUTHENTICATED_USER; const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId); @@ -234,7 +229,7 @@ export class Timeline { } private async getAllSavedTimeline(request: FrameworkRequest, options: SavedObjectsFindOptions) { - const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); + const userName = request.user?.username ?? UNAUTHENTICATED_USER; const savedObjectsClient = request.context.core.savedObjects.client; if (options.searchFields != null && options.searchFields.includes('favorite.keySearch')) { options.search = `${options.search != null ? options.search : ''} ${ diff --git a/x-pack/legacy/plugins/siem/server/plugin.ts b/x-pack/legacy/plugins/siem/server/plugin.ts index 25fa3bd6cde4c..8a47aa2a27082 100644 --- a/x-pack/legacy/plugins/siem/server/plugin.ts +++ b/x-pack/legacy/plugins/siem/server/plugin.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; +import { PluginSetupContract as SecurityPlugin } from '../../../../plugins/security/server'; import { PluginSetupContract as FeaturesSetupContract } from '../../../../plugins/features/server'; import { initServer } from './init_server'; import { compose } from './lib/compose/kibana'; @@ -15,8 +16,11 @@ import { timelineSavedObjectType, } from './saved_objects'; +export type SiemPluginSecurity = Pick; + export interface PluginsSetup { features: FeaturesSetupContract; + security: SiemPluginSecurity; } export class Plugin { @@ -33,7 +37,6 @@ export class Plugin { public setup(core: CoreSetup, plugins: PluginsSetup) { this.logger.debug('Shim plugin setup'); - plugins.features.registerFeature({ id: this.name, name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', { @@ -75,7 +78,7 @@ export class Plugin { }, }); - const libs = compose(core, this.context.env); + const libs = compose(core, plugins, this.context.env); initServer(libs); } } diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx index 49f5233db44e2..7134049c4cf8f 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { AdvancedSettingsSubtitle } from './advanced_settings_subtitle'; import { EuiCallOut } from '@elastic/eui'; +import { act } from '@testing-library/react'; describe('AdvancedSettingsSubtitle', () => { it('renders as expected', async () => { @@ -21,10 +22,10 @@ describe('AdvancedSettingsSubtitle', () => { ); // Wait for active space to resolve before requesting the component to update - await Promise.resolve(); - await Promise.resolve(); - - wrapper.update(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); expect(wrapper.find(EuiCallOut)).toHaveLength(1); }); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx index 7f2b6eee62c45..bf792ca2cdacf 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { AdvancedSettingsTitle } from './advanced_settings_title'; import { SpaceAvatar } from '../../../../components'; +import { act } from '@testing-library/react'; describe('AdvancedSettingsTitle', () => { it('renders without crashing', async () => { @@ -20,9 +21,11 @@ describe('AdvancedSettingsTitle', () => { Promise.resolve(space)} /> ); - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find(SpaceAvatar)).toHaveLength(1); }); }); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx index 590c0edc0073b..b3fd345b1d2b4 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import Boom from 'boom'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { mockManagementPlugin } from '../../../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; @@ -93,9 +93,10 @@ const setup = async (opts: SetupOpts = {}) => { if (!opts.returnBeforeSpacesLoad) { // Wait for spaces manager to complete and flyout to rerender - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); } return { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToCopy }; @@ -113,8 +114,10 @@ describe('CopyToSpaceFlyout', () => { expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(0); expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1); - await Promise.resolve(); - wrapper.update(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); expect(wrapper.find(CopyToSpaceForm)).toHaveLength(1); expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); @@ -160,14 +163,13 @@ describe('CopyToSpaceFlyout', () => { }); const startButton = findTestSubject(wrapper, 'cts-initiate-button'); - act(() => { + + await act(async () => { startButton.simulate('click'); + await nextTick(); + wrapper.update(); }); - await Promise.resolve(); - - wrapper.update(); - expect(mockSpacesManager.copySavedObjects).toHaveBeenCalled(); expect(mockToastNotifications.addError).toHaveBeenCalled(); }); @@ -214,13 +216,13 @@ describe('CopyToSpaceFlyout', () => { }); const startButton = findTestSubject(wrapper, 'cts-initiate-button'); - act(() => { + + await act(async () => { startButton.simulate('click'); + await nextTick(); + wrapper.update(); }); - await Promise.resolve(); - wrapper.update(); - expect(mockSpacesManager.copySavedObjects).toHaveBeenCalled(); expect(mockToastNotifications.addError).not.toHaveBeenCalled(); @@ -231,13 +233,13 @@ describe('CopyToSpaceFlyout', () => { overwriteButton.simulate('click'); const finishButton = findTestSubject(wrapper, 'cts-finish-button'); - act(() => { + + await act(async () => { finishButton.simulate('click'); + await nextTick(); + wrapper.update(); }); - await Promise.resolve(); - wrapper.update(); - expect(mockSpacesManager.resolveCopySavedObjectsErrors).toHaveBeenCalled(); expect(mockToastNotifications.addError).toHaveBeenCalled(); }); @@ -275,14 +277,13 @@ describe('CopyToSpaceFlyout', () => { }); const startButton = findTestSubject(wrapper, 'cts-initiate-button'); - act(() => { + + await act(async () => { startButton.simulate('click'); + await nextTick(); + wrapper.update(); }); - await Promise.resolve(); - - wrapper.update(); - expect(mockSpacesManager.copySavedObjects).toHaveBeenCalledWith( [{ type: savedObjectToCopy.type, id: savedObjectToCopy.id }], ['space-1', 'space-2'], @@ -350,14 +351,13 @@ describe('CopyToSpaceFlyout', () => { }); const startButton = findTestSubject(wrapper, 'cts-initiate-button'); - act(() => { + + await act(async () => { startButton.simulate('click'); + await nextTick(); + wrapper.update(); }); - await Promise.resolve(); - - wrapper.update(); - expect(wrapper.find(CopyToSpaceForm)).toHaveLength(0); expect(wrapper.find(ProcessingCopyToSpace)).toHaveLength(1); @@ -368,13 +368,13 @@ describe('CopyToSpaceFlyout', () => { overwriteButton.simulate('click'); const finishButton = findTestSubject(wrapper, 'cts-finish-button'); - act(() => { + + await act(async () => { finishButton.simulate('click'); + await nextTick(); + wrapper.update(); }); - await Promise.resolve(); - wrapper.update(); - expect(mockSpacesManager.resolveCopySavedObjectsErrors).toHaveBeenCalledWith( [{ type: savedObjectToCopy.type, id: savedObjectToCopy.id }], { @@ -420,14 +420,13 @@ describe('CopyToSpaceFlyout', () => { }); const startButton = findTestSubject(wrapper, 'cts-initiate-button'); - act(() => { + + await act(async () => { startButton.simulate('click'); + await nextTick(); + wrapper.update(); }); - await Promise.resolve(); - - wrapper.update(); - expect(wrapper.find(CopyToSpaceForm)).toHaveLength(0); expect(wrapper.find(ProcessingCopyToSpace)).toHaveLength(1); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap index 5e91c1bd0e36c..5879ff621d64a 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap @@ -190,12 +190,14 @@ exports[`EnabledFeatures renders as expected 1`] = ` Array [ Object { "app": Array [], + "icon": "spacesApp", "id": "feature-1", "name": "Feature 1", "privileges": Object {}, }, Object { "app": Array [], + "icon": "spacesApp", "id": "feature-2", "name": "Feature 2", "privileges": Object {}, diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx index 8f82e6d413350..f8bd4b889394a 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx @@ -16,12 +16,14 @@ const features: Feature[] = [ { id: 'feature-1', name: 'Feature 1', + icon: 'spacesApp', app: [], privileges: {}, }, { id: 'feature-2', name: 'Feature 2', + icon: 'spacesApp', app: [], privileges: {}, }, diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx index 1597e4684222c..c69a885ae0587 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ jest.mock('ui/kfetch', () => ({ - kfetch: () => Promise.resolve([{ id: 'feature-1', name: 'feature 1' }]), + kfetch: () => Promise.resolve([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]), })); import '../../../__mocks__/xpack_info'; import { EuiButton, EuiLink, EuiSwitch } from '@elastic/eui'; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx index 98ce64715f325..b37458aace2a2 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx @@ -45,7 +45,7 @@ export class NavControlPopover extends Component { }; } - public componentWillMount() { + public componentDidMount() { this.activeSpace$ = this.props.spacesManager.onActiveSpaceChange$.subscribe({ next: activeSpace => { this.setState({ diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx index 3a3bd33ff6d70..5b7702a0193ec 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx @@ -6,7 +6,7 @@ import React, { createContext, useContext, FC } from 'react'; -import { SavedSearch } from '../../../../../../../../src/legacy/core_plugins/kibana/public/discover/types'; +import { SavedSearch } from '../../../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/types'; import { IndexPattern, IndexPatternsContract, diff --git a/x-pack/legacy/plugins/transform/public/plugin.ts b/x-pack/legacy/plugins/transform/public/plugin.ts index 08a3a06fc24fc..5d1c39add4ff6 100644 --- a/x-pack/legacy/plugins/transform/public/plugin.ts +++ b/x-pack/legacy/plugins/transform/public/plugin.ts @@ -7,7 +7,7 @@ import { unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; -import { SavedSearchLoader } from '../../../../../src/legacy/core_plugins/kibana/public/discover/types'; +import { SavedSearchLoader } from '../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/types'; import { PLUGIN } from '../common/constants'; import { CLIENT_BASE_PATH } from './app/constants'; diff --git a/x-pack/legacy/plugins/transform/public/shim.ts b/x-pack/legacy/plugins/transform/public/shim.ts index 5d7c309ade211..f8edc752c9a21 100644 --- a/x-pack/legacy/plugins/transform/public/shim.ts +++ b/x-pack/legacy/plugins/transform/public/shim.ts @@ -12,7 +12,7 @@ import { docTitle } from 'ui/doc_title/doc_title'; // @ts-ignore: allow traversal to fail on x-pack build import { createUiStatsReporter } from '../../../../../src/legacy/core_plugins/ui_metric/public'; -import { SavedSearchLoader } from '../../../../../src/legacy/core_plugins/kibana/public/discover/types'; +import { SavedSearchLoader } from '../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/types'; export type npCore = typeof npStart.core; diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts b/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts index 8c340f5525a4c..8c47b318da9bd 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts @@ -8,7 +8,7 @@ import { ILicense } from '../../../../../../../plugins/licensing/server'; import { licenseCheck } from '../license'; describe('license check', () => { - let mockLicense: Pick; + let mockLicense: Pick; it('throws for null license', () => { expect(licenseCheck(undefined)).toMatchSnapshot(); @@ -16,7 +16,7 @@ describe('license check', () => { it('throws for unsupported license type', () => { mockLicense = { - isOneOf: jest.fn().mockReturnValue(false), + hasAtLeast: jest.fn().mockReturnValue(false), isActive: false, }; expect(licenseCheck(mockLicense)).toMatchSnapshot(); @@ -24,7 +24,7 @@ describe('license check', () => { it('throws for inactive license', () => { mockLicense = { - isOneOf: jest.fn().mockReturnValue(true), + hasAtLeast: jest.fn().mockReturnValue(true), isActive: false, }; expect(licenseCheck(mockLicense)).toMatchSnapshot(); @@ -32,7 +32,7 @@ describe('license check', () => { it('returns result for a valid license', () => { mockLicense = { - isOneOf: jest.fn().mockReturnValue(true), + hasAtLeast: jest.fn().mockReturnValue(true), isActive: true, }; expect(licenseCheck(mockLicense)).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts b/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts index 24ce0205414fa..b8b5722d79877 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts @@ -11,7 +11,7 @@ export interface UMLicenseStatusResponse { message: string; } export type UMLicenseCheck = ( - license?: Pick + license?: Pick ) => UMLicenseStatusResponse; export const licenseCheck: UMLicenseCheck = license => { @@ -21,7 +21,7 @@ export const licenseCheck: UMLicenseCheck = license => { statusCode: 400, }; } - if (!license.isOneOf(['basic', 'standard', 'gold', 'platinum', 'enterprise', 'trial'])) { + if (!license.hasAtLeast('basic')) { return { message: 'License not supported', statusCode: 401, diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js index 2edfbbc27fc45..2e0d608e522d7 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js @@ -312,6 +312,54 @@ describe('XPackInfo', () => { }); }); + it('onLicenseInfoChange() allows to subscribe to license update', async () => { + const license$ = new BehaviorSubject(createLicense()); + + const xPackInfo = new XPackInfo(mockServer, { + licensing: { + license$, + refresh: () => null, + }, + }); + + const watcherFeature = xPackInfo.feature('watcher'); + watcherFeature.registerLicenseCheckResultsGenerator(info => ({ + type: info.license.getType(), + })); + + const statuses = []; + xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults())); + + license$.next(createLicense({ type: 'basic' })); + expect(statuses).to.eql([{ type: 'basic' }]); + + license$.next(createLicense({ type: 'trial' })); + expect(statuses).to.eql([{ type: 'basic' }, { type: 'trial' }]); + }); + + it('refreshNow() leads to onLicenseInfoChange()', async () => { + const license$ = new BehaviorSubject(createLicense()); + + const xPackInfo = new XPackInfo(mockServer, { + licensing: { + license$, + refresh: () => license$.next({ type: 'basic' }), + }, + }); + + const watcherFeature = xPackInfo.feature('watcher'); + + watcherFeature.registerLicenseCheckResultsGenerator(info => ({ + type: info.license.getType(), + })); + + const statuses = []; + xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults())); + + await xPackInfo.refreshNow(); + expect(statuses).to.eql([{ type: 'basic' }]); + }); + it('getSignature() returns correct signature.', async () => { const license$ = new BehaviorSubject(createLicense()); const xPackInfo = new XPackInfo(mockServer, { diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts index fbb8929154c36..9d5a8e64645ec 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts +++ b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts @@ -101,6 +101,8 @@ export class XPackInfo { error: license.error, }; } + + this._licenseInfoChangedListeners.forEach(fn => fn()); }); this._license = new XPackInfoLicense(() => this._cache.license); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js index c369f90cec1d1..ed2043e00b9a7 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { licensingMock } from '../../../../../plugins/licensing/server/licensing.mock'; +import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; import { XPackInfoLicense } from './xpack_info_license'; function getXPackInfoLicense(getRawLicense) { diff --git a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts index fb7f4d6ee2600..4717d8762ffe2 100644 --- a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts +++ b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts @@ -4,14 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ObjectType } from '@kbn/config-schema'; import { RequestHandler } from 'src/core/server'; -export const catchErrorHandler: < - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType ->( +export const catchErrorHandler: ( fn: RequestHandler ) => RequestHandler = fn => { return async (context, request, response) => { diff --git a/x-pack/plugins/endpoint/kibana.json b/x-pack/plugins/endpoint/kibana.json index a7fd20b93f62d..f7a4acd629324 100644 --- a/x-pack/plugins/endpoint/kibana.json +++ b/x-pack/plugins/endpoint/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "endpoint"], - "requiredPlugins": ["embeddable"], + "requiredPlugins": ["features", "embeddable"], "server": true, "ui": true } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx new file mode 100644 index 0000000000000..1af27f039aca7 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import { CoreStart, AppMountParameters } from 'kibana/public'; +import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; + +/** + * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. + */ +export function renderApp(coreStart: CoreStart, { element }: AppMountParameters) { + coreStart.http.get('/api/endpoint/hello-world'); + + ReactDOM.render(, element); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +} + +const AppRoot = React.memo(() => ( + +

+ +

+
+)); diff --git a/x-pack/plugins/endpoint/public/plugin.ts b/x-pack/plugins/endpoint/public/plugin.ts index 21bf1b3cdea12..02514cc974af0 100644 --- a/x-pack/plugins/endpoint/public/plugin.ts +++ b/x-pack/plugins/endpoint/public/plugin.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Plugin, CoreSetup } from 'kibana/public'; +import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public'; import { IEmbeddableSetup } from 'src/plugins/embeddable/public'; +import { i18n } from '@kbn/i18n'; import { ResolverEmbeddableFactory } from './embeddables/resolver'; export type EndpointPluginStart = void; @@ -24,8 +25,20 @@ export class EndpointPlugin EndpointPluginSetupDependencies, EndpointPluginStartDependencies > { - public setup(_core: CoreSetup, plugins: EndpointPluginSetupDependencies) { + public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) { const resolverEmbeddableFactory = new ResolverEmbeddableFactory(); + core.application.register({ + id: 'endpoint', + title: i18n.translate('xpack.endpoint.pluginTitle', { + defaultMessage: 'Endpoint', + }), + async mount(params: AppMountParameters) { + const [coreStart] = await core.getStartServices(); + const { renderApp } = await import('./applications/endpoint'); + return renderApp(coreStart, params); + }, + }); + plugins.embeddable.registerEmbeddableFactory( resolverEmbeddableFactory.type, resolverEmbeddableFactory diff --git a/x-pack/plugins/endpoint/server/index.ts b/x-pack/plugins/endpoint/server/index.ts index f10bc7ee51b2c..eec836141ea5e 100644 --- a/x-pack/plugins/endpoint/server/index.ts +++ b/x-pack/plugins/endpoint/server/index.ts @@ -19,8 +19,8 @@ export const config = { }; export const plugin: PluginInitializer< - EndpointPluginStart, EndpointPluginSetup, - EndpointPluginStartDependencies, - EndpointPluginSetupDependencies + EndpointPluginStart, + EndpointPluginSetupDependencies, + EndpointPluginStartDependencies > = () => new EndpointPlugin(); diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts index 400b906c5230e..b41dfee1f78fd 100644 --- a/x-pack/plugins/endpoint/server/plugin.ts +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -3,28 +3,56 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { Plugin, CoreSetup } from 'kibana/server'; import { addRoutes } from './routes'; +import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server'; export type EndpointPluginStart = void; export type EndpointPluginSetup = void; -export interface EndpointPluginSetupDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface - export interface EndpointPluginStartDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface EndpointPluginSetupDependencies { + features: FeaturesPluginSetupContract; +} + export class EndpointPlugin implements Plugin< - EndpointPluginStart, EndpointPluginSetup, - EndpointPluginStartDependencies, - EndpointPluginSetupDependencies + EndpointPluginStart, + EndpointPluginSetupDependencies, + EndpointPluginStartDependencies > { - public setup(core: CoreSetup) { + public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) { + plugins.features.registerFeature({ + id: 'endpoint', + name: 'Endpoint', + icon: 'bug', + navLinkId: 'endpoint', + app: ['endpoint', 'kibana'], + privileges: { + all: { + api: ['resolver'], + savedObject: { + all: [], + read: [], + }, + ui: ['save'], + }, + read: { + api: [], + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + }); const router = core.http.createRouter(); addRoutes(router); } public start() {} + public stop() {} } diff --git a/x-pack/plugins/endpoint/server/routes/index.ts b/x-pack/plugins/endpoint/server/routes/index.ts index 517ee2a853660..8eab6cd384765 100644 --- a/x-pack/plugins/endpoint/server/routes/index.ts +++ b/x-pack/plugins/endpoint/server/routes/index.ts @@ -11,6 +11,9 @@ export function addRoutes(router: IRouter) { { path: '/api/endpoint/hello-world', validate: false, + options: { + tags: ['access:resolver'], + }, }, async function greetingIndex(_context, _request, response) { return response.ok({ diff --git a/x-pack/plugins/licensing/README.md b/x-pack/plugins/licensing/README.md index 339abb77ced88..7a155dfd405c5 100644 --- a/x-pack/plugins/licensing/README.md +++ b/x-pack/plugins/licensing/README.md @@ -1,5 +1,8 @@ # Licensing plugin +- [API](#api) +- [Migration example](#migration-example) +- [The list of breaking changes](#the-list-of-breaking-changes) Retrieves license data from Elasticsearch and becomes a source of license data for all Kibana plugins on server-side and client-side. ## API: @@ -13,7 +16,7 @@ Retrieves license data from Elasticsearch and becomes a source of license data f - `license$: Observable` Provides a steam of license data [ILicense](./common/types.ts). Plugin emits new value whenever it detects changes in license info. If the plugin cannot retrieve a license from **Kibana**, it will emit `an empty license` object. - `refresh: () => Promise` allows a plugin to enforce license retrieval. -## Migration path +## Migration example The new platform licensing plugin became stateless now. It means that instead of storing all your data from `checkLicense` within the plugin, you should react on license data change on both the client and server sides. ### Before @@ -56,7 +59,7 @@ chrome.navLinks.update('myPlugin', { "requiredPlugins": ["licensing"], // my_plugin/server/plugin.ts -import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing' +import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing/server' interface SetupDeps { licensing: LicensingPluginSetup; @@ -77,7 +80,8 @@ class MyPlugin { } } -// my_plugin/client/plugin.ts +// my_plugin/public/plugin.ts +import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing/public' class MyPlugin { setup(core: CoreSetup, deps: SetupDeps) { deps.licensing.license$.subscribe(license => { @@ -92,3 +96,42 @@ class MyPlugin { } } ``` +## The list of breaking changes + +#### state +**LP**: The plugin allows consumers to calculate state on `license change` event and store this +The signature calculation is based on this state + license content +**NP**: We decided that license service doesn't keep plugins state https://github.com/elastic/kibana/pull/49345#issuecomment-553451472. Plugins have to react on license change and calculate license state on every license change. If another plugin needs that information, it should be exposed via a plugin contract. +This change makes NP & LP licensing service not compatible. We have to keep both until all plugins migrate to the new platform service. The legacy plugin consumes license data from the LP plugin. + +#### Network request failures +**LP**: The licensing plugin didn’t emit a license in case of network errors. +**NP**: Emits the license even if the request failed. + +#### clusterSource +**LP**: Allows specifying cluster source to perform polling. +**NP**: The plugin always uses a `data` client. Provides `createLicensePoller` on the server-side to create a license poller with custom ES cluster. + +#### Initial value on the client +**LP**: Passed on the page via inlined `xpackInitialInfo` +**NP**: Should be fetched + +#### Config +**LP**: `xpack.xpack_main.xpack_api_polling_frequency_millis` +**NP**: `xpack.licensing.api_polling_frequency` + +#### License +**NP**: `mode` field is provided, but deprecated. + +#### sessionStorage +**LP**: License and signature were stored under different keys in session storage +**NP**: License and signature were stored under one key `xpack.licensing` + +#### isOneOf +`isOneOf` removed, use `check` or `hasAtLeast` instead + +#### Endpoint +`/api/xpack/v1/info` API endpoint is going to be removed. switch to `/api/licensing/info` instead + +#### Fetch error +`getUnavailableReason` doesn't return `Error` object anymore, but `string` diff --git a/x-pack/plugins/licensing/common/license.test.ts b/x-pack/plugins/licensing/common/license.test.ts index 8035a92952e10..622572a6a95ba 100644 --- a/x-pack/plugins/licensing/common/license.test.ts +++ b/x-pack/plugins/licensing/common/license.test.ts @@ -9,10 +9,10 @@ import { LICENSE_CHECK_STATE } from './types'; import { licenseMock } from './licensing.mock'; describe('License', () => { - const basicLicense = licenseMock.create(); - const basicExpiredLicense = licenseMock.create({ license: { status: 'expired' } }); - const goldLicense = licenseMock.create({ license: { type: 'gold' } }); - const enterpriseLicense = licenseMock.create({ license: { type: 'enterprise' } }); + const basicLicense = licenseMock.createLicense(); + const basicExpiredLicense = licenseMock.createLicense({ license: { status: 'expired' } }); + const goldLicense = licenseMock.createLicense({ license: { type: 'gold' } }); + const enterpriseLicense = licenseMock.createLicense({ license: { type: 'enterprise' } }); const errorMessage = 'unavailable'; const errorLicense = new License({ error: errorMessage, signature: '' }); @@ -50,34 +50,23 @@ describe('License', () => { expect(unavailableLicense.isActive).toBe(false); }); - it('isBasic', () => { - expect(basicLicense.isBasic).toBe(true); - expect(goldLicense.isBasic).toBe(false); - expect(enterpriseLicense.isBasic).toBe(false); - expect(errorLicense.isBasic).toBe(false); - expect(unavailableLicense.isBasic).toBe(false); - }); + it('hasAtLeast', () => { + expect(basicLicense.hasAtLeast('platinum')).toBe(false); + expect(basicLicense.hasAtLeast('gold')).toBe(false); + expect(basicLicense.hasAtLeast('basic')).toBe(true); - it('isNotBasic', () => { - expect(basicLicense.isNotBasic).toBe(false); - expect(goldLicense.isNotBasic).toBe(true); - expect(enterpriseLicense.isNotBasic).toBe(true); - expect(errorLicense.isNotBasic).toBe(false); - expect(unavailableLicense.isNotBasic).toBe(false); - }); + expect(errorLicense.hasAtLeast('basic')).toBe(false); - it('isOneOf', () => { - expect(basicLicense.isOneOf('platinum')).toBe(false); - expect(basicLicense.isOneOf(['platinum'])).toBe(false); - expect(basicLicense.isOneOf(['gold', 'platinum'])).toBe(false); - expect(basicLicense.isOneOf(['platinum', 'gold'])).toBe(false); - expect(basicLicense.isOneOf(['basic', 'gold'])).toBe(true); - expect(basicLicense.isOneOf(['basic'])).toBe(true); - expect(basicLicense.isOneOf('basic')).toBe(true); + expect(unavailableLicense.hasAtLeast('basic')).toBe(false); - expect(errorLicense.isOneOf(['basic', 'gold', 'platinum'])).toBe(false); + expect(goldLicense.hasAtLeast('basic')).toBe(true); + expect(goldLicense.hasAtLeast('gold')).toBe(true); + expect(goldLicense.hasAtLeast('platinum')).toBe(false); - expect(unavailableLicense.isOneOf(['basic', 'gold', 'platinum'])).toBe(false); + expect(enterpriseLicense.hasAtLeast('basic')).toBe(true); + expect(enterpriseLicense.hasAtLeast('platinum')).toBe(true); + expect(enterpriseLicense.hasAtLeast('enterprise')).toBe(true); + expect(enterpriseLicense.hasAtLeast('trial')).toBe(false); }); it('getUnavailableReason', () => { @@ -115,9 +104,13 @@ describe('License', () => { }); it('throws in case of unknown license type', () => { - expect( - () => basicLicense.check('ccr', 'any' as any).state - ).toThrowErrorMatchingInlineSnapshot(`"\\"any\\" is not a valid license type"`); + expect(() => basicLicense.check('ccr', 'any' as any)).toThrowErrorMatchingInlineSnapshot( + `"\\"any\\" is not a valid license type"` + ); + + expect(() => basicLicense.hasAtLeast('any' as any)).toThrowErrorMatchingInlineSnapshot( + `"\\"any\\" is not a valid license type"` + ); }); }); }); diff --git a/x-pack/plugins/licensing/common/license.ts b/x-pack/plugins/licensing/common/license.ts index 8423fed1d6a4e..41f3c73db9c06 100644 --- a/x-pack/plugins/licensing/common/license.ts +++ b/x-pack/plugins/licensing/common/license.ts @@ -26,8 +26,6 @@ export class License implements ILicense { public readonly error?: string; public readonly isActive: boolean; public readonly isAvailable: boolean; - public readonly isBasic: boolean; - public readonly isNotBasic: boolean; public readonly uid?: string; public readonly status?: LicenseStatus; @@ -70,8 +68,6 @@ export class License implements ILicense { } this.isActive = this.status === 'active'; - this.isBasic = this.isActive && this.type === 'basic'; - this.isNotBasic = this.isActive && this.type !== 'basic'; } toJSON() { @@ -89,23 +85,20 @@ export class License implements ILicense { } } - isOneOf(candidateLicenses: LicenseType | LicenseType[]) { - if (!this.type) { + hasAtLeast(minimumLicenseRequired: LicenseType) { + const type = this.type; + if (!type) { return false; } - if (!Array.isArray(candidateLicenses)) { - candidateLicenses = [candidateLicenses]; + if (!(minimumLicenseRequired in LICENSE_TYPE)) { + throw new Error(`"${minimumLicenseRequired}" is not a valid license type`); } - return candidateLicenses.includes(this.type); + return LICENSE_TYPE[minimumLicenseRequired] <= LICENSE_TYPE[type]; } check(pluginName: string, minimumLicenseRequired: LicenseType) { - if (!(minimumLicenseRequired in LICENSE_TYPE)) { - throw new Error(`"${minimumLicenseRequired}" is not a valid license type`); - } - if (!this.isAvailable) { return { state: LICENSE_CHECK_STATE.Unavailable, @@ -117,26 +110,24 @@ export class License implements ILicense { }; } - const type = this.type!; - if (!this.isActive) { return { state: LICENSE_CHECK_STATE.Expired, message: i18n.translate('xpack.licensing.check.errorExpiredMessage', { defaultMessage: 'You cannot use {pluginName} because your {licenseType} license has expired.', - values: { licenseType: type, pluginName }, + values: { licenseType: this.type!, pluginName }, }), }; } - if (LICENSE_TYPE[type] < LICENSE_TYPE[minimumLicenseRequired]) { + if (!this.hasAtLeast(minimumLicenseRequired)) { return { state: LICENSE_CHECK_STATE.Invalid, message: i18n.translate('xpack.licensing.check.errorUnsupportedMessage', { defaultMessage: 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', - values: { licenseType: type, pluginName }, + values: { licenseType: this.type!, pluginName }, }), }; } diff --git a/x-pack/plugins/licensing/common/license_update.test.ts b/x-pack/plugins/licensing/common/license_update.test.ts index e714edfbdd88c..7e2410913f698 100644 --- a/x-pack/plugins/licensing/common/license_update.test.ts +++ b/x-pack/plugins/licensing/common/license_update.test.ts @@ -7,7 +7,7 @@ import { Subject } from 'rxjs'; import { take, toArray } from 'rxjs/operators'; -import { ILicense, LicenseType } from './types'; +import { ILicense } from './types'; import { createLicenseUpdate } from './license_update'; import { licenseMock } from './licensing.mock'; @@ -15,14 +15,11 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const stop$ = new Subject(); describe('licensing update', () => { it('loads updates when triggered', async () => { - const types: LicenseType[] = ['basic', 'gold']; - const trigger$ = new Subject(); const fetcher = jest .fn() - .mockImplementation(() => - Promise.resolve(licenseMock.create({ license: { type: types.shift() } })) - ); + .mockResolvedValueOnce(licenseMock.createLicense({ license: { type: 'basic' } })) + .mockResolvedValueOnce(licenseMock.createLicense({ license: { type: 'gold' } })); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); @@ -38,8 +35,8 @@ describe('licensing update', () => { }); it('starts with initial value if presents', async () => { - const initialLicense = licenseMock.create({ license: { type: 'platinum' } }); - const fetchedLicense = licenseMock.create({ license: { type: 'gold' } }); + const initialLicense = licenseMock.createLicense({ license: { type: 'platinum' } }); + const fetchedLicense = licenseMock.createLicense({ license: { type: 'gold' } }); const trigger$ = new Subject(); const fetcher = jest.fn().mockResolvedValue(fetchedLicense); @@ -55,14 +52,11 @@ describe('licensing update', () => { it('does not emit if license has not changed', async () => { const trigger$ = new Subject(); - let i = 0; const fetcher = jest .fn() - .mockImplementation(() => - Promise.resolve( - ++i < 3 ? licenseMock.create() : licenseMock.create({ license: { type: 'gold' } }) - ) - ); + .mockResolvedValueOnce(licenseMock.createLicense()) + .mockResolvedValueOnce(licenseMock.createLicense()) + .mockResolvedValueOnce(licenseMock.createLicense({ license: { type: 'gold' } })); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); trigger$.next(); @@ -83,7 +77,7 @@ describe('licensing update', () => { it('new subscriptions does not force re-fetch', async () => { const trigger$ = new Subject(); - const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); + const fetcher = jest.fn().mockResolvedValue(licenseMock.createLicense()); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); @@ -103,9 +97,9 @@ describe('licensing update', () => { new Promise(resolve => { if (firstCall) { firstCall = false; - setTimeout(() => resolve(licenseMock.create()), delayMs); + setTimeout(() => resolve(licenseMock.createLicense()), delayMs); } else { - resolve(licenseMock.create({ license: { type: 'gold' } })); + resolve(licenseMock.createLicense({ license: { type: 'gold' } })); } }) ); @@ -126,7 +120,7 @@ describe('licensing update', () => { it('completes license$ stream when stop$ is triggered', () => { const trigger$ = new Subject(); - const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); + const fetcher = jest.fn().mockResolvedValue(licenseMock.createLicense()); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); let completed = false; @@ -138,7 +132,7 @@ describe('licensing update', () => { it('stops fetching when stop$ is triggered', () => { const trigger$ = new Subject(); - const fetcher = jest.fn().mockResolvedValue(licenseMock.create()); + const fetcher = jest.fn().mockResolvedValue(licenseMock.createLicense()); const { license$ } = createLicenseUpdate(trigger$, stop$, fetcher); const values: ILicense[] = []; @@ -152,8 +146,8 @@ describe('licensing update', () => { it('refreshManually guarantees license fetching', async () => { const trigger$ = new Subject(); - const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); - const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + const firstLicense = licenseMock.createLicense({ license: { uid: 'first', type: 'basic' } }); + const secondLicense = licenseMock.createLicense({ license: { uid: 'second', type: 'gold' } }); const fetcher = jest .fn() diff --git a/x-pack/plugins/licensing/common/licensing.mock.ts b/x-pack/plugins/licensing/common/licensing.mock.ts index 52721703fcb73..bbe63d5c0d70a 100644 --- a/x-pack/plugins/licensing/common/licensing.mock.ts +++ b/x-pack/plugins/licensing/common/licensing.mock.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 { PublicLicense, PublicFeatures } from './types'; +import { ILicense, PublicLicense, PublicFeatures, LICENSE_CHECK_STATE } from './types'; import { License } from './license'; function createLicense({ @@ -40,6 +40,22 @@ function createLicense({ }); } +const createLicenseMock = () => { + const mock: jest.Mocked = { + isActive: true, + isAvailable: true, + signature: '', + toJSON: jest.fn(), + getUnavailableReason: jest.fn(), + getFeature: jest.fn(), + check: jest.fn(), + hasAtLeast: jest.fn(), + }; + mock.check.mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }); + mock.hasAtLeast.mockReturnValue(true); + return mock; +}; export const licenseMock = { - create: createLicense, + createLicense, + createLicenseMock, }; diff --git a/x-pack/plugins/licensing/common/types.ts b/x-pack/plugins/licensing/common/types.ts index c4b9f4ebad19d..78c31963da9b1 100644 --- a/x-pack/plugins/licensing/common/types.ts +++ b/x-pack/plugins/licensing/common/types.ts @@ -140,16 +140,6 @@ export interface ILicense { */ isAvailable: boolean; - /** - * Determine if the type of the license is basic, and also active. - */ - isBasic: boolean; - - /** - * Determine if the type of the license is not basic, and also active. - */ - isNotBasic: boolean; - /** * Returns */ @@ -166,10 +156,10 @@ export interface ILicense { getUnavailableReason: () => string | undefined; /** - * Determine if the provided license types match against the license type. - * @param candidateLicenses license types to intersect against the license. + * Determine if license type >= minimal required license type. + * @param minimumLicenseRequired the minimum valid license required for the given feature */ - isOneOf(candidateLicenses: LicenseType | LicenseType[]): boolean; + hasAtLeast(minimumLicenseRequired: LicenseType): boolean; /** * For a given plugin and license type, receive information about the status of the license. diff --git a/x-pack/plugins/licensing/public/licensing.mock.ts b/x-pack/plugins/licensing/public/mocks.ts similarity index 89% rename from x-pack/plugins/licensing/public/licensing.mock.ts rename to x-pack/plugins/licensing/public/mocks.ts index e2ed070017847..68b280c5341f2 100644 --- a/x-pack/plugins/licensing/public/licensing.mock.ts +++ b/x-pack/plugins/licensing/public/mocks.ts @@ -8,7 +8,7 @@ import { LicensingPluginSetup } from './types'; import { licenseMock } from '../common/licensing.mock'; const createSetupMock = () => { - const license = licenseMock.create(); + const license = licenseMock.createLicense(); const mock: jest.Mocked = { license$: new BehaviorSubject(license), refresh: jest.fn(), @@ -20,5 +20,5 @@ const createSetupMock = () => { export const licensingMock = { createSetup: createSetupMock, - createLicense: licenseMock.create, + ...licenseMock, }; diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index 4469f26836b18..01545ee8d48b4 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -30,8 +30,12 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const firstLicense = licenseMock.create({ license: { uid: 'first', type: 'basic' } }); - const secondLicense = licenseMock.create({ license: { uid: 'second', type: 'gold' } }); + const firstLicense = licenseMock.createLicense({ + license: { uid: 'first', type: 'basic' }, + }); + const secondLicense = licenseMock.createLicense({ + license: { uid: 'second', type: 'gold' }, + }); coreSetup.http.get.mockResolvedValueOnce(firstLicense).mockResolvedValueOnce(secondLicense); const { license$, refresh } = await plugin.setup(coreSetup); @@ -53,7 +57,7 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const fetchedLicense = licenseMock.create(); + const fetchedLicense = licenseMock.createLicense(); coreSetup.http.get.mockResolvedValue(fetchedLicense); const { refresh } = await plugin.setup(coreSetup); @@ -71,7 +75,7 @@ describe('licensing plugin', () => { describe('#license$', () => { it('starts with license saved in sessionStorage if available', async () => { const sessionStorage = coreMock.createStorage(); - const savedLicense = licenseMock.create({ license: { uid: 'saved' } }); + const savedLicense = licenseMock.createLicense({ license: { uid: 'saved' } }); sessionStorage.getItem.mockReturnValue(JSON.stringify(savedLicense)); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); @@ -90,12 +94,12 @@ describe('licensing plugin', () => { const types: LicenseType[] = ['gold', 'platinum']; const sessionStorage = coreMock.createStorage(); - sessionStorage.getItem.mockReturnValue(JSON.stringify(licenseMock.create())); + sessionStorage.getItem.mockReturnValue(JSON.stringify(licenseMock.createLicense())); plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); coreSetup.http.get.mockImplementation(() => - Promise.resolve(licenseMock.create({ license: { type: types.shift() } })) + Promise.resolve(licenseMock.createLicense({ license: { type: types.shift() } })) ); const { license$, refresh } = await plugin.setup(coreSetup); @@ -123,7 +127,7 @@ describe('licensing plugin', () => { const coreSetup = coreMock.createSetup(); - const fetchedLicense = licenseMock.create({ license: { uid: 'fresh' } }); + const fetchedLicense = licenseMock.createLicense({ license: { uid: 'fresh' } }); coreSetup.http.get.mockResolvedValue(fetchedLicense); const { license$, refresh } = await plugin.setup(coreSetup); @@ -196,7 +200,7 @@ describe('licensing plugin', () => { const coreSetup = coreMock.createSetup(); - coreSetup.http.get.mockResolvedValue(licenseMock.create({ signature: 'signature-1' })); + coreSetup.http.get.mockResolvedValue(licenseMock.createLicense({ signature: 'signature-1' })); let registeredInterceptor: HttpInterceptor; coreSetup.http.intercept.mockImplementation((interceptor: HttpInterceptor) => { @@ -321,7 +325,7 @@ describe('licensing plugin', () => { const coreSetup = coreMock.createSetup(); coreSetup.http.get.mockResolvedValueOnce( - licenseMock.create({ license: { status: 'active', type: 'gold' } }) + licenseMock.createLicense({ license: { status: 'active', type: 'gold' } }) ); const { refresh } = await plugin.setup(coreSetup); @@ -338,8 +342,12 @@ describe('licensing plugin', () => { plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); const coreSetup = coreMock.createSetup(); - const activeLicense = licenseMock.create({ license: { status: 'active', type: 'gold' } }); - const expiredLicense = licenseMock.create({ license: { status: 'expired', type: 'gold' } }); + const activeLicense = licenseMock.createLicense({ + license: { status: 'active', type: 'gold' }, + }); + const expiredLicense = licenseMock.createLicense({ + license: { status: 'expired', type: 'gold' }, + }); coreSetup.http.get .mockResolvedValueOnce(activeLicense) .mockResolvedValueOnce(expiredLicense) diff --git a/x-pack/plugins/licensing/server/licensing.mock.ts b/x-pack/plugins/licensing/server/licensing.mock.ts deleted file mode 100644 index b2059e36fd0c0..0000000000000 --- a/x-pack/plugins/licensing/server/licensing.mock.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { BehaviorSubject } from 'rxjs'; -import { LicensingPluginSetup } from './types'; -import { licenseMock } from '../common/licensing.mock'; - -const createSetupMock = () => { - const license = licenseMock.create(); - const mock: jest.Mocked = { - license$: new BehaviorSubject(license), - refresh: jest.fn(), - createLicensePoller: jest.fn(), - }; - mock.refresh.mockResolvedValue(license); - mock.createLicensePoller.mockReturnValue({ - license$: mock.license$, - refresh: mock.refresh, - }); - - return mock; -}; - -export const licensingMock = { - createSetup: createSetupMock, - createLicense: licenseMock.create, -}; diff --git a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts index 20e7f34c3ce3c..91fb1672f020d 100644 --- a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts +++ b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts @@ -11,8 +11,8 @@ import { createRouteHandlerContext } from './licensing_route_handler_context'; describe('createRouteHandlerContext', () => { it('returns a function providing the last license value', async () => { - const firstLicense = licenseMock.create(); - const secondLicense = licenseMock.create(); + const firstLicense = licenseMock.createLicense(); + const secondLicense = licenseMock.createLicense(); const license$ = new BehaviorSubject(firstLicense); const routeHandler = createRouteHandlerContext(license$); diff --git a/x-pack/plugins/licensing/server/mocks.ts b/x-pack/plugins/licensing/server/mocks.ts index 237636d163017..d622e3f71eff5 100644 --- a/x-pack/plugins/licensing/server/mocks.ts +++ b/x-pack/plugins/licensing/server/mocks.ts @@ -3,5 +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 { BehaviorSubject } from 'rxjs'; +import { LicensingPluginSetup } from './types'; +import { licenseMock } from '../common/licensing.mock'; -export * from './licensing.mock'; +const createSetupMock = () => { + const license = licenseMock.createLicense(); + const mock: jest.Mocked = { + license$: new BehaviorSubject(license), + refresh: jest.fn(), + createLicensePoller: jest.fn(), + }; + mock.refresh.mockResolvedValue(license); + mock.createLicensePoller.mockReturnValue({ + license$: mock.license$, + refresh: mock.refresh, + }); + + return mock; +}; + +export const licensingMock = { + createSetup: createSetupMock, + ...licenseMock, +}; diff --git a/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts index 9acfcef0ac8df..9a588a9ac8460 100644 --- a/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts +++ b/x-pack/plugins/licensing/server/on_pre_response_handler.test.ts @@ -11,7 +11,7 @@ import { licenseMock } from '../common/licensing.mock'; describe('createOnPreResponseHandler', () => { it('sets license.signature header immediately for non-error responses', async () => { const refresh = jest.fn(); - const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const license$ = new BehaviorSubject(licenseMock.createLicense({ signature: 'foo' })); const toolkit = httpServiceMock.createOnPreResponseToolkit(); const interceptor = createOnPreResponseHandler(refresh, license$); @@ -26,8 +26,8 @@ describe('createOnPreResponseHandler', () => { }); }); it('sets license.signature header after refresh for non-error responses', async () => { - const updatedLicense = licenseMock.create({ signature: 'bar' }); - const license$ = new BehaviorSubject(licenseMock.create({ signature: 'foo' })); + const updatedLicense = licenseMock.createLicense({ signature: 'bar' }); + const license$ = new BehaviorSubject(licenseMock.createLicense({ signature: 'foo' })); const refresh = jest.fn().mockImplementation( () => new Promise(resolve => { diff --git a/x-pack/plugins/reporting/index.d.ts b/x-pack/plugins/reporting/index.d.ts index 65cdfa09fc4c6..9559de4a5bb03 100644 --- a/x-pack/plugins/reporting/index.d.ts +++ b/x-pack/plugins/reporting/index.d.ts @@ -7,7 +7,7 @@ import { CoreSetup, CoreStart, - HttpServiceBase, + HttpSetup, Plugin, PluginInitializerContext, NotificationsStart, @@ -16,7 +16,7 @@ import { export type JobId = string; export type JobStatus = 'completed' | 'pending' | 'processing' | 'failed'; -export type HttpService = HttpServiceBase; +export type HttpService = HttpSetup; export type NotificationsService = NotificationsStart; export interface SourceJob { diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts index 1c669b7899ec3..aeba2ca5406b8 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts @@ -5,7 +5,7 @@ */ import sinon, { stub } from 'sinon'; -import { HttpServiceBase, NotificationsStart } from '../../../../../src/core/public'; +import { HttpSetup, NotificationsStart } from '../../../../../src/core/public'; import { SourceJob, JobSummary, HttpService } from '../../index.d'; import { JobQueue } from './job_queue'; import { ReportingNotifierStreamHandler } from './stream_handler'; @@ -57,7 +57,7 @@ const httpMock: HttpService = ({ basePath: { prepend: stub(), }, -} as unknown) as HttpServiceBase; +} as unknown) as HttpSetup; const mockShowDanger = stub(); const mockShowSuccess = stub(); diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index f3b307cdeabfe..f4fa5e00e2387 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -5,21 +5,13 @@ */ import { of, BehaviorSubject } from 'rxjs'; -import { ILicense } from '../../../licensing/public'; +import { licensingMock } from '../../../licensing/public/mocks'; import { SecurityLicenseService } from './license_service'; -function getMockRawLicense({ isAvailable = false } = {}) { - return ({ - isAvailable, - isOneOf: jest.fn(), - getFeature: jest.fn(), - } as unknown) as jest.Mocked; -} - describe('license features', function() { it('should display error when ES is unavailable', () => { const serviceSetup = new SecurityLicenseService().setup({ - license$: of((undefined as unknown) as ILicense), + license$: of(undefined as any), }); expect(serviceSetup.license.getFeatures()).toEqual({ showLogin: true, @@ -33,8 +25,10 @@ describe('license features', function() { }); it('should display error when X-Pack is unavailable', () => { + const rawLicenseMock = licensingMock.createLicenseMock(); + rawLicenseMock.isAvailable = false; const serviceSetup = new SecurityLicenseService().setup({ - license$: of(getMockRawLicense({ isAvailable: false })), + license$: of(rawLicenseMock), }); expect(serviceSetup.license.getFeatures()).toEqual({ showLogin: true, @@ -48,7 +42,9 @@ describe('license features', function() { }); it('should notify consumers of licensed feature changes', () => { - const rawLicense$ = new BehaviorSubject(getMockRawLicense({ isAvailable: false })); + const rawLicenseMock = licensingMock.createLicenseMock(); + rawLicenseMock.isAvailable = false; + const rawLicense$ = new BehaviorSubject(rawLicenseMock); const serviceSetup = new SecurityLicenseService().setup({ license$: rawLicense$, }); @@ -71,7 +67,7 @@ describe('license features', function() { ] `); - rawLicense$.next(getMockRawLicense({ isAvailable: true })); + rawLicense$.next(licensingMock.createLicenseMock()); expect(subscriptionHandler).toHaveBeenCalledTimes(2); expect(subscriptionHandler.mock.calls[1]).toMatchInlineSnapshot(` Array [ @@ -92,10 +88,8 @@ describe('license features', function() { }); it('should show login page and other security elements, allow RBAC but forbid document level security if license is not platinum or trial.', () => { - const mockRawLicense = getMockRawLicense({ isAvailable: true }); - mockRawLicense.isOneOf.mockImplementation(licenses => - Array.isArray(licenses) ? licenses.includes('basic') : licenses === 'basic' - ); + const mockRawLicense = licensingMock.createLicenseMock(); + mockRawLicense.hasAtLeast.mockReturnValue(false); mockRawLicense.getFeature.mockReturnValue({ isEnabled: true, isAvailable: true }); const serviceSetup = new SecurityLicenseService().setup({ @@ -114,8 +108,8 @@ describe('license features', function() { }); it('should not show login page or other security elements if security is disabled in Elasticsearch.', () => { - const mockRawLicense = getMockRawLicense({ isAvailable: true }); - mockRawLicense.isOneOf.mockReturnValue(false); + const mockRawLicense = licensingMock.createLicenseMock(); + mockRawLicense.hasAtLeast.mockReturnValue(false); mockRawLicense.getFeature.mockReturnValue({ isEnabled: false, isAvailable: true }); const serviceSetup = new SecurityLicenseService().setup({ @@ -133,14 +127,9 @@ describe('license features', function() { }); it('should allow to login, allow RBAC and document level security if license >= platinum', () => { - const mockRawLicense = getMockRawLicense({ isAvailable: true }); - mockRawLicense.isOneOf.mockImplementation(licenses => { - const licenseArray = [licenses].flat(); - return ( - licenseArray.includes('trial') || - licenseArray.includes('platinum') || - licenseArray.includes('enterprise') - ); + const mockRawLicense = licensingMock.createLicenseMock(); + mockRawLicense.hasAtLeast.mockImplementation(license => { + return license === 'trial' || license === 'platinum' || license === 'enterprise'; }); mockRawLicense.getFeature.mockReturnValue({ isEnabled: true, isAvailable: true }); diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 17cd5cfde87e4..0f9da03f9f6ec 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -92,7 +92,7 @@ export class SecurityLicenseService { }; } - const isLicensePlatinumOrBetter = rawLicense.isOneOf(['enterprise', 'platinum', 'trial']); + const isLicensePlatinumOrBetter = rawLicense.hasAtLeast('platinum'); return { showLogin: true, allowLogin: true, diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 316f3373dd31e..3879d611d46eb 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -21,7 +21,7 @@ const validLicense = { isEnabled: true, }; }, - isOneOf: (...candidates) => true, + hasAtLeast: (...candidates) => true, } as ILicense; describe('SecurityNavControlService', () => { diff --git a/x-pack/plugins/security/server/audit/audit_logger.test.ts b/x-pack/plugins/security/server/audit/audit_logger.test.ts index 2ae8b6762c5d4..01cde02b7dfdd 100644 --- a/x-pack/plugins/security/server/audit/audit_logger.test.ts +++ b/x-pack/plugins/security/server/audit/audit_logger.test.ts @@ -14,7 +14,7 @@ const createMockAuditLogger = () => { describe(`#savedObjectsAuthorizationFailure`, () => { test('logs via auditLogger', () => { const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(auditLogger); + const securityAuditLogger = new SecurityAuditLogger(() => auditLogger); const username = 'foo-user'; const action = 'foo-action'; const types = ['foo-type-1', 'foo-type-2']; @@ -43,7 +43,7 @@ describe(`#savedObjectsAuthorizationFailure`, () => { describe(`#savedObjectsAuthorizationSuccess`, () => { test('logs via auditLogger when xpack.security.audit.enabled is true', () => { const auditLogger = createMockAuditLogger(); - const securityAuditLogger = new SecurityAuditLogger(auditLogger); + const securityAuditLogger = new SecurityAuditLogger(() => auditLogger); const username = 'foo-user'; const action = 'foo-action'; const types = ['foo-type-1', 'foo-type-2']; diff --git a/x-pack/plugins/security/server/audit/audit_logger.ts b/x-pack/plugins/security/server/audit/audit_logger.ts index 4c2c57d0e029e..df8df35f97b49 100644 --- a/x-pack/plugins/security/server/audit/audit_logger.ts +++ b/x-pack/plugins/security/server/audit/audit_logger.ts @@ -7,7 +7,7 @@ import { LegacyAPI } from '../plugin'; export class SecurityAuditLogger { - constructor(private readonly auditLogger: LegacyAPI['auditLogger']) {} + constructor(private readonly getAuditLogger: () => LegacyAPI['auditLogger']) {} savedObjectsAuthorizationFailure( username: string, @@ -16,7 +16,7 @@ export class SecurityAuditLogger { missing: string[], args?: Record ) { - this.auditLogger.log( + this.getAuditLogger().log( 'saved_objects_authorization_failure', `${username} unauthorized to ${action} ${types.join(',')}, missing ${missing.join(',')}`, { @@ -35,7 +35,7 @@ export class SecurityAuditLogger { types: string[], args?: Record ) { - this.auditLogger.log( + this.getAuditLogger().log( 'saved_objects_authorization_success', `${username} authorized to ${action} ${types.join(',')}`, { diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts index c18c071ca7675..dc406c17925dd 100644 --- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts +++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IClusterClient, Logger } from '../../../../../src/core/server'; +import { IClusterClient, Logger } from 'kibana/server'; import { RawKibanaPrivileges } from '../../common/model'; import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 14dd1e6ac00d3..cdd2a024310bb 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -13,8 +13,6 @@ import { Logger, PluginInitializerContext, RecursiveReadonly, - SavedObjectsLegacyService, - LegacyRequest, } from '../../../../src/core/server'; import { deepFreeze } from '../../../../src/core/utils'; import { SpacesPluginSetup } from '../../spaces/server'; @@ -43,7 +41,6 @@ export type FeaturesService = Pick; */ export interface LegacyAPI { isSystemAPIRequest: (request: KibanaRequest) => boolean; - savedObjects: SavedObjectsLegacyService; auditLogger: { log: (eventType: string, message: string, data?: Record) => void; }; @@ -153,6 +150,12 @@ export class Plugin { featuresService: features, }); + setupSavedObjects({ + auditLogger: new SecurityAuditLogger(() => this.getLegacyAPI().auditLogger), + authz, + savedObjects: core.savedObjects, + }); + core.capabilities.registerSwitcher(authz.disableUnauthorizedCapabilities); defineRoutes({ @@ -166,7 +169,6 @@ export class Plugin { csp: core.http.csp, }); - const adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); return deepFreeze({ authc, @@ -185,16 +187,7 @@ export class Plugin { }, __legacyCompat: { - registerLegacyAPI: (legacyAPI: LegacyAPI) => { - this.legacyAPI = legacyAPI; - - setupSavedObjects({ - auditLogger: new SecurityAuditLogger(legacyAPI.auditLogger), - adminClusterClient: adminClient, - authz, - legacyAPI, - }); - }, + registerLegacyAPI: (legacyAPI: LegacyAPI) => (this.legacyAPI = legacyAPI), registerPrivilegesWithCluster: async () => await authz.registerPrivilegesWithCluster(), diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 06acf5283fe97..465ea61e12a4e 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -60,7 +60,9 @@ export function defineSAMLRoutes({ router, logger, authc, csp, basePath }: Route router.get( { path: '/api/security/saml/start', - validate: { query: schema.object({ redirectURLFragment: schema.string() }) }, + validate: { + query: schema.object({ redirectURLFragment: schema.string() }), + }, options: { authRequired: false }, }, async (context, request, response) => { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts index de966d6f2a758..eb56143288747 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts @@ -13,7 +13,9 @@ export function defineDeleteRolesRoutes({ router, clusterClient }: RouteDefiniti router.delete( { path: '/api/security/role/{name}', - validate: { params: schema.object({ name: schema.string({ minLength: 1 }) }) }, + validate: { + params: schema.object({ name: schema.string({ minLength: 1 }) }), + }, }, createLicensedRouteHandler(async (context, request, response) => { try { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index 8c158bee1a15e..bf1140e2e6fd1 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -14,7 +14,9 @@ export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefi router.get( { path: '/api/security/role/{name}', - validate: { params: schema.object({ name: schema.string({ minLength: 1 }) }) }, + validate: { + params: schema.object({ name: schema.string({ minLength: 1 }) }), + }, }, createLicensedRouteHandler(async (context, request, response) => { try { diff --git a/x-pack/plugins/security/server/routes/licensed_route_handler.ts b/x-pack/plugins/security/server/routes/licensed_route_handler.ts index 1194e3d0a83cc..bc5a6a1139215 100644 --- a/x-pack/plugins/security/server/routes/licensed_route_handler.ts +++ b/x-pack/plugins/security/server/routes/licensed_route_handler.ts @@ -4,17 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandler } from 'src/core/server'; -import { ObjectType } from '@kbn/config-schema'; +import { RequestHandler } from 'kibana/server'; import { LICENSE_CHECK_STATE } from '../../../licensing/server'; -export const createLicensedRouteHandler = < - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType ->( - handler: RequestHandler -) => { +export const createLicensedRouteHandler = (handler: RequestHandler) => { const licensedRouteHandler: RequestHandler = (context, request, responseToolkit) => { const { license } = context.licensing; const licenseCheck = license.check('security', 'basic'); diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index 2bd7440d3ee70..556dc4fda85cf 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -4,60 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IClusterClient, KibanaRequest, LegacyRequest } from '../../../../../src/core/server'; +import { + CoreSetup, + KibanaRequest, + LegacyRequest, + SavedObjectsClient, +} from '../../../../../src/core/server'; import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper'; -import { LegacyAPI } from '../plugin'; import { Authorization } from '../authorization'; import { SecurityAuditLogger } from '../audit'; interface SetupSavedObjectsParams { - adminClusterClient: IClusterClient; auditLogger: SecurityAuditLogger; authz: Pick; - legacyAPI: Pick; + savedObjects: CoreSetup['savedObjects']; } -export function setupSavedObjects({ - adminClusterClient, - auditLogger, - authz, - legacyAPI: { savedObjects }, -}: SetupSavedObjectsParams) { +export function setupSavedObjects({ auditLogger, authz, savedObjects }: SetupSavedObjectsParams) { const getKibanaRequest = (request: KibanaRequest | LegacyRequest) => request instanceof KibanaRequest ? request : KibanaRequest.from(request); - savedObjects.setScopedSavedObjectsClientFactory(({ request }) => { - const kibanaRequest = getKibanaRequest(request); - if (authz.mode.useRbacForRequest(kibanaRequest)) { - const internalRepository = savedObjects.getSavedObjectsRepository( - adminClusterClient.callAsInternalUser - ); - return new savedObjects.SavedObjectsClient(internalRepository); - } - const callAsCurrentUserRepository = savedObjects.getSavedObjectsRepository( - adminClusterClient.asScoped(kibanaRequest).callAsCurrentUser + savedObjects.setClientFactory(({ request }) => { + const kibanaRequest = getKibanaRequest(request); + return new SavedObjectsClient( + authz.mode.useRbacForRequest(kibanaRequest) + ? savedObjects.createInternalRepository() + : savedObjects.createScopedRepository(kibanaRequest) ); - return new savedObjects.SavedObjectsClient(callAsCurrentUserRepository); }); - savedObjects.addScopedSavedObjectsClientWrapperFactory( - Number.MAX_SAFE_INTEGER - 1, - 'security', - ({ client, request }) => { - const kibanaRequest = getKibanaRequest(request); - if (authz.mode.useRbacForRequest(kibanaRequest)) { - return new SecureSavedObjectsClientWrapper({ + savedObjects.addClientWrapper(Number.MAX_SAFE_INTEGER - 1, 'security', ({ client, request }) => { + const kibanaRequest = getKibanaRequest(request); + return authz.mode.useRbacForRequest(kibanaRequest) + ? new SecureSavedObjectsClientWrapper({ actions: authz.actions, auditLogger, baseClient: client, checkSavedObjectsPrivilegesAsCurrentUser: authz.checkSavedObjectsPrivilegesWithRequest( kibanaRequest ), - errors: savedObjects.SavedObjectsClient.errors, - }); - } - - return client; - } - ); + errors: SavedObjectsClient.errors, + }) + : client; + }); } diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 4f78828b14dc2..4d8d08a487e9a 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -11,7 +11,7 @@ import { mockRouteContext, mockRouteContextWithInvalidLicense, } from '../__fixtures__'; -import { CoreSetup, IRouter, kibanaResponseFactory } from 'src/core/server'; +import { CoreSetup, IRouter, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server'; import { loggingServiceMock, elasticsearchServiceMock, @@ -22,10 +22,9 @@ import { SpacesService } from '../../../spaces_service'; import { SpacesAuditLogger } from '../../../lib/audit_logger'; import { SpacesClient } from '../../../lib/spaces_client'; import { initCopyToSpacesApi } from './copy_to_space'; -import { ObjectType } from '@kbn/config-schema'; -import { RouteSchemas } from 'src/core/server/http/router/route'; import { spacesConfig } from '../../../lib/__fixtures__'; import { securityMock } from '../../../../../security/server/mocks'; +import { ObjectType } from '@kbn/config-schema'; describe('copy to space', () => { const spacesSavedObjects = createSpaces(); @@ -78,19 +77,11 @@ describe('copy to space', () => { return { copyToSpace: { - routeValidation: ctsRouteDefinition.validate as RouteSchemas< - ObjectType, - ObjectType, - ObjectType - >, + routeValidation: ctsRouteDefinition.validate as RouteValidatorConfig<{}, {}, {}>, routeHandler: ctsRouteHandler, }, resolveConflicts: { - routeValidation: resolveRouteDefinition.validate as RouteSchemas< - ObjectType, - ObjectType, - ObjectType - >, + routeValidation: resolveRouteDefinition.validate as RouteValidatorConfig<{}, {}, {}>, routeHandler: resolveRouteHandler, }, savedObjectsRepositoryMock, @@ -150,7 +141,7 @@ describe('copy to space', () => { const { copyToSpace } = await setup(); expect(() => - copyToSpace.routeValidation.body!.validate(payload) + (copyToSpace.routeValidation.body as ObjectType).validate(payload) ).toThrowErrorMatchingInlineSnapshot(`"[spaces]: duplicate space ids are not allowed"`); }); @@ -163,7 +154,7 @@ describe('copy to space', () => { const { copyToSpace } = await setup(); expect(() => - copyToSpace.routeValidation.body!.validate(payload) + (copyToSpace.routeValidation.body as ObjectType).validate(payload) ).toThrowErrorMatchingInlineSnapshot( `"[spaces.1]: lower case, a-z, 0-9, \\"_\\", and \\"-\\" are allowed"` ); @@ -181,7 +172,7 @@ describe('copy to space', () => { const { copyToSpace } = await setup(); expect(() => - copyToSpace.routeValidation.body!.validate(payload) + (copyToSpace.routeValidation.body as ObjectType).validate(payload) ).toThrowErrorMatchingInlineSnapshot(`"[objects]: duplicate objects are not allowed"`); }); @@ -322,7 +313,7 @@ describe('copy to space', () => { const { resolveConflicts } = await setup(); expect(() => - resolveConflicts.routeValidation.body!.validate(payload) + (resolveConflicts.routeValidation.body as ObjectType).validate(payload) ).toThrowErrorMatchingInlineSnapshot(`"[objects]: duplicate objects are not allowed"`); }); @@ -343,7 +334,7 @@ describe('copy to space', () => { const { resolveConflicts } = await setup(); expect(() => - resolveConflicts.routeValidation.body!.validate(payload) + (resolveConflicts.routeValidation.body as ObjectType).validate(payload) ).toThrowErrorMatchingInlineSnapshot( `"[retries.key(\\"invalid-space-id!@#$%^&*()\\")]: Invalid space id: invalid-space-id!@#$%^&*()"` ); diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts index 86da3023c515e..28d5708a3873c 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts @@ -12,7 +12,7 @@ import { mockRouteContext, mockRouteContextWithInvalidLicense, } from '../__fixtures__'; -import { CoreSetup, IRouter, kibanaResponseFactory } from 'src/core/server'; +import { CoreSetup, IRouter, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server'; import { loggingServiceMock, elasticsearchServiceMock, @@ -23,10 +23,9 @@ import { SpacesService } from '../../../spaces_service'; import { SpacesAuditLogger } from '../../../lib/audit_logger'; import { SpacesClient } from '../../../lib/spaces_client'; import { initDeleteSpacesApi } from './delete'; -import { RouteSchemas } from 'src/core/server/http/router/route'; -import { ObjectType } from '@kbn/config-schema'; import { spacesConfig } from '../../../lib/__fixtures__'; import { securityMock } from '../../../../../security/server/mocks'; +import { ObjectType } from '@kbn/config-schema'; describe('Spaces Public API', () => { const spacesSavedObjects = createSpaces(); @@ -75,14 +74,16 @@ describe('Spaces Public API', () => { const [routeDefinition, routeHandler] = router.delete.mock.calls[0]; return { - routeValidation: routeDefinition.validate as RouteSchemas, + routeValidation: routeDefinition.validate as RouteValidatorConfig<{}, {}, {}>, routeHandler, }; }; it('requires a space id as part of the path', async () => { const { routeValidation } = await setup(); - expect(() => routeValidation.params!.validate({})).toThrowErrorMatchingInlineSnapshot( + expect(() => + (routeValidation.params as ObjectType).validate({}) + ).toThrowErrorMatchingInlineSnapshot( `"[id]: expected value of type [string] but got [undefined]"` ); }); diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts index 398b2e37191b6..d82ccaa8ff380 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts @@ -11,7 +11,7 @@ import { mockRouteContext, mockRouteContextWithInvalidLicense, } from '../__fixtures__'; -import { CoreSetup, kibanaResponseFactory, IRouter } from 'src/core/server'; +import { CoreSetup, kibanaResponseFactory, IRouter, RouteValidatorConfig } from 'src/core/server'; import { loggingServiceMock, elasticsearchServiceMock, @@ -22,10 +22,9 @@ import { SpacesService } from '../../../spaces_service'; import { SpacesAuditLogger } from '../../../lib/audit_logger'; import { SpacesClient } from '../../../lib/spaces_client'; import { initPostSpacesApi } from './post'; -import { RouteSchemas } from 'src/core/server/http/router/route'; -import { ObjectType } from '@kbn/config-schema'; import { spacesConfig } from '../../../lib/__fixtures__'; import { securityMock } from '../../../../../security/server/mocks'; +import { ObjectType } from '@kbn/config-schema'; describe('Spaces Public API', () => { const spacesSavedObjects = createSpaces(); @@ -74,7 +73,7 @@ describe('Spaces Public API', () => { const [routeDefinition, routeHandler] = router.post.mock.calls[0]; return { - routeValidation: routeDefinition.validate as RouteSchemas, + routeValidation: routeDefinition.validate as RouteValidatorConfig<{}, {}, {}>, routeHandler, savedObjectsRepositoryMock, }; @@ -159,7 +158,7 @@ describe('Spaces Public API', () => { const { routeValidation, routeHandler, savedObjectsRepositoryMock } = await setup(); const request = httpServerMock.createKibanaRequest({ - body: routeValidation.body!.validate(payload), + body: (routeValidation.body as ObjectType).validate(payload), method: 'post', }); diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts index 5c213b7f73f62..15837110f4d92 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts @@ -12,7 +12,7 @@ import { mockRouteContext, mockRouteContextWithInvalidLicense, } from '../__fixtures__'; -import { CoreSetup, IRouter, kibanaResponseFactory } from 'src/core/server'; +import { CoreSetup, IRouter, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server'; import { loggingServiceMock, elasticsearchServiceMock, @@ -23,10 +23,9 @@ import { SpacesService } from '../../../spaces_service'; import { SpacesAuditLogger } from '../../../lib/audit_logger'; import { SpacesClient } from '../../../lib/spaces_client'; import { initPutSpacesApi } from './put'; -import { RouteSchemas } from 'src/core/server/http/router/route'; -import { ObjectType } from '@kbn/config-schema'; import { spacesConfig } from '../../../lib/__fixtures__'; import { securityMock } from '../../../../../security/server/mocks'; +import { ObjectType } from '@kbn/config-schema'; describe('PUT /api/spaces/space', () => { const spacesSavedObjects = createSpaces(); @@ -75,7 +74,7 @@ describe('PUT /api/spaces/space', () => { const [routeDefinition, routeHandler] = router.put.mock.calls[0]; return { - routeValidation: routeDefinition.validate as RouteSchemas, + routeValidation: routeDefinition.validate as RouteValidatorConfig<{}, {}, {}>, routeHandler, savedObjectsRepositoryMock, }; @@ -156,7 +155,7 @@ describe('PUT /api/spaces/space', () => { params: { id: payload.id, }, - body: routeValidation.body!.validate(payload), + body: (routeValidation.body as ObjectType).validate(payload), method: 'post', }); diff --git a/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts b/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts index 3838b1d134ea2..13bed5ce58e2b 100644 --- a/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts +++ b/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts @@ -4,17 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandler } from 'src/core/server'; -import { ObjectType } from '@kbn/config-schema'; +import { RequestHandler } from 'kibana/server'; import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; -export const createLicensedRouteHandler = < - P extends ObjectType, - Q extends ObjectType, - B extends ObjectType ->( - handler: RequestHandler -) => { +export const createLicensedRouteHandler = (handler: RequestHandler) => { const licensedRouteHandler: RequestHandler = (context, request, responseToolkit) => { const { license } = context.licensing; const licenseCheck = license.check('spaces', 'basic'); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d2373261fb1f6..b7bac7dd170ed 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1551,7 +1551,6 @@ "kbn.discover.fieldChooser.filter.hideMissingFieldsLabel": "未入力のフィールドを非表示", "kbn.discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "インデックスとフィールド", "kbn.discover.fieldChooser.filter.popularTitle": "人気", - "kbn.discover.fieldChooser.filter.resetFiltersButtonLabel": "フィルターをリセット", "kbn.discover.fieldChooser.filter.searchableLabel": "検索可能", "kbn.discover.fieldChooser.filter.selectedFieldsTitle": "スクリプトフィールド", "kbn.discover.fieldChooser.filter.typeLabel": "タイプ", @@ -2493,8 +2492,6 @@ "kbn.context.olderDocumentsWarning": "アンカーよりも古いドキュメントは {docCount} 件しか見つかりませんでした。", "kbn.context.olderDocumentsWarningZero": "アンカーよりも古いドキュメントは見つかりませんでした。", "kbn.discover.fieldChooser.fieldFilterFacetButtonLabel": "フィルタリングされたフィールド", - "kbn.discover.fieldChooser.indexPattern.changeLinkTooltip": "現在のインデックスパターンを変更", - "kbn.discover.fieldChooser.indexPattern.changeLinkAriaLabel": "現在のインデックスパターンを変更", "kbn.discover.fieldChooser.searchPlaceHolder": "検索フィールド", "kbn.discover.histogram.partialData.bucketTooltipText": "選択された時間範囲にはこのバケット全体は含まれていませんが、一部データが含まれている可能性があります。", "kbn.doc.failedToLocateIndexPattern": "ID {indexPatternId} に一致するインデックスパターンがありません", @@ -2758,59 +2755,59 @@ "regionMap.visParams.colorSchemaLabel": "カラー図表", "regionMap.visParams.layerSettingsTitle": "レイヤー設定", "regionMap.visParams.outlineWeightLabel": "境界の太さ", - "server.sampleData.ecommerceSpec.averageSalesPerRegionTitle": "[e コマース] 地域ごとの平均売上", - "server.sampleData.ecommerceSpec.averageSalesPriceTitle": "[e コマース] 平均販売価格", - "server.sampleData.ecommerceSpec.averageSoldQuantityTitle": "[e コマース] 平均販売数", - "server.sampleData.ecommerceSpec.controlsTitle": "[e コマース] コントロール", - "server.sampleData.ecommerceSpec.markdownTitle": "[e コマース] マークダウン", - "server.sampleData.ecommerceSpec.ordersTitle": "[e コマース] 注文", - "server.sampleData.ecommerceSpec.promotionTrackingTitle": "[e コマース] プロモーショントラッキング", - "server.sampleData.ecommerceSpec.revenueDashboardDescription": "サンプルの e コマースの注文と収益を分析します", - "server.sampleData.ecommerceSpec.revenueDashboardTitle": "[e コマース] 収益ダッシュボード", - "server.sampleData.ecommerceSpec.salesByCategoryTitle": "[e コマース] カテゴリーごとの売上", - "server.sampleData.ecommerceSpec.salesByGenderTitle": "[e コマース] 性別ごとの売上", - "server.sampleData.ecommerceSpec.soldProductsPerDayTitle": "[e コマース] 1 日の販売製品", - "server.sampleData.ecommerceSpec.topSellingProductsTitle": "[e コマース] トップセラー製品", - "server.sampleData.ecommerceSpec.totalRevenueTitle": "[e コマース] 合計収益", - "server.sampleData.ecommerceSpecDescription": "e コマースの注文をトラッキングするサンプルデータ、ビジュアライゼーション、ダッシュボードです。", - "server.sampleData.ecommerceSpecTitle": "サンプル e コマース注文", - "server.sampleData.flightsSpec.airlineCarrierTitle": "[フライト] 航空会社", - "server.sampleData.flightsSpec.airportConnectionsTitle": "[フライト] 空港乗り継ぎ (空港にカーソルを合わせてください)", - "server.sampleData.flightsSpec.averageTicketPriceTitle": "[フライト] 平均運賃", - "server.sampleData.flightsSpec.controlsTitle": "[フライト] コントロール", - "server.sampleData.flightsSpec.delayBucketsTitle": "[フライト] 遅延バケット", - "server.sampleData.flightsSpec.delaysAndCancellationsTitle": "[フライト] 遅延・欠航", - "server.sampleData.flightsSpec.delayTypeTitle": "[フライト] 遅延タイプ", - "server.sampleData.flightsSpec.destinationWeatherTitle": "[フライト] 目的地の天候", - "server.sampleData.flightsSpec.flightCancellationsTitle": "[フライト] フライト欠航", - "server.sampleData.flightsSpec.flightCountAndAverageTicketPriceTitle": "[フライト] カウントと平均運賃", - "server.sampleData.flightsSpec.flightDelaysTitle": "[フライト] フライトの遅延", - "server.sampleData.flightsSpec.flightLogTitle": "[フライト] 飛行記録", - "server.sampleData.flightsSpec.globalFlightDashboardDescription": "ES-Air、Logstash Airways、Kibana Airlines、JetBeats のサンプル飛行データを分析します", - "server.sampleData.flightsSpec.globalFlightDashboardTitle": "[フライト] グローバルフライトダッシュボード", - "server.sampleData.flightsSpec.markdownInstructionsTitle": "[フライト] マークダウンの指示", - "server.sampleData.flightsSpec.originCountryTicketPricesTitle": "[フライト] 出発国の運賃", - "server.sampleData.flightsSpec.originCountryTitle": "[Flights] 出発国と到着国の比較", - "server.sampleData.flightsSpec.totalFlightCancellationsTitle": "[フライト] フライト欠航合計", - "server.sampleData.flightsSpec.totalFlightDelaysTitle": "[フライト] フライト遅延合計", - "server.sampleData.flightsSpec.totalFlightsTitle": "[フライト] フライト合計", - "server.sampleData.flightsSpecDescription": "飛行ルートを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", - "server.sampleData.flightsSpecTitle": "サンプル飛行データ", - "server.sampleData.logsSpec.fileTypeScatterPlotTitle": "[ログ] ファイルタイプ散布図", - "server.sampleData.logsSpec.goalsTitle": "[ログ] 目標", - "server.sampleData.logsSpec.heatmapTitle": "[ログ] ヒートマップ", - "server.sampleData.logsSpec.hostVisitsBytesTableTitle": "[ログ] ホスト、訪問数、バイト表", - "server.sampleData.logsSpec.inputControlsTitle": "[ログ] インプットコントロール", - "server.sampleData.logsSpec.markdownInstructionsTitle": "[ログ] マークダウンの指示", - "server.sampleData.logsSpec.responseCodesOverTimeTitle": "[ログ] 一定期間の応答コードと注釈", - "server.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle": "[ログ] ソースと行先のサンキーダイアグラム", - "server.sampleData.logsSpec.uniqueVisitorsByCountryTitle": "[ログ] 国ごとのユニークビジター", - "server.sampleData.logsSpec.uniqueVisitorsTitle": "[ログ] ユニークビジターと平均バイトの比較", - "server.sampleData.logsSpec.visitorOSTitle": "[ログ] OS 別のビジター", - "server.sampleData.logsSpec.webTrafficDescription": "Elastic Web サイトのサンプル Webトラフィックログデータを分析します", - "server.sampleData.logsSpec.webTrafficTitle": "[ログ] Web トラフィック", - "server.sampleData.logsSpecDescription": "Web ログを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", - "server.sampleData.logsSpecTitle": "サンプル Web ログ", + "home.sampleData.ecommerceSpec.averageSalesPerRegionTitle": "[e コマース] 地域ごとの平均売上", + "home.sampleData.ecommerceSpec.averageSalesPriceTitle": "[e コマース] 平均販売価格", + "home.sampleData.ecommerceSpec.averageSoldQuantityTitle": "[e コマース] 平均販売数", + "home.sampleData.ecommerceSpec.controlsTitle": "[e コマース] コントロール", + "home.sampleData.ecommerceSpec.markdownTitle": "[e コマース] マークダウン", + "home.sampleData.ecommerceSpec.ordersTitle": "[e コマース] 注文", + "home.sampleData.ecommerceSpec.promotionTrackingTitle": "[e コマース] プロモーショントラッキング", + "home.sampleData.ecommerceSpec.revenueDashboardDescription": "サンプルの e コマースの注文と収益を分析します", + "home.sampleData.ecommerceSpec.revenueDashboardTitle": "[e コマース] 収益ダッシュボード", + "home.sampleData.ecommerceSpec.salesByCategoryTitle": "[e コマース] カテゴリーごとの売上", + "home.sampleData.ecommerceSpec.salesByGenderTitle": "[e コマース] 性別ごとの売上", + "home.sampleData.ecommerceSpec.soldProductsPerDayTitle": "[e コマース] 1 日の販売製品", + "home.sampleData.ecommerceSpec.topSellingProductsTitle": "[e コマース] トップセラー製品", + "home.sampleData.ecommerceSpec.totalRevenueTitle": "[e コマース] 合計収益", + "home.sampleData.ecommerceSpecDescription": "e コマースの注文をトラッキングするサンプルデータ、ビジュアライゼーション、ダッシュボードです。", + "home.sampleData.ecommerceSpecTitle": "サンプル e コマース注文", + "home.sampleData.flightsSpec.airlineCarrierTitle": "[フライト] 航空会社", + "home.sampleData.flightsSpec.airportConnectionsTitle": "[フライト] 空港乗り継ぎ (空港にカーソルを合わせてください)", + "home.sampleData.flightsSpec.averageTicketPriceTitle": "[フライト] 平均運賃", + "home.sampleData.flightsSpec.controlsTitle": "[フライト] コントロール", + "home.sampleData.flightsSpec.delayBucketsTitle": "[フライト] 遅延バケット", + "home.sampleData.flightsSpec.delaysAndCancellationsTitle": "[フライト] 遅延・欠航", + "home.sampleData.flightsSpec.delayTypeTitle": "[フライト] 遅延タイプ", + "home.sampleData.flightsSpec.destinationWeatherTitle": "[フライト] 目的地の天候", + "home.sampleData.flightsSpec.flightCancellationsTitle": "[フライト] フライト欠航", + "home.sampleData.flightsSpec.flightCountAndAverageTicketPriceTitle": "[フライト] カウントと平均運賃", + "home.sampleData.flightsSpec.flightDelaysTitle": "[フライト] フライトの遅延", + "home.sampleData.flightsSpec.flightLogTitle": "[フライト] 飛行記録", + "home.sampleData.flightsSpec.globalFlightDashboardDescription": "ES-Air、Logstash Airways、Kibana Airlines、JetBeats のサンプル飛行データを分析します", + "home.sampleData.flightsSpec.globalFlightDashboardTitle": "[フライト] グローバルフライトダッシュボード", + "home.sampleData.flightsSpec.markdownInstructionsTitle": "[フライト] マークダウンの指示", + "home.sampleData.flightsSpec.originCountryTicketPricesTitle": "[フライト] 出発国の運賃", + "home.sampleData.flightsSpec.originCountryTitle": "[Flights] 出発国と到着国の比較", + "home.sampleData.flightsSpec.totalFlightCancellationsTitle": "[フライト] フライト欠航合計", + "home.sampleData.flightsSpec.totalFlightDelaysTitle": "[フライト] フライト遅延合計", + "home.sampleData.flightsSpec.totalFlightsTitle": "[フライト] フライト合計", + "home.sampleData.flightsSpecDescription": "飛行ルートを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", + "home.sampleData.flightsSpecTitle": "サンプル飛行データ", + "home.sampleData.logsSpec.fileTypeScatterPlotTitle": "[ログ] ファイルタイプ散布図", + "home.sampleData.logsSpec.goalsTitle": "[ログ] 目標", + "home.sampleData.logsSpec.heatmapTitle": "[ログ] ヒートマップ", + "home.sampleData.logsSpec.hostVisitsBytesTableTitle": "[ログ] ホスト、訪問数、バイト表", + "home.sampleData.logsSpec.inputControlsTitle": "[ログ] インプットコントロール", + "home.sampleData.logsSpec.markdownInstructionsTitle": "[ログ] マークダウンの指示", + "home.sampleData.logsSpec.responseCodesOverTimeTitle": "[ログ] 一定期間の応答コードと注釈", + "home.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle": "[ログ] ソースと行先のサンキーダイアグラム", + "home.sampleData.logsSpec.uniqueVisitorsByCountryTitle": "[ログ] 国ごとのユニークビジター", + "home.sampleData.logsSpec.uniqueVisitorsTitle": "[ログ] ユニークビジターと平均バイトの比較", + "home.sampleData.logsSpec.visitorOSTitle": "[ログ] OS 別のビジター", + "home.sampleData.logsSpec.webTrafficDescription": "Elastic Web サイトのサンプル Webトラフィックログデータを分析します", + "home.sampleData.logsSpec.webTrafficTitle": "[ログ] Web トラフィック", + "home.sampleData.logsSpecDescription": "Web ログを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", + "home.sampleData.logsSpecTitle": "サンプル Web ログ", "server.stats.notReadyMessage": "まだ統計が準備できていません。後程再試行してください", "server.status.disabledTitle": "無効", "server.status.greenTitle": "緑", @@ -3552,7 +3549,6 @@ "xpack.apm.fetcher.error.status": "エラー", "xpack.apm.fetcher.error.title": "リソースの取得中にエラーが発生しました", "xpack.apm.fetcher.error.url": "URL", - "xpack.apm.loading.prompt": "読み込み中…", "xpack.apm.localFilters.titles.agentName": "エージェント名", "xpack.apm.localFilters.titles.containerId": "コンテナー ID", "xpack.apm.localFilters.titles.host": "ホスト", @@ -10829,7 +10825,6 @@ "xpack.siem.eventsOverTime.showing": "表示中", "xpack.siem.eventsOverTime.unit": "{totalCount, plural, =1 {event} other {events}}", "xpack.siem.flyout.button.text": "タイムライン", - "xpack.siem.footer.loadingEventsData": "イベントを読み込み中", "xpack.siem.network.navigation.anomaliesTitle": "異常", "xpack.siem.network.navigation.dnsTitle": "DNS", "xpack.siem.network.navigation.flowsTitle": "Flow", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d59d50a8de1b5..7ade936ba1044 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1552,7 +1552,6 @@ "kbn.discover.fieldChooser.filter.hideMissingFieldsLabel": "隐藏缺失字段", "kbn.discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "索引和字段", "kbn.discover.fieldChooser.filter.popularTitle": "常用", - "kbn.discover.fieldChooser.filter.resetFiltersButtonLabel": "重置筛选", "kbn.discover.fieldChooser.filter.searchableLabel": "可搜索", "kbn.discover.fieldChooser.filter.selectedFieldsTitle": "选定字段", "kbn.discover.fieldChooser.filter.typeLabel": "类型", @@ -2494,8 +2493,6 @@ "kbn.context.olderDocumentsWarning": "仅可以找到 {docCount} 个比定位标记旧的文档。", "kbn.context.olderDocumentsWarningZero": "找不到比定位标记旧的文档。", "kbn.discover.fieldChooser.fieldFilterFacetButtonLabel": "已筛选字段", - "kbn.discover.fieldChooser.indexPattern.changeLinkTooltip": "更改当前索引模式", - "kbn.discover.fieldChooser.indexPattern.changeLinkAriaLabel": "更改当前索引模式", "kbn.discover.fieldChooser.searchPlaceHolder": "搜索字段", "kbn.discover.histogram.partialData.bucketTooltipText": "选定的时间范围不包括此整个存储桶,其可能包含部分数据。", "kbn.doc.failedToLocateIndexPattern": "无索引模式匹配 ID {indexPatternId}", @@ -2759,59 +2756,59 @@ "regionMap.visParams.colorSchemaLabel": "颜色模式", "regionMap.visParams.layerSettingsTitle": "图层设置", "regionMap.visParams.outlineWeightLabel": "边框粗细", - "server.sampleData.ecommerceSpec.averageSalesPerRegionTitle": "[电子商务] 每地区平均销售额", - "server.sampleData.ecommerceSpec.averageSalesPriceTitle": "[电子商务] 平均销售价格", - "server.sampleData.ecommerceSpec.averageSoldQuantityTitle": "[电子商务] 平均销售数量", - "server.sampleData.ecommerceSpec.controlsTitle": "[电子商务] 控件", - "server.sampleData.ecommerceSpec.markdownTitle": "[电子商务] Markdown", - "server.sampleData.ecommerceSpec.ordersTitle": "[电子商务] 订单", - "server.sampleData.ecommerceSpec.promotionTrackingTitle": "[电子商务] 促销追踪", - "server.sampleData.ecommerceSpec.revenueDashboardDescription": "分析模拟的电子商务订单和收入", - "server.sampleData.ecommerceSpec.revenueDashboardTitle": "[电子商务] 收入仪表板", - "server.sampleData.ecommerceSpec.salesByCategoryTitle": "[电子商务] 按类别划分的销售额", - "server.sampleData.ecommerceSpec.salesByGenderTitle": "[电子商务] 按性别划分的销售额", - "server.sampleData.ecommerceSpec.soldProductsPerDayTitle": "[电子商务] 每天已售产品", - "server.sampleData.ecommerceSpec.topSellingProductsTitle": "[电子商务] 热卖产品", - "server.sampleData.ecommerceSpec.totalRevenueTitle": "[电子商务] 总收入", - "server.sampleData.ecommerceSpecDescription": "用于追踪电子商务订单的样例数据、可视化和仪表板。", - "server.sampleData.ecommerceSpecTitle": "样例电子商务订单", - "server.sampleData.flightsSpec.airlineCarrierTitle": "[航班] 航空公司", - "server.sampleData.flightsSpec.airportConnectionsTitle": "[航班] 机场航线(将鼠标悬停在机场上)", - "server.sampleData.flightsSpec.averageTicketPriceTitle": "[航班] 平均票价", - "server.sampleData.flightsSpec.controlsTitle": "[航班] 控件", - "server.sampleData.flightsSpec.delayBucketsTitle": "[航班] 延误存储桶", - "server.sampleData.flightsSpec.delaysAndCancellationsTitle": "[航班] 延误与取消", - "server.sampleData.flightsSpec.delayTypeTitle": "[航班] 延误类型", - "server.sampleData.flightsSpec.destinationWeatherTitle": "[航班] 到达地天气", - "server.sampleData.flightsSpec.flightCancellationsTitle": "[航班] 航班取消", - "server.sampleData.flightsSpec.flightCountAndAverageTicketPriceTitle": "[航班] 航班计数和平均票价", - "server.sampleData.flightsSpec.flightDelaysTitle": "[航班] 航班延误", - "server.sampleData.flightsSpec.flightLogTitle": "[航班] 飞行日志", - "server.sampleData.flightsSpec.globalFlightDashboardDescription": "分析 ES-Air、Logstash Airways、Kibana Airlines 和 JetBeats 的模拟航班数据", - "server.sampleData.flightsSpec.globalFlightDashboardTitle": "[航班] 全球航班仪表板", - "server.sampleData.flightsSpec.markdownInstructionsTitle": "[航班] Markdown 说明", - "server.sampleData.flightsSpec.originCountryTicketPricesTitle": "[航班] 始发国/地区票价", - "server.sampleData.flightsSpec.originCountryTitle": "[航班] 始发国/地区与到达国/地区", - "server.sampleData.flightsSpec.totalFlightCancellationsTitle": "[航班] 航班取消总数", - "server.sampleData.flightsSpec.totalFlightDelaysTitle": "[航班] 航班延误总数", - "server.sampleData.flightsSpec.totalFlightsTitle": "[航班] 航班总数", - "server.sampleData.flightsSpecDescription": "用于监测航班路线的样例数据、可视化和仪表板。", - "server.sampleData.flightsSpecTitle": "样例航班数据", - "server.sampleData.logsSpec.fileTypeScatterPlotTitle": "[日志] 文件类型散点图", - "server.sampleData.logsSpec.goalsTitle": "[日志] 目标", - "server.sampleData.logsSpec.heatmapTitle": "[日志] 热图", - "server.sampleData.logsSpec.hostVisitsBytesTableTitle": "[日志] 主机、访问和字节表", - "server.sampleData.logsSpec.inputControlsTitle": "[日志] 输入控件", - "server.sampleData.logsSpec.markdownInstructionsTitle": "[日志] Markdown 说明", - "server.sampleData.logsSpec.responseCodesOverTimeTitle": "[日志] 时移响应代码 + 注释", - "server.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle": "[日志] 始发地和到达地 Sankey 图", - "server.sampleData.logsSpec.uniqueVisitorsByCountryTitle": "[日志] 按国家/地区划分的独立访客", - "server.sampleData.logsSpec.uniqueVisitorsTitle": "[日志] 独立访客与平均字节数", - "server.sampleData.logsSpec.visitorOSTitle": "[日志] 按 OS 划分的访客", - "server.sampleData.logsSpec.webTrafficDescription": "分析 Elastic 网站的模拟网络流量日志数据", - "server.sampleData.logsSpec.webTrafficTitle": "[日志] 网络流量", - "server.sampleData.logsSpecDescription": "用于监测 Web 日志的样例数据、可视化和仪表板。", - "server.sampleData.logsSpecTitle": "样例 Web 日志", + "home.sampleData.ecommerceSpec.averageSalesPerRegionTitle": "[电子商务] 每地区平均销售额", + "home.sampleData.ecommerceSpec.averageSalesPriceTitle": "[电子商务] 平均销售价格", + "home.sampleData.ecommerceSpec.averageSoldQuantityTitle": "[电子商务] 平均销售数量", + "home.sampleData.ecommerceSpec.controlsTitle": "[电子商务] 控件", + "home.sampleData.ecommerceSpec.markdownTitle": "[电子商务] Markdown", + "home.sampleData.ecommerceSpec.ordersTitle": "[电子商务] 订单", + "home.sampleData.ecommerceSpec.promotionTrackingTitle": "[电子商务] 促销追踪", + "home.sampleData.ecommerceSpec.revenueDashboardDescription": "分析模拟的电子商务订单和收入", + "home.sampleData.ecommerceSpec.revenueDashboardTitle": "[电子商务] 收入仪表板", + "home.sampleData.ecommerceSpec.salesByCategoryTitle": "[电子商务] 按类别划分的销售额", + "home.sampleData.ecommerceSpec.salesByGenderTitle": "[电子商务] 按性别划分的销售额", + "home.sampleData.ecommerceSpec.soldProductsPerDayTitle": "[电子商务] 每天已售产品", + "home.sampleData.ecommerceSpec.topSellingProductsTitle": "[电子商务] 热卖产品", + "home.sampleData.ecommerceSpec.totalRevenueTitle": "[电子商务] 总收入", + "home.sampleData.ecommerceSpecDescription": "用于追踪电子商务订单的样例数据、可视化和仪表板。", + "home.sampleData.ecommerceSpecTitle": "样例电子商务订单", + "home.sampleData.flightsSpec.airlineCarrierTitle": "[航班] 航空公司", + "home.sampleData.flightsSpec.airportConnectionsTitle": "[航班] 机场航线(将鼠标悬停在机场上)", + "home.sampleData.flightsSpec.averageTicketPriceTitle": "[航班] 平均票价", + "home.sampleData.flightsSpec.controlsTitle": "[航班] 控件", + "home.sampleData.flightsSpec.delayBucketsTitle": "[航班] 延误存储桶", + "home.sampleData.flightsSpec.delaysAndCancellationsTitle": "[航班] 延误与取消", + "home.sampleData.flightsSpec.delayTypeTitle": "[航班] 延误类型", + "home.sampleData.flightsSpec.destinationWeatherTitle": "[航班] 到达地天气", + "home.sampleData.flightsSpec.flightCancellationsTitle": "[航班] 航班取消", + "home.sampleData.flightsSpec.flightCountAndAverageTicketPriceTitle": "[航班] 航班计数和平均票价", + "home.sampleData.flightsSpec.flightDelaysTitle": "[航班] 航班延误", + "home.sampleData.flightsSpec.flightLogTitle": "[航班] 飞行日志", + "home.sampleData.flightsSpec.globalFlightDashboardDescription": "分析 ES-Air、Logstash Airways、Kibana Airlines 和 JetBeats 的模拟航班数据", + "home.sampleData.flightsSpec.globalFlightDashboardTitle": "[航班] 全球航班仪表板", + "home.sampleData.flightsSpec.markdownInstructionsTitle": "[航班] Markdown 说明", + "home.sampleData.flightsSpec.originCountryTicketPricesTitle": "[航班] 始发国/地区票价", + "home.sampleData.flightsSpec.originCountryTitle": "[航班] 始发国/地区与到达国/地区", + "home.sampleData.flightsSpec.totalFlightCancellationsTitle": "[航班] 航班取消总数", + "home.sampleData.flightsSpec.totalFlightDelaysTitle": "[航班] 航班延误总数", + "home.sampleData.flightsSpec.totalFlightsTitle": "[航班] 航班总数", + "home.sampleData.flightsSpecDescription": "用于监测航班路线的样例数据、可视化和仪表板。", + "home.sampleData.flightsSpecTitle": "样例航班数据", + "home.sampleData.logsSpec.fileTypeScatterPlotTitle": "[日志] 文件类型散点图", + "home.sampleData.logsSpec.goalsTitle": "[日志] 目标", + "home.sampleData.logsSpec.heatmapTitle": "[日志] 热图", + "home.sampleData.logsSpec.hostVisitsBytesTableTitle": "[日志] 主机、访问和字节表", + "home.sampleData.logsSpec.inputControlsTitle": "[日志] 输入控件", + "home.sampleData.logsSpec.markdownInstructionsTitle": "[日志] Markdown 说明", + "home.sampleData.logsSpec.responseCodesOverTimeTitle": "[日志] 时移响应代码 + 注释", + "home.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle": "[日志] 始发地和到达地 Sankey 图", + "home.sampleData.logsSpec.uniqueVisitorsByCountryTitle": "[日志] 按国家/地区划分的独立访客", + "home.sampleData.logsSpec.uniqueVisitorsTitle": "[日志] 独立访客与平均字节数", + "home.sampleData.logsSpec.visitorOSTitle": "[日志] 按 OS 划分的访客", + "home.sampleData.logsSpec.webTrafficDescription": "分析 Elastic 网站的模拟网络流量日志数据", + "home.sampleData.logsSpec.webTrafficTitle": "[日志] 网络流量", + "home.sampleData.logsSpecDescription": "用于监测 Web 日志的样例数据、可视化和仪表板。", + "home.sampleData.logsSpecTitle": "样例 Web 日志", "server.stats.notReadyMessage": "统计尚未就绪。请稍后重试", "server.status.disabledTitle": "已禁用", "server.status.greenTitle": "绿", @@ -3553,7 +3550,6 @@ "xpack.apm.fetcher.error.status": "错误", "xpack.apm.fetcher.error.title": "提取资源时出错", "xpack.apm.fetcher.error.url": "URL", - "xpack.apm.loading.prompt": "正在加载……", "xpack.apm.localFilters.titles.agentName": "代理名称", "xpack.apm.localFilters.titles.containerId": "容器 ID", "xpack.apm.localFilters.titles.host": "主机", @@ -10918,7 +10914,6 @@ "xpack.siem.eventsOverTime.showing": "显示", "xpack.siem.eventsOverTime.unit": "{totalCount, plural, =1 {event} other {events}}", "xpack.siem.flyout.button.text": "时间线", - "xpack.siem.footer.loadingEventsData": "正在加载事件", "xpack.siem.network.navigation.anomaliesTitle": "异常", "xpack.siem.network.navigation.dnsTitle": "DNS", "xpack.siem.network.navigation.flowsTitle": "Flows", diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 6749c11c77036..61e7ab3fe4639 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -77,6 +77,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, + `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'task_manager')}`, `--server.xsrf.whitelist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`, ...(ssl ? [ diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/index.ts new file mode 100644 index 0000000000000..3bfad59b71166 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/index.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const taskManagerQuery = (...filters: any[]) => ({ + bool: { + filter: { + bool: { + must: filters, + }, + }, + }, +}); + +const tasksForAlerting = { + term: { + 'task.scope': 'alerting', + }, +}; +const taskByIdQuery = (id: string) => ({ + ids: { + values: [`task:${id}`], + }, +}); + +// eslint-disable-next-line import/no-default-export +export default function(kibana: any) { + return new kibana.Plugin({ + name: 'taskManagerHelpers', + require: ['elasticsearch', 'task_manager'], + + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + + init(server: any) { + const taskManager = server.plugins.task_manager; + + server.route({ + path: '/api/alerting_tasks/{taskId}', + method: 'GET', + async handler(request: any) { + try { + return taskManager.fetch({ + query: taskManagerQuery(tasksForAlerting, taskByIdQuery(request.params.taskId)), + }); + } catch (err) { + return err; + } + }, + }); + }, + }); +} diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/package.json b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/package.json new file mode 100644 index 0000000000000..532b597829561 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/package.json @@ -0,0 +1,12 @@ +{ + "name": "alerting_task_plugin", + "version": "1.0.0", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "dependencies": { + "joi": "^13.5.2" + } +} diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index 12b38e939712a..487f396d7a3dc 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -183,6 +183,7 @@ export class AlertUtils { throttle: '1m', tags: [], alertTypeId: 'test.always-firing', + consumer: 'bar', params: { index: ES_TEST_INDEX_NAME, reference, diff --git a/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts b/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts index 8655764e3fb8f..76f78809d5d11 100644 --- a/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts +++ b/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts @@ -10,6 +10,7 @@ export function getTestAlertData(overwrites = {}) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', + consumer: 'bar', schedule: { interval: '1m' }, throttle: '1m', actions: [], diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 4a089e15eb458..69bc547e3bfc1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -467,6 +467,7 @@ export default function alertTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'superuser at space1': + expect(response.statusCode).to.eql(200); // Wait until alerts scheduled actions 3 times before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('alert:test.always-firing', reference, 3); await alertUtils.disable(response.body.id); @@ -582,6 +583,7 @@ export default function alertTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'superuser at space1': + expect(response.statusCode).to.eql(200); // Actions should execute twice before widning things down await esTestIndexTool.waitForDocs('action:test.index-record', reference, 2); await alertUtils.disable(response.body.id); @@ -599,7 +601,16 @@ export default function alertTests({ getService }: FtrProviderContext) { } }); - it(`shouldn't schedule actions when alert is muted`, async () => { + /** + * Skipping due to an issue we've discovered in the `muteAll` api + * which corrupts the apiKey and causes this test to exhibit flaky behaviour. + * Failed CIs for example: + * 1. https://github.com/elastic/kibana/issues/53690 + * 2. https://github.com/elastic/kibana/issues/53683 + * + * This will be fixed and reverted in PR: https://github.com/elastic/kibana/pull/53333 + */ + it.skip(`shouldn't schedule actions when alert is muted`, async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await alertUtils.createAlwaysFiringAction({ diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index 3e736a2dcf8c4..a098a1fe02c1a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -87,6 +87,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { ], enabled: true, alertTypeId: 'test.noop', + consumer: 'bar', params: {}, createdBy: user.username, schedule: { interval: '1m' }, @@ -201,10 +202,10 @@ export default function createAlertTests({ getService }: FtrProviderContext) { statusCode: 400, error: 'Bad Request', message: - 'child "name" fails because ["name" is required]. child "alertTypeId" fails because ["alertTypeId" is required]. child "schedule" fails because ["schedule" is required]. child "params" fails because ["params" is required]. child "actions" fails because ["actions" is required]', + 'child "name" fails because ["name" is required]. child "alertTypeId" fails because ["alertTypeId" is required]. child "consumer" fails because ["consumer" is required]. child "schedule" fails because ["schedule" is required]. child "params" fails because ["params" is required]. child "actions" fails because ["actions" is required]', validation: { source: 'payload', - keys: ['name', 'alertTypeId', 'schedule', 'params', 'actions'], + keys: ['name', 'alertTypeId', 'consumer', 'schedule', 'params', 'actions'], }, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 4da6c059c5a5e..92d8447e8f7d5 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -59,6 +59,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', + consumer: 'bar', schedule: { interval: '1m' }, enabled: true, actions: [], @@ -138,6 +139,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', + consumer: 'bar', schedule: { interval: '1m' }, enabled: false, actions: [ diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 9c1f7fea93292..eaa361155b61f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -53,6 +53,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', + consumer: 'bar', schedule: { interval: '1m' }, enabled: true, actions: [], diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 0e2ec0f7bc534..8cb01b5467388 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { Response as SupertestResponse } from 'supertest'; import { UserAtSpaceScenarios } from '../../scenarios'; import { getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -13,6 +14,14 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function createUpdateTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const retry = getService('retry'); + + function getAlertingTaskById(taskId: string) { + return supertest + .get(`/api/alerting_tasks/${taskId}`) + .expect(200) + .then((response: SupertestResponse) => response.body); + } describe('update', () => { const objectRemover = new ObjectRemover(supertest); @@ -64,6 +73,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { ...updatedData, id: createdAlert.id, alertTypeId: 'test.noop', + consumer: 'bar', createdBy: 'elastic', enabled: true, updatedBy: user.username, @@ -274,7 +284,13 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { .put(`${getUrlPrefix(space.id)}/api/alert/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) - .send(getTestAlertData({ schedule: { interval: '10x' }, enabled: undefined })); + .send( + getTestAlertData({ + schedule: { interval: '10x' }, + enabled: undefined, + consumer: undefined, + }) + ); switch (scenario.id) { case 'no_kibana_privileges at space1': @@ -311,7 +327,75 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); } }); + + it('should handle updates to an alert schedule by rescheduling the underlying task', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + schedule: { interval: '30m' }, + }) + ) + .expect(200); + objectRemover.add(space.id, createdAlert.id, 'alert'); + + await retry.try(async () => { + const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + expect(alertTask.status).to.eql('idle'); + // ensure the alert inital run has completed and it's been rescheduled to half an hour from now + ensureDatetimeIsWithinRange(Date.parse(alertTask.runAt), 30 * 60 * 1000); + }); + + const updatedData = { + name: 'bcd', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '1m' }, + actions: [], + throttle: '2m', + }; + const response = await supertestWithoutAuth + .put(`${getUrlPrefix(space.id)}/api/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send(updatedData); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + expect(response.statusCode).to.eql(404); + expect(response.body).to.eql({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }); + break; + case 'superuser at space1': + case 'space_1_all at space1': + expect(response.statusCode).to.eql(200); + await retry.try(async () => { + const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + expect(alertTask.status).to.eql('idle'); + // ensure the alert is rescheduled to a minute from now + ensureDatetimeIsWithinRange(Date.parse(alertTask.runAt), 60 * 1000); + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); }); } }); } + +function ensureDatetimeIsWithinRange(scheduledRunTime: number, expectedDiff: number) { + const buffer = 10000; + const diff = scheduledRunTime - Date.now(); + expect(diff).to.be.greaterThan(expectedDiff - buffer); + expect(diff).to.be.lessThan(expectedDiff + buffer); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 0e9011729eb3e..c61c94bd603fb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -69,11 +69,13 @@ export default function createAlertTests({ getService }: FtrProviderContext) { ], enabled: true, alertTypeId: 'test.noop', + consumer: 'bar', params: {}, createdBy: null, schedule: { interval: '1m' }, scheduledTaskId: response.body.scheduledTaskId, updatedBy: null, + apiKeyOwner: null, throttle: '1m', muteAll: false, mutedInstanceIds: [], diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 3fdd9168eb5cb..1ee814aace797 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -42,11 +42,13 @@ export default function createFindTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', + consumer: 'bar', schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, createdBy: null, + apiKeyOwner: null, scheduledTaskId: match.scheduledTaskId, updatedBy: null, throttle: '1m', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index a49d3478d336d..328b0a01d5cbd 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -36,6 +36,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', + consumer: 'bar', schedule: { interval: '1m' }, enabled: true, actions: [], @@ -43,6 +44,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { createdBy: null, scheduledTaskId: response.body.scheduledTaskId, updatedBy: null, + apiKeyOwner: null, throttle: '1m', muteAll: false, mutedInstanceIds: [], diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index 46822781c0cd3..fd6d81e296ef0 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -44,6 +44,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { id: createdAlert.id, tags: ['bar'], alertTypeId: 'test.noop', + consumer: 'bar', createdBy: null, enabled: true, updatedBy: null, diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index f613473dd87fb..34b2c5e324187 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -114,6 +114,7 @@ export default function({ getService }: FtrProviderContext) { 'maps', 'uptime', 'siem', + 'endpoint', ].sort() ); }); diff --git a/x-pack/test/api_integration/apis/infra/log_item.ts b/x-pack/test/api_integration/apis/infra/log_item.ts index 2c1fc73de9595..bae2d0ebb891b 100644 --- a/x-pack/test/api_integration/apis/infra/log_item.ts +++ b/x-pack/test/api_integration/apis/infra/log_item.ts @@ -5,168 +5,148 @@ */ import expect from '@kbn/expect'; -import { flyoutItemQuery } from '../../../../legacy/plugins/infra/public/containers/logs/flyout_item.gql_query'; -import { FlyoutItemQuery } from '../../../../legacy/plugins/infra/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { + LOG_ENTRIES_ITEM_PATH, + logEntriesItemRequestRT, +} from '../../../../legacy/plugins/infra/common/http_api'; + +const COMMON_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const client = getService('infraOpsGraphQLClient'); - describe('Log Item GraphQL Endpoint', () => { + const supertest = getService('supertest'); + + describe('Log Item Endpoint', () => { before(() => esArchiver.load('infra/metrics_and_logs')); after(() => esArchiver.unload('infra/metrics_and_logs')); - it('should basically work', () => { - return client - .query({ - query: flyoutItemQuery, - variables: { + it('should basically work', async () => { + const { body } = await supertest + .post(LOG_ENTRIES_ITEM_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesItemRequestRT.encode({ sourceId: 'default', - itemId: 'yT2Mg2YBh-opCxJv8Vqj', - }, - }) - .then(resp => { - expect(resp.data.source).to.have.property('logItem'); - const { logItem } = resp.data.source; - if (!logItem) { - throw new Error('Log item should not be falsey'); - } - expect(logItem).to.have.property('id', 'yT2Mg2YBh-opCxJv8Vqj'); - expect(logItem).to.have.property('index', 'filebeat-7.0.0-alpha1-2018.10.17'); - expect(logItem).to.have.property('fields'); - expect(logItem.fields).to.eql([ - { - field: '@timestamp', - value: '2018-10-17T19:42:22.000Z', - __typename: 'InfraLogItemField', - }, - { - field: '_id', - value: 'yT2Mg2YBh-opCxJv8Vqj', - __typename: 'InfraLogItemField', - }, - { - field: '_index', - value: 'filebeat-7.0.0-alpha1-2018.10.17', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.body_sent.bytes', - value: '1336', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.http_version', - value: '1.1', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.method', - value: 'GET', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.referrer', - value: '-', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.remote_ip', - value: '10.128.0.11', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.response_code', - value: '200', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.url', - value: '/a-fresh-start-will-put-you-on-your-way', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.user_agent.device', - value: 'Other', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.user_agent.name', - value: 'Other', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.user_agent.os', - value: 'Other', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.user_agent.os_name', - value: 'Other', - __typename: 'InfraLogItemField', - }, - { - field: 'apache2.access.user_name', - value: '-', - __typename: 'InfraLogItemField', - }, - { - field: 'beat.hostname', - value: 'demo-stack-apache-01', - __typename: 'InfraLogItemField', - }, - { - field: 'beat.name', - value: 'demo-stack-apache-01', - __typename: 'InfraLogItemField', - }, - { - field: 'beat.version', - value: '7.0.0-alpha1', - __typename: 'InfraLogItemField', - }, - { - field: 'fileset.module', - value: 'apache2', - __typename: 'InfraLogItemField', - }, - { - field: 'fileset.name', - value: 'access', - __typename: 'InfraLogItemField', - }, - { - field: 'host.name', - value: 'demo-stack-apache-01', - __typename: 'InfraLogItemField', - }, - { - field: 'input.type', - value: 'log', - __typename: 'InfraLogItemField', - }, - { - field: 'offset', - value: '5497614', - __typename: 'InfraLogItemField', - }, - { - field: 'prospector.type', - value: 'log', - __typename: 'InfraLogItemField', - }, - { - field: 'read_timestamp', - value: '2018-10-17T19:42:23.160Z', - __typename: 'InfraLogItemField', - }, - { - field: 'source', - value: '/var/log/apache2/access.log', - __typename: 'InfraLogItemField', - }, - ]); - }); + id: 'yT2Mg2YBh-opCxJv8Vqj', + }) + ) + .expect(200); + + const logItem = body.data; + + expect(logItem).to.have.property('id', 'yT2Mg2YBh-opCxJv8Vqj'); + expect(logItem).to.have.property('index', 'filebeat-7.0.0-alpha1-2018.10.17'); + expect(logItem).to.have.property('fields'); + expect(logItem.fields).to.eql([ + { + field: '@timestamp', + value: '2018-10-17T19:42:22.000Z', + }, + { + field: '_id', + value: 'yT2Mg2YBh-opCxJv8Vqj', + }, + { + field: '_index', + value: 'filebeat-7.0.0-alpha1-2018.10.17', + }, + { + field: 'apache2.access.body_sent.bytes', + value: '1336', + }, + { + field: 'apache2.access.http_version', + value: '1.1', + }, + { + field: 'apache2.access.method', + value: 'GET', + }, + { + field: 'apache2.access.referrer', + value: '-', + }, + { + field: 'apache2.access.remote_ip', + value: '10.128.0.11', + }, + { + field: 'apache2.access.response_code', + value: '200', + }, + { + field: 'apache2.access.url', + value: '/a-fresh-start-will-put-you-on-your-way', + }, + { + field: 'apache2.access.user_agent.device', + value: 'Other', + }, + { + field: 'apache2.access.user_agent.name', + value: 'Other', + }, + { + field: 'apache2.access.user_agent.os', + value: 'Other', + }, + { + field: 'apache2.access.user_agent.os_name', + value: 'Other', + }, + { + field: 'apache2.access.user_name', + value: '-', + }, + { + field: 'beat.hostname', + value: 'demo-stack-apache-01', + }, + { + field: 'beat.name', + value: 'demo-stack-apache-01', + }, + { + field: 'beat.version', + value: '7.0.0-alpha1', + }, + { + field: 'fileset.module', + value: 'apache2', + }, + { + field: 'fileset.name', + value: 'access', + }, + { + field: 'host.name', + value: 'demo-stack-apache-01', + }, + { + field: 'input.type', + value: 'log', + }, + { + field: 'offset', + value: '5497614', + }, + { + field: 'prospector.type', + value: 'log', + }, + { + field: 'read_timestamp', + value: '2018-10-17T19:42:23.160Z', + }, + { + field: 'source', + value: '/var/log/apache2/access.log', + }, + ]); }); }); } diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index d4c8a3e68c50e..7b1984222404b 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -37,6 +37,7 @@ export default function({ getService }: FtrProviderContext) { uptime: ['all', 'read'], apm: ['all', 'read'], siem: ['all', 'read'], + endpoint: ['all', 'read'], }, global: ['all', 'read'], space: ['all', 'read'], diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index efc1f9d5e5e40..c7a9764c6fb58 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { createDashboardEditUrl, DashboardConstants, -} from '../../../../../../src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants'; +} from '../../../../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts index d98ad2db09430..127141b156cd8 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { createDashboardEditUrl, DashboardConstants, -} from '../../../../../../src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants'; +} from '../../../../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { diff --git a/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts b/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts new file mode 100644 index 0000000000000..6b3b423e293c2 --- /dev/null +++ b/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['common']); + const spacesService = getService('spaces'); + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('spaces', () => { + describe('space with no features disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it('shows endpoint navlink', async () => { + await pageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('EEndpoint'); + }); + + it(`endpoint app shows 'Hello World'`, async () => { + await pageObjects.common.navigateToApp('endpoint', { + basePath: '/s/custom_space', + }); + await testSubjects.existOrFail('welcomeTitle'); + }); + }); + + describe('space with endpoint disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['endpoint'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it(`doesn't show endpoint navlink`, async () => { + await pageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('EEndpoint'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/endpoint/feature_controls/index.ts b/x-pack/test/functional/apps/endpoint/feature_controls/index.ts new file mode 100644 index 0000000000000..5f7e611fd966c --- /dev/null +++ b/x-pack/test/functional/apps/endpoint/feature_controls/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('feature controls', function() { + this.tags('skipFirefox'); + loadTestFile(require.resolve('./endpoint_spaces')); + }); +} diff --git a/x-pack/test/functional/apps/endpoint/index.ts b/x-pack/test/functional/apps/endpoint/index.ts new file mode 100644 index 0000000000000..1a0d3e973285b --- /dev/null +++ b/x-pack/test/functional/apps/endpoint/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('endpoint', function() { + this.tags('ciGroup7'); + + loadTestFile(require.resolve('./feature_controls')); + }); +} diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index efe60b41badcc..a68637600be8b 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -22,7 +22,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { state: undefined, }; const expectedSearchString = - "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default&_g=()"; + "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default"; const expectedRedirect = `/logs/stream?${expectedSearchString}`; await pageObjects.common.navigateToActualUrl( diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index cbe7c98cd881d..89a6c6ea82e53 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -18,6 +18,9 @@ const EXPECTED_JOIN_VALUES = { }; const VECTOR_SOURCE_ID = 'n1t6f'; +const CIRCLE_STYLE_LAYER_INDEX = 0; +const FILL_STYLE_LAYER_INDEX = 2; +const LINE_STYLE_LAYER_INDEX = 3; export default function({ getPageObjects, getService }) { const PageObjects = getPageObjects(['maps']); @@ -82,19 +85,21 @@ export default function({ getPageObjects, getService }) { const layersForVectorSource = mapboxStyle.layers.filter(mbLayer => { return mbLayer.id.startsWith(VECTOR_SOURCE_ID); }); + // Color is dynamically obtained from eui source lib - const dynamicColor = layersForVectorSource[0].paint['circle-stroke-color']; + const dynamicColor = + layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX].paint['circle-stroke-color']; //circle layer for points - expect(layersForVectorSource[0]).to.eql( + expect(layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX]).to.eql( _.set(MAPBOX_STYLES.POINT_LAYER, 'paint.circle-stroke-color', dynamicColor) ); //fill layer - expect(layersForVectorSource[1]).to.eql(MAPBOX_STYLES.FILL_LAYER); + expect(layersForVectorSource[FILL_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.FILL_LAYER); //line layer for borders - expect(layersForVectorSource[2]).to.eql( + expect(layersForVectorSource[LINE_STYLE_LAYER_INDEX]).to.eql( _.set(MAPBOX_STYLES.LINE_LAYER, 'paint.line-color', dynamicColor) ); }); diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts index 49ad137b8da6b..d0fdc7c95ea38 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { VisualizeConstants } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize/visualize_constants'; +import { VisualizeConstants } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 8981c94b83578..17235c61c7d8c 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -56,6 +56,7 @@ export default async function({ readConfigFile }) { resolve(__dirname, './apps/cross_cluster_replication'), resolve(__dirname, './apps/remote_clusters'), resolve(__dirname, './apps/transform'), + resolve(__dirname, './apps/endpoint'), // This license_management file must be last because it is destructive. resolve(__dirname, './apps/license_management'), ], @@ -86,6 +87,7 @@ export default async function({ readConfigFile }) { '--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', '--telemetry.banner=false', '--timelion.ui.enabled=true', + '--xpack.endpoint.enabled=true', ], }, uiSettings: { @@ -197,6 +199,9 @@ export default async function({ readConfigFile }) { pathname: '/app/kibana/', hash: '/management/elasticsearch/transform', }, + endpoint: { + pathname: '/app/endpoint', + }, }, // choose where esArchiver should load archives from diff --git a/yarn.lock b/yarn.lock index dea42c933661d..dff0e9d46e7d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29217,10 +29217,10 @@ utila@^0.4.0, utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= -utility-types@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.7.0.tgz#51f1c29fa35d4267488345706efcf3f68f2b1933" - integrity sha512-mqRJXN7dEArK/NZNJUubjr9kbFFVZcmF/JHDc9jt5O/aYXUVmopHYujDMhLmLil1Bxo2+khe6KAIVvDH9Yc4VA== +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== utils-copy-error@^1.0.0: version "1.0.1"